Skip to content

Commit

Permalink
Support inline edit for assets
Browse files Browse the repository at this point in the history
  • Loading branch information
sunnavy committed Dec 8, 2023
1 parent c9193de commit dddfb22
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 15 deletions.
9 changes: 9 additions & 0 deletions etc/RT_Config.pm.in
Original file line number Diff line number Diff line change
Expand Up @@ -2593,6 +2593,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
Expand Down
46 changes: 45 additions & 1 deletion share/html/Asset/Display.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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>
93 changes: 93 additions & 0 deletions share/html/Asset/Elements/EditPeopleInline
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
%# BEGIN BPS TAGGED BLOCK {{{
%#
%# COPYRIGHT:
%#
%# This software is Copyright (c) 1996-2023 Best Practical Solutions, LLC
%# <[email protected]>
%#
%# (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., 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
%# you are the copyright holder for those contributions and you grant
%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
%# 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 }}}

% 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>
% }

<div class="add-user">
<h5 class="mt-2"><&|/l&>Add a person</&></h5>
<div class="form-row">
<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>

<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>
61 changes: 55 additions & 6 deletions share/html/Asset/Elements/ShowSummary
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
%# END BPS TAGGED BLOCK }}}
<%args>
$AssetObj
$InlineEdit => 0
</%args>
<%init>
my @sections = (
Expand All @@ -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";
Expand All @@ -67,20 +65,71 @@ 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'} || {} };
}

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 &>
<& /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'
&>
Expand Down
1 change: 1 addition & 0 deletions share/html/Asset/Search/Bulk.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
13 changes: 10 additions & 3 deletions share/html/Elements/CollectionAsTable/Row
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -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>' );
Expand Down
2 changes: 1 addition & 1 deletion share/html/Elements/CollectionList
Original file line number Diff line number Diff line change
Expand Up @@ -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"');
Expand Down
26 changes: 25 additions & 1 deletion share/html/Elements/ColumnMap
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}
Expand Down
6 changes: 5 additions & 1 deletion share/html/Elements/RT__Asset/ColumnMap
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit dddfb22

Please sign in to comment.