[Rt-commit] rt branch 5.0/asset-inline-edit created. rt-5.0.5-77-g6ae9368c1c

BPS Git Server git at git.bestpractical.com
Wed Dec 20 19:22:23 UTC 2023


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".

The branch, 5.0/asset-inline-edit has been created
        at  6ae9368c1c32c8f1d73fe1871ad9cb74679af26b (commit)

- Log -----------------------------------------------------------------
commit 6ae9368c1c32c8f1d73fe1871ad9cb74679af26b
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Dec 11 13:41:04 2023 -0500

    Make sure inline edit is disabled for unprivileged users
    
    Inline edit doesn't actually work for them anyway, because of the absent
    helper component.
    
    Previously we disabled it in /SelfService/Elements/MyRequests, which is
    not enough any more as now we support various components for SelfService
    especially via dashboards.

diff --git a/share/html/Elements/CollectionList b/share/html/Elements/CollectionList
index 273931177f..6aaa169cdc 100644
--- a/share/html/Elements/CollectionList
+++ b/share/html/Elements/CollectionList
@@ -125,7 +125,8 @@ foreach my $col (@Format) {
 
 $Class ||= $Collection->ColumnMapClassName;
 
-$InlineEdit = 0 unless $Collection->isa('RT::Tickets') || $Collection->isa('RT::Assets');
+$InlineEdit = 0
+    unless $session{CurrentUser}->Privileged && ( $Collection->isa('RT::Tickets') || $Collection->isa('RT::Assets') );
 
 $m->out('<div class="table-responsive">');
 $m->out('<table cellspacing="0"');

commit fae6539af967584f3592cabd28707eb5f2a67de8
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Dec 8 16:50:25 2023 -0500

    Fix typo: selectpicker is not for text inputs

diff --git a/share/html/Asset/Elements/EditRoleMembers b/share/html/Asset/Elements/EditRoleMembers
index 4dcd65c1f4..008c0a12de 100644
--- a/share/html/Asset/Elements/EditRoleMembers
+++ b/share/html/Asset/Elements/EditRoleMembers
@@ -59,7 +59,7 @@ my $field_name = "RemoveRoleMember-" . $Role;
 % if ($Object->Role($Role)->{Single}) {
 % my $user = $Users->First || RT->Nobody;
   <li class="list-group-item">
-    <input class="form-control selectpicker" type="text" value="<% $user->Name %>" name="SetRoleMember-<% $Role %>" id="SetRoleMember-<% $Role %>" data-autocomplete="Users" data-autocomplete-return="Name" />
+    <input class="form-control" type="text" value="<% $user->Name %>" name="SetRoleMember-<% $Role %>" id="SetRoleMember-<% $Role %>" data-autocomplete="Users" data-autocomplete-return="Name" />
   </li>
 % } else {
 % while ( my $user = $Users->Next ) {

commit fe4a3ae80603d0e6460efcc38f1635e7b34d2b0e
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Dec 8 09:52:31 2023 -0500

    Support inline edit for assets

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 31e3163168..2386026f7c 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2583,6 +2583,11 @@ click, but also specifies different behaviors for several other panels.
 Note that the non-standard panel names "Grouping Name" and "Another
 Grouping" are created by the L</%CustomFieldGroupings> setting.
 
+As Assets don't have editable core date fields, inline edit is thus
+disabled in the "Dates" widget by default. If you have any date custom
+fields that are grouped there via L</%CustomFieldGroupings>, you can
+explicitly enable it via this config.
+
     Set(%InlineEditPanelBehavior,
         'RT::Ticket' => {
             '_default'          => 'click',
@@ -2593,6 +2598,15 @@ Grouping" are created by the L</%CustomFieldGroupings> setting.
             'Links'             => 'hide',
             'People'            => 'link',
         },
+        'RT::Asset' => {
+            '_default'          => 'click',
+
+            'Grouping Name'     => 'link',
+            'Another Grouping'  => 'click',
+            'Dates'             => 'hide',
+            'Links'             => 'hide',
+            'People'            => 'link',
+        },
     );
 
 =back
diff --git a/share/html/Asset/Display.html b/share/html/Asset/Display.html
index 553fb4f66a..b1035b45fe 100644
--- a/share/html/Asset/Display.html
+++ b/share/html/Asset/Display.html
@@ -53,7 +53,7 @@
 <& /Elements/ListActions, actions => \@results &>
 
 <span class="catalog <% CSSClass($asset->CatalogObj->Name) %>">
-<& Elements/ShowSummary, AssetObj => $asset &>
+<& Elements/ShowSummary, AssetObj => $asset, InlineEdit => $InlineEdit &>
 
 % $m->callback(CallbackName => 'AfterShowSummary', ARGSRef => \%ARGS, Asset => $asset);
 
@@ -68,9 +68,53 @@
 
 <%args>
 $id => undef
+$InlineEdit => RT->Config->Get( 'InlineEdit', $session{CurrentUser} )
 </%args>
 <%init>
 my @results;
 my $asset = LoadAsset($id);
+
+# fill ACL cache
+$asset->CurrentUser->PrincipalObj->HasRights( Object => $asset );
+
+my $SkipProcessing;
+
+$m->callback( CallbackName => 'BeforeProcessArguments',
+    AssetObj => $asset,
+    ActionsRef => \@results, ARGSRef => \%ARGS,
+    SkipProcessing => \$SkipProcessing );
+
+my ($status, @msg) = $m->comp(
+    '/Elements/ValidateCustomFields',
+    Object       => $asset,
+    CustomFields => $asset->CustomFields,
+    ARGSRef      => \%ARGS,
+);
+unless ($status) {
+    push @results, @msg;
+    $SkipProcessing = 1;
+}
+
+if ( !$SkipProcessing ) {
+
+    push @results, ProcessAssetRoleMembers( $asset => %ARGS );
+    push @results, ProcessRecordLinks( RecordObj => $asset, ARGSRef => \%ARGS );
+    push @results, ProcessObjectCustomFieldUpdates( Object => $asset, ARGSRef => \%ARGS );
+
+    push @results, UpdateRecordObject(
+        Object        => $asset,
+        AttributesRef => [ $asset->WritableAttributes ],
+        ARGSRef       => \%ARGS,
+    );
+
+}
+
 $m->callback(CallbackName => 'BeforeDisplay', ARGSRef => \%ARGS, Asset => $asset, Results => \@results);
+
+MaybeRedirectForResults(
+    Actions   => \@results,
+    Path      => '/Asset/Display.html',
+    Anchor    => $ARGS{'Anchor'},
+    Arguments => { id => $asset->Id },
+);
 </%init>
diff --git a/share/html/Asset/Elements/ShowSummary b/share/html/Asset/Elements/EditPeopleInline
similarity index 62%
copy from share/html/Asset/Elements/ShowSummary
copy to share/html/Asset/Elements/EditPeopleInline
index 3aa12a3b22..b0a3fbaf6f 100644
--- a/share/html/Asset/Elements/ShowSummary
+++ b/share/html/Asset/Elements/EditPeopleInline
@@ -45,44 +45,49 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<%args>
-$AssetObj
-</%args>
-<%init>
-my @sections = (
-    "Basics",   #loc
-    "People",   #loc
-    "Dates",    #loc
-    "Links",    #loc
-);
 
-my $can_edit = $session{CurrentUser}->Privileged
-    && $AssetObj->CurrentUserHasRight("ModifyAsset");
+% for my $role ($AssetObj->Roles( ACLOnly => 0 )) {
+  <div class="role-<% CSSClass($role) %> role">
+    <h5 class="mt-2"><% $AssetObj->LabelForRole($role) %></h5>
+    <& EditRoleMembers, Object => $AssetObj, Role => $role &>
+  </div>
+% }
 
-my %link;
-for my $section (@sections) {
-    my $page = $section eq 'Basics' ? "Modify.html" : "Modify$section.html";
-    $link{$section} =
-        RT->Config->Get("WebPath")
-        . "/Asset/$page?id="
-        . $AssetObj->id;
-}
-</%init>
-<div class="asset-metadata">
+<div class="add-user">
+  <h5 class="mt-2"><&|/l&>Add a person</&></h5>
   <div class="form-row">
-% for my $section (@sections) {
-      <div class="col-4">
-        <&| /Widgets/TitleBox, title => loc($section), title_href => $can_edit ? $link{$section} : "", title_class => "inverse", class => "asset-\L$section" &>
-          <& "Show$section", AssetObj => $AssetObj &>
-        </&>
-      </div>
-% }
+    <div class="col-3">
+      <& SelectRoleType, Object => $AssetObj, Name => "AddUserRoleMember-Role" &>
+    </div>
+    <div class="col-9">
+      <input type="text" name="AddUserRoleMember"
+        data-autocomplete="Users"
+        data-autocomplete-return="Name"
+        placeholder="<% loc("Find a user...") %>"
+        class="form-control"
+      >
+    </div>
+  </div>
+</div>
 
-    <& /Elements/ShowCustomFieldCustomGroupings,
-      Object => $AssetObj,
-      title_href => $can_edit ? RT->Config->Get("WebPath") . "/Asset/ModifyCFs.html" : "",
-      TitleBoxARGS => { title_class => "inverse" },
-      GroupingClass => 'col-4'
-    &>
+<div class="add-group">
+  <h5 class="mt-2"><&|/l&>Add a group</&></h5>
+  <div class="form-row">
+    <div class="col-3">
+      <& SelectRoleType, Object => $AssetObj, Name => "AddGroupRoleMember-Role" &>
+    </div>
+    <div class="col-9">
+      <input type="text" name="AddGroupRoleMember"
+        data-autocomplete="Groups"
+        data-autocomplete-return="Name"
+        placeholder="<% loc("Find a group...") %>"
+        class="form-control"
+      >
+    </div>
   </div>
 </div>
+
+<& /Elements/EditCustomFields, Object => $AssetObj, Grouping => 'People', InTable => 1 &>
+<%ARGS>
+$AssetObj => undef
+</%ARGS>
diff --git a/share/html/Asset/Elements/ShowSummary b/share/html/Asset/Elements/ShowSummary
index 3aa12a3b22..b86e1ded2a 100644
--- a/share/html/Asset/Elements/ShowSummary
+++ b/share/html/Asset/Elements/ShowSummary
@@ -47,6 +47,7 @@
 %# END BPS TAGGED BLOCK }}}
 <%args>
 $AssetObj
+$InlineEdit => 0
 </%args>
 <%init>
 my @sections = (
@@ -56,9 +57,6 @@ my @sections = (
     "Links",    #loc
 );
 
-my $can_edit = $session{CurrentUser}->Privileged
-    && $AssetObj->CurrentUserHasRight("ModifyAsset");
-
 my %link;
 for my $section (@sections) {
     my $page = $section eq 'Basics' ? "Modify.html" : "Modify$section.html";
@@ -67,22 +65,75 @@ for my $section (@sections) {
         . "/Asset/$page?id="
         . $AssetObj->id;
 }
+
+my $can_modify = $AssetObj->CurrentUserHasRight('ModifyAsset');
+my $can_modify_cf = $AssetObj->CurrentUserHasRight('ModifyCustomField');
+
+$m->callback( CallbackName => 'ModifyRights', %ARGS, AssetObj => $AssetObj, ARGSRef => \%ARGS,
+    CanModify => \$can_modify, CanModifyCF => \$can_modify_cf );
+
+my $edit_label = $m->interp->apply_escapes( loc("Edit"), 'h' );
+my $cancel_label = $m->interp->apply_escapes( loc("Cancel"), 'h' );
+
+my %inline_edit_behavior;
+if (RT->Config->Get('InlineEditPanelBehavior')) {
+    %inline_edit_behavior = %{ RT->Config->Get('InlineEditPanelBehavior')->{'RT::Asset'} || { Dates => 'hide' } };
+}
+
+my $modify_inline
+    = '<a class="inline-edit-toggle edit" href="%s">'
+    . qq{<span class="fas fa-pencil-alt icon-bordered fa-2x" alt="$edit_label" data-toggle="tooltip" data-placement="top" data-original-title="$edit_label"></span>}
+    . '</a>'
+    . '<a class="inline-edit-toggle cancel hidden" href="#">'
+    . qq{<span class="fas fa-times icon-bordered fa-2x" alt="$cancel_label" data-toggle="tooltip" data-placement="top" data-original-title="$cancel_label"></span>}
+    . '</a>';
 </%init>
 <div class="asset-metadata">
   <div class="form-row">
 % for my $section (@sections) {
+% my $modify_url = sprintf( $modify_inline, $m->interp->apply_escapes( $link{$section}, 'h' ) );
+% my $modify_behavior = $InlineEdit ? ($inline_edit_behavior{$section} || $inline_edit_behavior{_default} || 'link') : 'hide';
+
       <div class="col-4">
-        <&| /Widgets/TitleBox, title => loc($section), title_href => $can_edit ? $link{$section} : "", title_class => "inverse", class => "asset-\L$section" &>
-          <& "Show$section", AssetObj => $AssetObj &>
+        <&| /Widgets/TitleBox, title => loc($section), title_href => ($can_modify || $can_modify_cf) ? $link{$section} : "", title_class => "inverse",
+        (($can_modify || $can_modify_cf) && $modify_behavior =~ /^(link|click)$/ ? (titleright_raw => $modify_url) : ()),
+        class => (join " ", "asset-\L$section", ($modify_behavior eq 'always' ? 'editing' : ())),
+        data => { 'inline-edit-behavior' => $modify_behavior }
+        &>
+%       unless ($modify_behavior eq 'always') {
+          <div class="inline-edit-display">
+            <& "Show$section", AssetObj => $AssetObj &>
+          </div>
+%       }
+%       if ($modify_behavior ne 'hide') {
+          <form class="inline-edit" action="<%RT->Config->Get('WebPath')%>/Asset/Display.html" method="post" enctype="multipart/form-data">
+            <input type="hidden" class="hidden" name="id" value="<% $AssetObj->id %>" />
+%           if ( $section eq 'Links' ) {
+              <& /Elements/EditLinks, Object => $AssetObj, TwoColumn => 0 &>
+              <& /Elements/EditCustomFields, Object => $AssetObj, Grouping => $section, InTable => 1 &>
+%           } elsif ( $section eq 'People' ) {
+              <& /Asset/Elements/EditPeopleInline, AssetObj => $AssetObj &>
+%           } else {
+              <& "/Asset/Elements/Edit$section", AssetObj => $AssetObj &>
+%           }
+            <div class="form-row">
+              <div class="col-12 text-right">
+                <input type="submit" class="button btn btn-primary" value="<&|/l&>Save</&>" />
+              </div>
+            </div>
+          </form>
+%       }
         </&>
       </div>
 % }
 
     <& /Elements/ShowCustomFieldCustomGroupings,
       Object => $AssetObj,
-      title_href => $can_edit ? RT->Config->Get("WebPath") . "/Asset/ModifyCFs.html" : "",
+      title_href => ($can_modify || $can_modify_cf) ? RT->Config->Get("WebPath") . "/Asset/ModifyCFs.html" : "",
       TitleBoxARGS => { title_class => "inverse" },
-      GroupingClass => 'col-4'
+      GroupingClass => 'col-4',
+      InlineEdit => ($can_modify || $can_modify_cf) ? $InlineEdit : 0,
+      ActionURL  => RT->Config->Get('WebPath') . '/Asset/Display.html',
     &>
   </div>
 </div>
diff --git a/share/html/Asset/Search/Bulk.html b/share/html/Asset/Search/Bulk.html
index 1f7e94d9ed..41ade9d9f8 100644
--- a/share/html/Asset/Search/Bulk.html
+++ b/share/html/Asset/Search/Bulk.html
@@ -62,6 +62,7 @@
     Collection      => $assets,
     AllowSorting    => 1,
     DisplayFormat   => $DisplayFormat,
+    InlineEdit      => 0,
     &>
 % if (not $assets->Count) {
 <em><&|/l&>No assets matching search criteria found.</&></em>
diff --git a/share/html/Elements/CollectionAsTable/Row b/share/html/Elements/CollectionAsTable/Row
index d8265e1d9d..5cd3e8ecbc 100644
--- a/share/html/Elements/CollectionAsTable/Row
+++ b/share/html/Elements/CollectionAsTable/Row
@@ -92,9 +92,15 @@ foreach my $column (@Format) {
 
     my %attrs;
     my @possible_attrs = qw(style align);
-    if ($InlineEdit && $record->isa('RT::Ticket') && $record->CurrentUserHasRight('ModifyTicket')) {
-        push(@possible_attrs, 'edit');
+    if (
+        $InlineEdit
+        && (   $record->isa('RT::Ticket') && $record->CurrentUserHasRight('ModifyTicket')
+            || $record->isa('RT::Asset') && $record->CurrentUserHasRight('ModifyAsset') )
+        )
+    {
+        push( @possible_attrs, 'edit' );
     }
+
     foreach my $attr (@possible_attrs) {
         if ( defined $column->{ $attr } ) {
             $attrs{ $attr } = $column->{ $attr };
@@ -171,7 +177,8 @@ foreach my $column (@Format) {
     $m->out('>');
 
     if ( $attrs{edit} ) {
-        $m->out( '<form method="POST" action="' . RT->Config->Get('WebPath') . '/Helpers/TicketUpdate?id=' . $record->id . '" class="editor" autocomplete="off">' );
+        my $helper_name = $record->isa('RT::Ticket') ? 'TicketUpdate' : 'AssetUpdate';
+        $m->out( '<form method="POST" action="' . RT->Config->Get('WebPath') . "/Helpers/$helper_name?id=" . $record->id . '" class="editor" autocomplete="off">' );
         $m->out( $attrs{edit} );
         $m->out( '<span class="cancel text-danger far fa-times-circle" data-toggle="tooltip" data-placement="left" data-original-title="' . loc('Cancel') . '"></span>' );
         $m->out( '<span class="submit text-success far fa-check-circle" data-toggle="tooltip" data-placement="right" data-original-title="' . loc('Save') . '"></span>' );
diff --git a/share/html/Elements/CollectionList b/share/html/Elements/CollectionList
index 2f6aeceeb9..273931177f 100644
--- a/share/html/Elements/CollectionList
+++ b/share/html/Elements/CollectionList
@@ -125,7 +125,7 @@ foreach my $col (@Format) {
 
 $Class ||= $Collection->ColumnMapClassName;
 
-$InlineEdit = 0 unless $Collection->isa('RT::Tickets');
+$InlineEdit = 0 unless $Collection->isa('RT::Tickets') || $Collection->isa('RT::Assets');
 
 $m->out('<div class="table-responsive">');
 $m->out('<table cellspacing="0"');
diff --git a/share/html/Elements/ColumnMap b/share/html/Elements/ColumnMap
index dae63ccca9..22fff375d8 100644
--- a/share/html/Elements/ColumnMap
+++ b/share/html/Elements/ColumnMap
@@ -314,7 +314,20 @@ $WCOLUMN_MAP = $COLUMN_MAP = {
             my $role   = $self->{load}->(@_);
             return unless $role->Id;
             if ($role->SingleValue) {
-                return \($m->scomp("/Elements/SingleUserRoleInput", role => $role, Ticket => $_[0]));
+                if ( $_[0]->isa('RT::Ticket') ) {
+                    return \($m->scomp("/Elements/SingleUserRoleInput", role => $role, Ticket => $_[0]));
+                }
+                elsif ( $_[0]->isa('RT::Asset') ) {
+                    my $group = $_[0]->RoleGroup( $role->GroupType);
+                    my $user       = $group->UserMembersObj()->First || RT->Nobody;
+                    my $user_name  = $m->interp->apply_escapes( $user->Name, 'h' );
+                    my $group_type = $role->GroupType;
+                    return \qq{<input class="form-control" type="text" value="$user_name" name="SetRoleMember-$group_type" data-autocomplete="Users" data-autocomplete-return="Name" />};
+                }
+                else {
+                    RT->Logger->warning( "Invalid object for custom roles: " . ref $_[0] );
+                    return undef;
+                }
             }
             else {
                 return undef;
@@ -447,6 +460,17 @@ if ($RecordClass->DOES("RT::Record::Role::Roles")) {
                     }
                 },
                 value => sub { return $role_value->($role, @_, @_ == 2 ? '' : () ) },
+                edit => sub {
+                    if ($attrs->{Single} && $RecordClass eq 'RT::Asset' ) {
+                        my $group      = $_[0]->RoleGroup($role);
+                        my $user       = $group->UserMembersObj()->First || RT->Nobody;
+                        my $user_name  = $m->interp->apply_escapes( $user->Name, 'h' );
+                        return \qq{<input class="form-control" type="text" value="$user_name" name="SetRoleMember-$role" data-autocomplete="Users" data-autocomplete-return="Name" />};
+                    }
+                    else {
+                        return undef;
+                    }
+                },
             };
 
             $ROLE_MAP->{$RecordClass}{$role . "s"} = $ROLE_MAP->{$RecordClass}{$role}
diff --git a/share/html/Elements/RT__Asset/ColumnMap b/share/html/Elements/RT__Asset/ColumnMap
index 578ef038ec..9934d27dd3 100644
--- a/share/html/Elements/RT__Asset/ColumnMap
+++ b/share/html/Elements/RT__Asset/ColumnMap
@@ -77,21 +77,25 @@ my $COLUMN_MAP = {
         attribute => 'Name',
         title     => 'Name',
         value     => sub { $_[0]->Name },
+        edit      => sub { return \('<input name="Name" class="form-control" value="'.$m->interp->apply_escapes( $_[0]->Name, 'h' ).'" />') },
     },
     Description => {
         attribute => 'Description',
         title     => 'Description',
         value     => sub { $_[0]->Description },
+        edit      => sub { return \('<input name="Description" class="form-control" value="'.$m->interp->apply_escapes( $_[0]->Description, 'h' ).'" />') },
     },
     Catalog => {
         attribute => 'Catalog',
         title     => 'Catalog', # loc
         value     => sub { $_[0]->CatalogObj->Name },
+        edit      => sub { return \($m->scomp('/Asset/Elements/SelectCatalog', Default => $_[0]->Catalog, Name => 'Catalog', ShowNullOption => 0)) },
     },
     Status => {
         title     => 'Status',
         attribute => 'Status',
-        value     => sub { loc($_[0]->Status) }
+        value     => sub { loc($_[0]->Status) },
+        edit      => sub { return \($m->scomp("/Asset/Elements/SelectStatus", AssetObj => $_[0], Name => 'Status' ) ) },
     },
     ActiveTickets => {
         title     => 'Active tickets', # loc
diff --git a/share/html/Elements/ShowCustomFieldCustomGroupings b/share/html/Elements/ShowCustomFieldCustomGroupings
index 890a80a1c4..ddbefb35c1 100644
--- a/share/html/Elements/ShowCustomFieldCustomGroupings
+++ b/share/html/Elements/ShowCustomFieldCustomGroupings
@@ -124,7 +124,7 @@ $css_class .= '-info-cfs';
 
 my $TitleBoxARGS = delete $ARGS{TitleBoxARGS} || {};
 
-$InlineEdit = 0 unless $Object->isa('RT::Ticket');
+$InlineEdit = 0 unless $Object->isa('RT::Ticket') || $Object->isa('RT::Asset');
 my %inline_edit_behavior;
 if ( my $config = RT->Config->Get('InlineEditPanelBehavior') ) {
     %inline_edit_behavior = %{ $config->{ (RT::CustomField->_GroupingClass($Object))[0] } || $config->{'RT::Ticket'} || {} };
diff --git a/share/html/Asset/Display.html b/share/html/Helpers/AssetUpdate
similarity index 71%
copy from share/html/Asset/Display.html
copy to share/html/Helpers/AssetUpdate
index 553fb4f66a..b95e163c48 100644
--- a/share/html/Asset/Display.html
+++ b/share/html/Helpers/AssetUpdate
@@ -45,32 +45,35 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => loc("Asset #[_1]: [_2]", $asset->id, $asset->Name) &>
-<& /Elements/Tabs &>
+% $r->content_type('application/json; charset=utf-8');
+<% JSON( { actions => \@results } ) |n %>
+% $m->abort;
 
-% $m->callback(CallbackName => 'BeforeActionList', ARGSRef => \%ARGS, Asset => $asset, Results => \@results);
+<%ARGS>
+$id
+</%ARGS>
 
-<& /Elements/ListActions, actions => \@results &>
+<%INIT>
+my @results;
 
-<span class="catalog <% CSSClass($asset->CatalogObj->Name) %>">
-<& Elements/ShowSummary, AssetObj => $asset &>
+my $asset = LoadAsset($id);
 
-% $m->callback(CallbackName => 'AfterShowSummary', ARGSRef => \%ARGS, Asset => $asset);
+# fill ACL cache
+$asset->CurrentUser->PrincipalObj->HasRights( Object => $asset );
 
-<& /Elements/ShowHistory,
-    Object => $asset,
-    ShowDisplayModes => 0,
-    DisplayPath => 'History.html',
-&>
+$m->callback(CallbackName => 'ProcessArguments',
+             Asset => $asset,
+             ARGSRef => \%ARGS,
+             results => \@results);
 
-% $m->callback(CallbackName => 'AfterShowHistory', ARGSRef => \%ARGS, Asset => $asset);
-</span>
+push @results, ProcessAssetRoleMembers( $asset => %ARGS );
+push @results, ProcessRecordLinks( RecordObj => $asset, ARGSRef => \%ARGS );
+push @results, ProcessObjectCustomFieldUpdates( Object => $asset, ARGSRef => \%ARGS );
 
-<%args>
-$id => undef
-</%args>
-<%init>
-my @results;
-my $asset = LoadAsset($id);
-$m->callback(CallbackName => 'BeforeDisplay', ARGSRef => \%ARGS, Asset => $asset, Results => \@results);
-</%init>
+push @results, UpdateRecordObject(
+    Object        => $asset,
+    AttributesRef => [ $asset->WritableAttributes ],
+    ARGSRef       => \%ARGS
+);
+
+</%INIT>
diff --git a/share/html/Helpers/CollectionListRow b/share/html/Helpers/CollectionListRow
index a993f767d8..6d1cdc2905 100644
--- a/share/html/Helpers/CollectionListRow
+++ b/share/html/Helpers/CollectionListRow
@@ -60,7 +60,7 @@ $Warning       => undef
 $DisplayFormat = $m->comp('/Elements/ScrubHTML', Content => $DisplayFormat);
 my @Format = $m->comp('/Elements/CollectionAsTable/ParseFormat', Format => $DisplayFormat);
 
-$m->abort unless ( $ObjectClass // '' ) eq 'RT::Ticket';
+$m->abort unless ( $ObjectClass // '' ) =~ /^RT::(?:Ticket|Asset)$/;
 
 my $record = $ObjectClass->new($session{CurrentUser});
 $record->Load($ObjectId);
diff --git a/t/web/helpers-http-cache-headers.t b/t/web/helpers-http-cache-headers.t
index 2e661a231b..1f03859a8f 100644
--- a/t/web/helpers-http-cache-headers.t
+++ b/t/web/helpers-http-cache-headers.t
@@ -57,6 +57,19 @@ diag "create a ticket via the API";
     $ticket_id = $id;
 }
 
+my $asset_id;
+# Create an asset so requests to /Helpers/AssetUpdate can succeed.
+diag "create an asset via the API";
+{
+    my $asset = RT::Asset->new( RT->SystemUser );
+    my ($id, $txn, $msg) = $asset->Create(
+        Catalog => 'General assets',
+        Name    => 'test asset',
+    );
+    ok $id, 'created a asset #'. $id or diag "error: $msg";
+    is $asset->Name, 'test asset', 'correct name';
+    $asset_id = $id;
+}
 
 my $expected;
 diag "set up expected date headers";
@@ -81,7 +94,12 @@ diag "set up expected date headers";
 }
 
 foreach my $endpoint ( @endpoints ) {
-  $m->get_ok( $endpoint . "?id=${ticket_id}&Status=open&Requestor=root" );
+  if ( $endpoint =~ m{/Helpers/AssetUpdate} ) {
+    $m->get_ok( $endpoint . "?id=${asset_id}&Status=allocated" );
+  }
+  else {
+    $m->get_ok( $endpoint . "?id=${ticket_id}&Status=open&Requestor=root" );
+  }
 
   my $header_key = 'default';
   if ( $endpoint =~ m|Autocomplete| ) {

commit c9193de32b336b5747d90815dd3415053c16a26e
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Dec 8 15:21:55 2023 -0500

    Use role Name/GroupType in case corresponding role groups do not exist yet
    
    Custom role groups could be absent if the corresponding custom roles
    were created/applied after some assets or the lazy option is enabled.

diff --git a/share/html/Asset/Elements/EditRoleMembers b/share/html/Asset/Elements/EditRoleMembers
index e498f0b7fe..4dcd65c1f4 100644
--- a/share/html/Asset/Elements/EditRoleMembers
+++ b/share/html/Asset/Elements/EditRoleMembers
@@ -52,14 +52,14 @@ $Recursively => 0
 </%args>
 <%init>
 my $Group = $Object->RoleGroup($Role);
-my $field_name = "RemoveRoleMember-" . $Group->Name;
+my $field_name = "RemoveRoleMember-" . $Role;
 </%init>
 <ul class="role-members list-group list-group-compact">
 % my $Users = $Group->UserMembersObj( Recursively => $Recursively );
 % if ($Object->Role($Role)->{Single}) {
 % my $user = $Users->First || RT->Nobody;
   <li class="list-group-item">
-    <input class="form-control selectpicker" type="text" value="<% $user->Name %>" name="SetRoleMember-<% $Group->Name %>" id="SetRoleMember-<% $Group->Name %>" data-autocomplete="Users" data-autocomplete-return="Name" />
+    <input class="form-control selectpicker" type="text" value="<% $user->Name %>" name="SetRoleMember-<% $Role %>" id="SetRoleMember-<% $Role %>" data-autocomplete="Users" data-autocomplete-return="Name" />
   </li>
 % } else {
 % while ( my $user = $Users->Next ) {
diff --git a/t/assets/web.t b/t/assets/web.t
index 7f3e2f8d52..3b3aca6b11 100644
--- a/t/assets/web.t
+++ b/t/assets/web.t
@@ -145,6 +145,90 @@ diag "Bulk update";
     # TODO: test more bulk update actions
 }
 
+diag "People update";
+{
+    my $asset = create_asset( Name => "Test asset", Catalog => $catalog->Id );
+    $m->get_ok( '/Asset/Display.html?id=' . $asset->Id );
+    $m->follow_link_ok( { text => 'People' } );
+
+    my $form = $m->form_id('ModifyAssetPeople');
+    my $owner_input = $form->find_input('SetRoleMember-Owner');
+    ok( $owner_input, 'Found owner input' );
+    is( $owner_input->value, 'Nobody', 'Default owner is Nobody' );
+    $m->submit_form_ok(
+        {
+            fields => {
+                'SetRoleMember-Owner' => 'root',
+            },
+            button => 'Update',
+        },
+        'Submit form ModifyAssetPeople'
+    );
+    $m->text_contains('Owner set to root');
+
+    $form = $m->form_id('ModifyAssetPeople');
+    $owner_input = $form->find_input('SetRoleMember-Owner');
+    ok( $owner_input, 'Found owner input' );
+    is( $owner_input->value, 'root', 'Input value of owner is root' );
+
+    my $staff = RT::Test->load_or_create_group('Staff');
+    $m->submit_form_ok(
+        {
+            fields => {
+                'AddUserRoleMember-Role' => 'Contact',
+                AddUserRoleMember => 'alice at localhost',
+                'AddGroupRoleMember-Role' => 'HeldBy',
+                AddGroupRoleMember => 'Staff',
+            },
+            button => 'Update',
+        },
+        'Submit form ModifyAssetPeople'
+    );
+    $m->text_contains('Member added: alice at localhost');
+    $m->text_contains('Member added: Staff');
+
+    $form = $m->form_id('ModifyAssetPeople');
+    my $alice = RT::Test->load_or_create_user( Name => 'alice at localhost' );
+    $m->tick('RemoveRoleMember-Contact', $alice->Id);
+    $m->tick('RemoveRoleMember-HeldBy', $staff->Id);
+
+    $m->submit_form_ok(
+        {
+            button => 'Update',
+        },
+        'Submit form ModifyAssetPeople'
+    );
+    $m->text_contains('Member deleted');
+
+
+    # Add manager later to test if the page works with absent role groups.
+    my $manager = RT::CustomRole->new( RT->SystemUser );
+    ok(
+        $manager->Create(
+            Name       => 'Manager',
+            LookupType => RT::Asset->CustomFieldLookupType,
+            MaxValues  => 1,
+        )
+    );
+    ok( $manager->AddToObject( $catalog->Id ) );
+
+    $m->reload;
+    $form = $m->form_id('ModifyAssetPeople');
+    my $manager_input = $form->find_input( 'SetRoleMember-' . $manager->GroupType );
+    ok( $manager_input, 'Found manager input' );
+    is( $manager_input->value, 'Nobody', 'Default manager is Nobody' );
+    $m->submit_form_ok(
+        {
+            fields => {
+                'SetRoleMember-' . $manager->GroupType => 'root',
+            },
+            button => 'Update',
+        },
+        'Submit form ModifyAssetPeople'
+    );
+    $m->text_contains('Manager set to root');
+}
+
 # XXX TODO: test other modify pages
 
 done_testing;

-----------------------------------------------------------------------


hooks/post-receive
-- 
rt


More information about the rt-commit mailing list