[Rt-commit] rt branch, gui-help, created. rt-5.0.1-431-gef71d493e4
Steve Burr
steve at bestpractical.com
Tue Jun 1 13:44:26 EDT 2021
The branch, gui-help has been created
at ef71d493e435d91080a69600e46a61ff3f74ca27 (commit)
- Log -----------------------------------------------------------------
commit 18875fe557b428641f6be8bd200818f083ba8eeb
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri May 14 02:10:29 2021 +0800
Migrate "RT at a glance" homepage to dashboard
Technically, "HomepageSettings" attributes are converted to dashboards
named "Homepage", and "DefaultDashboard" attributes keep the id of the
chosen dashboards to render "RT at a glance".
As there is no special format for "HomepageSettings", method
UpdateDashboard is simplifed to handle dashboards only.
diff --git a/etc/initialdata b/etc/initialdata
index 1ebe6d7fba..8e27963059 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -900,50 +900,6 @@ Hour: { $SubscriptionObj->SubValue('Hour') }
OrderBy => 'LastUpdated',
Order => 'DESC' },
},
- {
- Name => 'HomepageSettings',
- Description => 'HomepageSettings',
- Content => {
- 'body' => # loc_left_pair
- [
- {
- type => 'system',
- name => 'My Tickets', # loc
- },
- {
- type => 'system',
- name => 'Unowned Tickets' # loc
- },
- {
- type => 'system',
- name => 'Bookmarked Tickets' # loc
- },
- {
- type => 'component',
- name => 'QuickCreate' # loc
- },
- ],
- 'sidebar' => # loc_left_pair
- [
- {
- type => 'component',
- name => 'MyReminders' # loc
- },
- {
- type => 'component',
- name => 'QueueList' # loc
- },
- {
- type => 'component',
- name => 'Dashboards' # loc
- },
- {
- type => 'component',
- name => 'RefreshHomepage' # loc
- },
- ],
- },
- },
# initial reports
{ Name => 'ReportsInMenu',
Description => 'Content of the Reports menu', #loc
@@ -983,3 +939,95 @@ Hour: { $SubscriptionObj->SubValue('Hour') }
MaxValues => 1,
},
);
+
+ at Final = (
+ sub {
+ my $dashboard = RT::Dashboard->new( RT->SystemUser );
+ my ( $ret, $msg ) = $dashboard->Save(
+ Name => 'Homepage',
+ Privacy => join( '-', ref( RT->System ), RT->System->Id ),
+ );
+
+ if ($ret) {
+ my @searches;
+ for my $search_name ( 'My Tickets', 'Unowned Tickets', 'Bookmarked Tickets' ) {
+ my ($search) = RT::System->new( RT->SystemUser )->Attributes->Named( 'Search - ' . $search_name );
+ if ($search) {
+ push @searches,
+ {
+ pane => 'body',
+ portlet_type => 'search',
+ id => $search->Id,
+ description => "Saved Search: $search_name",
+ privacy => join( '-', ref( RT->System ), RT->System->Id ),
+ };
+ }
+ else {
+ RT->Logger->error("Couldn't find search $search_name");
+ }
+ }
+
+ my $panes = {
+ body => [
+ @searches,
+ { pane => 'body',
+ portlet_type => 'component',
+ component => 'QuickCreate',
+ description => 'QuickCreate',
+ path => '/Elements/QuickCreate',
+ },
+ ],
+ sidebar => [
+ { pane => 'sidebar',
+ portlet_type => 'component',
+ component => 'MyReminders',
+ description => 'MyReminders',
+ path => '/Elements/MyReminders',
+ },
+ { pane => 'sidebar',
+ portlet_type => 'component',
+ component => 'QueueList',
+ description => 'QueueList',
+ path => '/Elements/QueueList',
+ },
+ { pane => 'sidebar',
+ portlet_type => 'component',
+ component => 'MyReminders',
+ description => 'MyReminders',
+ path => '/Elements/MyReminders',
+ },
+ { pane => 'sidebar',
+ portlet_type => 'component',
+ component => 'Dashboards',
+ description => 'Dashboards',
+ path => '/Elements/Dashboards',
+ },
+ { pane => 'sidebar',
+ portlet_type => 'component',
+ component => 'RefreshHomepage',
+ description => 'RefreshHomepage',
+ path => '/Elements/RefreshHomepage',
+ },
+ ]
+ };
+
+ # fill content
+ my ( $ret, $msg ) = $dashboard->Update( Panes => $panes );
+ if ( !$ret ) {
+ RT->Logger->error("Couldn't update content for dashboard Homepage: $msg");
+ }
+
+ ( $ret, $msg ) = RT->System->SetAttribute(
+ 'Name' => 'DefaultDashboard',
+ 'Description' => 'Default Dashboard',
+ 'Content' => $dashboard->Id,
+ );
+ if ( !$ret ) {
+ RT->Logger->error("Couldn't set DefaultDashboard: $msg");
+ }
+ }
+ else {
+ RT->Logger->error("Couldn't create dashboard Homepage: $msg");
+ }
+ },
+);
diff --git a/etc/upgrade/5.0.2/content b/etc/upgrade/5.0.2/content
new file mode 100644
index 0000000000..0fba4ab9fd
--- /dev/null
+++ b/etc/upgrade/5.0.2/content
@@ -0,0 +1,149 @@
+use strict;
+use warnings;
+
+our @Final = (
+ sub {
+ my $attrs = RT::Attributes->new( RT->SystemUser );
+ $attrs->Limit( FIELD => 'Name', VALUE => [ 'Pref-HomepageSettings', 'HomepageSettings' ], OPERATOR => 'IN' );
+ OUTER: while ( my $attr = $attrs->Next ) {
+ my $attr_id = $attr->Id;
+ my $object = $attr->Object;
+ my $content = $attr->Content;
+
+ if ( $object && ( $object->isa('RT::User') || $object->isa('RT::System') ) && $content ) {
+ my $dashboard = RT::Dashboard->new( RT->SystemUser );
+ my $panes = {};
+
+ for my $pane ( sort keys %$content ) {
+ my $list = $content->{$pane} or next;
+ for my $entry (@$list) {
+ my $new_entry = { pane => $pane };
+ if ( $entry->{type} eq 'system' ) {
+ if ( my $name = $entry->{name} ) {
+ my ($search)
+ = RT::System->new( RT->SystemUser )->Attributes->Named( 'Search - ' . $name );
+
+ # Check user created system searches
+ if ( !$search ) {
+ my (@searches)
+ = RT::System->new( RT->SystemUser )->Attributes->Named('SavedSearch');
+ for my $custom (@searches) {
+ if ( $custom->Description eq $entry->{name} ) {
+ $search = $custom;
+ last;
+ }
+ }
+ }
+
+ if ( $search ) {
+ $new_entry->{portlet_type} = 'search';
+ $new_entry->{id} = $search->Id;
+ $new_entry->{description} = "Saved Search: $name";
+ $new_entry->{privacy} = 'RT::System-1';
+ }
+ else {
+ RT->Logger->warning(
+ "System search $name in attribute #$attr_id not found, skipping");
+ next;
+ }
+ }
+ else {
+ RT->Logger->warning("Missing system search name in attribute #$attr_id, skipping");
+ next;
+ }
+ }
+ elsif ( $entry->{type} eq 'saved' ) {
+ if ( my $name = $entry->{name} ) {
+ if ( $name =~ /(.+)-SavedSearch-(\d+)/ ) {
+ $new_entry->{privacy} = $1;
+ $new_entry->{id} = $2;
+ $new_entry->{portlet_type} = 'search';
+ my $search = RT::Attribute->new( RT->SystemUser );
+ $search->Load( $new_entry->{id} );
+ if ( $search->Id ) {
+ $new_entry->{description} = "Saved Search: " . $search->Description;
+ }
+ else {
+ RT->Logger->warning(
+ "Saved search $name in attribute #$attr_id not found, skipping");
+ next;
+ }
+ }
+ else {
+ RT->Logger->warning(
+ "System search $name in attribute #$attr_id not found, skipping");
+ next;
+ }
+ }
+ else {
+ RT->Logger->warning("Missing system search name in attribute #$attr_id, skipping");
+ next;
+ }
+ }
+ elsif ( $entry->{type} eq 'component' ) {
+ $new_entry->{portlet_type} = 'component';
+ $new_entry->{component} = $entry->{name};
+ $new_entry->{description} = $entry->{name};
+ $new_entry->{path} = "/Elements/$entry->{name}";
+ }
+ else {
+ RT->Logger->warning("Unsupported type $entry->{type} in attribute #$attr_id, skipping");
+ next;
+ }
+ push @{$panes->{$pane}}, $new_entry;
+ }
+ }
+
+ $RT::Handle->BeginTransaction;
+ my %new_values = (
+ 'Name' => 'Dashboard',
+ 'Description' => 'Homepage',
+ 'Content' => { Panes => $panes },
+ );
+
+ for my $field ( sort keys %new_values ) {
+ my $method = "Set$field";
+ my ( $ret, $msg ) = $attr->$method( $new_values{$field} );
+ if ( !$ret ) {
+ RT->Logger->error("Couldn't update $field of attribute #$attr_id: $msg");
+ $RT::Handle->Rollback;
+ next OUTER;
+ }
+ }
+
+ my ( $id, $msg ) = $object->SetAttribute(
+ 'Name' => $object->isa('RT::User') ? 'Pref-DefaultDashboard' : 'DefaultDashboard',
+ 'Description' => 'Default Dashboard',
+ 'Content' => $attr_id,
+ );
+ if ($id) {
+ $RT::Handle->Commit;
+ }
+ else {
+ RT->Logger->error("Couldn't set DefaultDashboard to $id for attribute #$attr_id: $msg");
+ $RT::Handle->Rollback;
+ }
+ }
+ }
+ },
+ sub {
+ my $acl = RT::ACL->new(RT->SystemUser);
+
+ # Grant dashboard rights so users with ModifySelf can still
+ # customize MyRT
+ $acl->Limit( FIELD => 'RightName', VALUE => 'ModifySelf' );
+ while ( my $ace = $acl->Next ) {
+ my $object = $ace->Object;
+ my $principal = $ace->PrincipalObj;
+
+ for my $right ( 'SeeOwnDashboard', 'CreateOwnDashboard', 'ModifyOwnDashboard', 'DeleteOwnDashboard' ) {
+ if ( !$principal->HasRight( Object => $object, Right => $right ) ) {
+ my ( $ret, $msg ) = $principal->GrantRight( Object => $object, Right => $right );
+ if ( !$ret ) {
+ RT->Logger->error( "Couldn't grant $right to user #" . $object->Id . ": $msg" );
+ }
+ }
+ }
+ }
+ },
+);
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 97140de731..c15054be06 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -4799,81 +4799,63 @@ sub UpdateDashboard {
}
my ( $ok, $msg );
- if ( $id eq 'MyRT' ) {
- my $user = $session{CurrentUser};
-
- if ( my $user_id = $args->{user_id} ) {
- my $UserObj = RT::User->new( $session{'CurrentUser'} );
- ( $ok, $msg ) = $UserObj->Load($user_id);
- return ( $ok, $msg ) unless $ok;
-
- return ( $ok, $msg ) = $UserObj->SetPreferences( 'HomepageSettings', $data->{panes} );
- } elsif ( $args->{is_global} ) {
- my $sys = RT::System->new( $session{'CurrentUser'} );
- my ($default_portlets) = $sys->Attributes->Named('HomepageSettings');
- return ( $ok, $msg ) = $default_portlets->SetContent( $data->{panes} );
- } else {
- return ( $ok, $msg ) = $user->SetPreferences( 'HomepageSettings', $data->{panes} );
- }
- } else {
- my $class = $args->{self_service_dashboard} ? 'RT::Dashboard::SelfService' : 'RT::Dashboard';
- my $Dashboard = $class->new( $session{'CurrentUser'} );
- ( $ok, $msg ) = $Dashboard->LoadById($id);
-
- # report error at the bottom
- return ( $ok, $msg ) unless $ok && $Dashboard->Id;
-
- my $content;
- for my $pane_name ( keys %{ $data->{panes} } ) {
- my @pane;
-
- for my $item ( @{ $data->{panes}{$pane_name} } ) {
- my %saved;
- $saved{pane} = $pane_name;
- $saved{portlet_type} = $item->{type};
-
- $saved{description} = $available_items->{ $item->{type} }{ $item->{name} }{label};
-
- if ( $item->{type} eq 'component' ) {
- $saved{component} = $item->{name};
-
- # Absolute paths stay absolute, relative paths go into
- # /Elements. This way, extensions that add portlets work.
- my $path = $item->{name};
- $path = "/Elements/$path" if substr( $path, 0, 1 ) ne '/';
-
- $saved{path} = $path;
- } elsif ( $item->{type} eq 'saved' ) {
- $saved{portlet_type} = 'search';
-
- $item->{searchType} = $available_items->{ $item->{type} }{ $item->{name} }{search_type}
- if exists $available_items->{ $item->{type} }{ $item->{name} }{search_type};
-
- my $type = $item->{searchType};
- $type = 'Saved Search' if !$type || $type eq 'Ticket';
- $saved{description} = loc($type) . ': ' . $saved{description};
-
- $item->{searchId} = $available_items->{ $item->{type} }{ $item->{name} }{search_id}
- if exists $available_items->{ $item->{type} }{ $item->{name} }{search_id};
-
- my ( $obj_type, $obj_id, undef, $search_id ) = split '-', $item->{name};
- $saved{privacy} = "$obj_type-$obj_id";
- $saved{id} = $search_id;
- } elsif ( $item->{type} eq 'dashboard' ) {
- my ( undef, $dashboard_id, $obj_type, $obj_id ) = split '-', $item->{name};
- $saved{privacy} = "$obj_type-$obj_id";
- $saved{id} = $dashboard_id;
- $saved{description} = loc('Dashboard') . ': ' . $saved{description};
- }
-
- push @pane, \%saved;
+ my $class = $args->{self_service_dashboard} ? 'RT::Dashboard::SelfService' : 'RT::Dashboard';
+ my $Dashboard = $class->new( $session{'CurrentUser'} );
+ ( $ok, $msg ) = $Dashboard->LoadById($id);
+
+ # report error at the bottom
+ return ( $ok, $msg ) unless $ok && $Dashboard->Id;
+
+ my $content;
+ for my $pane_name ( keys %{ $data->{panes} } ) {
+ my @pane;
+
+ for my $item ( @{ $data->{panes}{$pane_name} } ) {
+ my %saved;
+ $saved{pane} = $pane_name;
+ $saved{portlet_type} = $item->{type};
+
+ $saved{description} = $available_items->{ $item->{type} }{ $item->{name} }{label};
+
+ if ( $item->{type} eq 'component' ) {
+ $saved{component} = $item->{name};
+
+ # Absolute paths stay absolute, relative paths go into
+ # /Elements. This way, extensions that add portlets work.
+ my $path = $item->{name};
+ $path = "/Elements/$path" if substr( $path, 0, 1 ) ne '/';
+
+ $saved{path} = $path;
+ } elsif ( $item->{type} eq 'saved' ) {
+ $saved{portlet_type} = 'search';
+
+ $item->{searchType} = $available_items->{ $item->{type} }{ $item->{name} }{search_type}
+ if exists $available_items->{ $item->{type} }{ $item->{name} }{search_type};
+
+ my $type = $item->{searchType};
+ $type = 'Saved Search' if !$type || $type eq 'Ticket';
+ $saved{description} = loc($type) . ': ' . $saved{description};
+
+ $item->{searchId} = $available_items->{ $item->{type} }{ $item->{name} }{search_id}
+ if exists $available_items->{ $item->{type} }{ $item->{name} }{search_id};
+
+ my ( $obj_type, $obj_id, undef, $search_id ) = split '-', $item->{name};
+ $saved{privacy} = "$obj_type-$obj_id";
+ $saved{id} = $search_id;
+ } elsif ( $item->{type} eq 'dashboard' ) {
+ my ( undef, $dashboard_id, $obj_type, $obj_id ) = split '-', $item->{name};
+ $saved{privacy} = "$obj_type-$obj_id";
+ $saved{id} = $dashboard_id;
+ $saved{description} = loc('Dashboard') . ': ' . $saved{description};
}
- $content->{$pane_name} = \@pane;
+ push @pane, \%saved;
}
- return ( $ok, $msg ) = $Dashboard->Update( Panes => $content );
+ $content->{$pane_name} = \@pane;
}
+
+ return ( $ok, $msg ) = $Dashboard->Update( Panes => $content );
}
=head2 ListOfReports
@@ -5184,6 +5166,84 @@ sub PreprocessTimeUpdates {
RT::Interface::Web::PreprocessTimeUpdates(@_);
}
+
+=head2 GetDashboards Objects => ARRAY, CurrentUser => CURRENT_USER
+
+Return available dashboards that are saved in the name of objects for
+specified user.
+
+=cut
+
+sub GetDashboards {
+ my %args = (
+ Objects => undef,
+ CurrentUser => $session{CurrentUser},
+ @_,
+ );
+
+ return unless $args{CurrentUser};
+
+ $args{Objects} ||= [ RT::Dashboard->new( $args{CurrentUser} )->ObjectsForLoading( IncludeSuperuserGroups => 1 ) ];
+
+ my ($system_default) = RT::System->new( $args{'CurrentUser'} )->Attributes->Named('DefaultDashboard');
+ my $default_dashboard_id = $system_default ? $system_default->Content : 0;
+
+ my $found_system_default;
+
+ require RT::Dashboards;
+ my @dashboards;
+ my %system_default;
+ foreach my $object ( @{ $args{Objects} } ) {
+ my $list = RT::Dashboards->new( $args{CurrentUser} );
+ $list->LimitToPrivacy( join( '-', ref($object), $object->Id ) );
+ my $title;
+ if ( ref $object eq 'RT::User' && $object->Id == $session{CurrentUser}->Id ) {
+ $title = loc("My dashboards");
+ }
+ else {
+ $title = loc( "[_1]'s dashboards", $object->Name );
+ }
+
+ while ( my $dashboard = $list->Next ) {
+ my $name = join( ': ', $title, $dashboard->Name );
+
+ # Use current logged in user to determine if to return link or not
+ $dashboard->CurrentUser( $session{CurrentUser} );
+ push @dashboards,
+ {
+ id => $dashboard->Id,
+ name => $name,
+ link => $dashboard->CurrentUserCanSee( $args{CurrentUser} )
+ ? join( '/', RT->Config->Get('WebPath'), 'Dashboards', $dashboard->Id, $dashboard->Name )
+ : '',
+ };
+
+ if ( $dashboard->Id == $default_dashboard_id ) {
+ %system_default = %{ $dashboards[-1] };
+ }
+ }
+ }
+
+ if (%system_default) {
+ push @dashboards,
+ {
+ id => 0,
+ name => loc('System Default') . " ($system_default{name})",
+ link => $system_default{link},
+ };
+ }
+ else {
+ push @dashboards, {
+ id => 0,
+ name => loc('System Default'),
+ link => '',
+ };
+ }
+
+ @dashboards = sort { lc $a->{name} cmp lc $b->{name} } @dashboards;
+ return @dashboards;
+}
+
package RT::Interface::Web;
RT::Base->_ImportOverlays();
diff --git a/share/html/Admin/Global/MyRT.html b/share/html/Admin/Global/MyRT.html
index f5728279a1..f6c469cfb8 100644
--- a/share/html/Admin/Global/MyRT.html
+++ b/share/html/Admin/Global/MyRT.html
@@ -49,129 +49,36 @@
<& /Elements/Tabs &>
<& /Elements/ListActions, actions => \@results &>
-<form method="post" name="UpdateSearches" class="mx-auto max-width-lg">
- <& /Widgets/SearchSelection,
- pane_name => \%pane_name,
- sections => \@sections,
- selected => \%selected,
- filters => \@filters,
- &>
- <input type="hidden" name="dashboard_id" value="MyRT">
- <input type="hidden" name="is_global" value="1">
- <& /Elements/Submit, Name => "UpdateSearches", Label => loc('Save') &>
+<&|/Widgets/TitleBox, title => loc('Set Homepage'), bodyclass => "", class => "mx-auto max-width-lg" &>
+<form method="post" name="UpdateDefaultDashboard" class="mx-auto max-width-lg">
+ <div class="form-row">
+ <div class="col-12">
+ <& /Elements/SelectDashboard, Dashboards => [ GetDashboards( Objects => [ RT->System ] ) ], Default => $default_dashboard_id, ShowEmpty => 0 &>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="col-12">
+ <& /Elements/Submit, Name => "UpdateDefaultDashboard", Label => loc('Save') &>
+ </div>
+ </div>
</form>
+</&>
<%INIT>
+Abort( loc("Permission Denied") ) unless $session{CurrentUser}->HasRight( Right => 'SuperUser', Object => RT->System );
+
my @results;
my $title = loc("Customize").' '.loc("Global RT at a glance");
-my $portlets;
-my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
-$portlets = $defaults ? $defaults->Content : {};
-
-my @sections;
-my %item_for;
-
-my @components = map { type => "component", name => $_, label => loc($_) }, @{RT->Config->Get('HomepageComponents')};
-
-$item_for{ $_->{type} }{ $_->{name} } = $_ for @components;
-
-push @sections, {
- id => 'components',
- label => loc("Components"),
- items => \@components,
-};
-
-my $sys = RT::System->new($session{'CurrentUser'});
-my @objs = ($sys);
-
-push @objs, RT::SavedSearch->new( $session{CurrentUser} )->ObjectsForLoading
- if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch',
- Object => $RT::System );
-
-for my $object (@objs) {
- my @items;
- my $object_id = ref($object) . '-' . $object->Id;
-
- for ($m->comp("/Search/Elements/SearchesForObject", Object => $object)) {
- my ($desc, $loc_desc, $search) = @$_;
-
- my $SearchType = 'Ticket';
- if ((ref($search->Content)||'') eq 'HASH') {
- $SearchType = $search->Content->{'SearchType'}
- if $search->Content->{'SearchType'};
- }
- else {
- $RT::Logger->debug("Search ".$search->id." ($desc) appears to have no Content");
- }
-
- my $item;
- if ($object eq $sys && $SearchType eq 'Ticket') {
- $item = { type => 'system', name => $desc, label => $loc_desc };
- }
- else {
- my $oid = $object_id.'-SavedSearch-'.$search->Id;
- $item = { type => 'saved', name => $oid, search_type => $SearchType, label => $loc_desc };
- }
-
- $item_for{ $item->{type} }{ $item->{name} } = $item;
- push @items, $item;
- }
-
- my $label = $object eq $sys ? loc('System')
- : $object->isa('RT::Group') ? $object->Label
- : $object->Name;
-
- push @sections, {
- id => $object_id,
- label => $label,
- items => [ sort { lc($a->{label}) cmp lc($b->{label}) } @items ],
- };
-}
-
-my %selected;
-for my $pane (keys %$portlets) {
- my @items;
-
- for my $saved (@{ $portlets->{$pane} }) {
- my $item = $item_for{ $saved->{type} }{ $saved->{name} };
- if ($item) {
- push @items, $item;
- }
- else {
- push @results, loc('Unable to find [_1] [_2]', $saved->{type}, $saved->{name});
- }
- }
-
- $selected{$pane} = \@items;
-}
-
-my %pane_name = (
- 'body' => loc('Body'),
- 'sidebar' => loc('Sidebar'),
-);
-
-my @filters = (
- [ 'component' => loc('Components') ],
- [ 'ticket' => loc('Tickets') ],
- [ 'chart' => loc('Charts') ],
-);
-
-$m->callback(
- CallbackName => 'Default',
- pane_name => \%pane_name,
- sections => \@sections,
- selected => \%selected,
- filters => \@filters,
-);
-
-if ($ARGS{UpdateSearches}) {
- my ($ok, $msg) = UpdateDashboard( \%ARGS, \%item_for );
- push @results, $ok ? loc('Preferences saved.') : $msg;
+my ($system_default) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named('DefaultDashboard');
+my $default_dashboard_id = $system_default ? $system_default->Content : 0;
+if ( $ARGS{UpdateDefaultDashboard} && $ARGS{DefaultDashboard} ) {
+ my ( $ret, $msg ) = RT->System->SetAttribute( Name => 'DefaultDashboard', Description => 'Default Dashboard', Content => $ARGS{DefaultDashboard} );
+ push @results, $ret ? loc('Preferences saved.') : $msg;
MaybeRedirectForResults(
- Actions => \@results,
- Path => "/Admin/Global/MyRT.html",
+ Actions => \@results,
+ Path => "/Admin/Global/MyRT.html",
);
}
diff --git a/share/html/Admin/Users/MyRT.html b/share/html/Admin/Users/MyRT.html
index 7f97e567a1..91f14aa7d9 100644
--- a/share/html/Admin/Users/MyRT.html
+++ b/share/html/Admin/Users/MyRT.html
@@ -49,138 +49,45 @@
<& /Elements/Tabs &>
<& /Elements/ListActions, actions => \@actions &>
+<&|/Widgets/TitleBox, title => loc('Set Homepage'), bodyclass => "", class => "mx-auto max-width-lg" &>
<form method="post" action="MyRT.html" name="UpdateSearches" class="mx-auto max-width-lg">
- <& /Widgets/SearchSelection,
- pane_name => \%pane_name,
- sections => \@sections,
- selected => \%selected,
- filters => \@filters,
- &>
-<input type="hidden" name="id" value="<% $id %>"/>
-<input type="hidden" name="dashboard_id" value="MyRT">
-<& /Elements/Submit, Name => "UpdateSearches", Label => loc('Save') &>
-</form>
-<form method="post" action="MyRT.html?id=<% $id %>" class="mx-auto max-width-lg">
- <input type="hidden" name="Reset" value="1" />
- <& /Elements/Submit, Label => loc('Reset to default') &>
+ <input type="hidden" name="id" value="<% $id %>" />
+ <div class="form-row">
+ <div class="col-12">
+ <& /Elements/SelectDashboard, Dashboards => [ GetDashboards( CurrentUser => $UserObj ) ], Default => $default_dashboard_id &>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="col-12">
+ <& /Elements/Submit, Name => "UpdateDefaultDashboard", Label => loc('Save') &>
+ </div>
+ </div>
</form>
+</&>
<%init>
my @actions;
my $UserObj = RT::User->new($session{'CurrentUser'});
$UserObj->Load($id) || Abort("Couldn't load user '" . ($id || '') . "'");
-my $user = RT::User->new($session{'CurrentUser'});
my $title = loc("RT at a glance for the user [_1]", $UserObj->Name);
-if ($ARGS{Reset}) {
- for my $pref_name ('HomepageSettings', 'SummaryRows') {
- next unless $UserObj->Preferences($pref_name);
- my ($ok, $msg) = $UserObj->DeletePreferences($pref_name);
- push @actions, $msg unless $ok;
- }
- push @actions, loc('Preferences saved for user [_1].', $UserObj->Name) unless @actions;
-}
-
-my $portlets = $UserObj->Preferences('HomepageSettings');
-unless ($portlets) {
- my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
- $portlets = $defaults ? $defaults->Content : {};
-}
-
-my @sections;
-my %item_for;
-
-my @components = map { type => "component", name => $_, label => loc($_) }, @{RT->Config->Get('HomepageComponents')};
-$item_for{ $_->{type} }{ $_->{name} } = $_ for @components;
-
-push @sections, {
- id => 'components',
- label => loc("Components"),
- items => \@components,
- };
-my $sys = RT::System->new($session{'CurrentUser'});
-$sys->Load($id) || Abort("Couldn't load user '" . ($id || '') . "'");
-my @objs = ($sys);
-push @objs, RT::SavedSearch->new ($session{'CurrentUser'})->ObjectsForLoading
- if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch',
- Object => $RT::System );
-for my $object (@objs) {
- my @items;
- my $object_id = ref($object) . '-' . $object->Id;
- $object_id = 'system' if $object eq $sys;
-
- for ($m->comp("/Search/Elements/SearchesForObject", Object => $object)) {
- my ($desc, $loc_desc, $search) = @$_;
+my $default_dashboard_id = $UserObj->Preferences( DefaultDashboard => 0 );
- my $SearchType = 'Ticket';
- if ((ref($search->Content)||'') eq 'HASH') {
- $SearchType = $search->Content->{'SearchType'}
- if $search->Content->{'SearchType'};
- }
- else {
- $RT::Logger->debug("Search ".$search->id." ($desc) appears to have no Content");
- }
-
- my $item;
- if ($object eq $sys && $SearchType eq 'Ticket') {
- $item = { type => 'system', name => $desc, label => $loc_desc };
- }
- else {
- my $oid = $object_id.'-SavedSearch-'.$search->Id;
- $item = { type => 'saved', name => $oid, search_type => $SearchType, label => $loc_desc };
- }
- $item_for{ $item->{type} }{ $item->{name} } = $item;
- push @items, $item;
- }
-
- my $label = $object eq $sys ? loc('System')
- : $object->isa('RT::Group') ? $object->Label
- : $object->Name;
- push @sections, {
- id => $object_id,
- label => $label,
- items => [ sort { lc($a->{label}) cmp lc($b->{label}) } @items ],
- };
- }
-
-my %selected;
-for my $pane (keys %$portlets) {
- my @items;
-
- for my $saved (@{ $portlets->{$pane} }) {
- my $item = $item_for{ $saved->{type} }{ $saved->{name} };
- if ($item) {
- push @items, $item;
+if ( $ARGS{UpdateDefaultDashboard} && defined $ARGS{DefaultDashboard} ) {
+ my ( $ret, $msg );
+ if ( $ARGS{DefaultDashboard} ) {
+ ( $ret, $msg ) = $UserObj->SetPreferences( 'DefaultDashboard', $ARGS{DefaultDashboard} );
+ }
+ else {
+ if ( $default_dashboard_id ) {
+ ( $ret, $msg ) = $UserObj->DeletePreferences( 'DefaultDashboard' );
}
else {
- push @actions, loc('Unable to find [_1] [_2]', $saved->{type}, $saved->{name});
+ $ret = 1;
}
}
- $selected{$pane} = \@items;
- }
-
-my %pane_name = (
- 'body' => loc('Body'),
- 'sidebar' => loc('Sidebar'),
-);
-
-my @filters = (
- [ 'component' => loc('Components') ],
- [ 'ticket' => loc('Tickets') ],
- [ 'chart' => loc('Charts') ],
-);
-$m->callback(
- CallbackName => 'Default',
- pane_name => \%pane_name,
- sections => \@sections,
- selected => \%selected,
- filters => \@filters,
-);
-if ( $ARGS{UpdateSearches} ) {
- $ARGS{user_id} = $ARGS{id};
- my ($ok, $msg) = UpdateDashboard( \%ARGS, \%item_for );
- push @actions, $ok ? loc('Preferences saved for user [_1].', $UserObj->Name) : $msg;
+ push @actions, $ret ? loc('Preferences saved for user [_1].', $UserObj->Name) : $msg;
MaybeRedirectForResults(
Actions => \@actions,
diff --git a/share/html/Elements/MyRT b/share/html/Elements/MyRT
index 85271e7275..5d20c5b747 100644
--- a/share/html/Elements/MyRT
+++ b/share/html/Elements/MyRT
@@ -66,10 +66,18 @@ my %allowed_components = map {$_ => 1} @{RT->Config->Get('HomepageComponents')};
my $user = $session{'CurrentUser'}->UserObj;
unless ( $Portlets ) {
- my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
- $Portlets = $user->Preferences(
- HomepageSettings => $defaults ? $defaults->Content : {}
- );
+ my ($system_default) = RT::System->new($session{'CurrentUser'})->Attributes->Named('DefaultDashboard');
+ my $system_default_id = $system_default ? $system_default->Content : 0;
+ my $dashboard_id = $user->Preferences( DefaultDashboard => $system_default_id ) or return;
+
+ # Allow any user to read system default dashboard
+ my $dashboard = RT::Dashboard->new($system_default_id == $dashboard_id ? RT->SystemUser : $session{'CurrentUser'});
+ my ( $ok, $msg ) = $dashboard->LoadById( $dashboard_id );
+ if ( !$ok ) {
+ $m->out($msg);
+ return;
+ }
+ $Portlets = $dashboard->Panes;
}
$m->callback( CallbackName => 'MassagePortlets', Portlets => $Portlets );
@@ -83,10 +91,14 @@ $sidebar = undef unless $sidebar && @$sidebar;
my $Rows = $user->Preferences( 'SummaryRows', ( RT->Config->Get('DefaultSummaryRows') || 10 ) );
-my $show_cb = sub {
+my $show_cb;
+$show_cb = sub {
my $entry = shift;
- my $type = $entry->{type};
- my $name = $entry->{'name'};
+ my $depth = shift || 0;
+ Abort("Possible recursive dashboard detected.", SuppressHeader => 1) if $depth > 8;
+
+ my $type = $entry->{portlet_type};
+ my $name = $entry->{component};
if ( $type eq 'component' ) {
if (!$allowed_components{$name}) {
$m->out( $m->interp->apply_escapes( loc("Invalid portlet [_1]", $name), "h" ) );
@@ -98,10 +110,19 @@ my $show_cb = sub {
else {
$m->comp( $name, %{ $entry->{arguments} || {} } );
}
- } elsif ( $type eq 'system' ) {
- $m->comp( '/Elements/ShowSearch', Name => $name, Override => { Rows => $Rows } );
- } elsif ( $type eq 'saved' ) {
- $m->comp( '/Elements/ShowSearch', SavedSearch => $name, Override => { Rows => $Rows } );
+ } elsif ( $type eq 'search' ) {
+ $m->comp( '/Elements/ShowSearch', RT::Dashboard->ShowSearchName($entry), Override => { Rows => $Rows } );
+ } elsif ( $type eq 'dashboard' ) {
+ my $current_dashboard = RT::Dashboard->new($session{CurrentUser});
+ my ($ok, $msg) = $current_dashboard->LoadById($entry->{id});
+ if (!$ok) {
+ $m->out($msg);
+ return;
+ }
+ my @panes = @{ $current_dashboard->Panes->{$entry->{pane}} || [] };
+ for my $portlet (@panes) {
+ $show_cb->($portlet, $depth + 1);
+ }
} else {
$RT::Logger->error("unknown portlet type '$type'");
}
diff --git a/share/html/Elements/SelectDashboard b/share/html/Elements/SelectDashboard
new file mode 100644
index 0000000000..10382a99fd
--- /dev/null
+++ b/share/html/Elements/SelectDashboard
@@ -0,0 +1,69 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC
+%# <sales at 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., 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 $dashboard ( @Dashboards ) {
+% next if !$ShowEmpty && !$dashboard->{id};
+ <div class="custom-control custom-radio">
+ <input type="radio" id="<% "$Name-$dashboard->{id}" %>" name="<% $Name %>" class="custom-control-input" value="<% $dashboard->{id} %>" <% $dashboard->{id} == $Default ? ' checked="checked"': '' |n %>>
+ <label class="custom-control-label" for="<% "$Name-$dashboard->{id}" %>">
+% if ( $dashboard->{link} ) {
+ <a href="<% $dashboard->{link} %>" target="_blank"><% $dashboard->{name} %></a>
+% } else {
+ <% $dashboard->{name} %>
+% }
+ </label>
+ </div>
+% }
+
+
+<%ARGS>
+$Name => 'DefaultDashboard'
+ at Dashboards => ()
+$Default => 0
+$ShowEmpty => 1
+</%ARGS>
diff --git a/share/html/Elements/ShowSearch b/share/html/Elements/ShowSearch
index f8c05d5ab2..d647a1c82f 100644
--- a/share/html/Elements/ShowSearch
+++ b/share/html/Elements/ShowSearch
@@ -78,6 +78,11 @@ if ($SavedSearch) {
$m->out(loc("Saved search [_1] not found", $m->interp->apply_escapes($SavedSearch, 'h'))) unless $IgnoreMissing;
return;
}
+
+ if ( $search->Object->isa('RT::System') ) {
+ $SearchArg = $user->Preferences( $search, $search->Content );
+ }
+
$SearchArg->{'SavedSearchId'} ||= $SavedSearch;
$SearchArg->{'SearchType'} ||= 'Ticket';
if ( $SearchArg->{SearchType} eq 'Transaction' ) {
@@ -108,11 +113,19 @@ if ($SavedSearch) {
$query_link_url = RT->Config->Get('WebPath') . "/Search/$SearchArg->{SearchType}.html";
$ShowCount = 0;
} elsif ($ShowCustomize) {
- $customize = RT->Config->Get('WebPath') . '/Search/Build.html?'
- . $m->comp( '/Elements/QueryString',
- SavedSearchLoad => $SavedSearch );
+ if ( $search->Object->isa('RT::System') ) {
+ $customize = RT->Config->Get('WebPath') . '/Prefs/Search.html?'
+ . $m->comp( '/Elements/QueryString',
+ name => ref($search) . '-' . $search->Id );
+ }
+ else {
+ $customize = RT->Config->Get('WebPath') . '/Search/Build.html?'
+ . $m->comp( '/Elements/QueryString',
+ SavedSearchLoad => $SavedSearch );
+ }
}
} else {
+ RT->Deprecated( Message => 'Passing bare $Name is deprecated', Instead => '$SavedSearch', Remove => '5.2' );
($search) = RT::System->new( $session{'CurrentUser'} ) ->Attributes->Named( 'Search - ' . $Name );
unless ( $search && $search->Id ) {
my (@custom_searches) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named('SavedSearch');
diff --git a/share/html/Prefs/MyRT.html b/share/html/Prefs/MyRT.html
index 2e38498e70..77acef718b 100644
--- a/share/html/Prefs/MyRT.html
+++ b/share/html/Prefs/MyRT.html
@@ -49,16 +49,20 @@
<& /Elements/Tabs &>
<& /Elements/ListActions, actions => \@results &>
-<form method="post" name="UpdateSearches" class="mx-auto max-width-lg">
- <& /Widgets/SearchSelection,
- pane_name => \%pane_name,
- sections => \@sections,
- selected => \%selected,
- filters => \@filters,
- &>
- <input type="hidden" name="dashboard_id" value="MyRT">
- <& /Elements/Submit, Name => "UpdateSearches", Label => loc('Save') &>
+<&|/Widgets/TitleBox, title => loc('Set Homepage'), bodyclass => "", class => "mx-auto max-width-lg" &>
+<form method="post" name="UpdateDefaultDashboard" class="mx-auto max-width-lg">
+ <div class="form-row">
+ <div class="col-12">
+ <& /Elements/SelectDashboard, Dashboards => [ GetDashboards() ], Default => $default_dashboard_id &>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="col-12">
+ <& /Elements/Submit, Name => "UpdateDefaultDashboard", Label => loc('Save') &>
+ </div>
+ </div>
</form>
+</&>
<&|/Widgets/TitleBox, title => loc('Options'), bodyclass => "", class => "mx-auto max-width-lg" &>
<form method="post" action="MyRT.html">
@@ -75,13 +79,6 @@
</div>
</form>
</&>
-<&|/Widgets/TitleBox, title => loc("Reset RT at a glance"), class => "mx-auto max-width-lg" &>
-<form method="post" action="MyRT.html">
-<input type="hidden" name="Reset" value="1" />
-<input type="submit" class="button form-control btn btn-primary" value="<% loc('Reset to default') %>">
-</form>
-</&>
-
<%INIT>
my @results;
@@ -100,124 +97,27 @@ if ( $ARGS{'UpdateSummaryRows'} ) {
}
$ARGS{'SummaryRows'} ||= $user->Preferences('SummaryRows', RT->Config->Get('DefaultSummaryRows'));
-if ($ARGS{Reset}) {
- for my $pref_name ('HomepageSettings', 'SummaryRows') {
- next unless $user->Preferences($pref_name);
- my ($ok, $msg) = $user->DeletePreferences($pref_name);
- push @results, $msg unless $ok;
- }
- push @results, loc('Preferences saved.') unless @results;
-}
-
-my $portlets = $user->Preferences('HomepageSettings');
-unless ($portlets) {
- my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
- $portlets = $defaults ? $defaults->Content : {};
-}
-
-my @sections;
-my %item_for;
-
-my @components = map { type => "component", name => $_, label => loc($_) }, @{RT->Config->Get('HomepageComponents')};
-
-$item_for{ $_->{type} }{ $_->{name} } = $_ for @components;
-
-push @sections, {
- id => 'components',
- label => loc("Components"),
- items => \@components,
-};
-
-my $sys = RT::System->new($session{'CurrentUser'});
-my @objs = ($sys);
-
-push @objs, RT::SavedSearch->new( $session{CurrentUser} )->ObjectsForLoading
- if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch',
- Object => $RT::System );
-
-for my $object (@objs) {
- my @items;
- my $object_id = ref($object) . '-' . $object->Id;
-
- for ($m->comp("/Search/Elements/SearchesForObject", Object => $object)) {
- my ($desc, $loc_desc, $search) = @$_;
+my $default_dashboard_id = $session{'CurrentUser'}->Preferences( DefaultDashboard => 0 );
- my $SearchType = 'Ticket';
- if ((ref($search->Content)||'') eq 'HASH') {
- $SearchType = $search->Content->{'SearchType'}
- if $search->Content->{'SearchType'};
- }
- else {
- $RT::Logger->debug("Search ".$search->id." ($desc) appears to have no Content");
- }
-
- my $item;
- if ($object eq $sys && $SearchType eq 'Ticket') {
- $item = { type => 'system', name => $desc, label => $loc_desc };
- }
- else {
- my $oid = $object_id.'-SavedSearch-'.$search->Id;
- $item = { type => 'saved', name => $oid, search_type => $SearchType, label => $loc_desc };
- }
-
- $item_for{ $item->{type} }{ $item->{name} } = $item;
- push @items, $item;
+if ( $ARGS{UpdateDefaultDashboard} && defined $ARGS{DefaultDashboard} ) {
+ my ( $ret, $msg );
+ if ( $ARGS{DefaultDashboard} ) {
+ ( $ret, $msg ) = $session{CurrentUser}->SetPreferences( 'DefaultDashboard', $ARGS{DefaultDashboard} );
}
-
- my $label = $object eq $sys ? loc('System')
- : $object->isa('RT::Group') ? $object->Label
- : $object->Name;
-
- push @sections, {
- id => $object_id,
- label => $label,
- items => [ sort { lc($a->{label}) cmp lc($b->{label}) } @items ],
- };
-}
-
-my %selected;
-for my $pane (keys %$portlets) {
- my @items;
-
- for my $saved (@{ $portlets->{$pane} }) {
- my $item = $item_for{ $saved->{type} }{ $saved->{name} };
- if ($item) {
- push @items, $item;
+ else {
+ if ( $default_dashboard_id ) {
+ ( $ret, $msg ) = $session{CurrentUser}->DeletePreferences( 'DefaultDashboard' );
}
else {
- push @results, loc('Unable to find [_1] [_2]', $saved->{type}, $saved->{name});
+ $ret = 1;
}
}
- $selected{$pane} = \@items;
-}
-
-my %pane_name = (
- 'body' => loc('Body'),
- 'sidebar' => loc('Sidebar'),
-);
-
-my @filters = (
- [ 'component' => loc('Components') ],
- [ 'ticket' => loc('Tickets') ],
- [ 'chart' => loc('Charts') ],
-);
-
-$m->callback(
- CallbackName => 'Default',
- pane_name => \%pane_name,
- sections => \@sections,
- selected => \%selected,
- filters => \@filters,
-);
-
-if ($ARGS{UpdateSearches}) {
- my ($ok, $msg) = UpdateDashboard( \%ARGS, \%item_for );
- push @results, $ok ? loc('Preferences saved.') : $msg;
+ push @results, $ret ? loc('Preferences saved.') : $msg;
MaybeRedirectForResults(
- Actions => \@results,
- Path => "/Prefs/MyRT.html",
+ Actions => \@results,
+ Path => "/Prefs/MyRT.html",
);
}
commit 287d6649d55c7e77ab0c327b336c813a3965da67
Author: Steven Burr <steve at bestpractical.com>
Date: Tue May 25 14:31:48 2021 -0400
WIP
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a89ad6a4b9..a444e5b552 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -706,6 +706,33 @@ sub DeleteValue {
}
+sub ValidateValue {
+ my $self = shift;
+ my $value = shift;
+
+ my $valid = $self->SUPER::ValidateValue($value);
+ warn "XXX super valid = $valid";
+ return 0 unless $valid;
+
+ # NB: ensuring that the value (including possibly an empty one)
+ # matches any validation pattern defined is already checked in
+ # AddValueForObject and DeleteValueForObject
+
+ # make sure the value is legal for Select custom fields
+ if ( $self->Type eq "Select" ) {
+ if ( $value ) {
+ my $cfvs = $self->Values;
+ while (my $cfv = $cfvs->Next) {
+ my $name = $cfv->Name;
+ return 1 if $name eq $value;
+ }
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
=head2 ValidateQueue Queue
Make sure that the name specified is valid
diff --git a/lib/RT/Record.pm b/lib/RT/Record.pm
index 0cd12b73e5..67b70725fe 100644
--- a/lib/RT/Record.pm
+++ b/lib/RT/Record.pm
@@ -943,16 +943,13 @@ sub Update {
# items. If it fails, we don't care
do {
no warnings "uninitialized";
-
- if ( $attribute ne 'Lifecycle' ) {
- local $@;
- my $name = eval {
- my $object = $attribute . "Obj";
- $self->$object->Name;
- };
- unless ($@) {
- next if $name eq $value || $name eq ( $value || 0 );
- }
+ local $@;
+ my $name = eval {
+ my $object = $attribute . "Obj";
+ $self->$object->Name;
+ };
+ unless ($@) {
+ next if $name eq $value || $name eq ($value || 0);
}
next if $truncated_value eq $self->$attribute();
@@ -2181,7 +2178,7 @@ sub CustomFieldValueIsEmpty {
: $self->LoadCustomFieldByIdentifier( $args{'Field'} );
if ($cf) {
- if ( $cf->__Value('Type') =~ /^Date(?:Time)?$/ ) {
+ if ( $cf->Type =~ /^Date(?:Time)?$/ ) {
my $DateObj = RT::Date->new( $self->CurrentUser );
$DateObj->Set(
Format => 'unknown',
@@ -2592,9 +2589,7 @@ sub CustomDateRange {
# Prefer the schedule/timezone specified in %ServiceAgreements for current object
if ( $self->isa('RT::Ticket') && !$self->QueueObj->SLADisabled && $self->SLA ) {
if ( my $config = RT->Config->Get('ServiceAgreements') ) {
- if ( ref( $config->{QueueDefault}{ $self->QueueObj->Name } ) eq 'HASH' ) {
- $timezone = $config->{QueueDefault}{ $self->QueueObj->Name }{Timezone};
- }
+ $timezone = $config->{QueueDefault}{ $self->QueueObj->Name }{Timezone};
# Each SLA could have its own schedule and timezone
if ( my $agreement = $config->{Levels}{ $self->SLA } ) {
diff --git a/share/html/Elements/PopupHelp b/share/html/Elements/PopupHelp
new file mode 100644
index 0000000000..1053a95a78
--- /dev/null
+++ b/share/html/Elements/PopupHelp
@@ -0,0 +1,150 @@
+<%args>
+$key => ''
+</%args>
+<%init>
+my $has_help;
+my $help_class;
+my $help_content;
+
+# TODO: put in RT::Site::Siemens
+# TODO: add support for langs (cf?)
+sub GetArticleContent {
+ my $article_class = shift || return ''; # required
+ my $article_name = shift || return ''; # required
+ my $article_context = shift || ""; # optional
+ my $article_locales = shift || []; # optional
+
+ # lookup the article class
+ my $Class = RT::Class->new( RT->SystemUser );
+ my ($ret, $msg) = $Class->Load( $article_class );
+ my $article_classid = $Class->Id;
+ if ($ret and $Class->Id) {
+ RT::Logger->debug("Found article class id: " . $Class->Id);
+ } else {
+ RT::Logger->debug("No article class found for '$article_class' : $msg");
+ return '';
+ }
+
+ # find the article of the given class
+ my $Article = RT::Article->new( RT->SystemUser );
+ ($ret, $msg) = $Article->LoadByCols( Name => $article_name, Class => $Class->Id );
+ if ( $Article and $Article->Id ) {
+ RT::Logger->debug("Found article id: " . $Article->Id);
+ my $class = $Article->ClassObj;
+ my $cfs = $class->ArticleCustomFields;
+ while (my $cf = $cfs->Next) {
+ if ($cf->Name eq 'Content') {
+ my $ocfvs = $Article->CustomFieldValues($cf->Id);
+ my $ocfv = $ocfvs->First;
+ return HTML::Mason::Commands::ScrubHTML($ocfv->Content);
+ }
+ }
+ } else {
+ RT::Logger->debug("No article found for '$article_name'");
+ return '';
+ }
+ return;
+}
+
+# TODO: put in RT::Site::Siemens
+sub GetArticleContentNext {
+ my $article_class = shift || return ''; # required
+ my $article_name = shift || return ''; # required
+ my $article_context = shift || ""; # optional
+ my $article_locales = shift || []; # optional
+
+ # lookup the article class
+ my $cls = RT::Class->new( RT->SystemUser );
+ my ($ret, $msg) = $cls->Load( $article_class );
+ my $article_classid = $cls->Id;
+ if ($ret and $article_classid) {
+ RT::Logger->debug("Found article class id: $article_classid");
+ } else {
+ RT::Logger->debug("No article class found for '$article_class' : $msg");
+ return '';
+ }
+
+ # find the article of the given class
+ my $art = RT::Article->new( RT->SystemUser );
+ ($ret, $msg) = $art->LoadByCols( Name => $article_name, Class => $article_classid );
+ my $article_id = $art->Id;
+ if ( $ret and $article_id ) {
+ RT::Logger->debug("Found article id: $article_id");
+ my $cls = $art->ClassObj;
+ my $cfs = $cls->ArticleCustomFields;
+ while (my $cf = $cfs->Next) {
+ if ($cf->Name eq 'Content') {
+ my $ocfvs = $art->CustomFieldValues($cf->Id);
+ my $ocfv = $ocfvs->First;
+ return HTML::Mason::Commands::ScrubHTML($ocfv->Content);
+ }
+ }
+ } else {
+ RT::Logger->debug("No article found for '$article_name'");
+ return '';
+ }
+ return;
+}
+# TODO: put in RT::Site::Siemens
+#sub GetArticleContentWithClass {
+# my $article_class = shift || return ''; # required
+# my $article_name = shift || return ''; # required
+# my $article_context = shift || ""; # optional
+# my $article_locales = shift || []; # optional
+#
+# # lookup the article class
+# my $cls = RT::Class->new( RT->SystemUser );
+# my ($ret, $msg) = $cls->Load( $article_class );
+# my $article_classid = $cls->Id;
+# if ($ret and $cls->Id) {
+# RT::Logger->debug("Found article class id: " . $cls->Id);
+# } else {
+# RT::Logger->debug("No article class found for '$article_class' : $msg");
+# return '';
+# }
+#
+# # find the article of the given class
+# my $articles = RT::Articles->new( RT->SystemUser );
+# $articles->Limit({ FIELD => 'Name', OPERATOR => '=', VALUE => $article_name });
+# $articles->Limit({ FIELD => 'Class', OPERATOR => '=', VALUE => $cls->Id, ENTRYAGGREGATOR => 'AND' });
+# if ($article_context) {
+# my $ctx_cf = RT::CustomField->new( RT->SystemUser );
+# my ($ret, $msg) = $ctx_cf->Load( 'Context' );
+# $articles->LimitCustomField({ FIELD => $ctx_cf->Name, OPERATOR => '=', VALUE => $article_context, ENTRYAGGREGATOR => 'AND' });
+# }
+# if ($article_locales->length) {
+# my $loc_cf = RT::CustomField->new( RT->SystemUser );
+# my ($ret, $msg) = $loc_cf->Load( 'Locale' );
+# $articles->LimitCustomField({ FIELD => $loc_cf->Name, OPERATOR => '=', VALUE => $loc, ENTRYAGGREGATOR => 'AND' });
+# }
+#
+# $Articles->LimitCustomField({ FIELD => '' })
+# if ( $Article and $Article->Id ) {
+# RT::Logger->debug("Found article id: " . $Article->Id);
+# my $class = $Article->ClassObj;
+# my $cfs = $class->ArticleCustomFields;
+# while (my $cf = $cfs->Next) {
+# my $ocfvs = $Article->CustomFieldValues($cf->Id);
+# my $ocfv = $ocfvs->First;
+# HTML::Mason::Commands::ScrubHTML($ocfv->Content);
+# }
+# } else {
+# RT::Logger->debug("No article found for " . $article_name);
+# return '';
+# }
+# return;
+#}
+
+if ($key) {
+ my $lh = $session{'CurrentUser'}->LanguageHandle;
+ my @locs = [ $lh->language_tag(), $lh->fallback_languages() ];
+ my $help_class = RT::Config->Get( 'PopupHelpArticleClass' ) || 'Help';
+ $help_content = GetArticleContent( $help_class, $key, \@locs );
+ $has_help = $help_content;
+}
+</%init>
+% if ($has_help) {
+<a class="popup-help" tabindex="0" role="button" data-toggle="popover" title="<% $key %>" data-content="<% $help_content %>" data-html="true" data-trigger="focus">
+ <img src="/static/images/question.svg" data-toggle="popover" title="<% $key %>" data-content="<% $help_content %>" data-html="true" data-trigger="focus" />
+</a>
+% }
diff --git a/share/html/Helpers/InitialPrefs b/share/html/Helpers/InitialPrefs
new file mode 100644
index 0000000000..75b8d47d95
--- /dev/null
+++ b/share/html/Helpers/InitialPrefs
@@ -0,0 +1,66 @@
+<%args>
+$Update => 0,
+$PreferredKey => undef,
+</%args>
+<%init>
+my @results = ();
+my $UserObj = $session{'CurrentUser'}->UserObj;
+my $preferences = $UserObj->Preferences( $RT::System );
+
+if (defined($PreferredKey) and (not $UserObj->FirstAttribute('PreferredKey')
+ or $PreferredKey ne $UserObj->FirstAttribute('PreferredKey')->Content)) {
+ my ($code, $msg) = $UserObj->SetAttribute(Name => 'PreferredKey', Content => $PreferredKey);
+ push @results, loc('Preferred Key: [_1]', $msg) unless $code;
+}
+
+if ( $Update ) {
+ warn "XXX - updating";
+ $preferences ||= {};
+ $m->comp( '/Widgets/BulkProcess', Meta => {
+ map { $_ => RT->Config->Meta($_) } RT->Config->Options
+ },
+ Store => $preferences,
+ Types => [RT->Config->Options], Default => 1, Arguments => \%ARGS,
+ DefaultValue => { map { $_ => RT->Config->Get($_) }
+ RT->Config->Options
+ }, );
+
+ my ($ok, $msg) = $UserObj->SetPreferences( $RT::System, $preferences );
+ push @results, $ok ? loc("Preferences saved.") : $msg;
+ warn "XXX - updated";
+ $m->redirect( '/' );
+}
+</%init>
+<div class="modal-dialog modal-dialog-centered" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title"><&|/l&>Welcome to Vurious</&></h5>
+ <a href="javascript:void(0)" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </a>
+ </div>
+ <div class="modal-body">
+ <p>Since this appears to be your first time accessing Vurious, please take a moment to help us better customize the experience for you.</p>
+ <form method="post" action="/Helpers/InitialPrefs" name="InitialPreferences" id="InitialPreferences">
+ <&|/Widgets/TitleBox, title => loc( 'Preferences' ) &>
+% my $option = 'EmailFrequency'; # 'DashboardEmailLanguage'
+% my $meta = RT->Config->Meta( $option );
+ <& $meta->{'Widget'},
+ Default => 1,
+ %{ $m->comp('/Widgets/FinalizeWidgetArguments', WidgetArguments => $meta->{'WidgetArguments'} ) },
+ Name => $option,
+ DefaultValue => scalar RT->Config->Get( $option ),
+ CurrentValue => $preferences->{ $option },
+ &>
+ </&>
+
+ <div class="form-row">
+ <div class="col-12">
+ <& /Elements/Submit, Name => 'Update', Label => loc('Save Changes') &>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+% $m->abort;
diff --git a/share/html/Ticket/Display.html b/share/html/Ticket/Display.html
index 87883d5419..192d333fbb 100644
--- a/share/html/Ticket/Display.html
+++ b/share/html/Ticket/Display.html
@@ -56,6 +56,11 @@
<& Elements/ShowUpdateStatus, Ticket => $TicketObj &>
<& Elements/ShowDependencyStatus, Ticket => $TicketObj &>
+<div border="2px solid #1278c6; margin: 10px; padding: 10px;">
+<button class="btn btn-danger" id="show-help">Show Help</button>
+<button class="btn btn-danger" id="hide-help">Hide Help</button>
+</div>
+
% $m->callback( %ARGS, Ticket => $TicketObj, Transactions => $transactions, Attachments => $attachments, CallbackName => 'BeforeShowSummary' );
<%PERL>
@@ -181,6 +186,7 @@ if ($ARGS{'id'} eq 'new') {
my $SkipProcessing;
$TicketObj->Atomic(sub{
+
$m->callback( CallbackName => 'BeforeProcessArguments',
TicketObj => $TicketObj,
ActionsRef => \@Actions, ARGSRef => \%ARGS,
diff --git a/share/html/Ticket/Elements/ShowBasics b/share/html/Ticket/Elements/ShowBasics
index ae90b2d13f..e3574f86c5 100644
--- a/share/html/Ticket/Elements/ShowBasics
+++ b/share/html/Ticket/Elements/ShowBasics
@@ -45,13 +45,27 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
+
+<script>
+jQuery(document).ready(function () {
+ jQuery('[data-toggle="popover"]').popover({trigger: 'focus'})
+ jQuery('#show-help').click(function(e) { jQuery('.popup-help').show() })
+ jQuery('#hide-help').click(function(e) { jQuery('.popup-help').hide() })
+})
+</script>
+<style>
+.popup-help:active, .popup-help:hover { text-decoration: none; }
+.popup-help, .popup-help:focus { outline: none; }
+.popup-help img { padding: 3px; }
+</style>
+
<div>
<div class="id form-row">
- <div class="label col-3"><&|/l&>Id</&>:</div>
+ <div class="label col-3"><&|/l&>Id</&><& /Elements/PopupHelp, key => 'Ticket Id' &>:</div>
<div class="value col-9"><span><%$Ticket->Id %></span></div>
</div>
<div class="status form-row">
- <div class="label col-3"><&|/l&>Status</&>:</div>
+ <div class="label col-3" data-rt-help-key="TicketStatus"><&|/l&>Status</&><& /Elements/PopupHelp, key => 'Ticket Status' &>:</div>
<div class="value col-9"><span class="current-value"><% loc($Ticket->Status) %></span></div>
</div>
% if ( !$Ticket->QueueObj->SLADisabled ) {
@@ -113,7 +127,7 @@
%# This will check SeeQueue at the ticket role level, queue level, and global level
% if ($Ticket->CurrentUserHasRight('SeeQueue')) {
<div class="queue form-row">
- <div class="label col-3"><&|/l&>Queue</&>:</div>
+ <div class="label col-3"><&|/l&>Queue</&><& /Elements/PopupHelp, key => 'Ticket Queue' &>:</div>
<div class="value col-9"><span class="current-value"><& ShowQueue, Ticket => $Ticket, QueueObj => $Ticket->QueueObj &></span></div>
</div>
% }
diff --git a/share/static/images/question.svg b/share/static/images/question.svg
new file mode 100644
index 0000000000..89ebf0e2fc
--- /dev/null
+++ b/share/static/images/question.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zM6.92 6.085c.081-.16.19-.299.34-.398.145-.097.371-.187.74-.187.28 0 .553.087.738.225A.613.613 0 019 6.25c0 .177-.04.264-.077.318a.956.956 0 01-.277.245c-.076.051-.158.1-.258.161l-.007.004a7.728 7.728 0 00-.313.195 2.416 2.416 0 00-.692.661.75.75 0 001.248.832.956.956 0 01.276-.245 6.3 6.3 0 01.26-.16l.006-.004c.093-.057.204-.123.313-.195.222-.149.487-.355.692-.662.214-.32.329-.702.329-1.15 0-.76-.36-1.348-.863-1.725A2.76 2.76 0 008 4c-.631 0-1.155.16-1.572.438-.413.276-.68.638-.849.977a.75.75 0 101.342.67z"></path></svg>
\ No newline at end of file
commit f462fd8263e4c127e9dcd406c928b647d9862e02
Author: sunnavy <sunnavy at bestpractical.com>
Date: Sat May 15 00:59:13 2021 +0800
Add "New Dashboard" to prefs/global MyRT page menu for convenience
diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index 3b3b67c554..6d42200acb 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -933,6 +933,20 @@ sub BuildMainNav {
}
}
+ # Top menu already has the create link, adding it to page menu is just
+ # for convenience. As user admin page already has quite a few items in
+ # page menu and it's unlikely that admins want to create new dashboard
+ # when editing a user's preference, here we don't touch admin user page.
+ if ( $request_path =~ m{^/(Prefs|Admin/Global)/MyRT\.html} ) {
+ if ( RT::Dashboard->new($current_user)->CurrentUserCanCreateAny ) {
+ $page->child(
+ 'dashboard_create' => title => loc('New Dashboard'),
+ path => "/Dashboards/Modify.html?Create=1"
+ );
+ }
+ }
+
+
if ( $request_path =~ /^\/(?:index.html|$)/ ) {
my $alt = loc('Edit');
$page->child( edit => raw_html => q[<a id="page-edit" class="menu-item" href="] . RT->Config->Get('WebPath') . qq[/Prefs/MyRT.html"><span class="fas fa-cog" alt="$alt" data-toggle="tooltip" data-placement="top" data-original-title="$alt"></span></a>] );
commit 0d8924a76f2903e41eeaca56589b4a0ad96cd0df
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri May 14 06:13:19 2021 +0800
Update tests for the migration of "RT at a glance" => dashboard
diff --git a/t/api/initialdata-roundtrip.t b/t/api/initialdata-roundtrip.t
index a5b2b68329..69d364e123 100644
--- a/t/api/initialdata-roundtrip.t
+++ b/t/api/initialdata-roundtrip.t
@@ -1026,10 +1026,10 @@ my @tests = (
present => sub {
# Provided in core initialdata
my $homepage = RT::Attribute->new(RT->SystemUser);
- $homepage->LoadByNameAndObject(Name => 'HomepageSettings', Object => RT->System);
+ $homepage->LoadByNameAndObject(Name => 'Dashboard', Description => 'Homepage', Object => RT->System);
ok($homepage->Id, 'Loaded homepage attribute');
- is($homepage->Name, 'HomepageSettings', 'Name is HomepageSettings');
- is($homepage->Description, 'HomepageSettings', 'Description is HomepageSettings');
+ is($homepage->Name, 'Dashboard', 'Name is Dashboard');
+ is($homepage->Description, 'Homepage', 'Description is Homepage');
is($homepage->ContentType, 'storable', 'ContentType is storable');
my $root = RT::User->new(RT->SystemUser);
diff --git a/t/web/custom_frontpage.t b/t/web/custom_frontpage.t
index cbfcde0c55..3db735e054 100644
--- a/t/web/custom_frontpage.t
+++ b/t/web/custom_frontpage.t
@@ -16,6 +16,10 @@ $user_obj->PrincipalObj->GrantRight(Right => 'LoadSavedSearch');
$user_obj->PrincipalObj->GrantRight(Right => 'EditSavedSearches');
$user_obj->PrincipalObj->GrantRight(Right => 'CreateSavedSearch');
$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
+$user_obj->PrincipalObj->GrantRight(Right => 'SeeDashboard');
+$user_obj->PrincipalObj->GrantRight(Right => 'SeeOwnDashboard');
+$user_obj->PrincipalObj->GrantRight(Right => 'CreateOwnDashboard');
+$user_obj->PrincipalObj->GrantRight(Right => 'ModifyOwnDashboard');
ok $m->login( customer => 'customer' ), "logged in";
@@ -28,11 +32,24 @@ $m->field ( "ValueOfAttachment" => 'stupid');
$m->field ( "SavedSearchDescription" => 'stupid tickets');
$m->click_button (name => 'SavedSearchSave');
-$m->get ( $url.'Prefs/MyRT.html' );
+$m->get_ok( $url . "Dashboards/Modify.html?Create=1" );
+$m->form_name('ModifyDashboard');
+$m->field( Name => 'My homepage' );
+$m->click_button( value => 'Create' );
+
+$m->follow_link_ok( { text => 'Content' } );
$m->content_contains('stupid tickets', 'saved search listed in rt at a glance items');
ok $m->login('root', 'password', logout => 1), 'we did log in as root';
+$m->get_ok( $url . "Dashboards/Modify.html?Create=1" );
+$m->form_name('ModifyDashboard');
+$m->field( Name => 'My homepage' );
+$m->click_button( value => 'Create' );
+
+my ($id) = ( $m->uri =~ /id=(\d+)/ );
+ok( $id, "got a dashboard ID, $id" );
+
my $args = {
UpdateSearches => "Save",
dashboard_id => "MyRT",
@@ -41,18 +58,29 @@ my $args = {
};
# remove all portlets from the body pane except 'newest unowned tickets'
+$m->follow_link_ok( { text => 'Content' } );
push(
@{$args->{body}},
- ( "system-Unowned Tickets", )
+ "saved-" . $m->dom->find('[data-description="Unowned Tickets"]')->first->attr('data-name'),
);
my $res = $m->post(
- $url . 'Prefs/MyRT.html',
+ $url . "Dashboards/Queries.html?id=$id",
$args,
);
is( $res->code, 200, "remove all portlets from body except 'newest unowned tickets'" );
like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' );
+$m->content_contains( 'Dashboard updated' );
+
+$m->get_ok( $url . 'Prefs/MyRT.html' );
+$m->submit_form_ok(
+ { form_name => 'UpdateDefaultDashboard',
+ fields => { DefaultDashboard => $id },
+ button => 'UpdateDefaultDashboard',
+ },
+);
+
$m->content_contains( 'Preferences saved' );
$m->get( $url );
@@ -61,10 +89,14 @@ $m->content_lacks( 'highest priority tickets', "'highest priority tickets' is no
$m->content_lacks( 'Bookmarked Tickets<span class="results-count">', "'Bookmarked Tickets' is not present" ); # 'Bookmarked Tickets' also shows up in the nav, so we need to be more specific
$m->content_lacks( 'Quick ticket creation', "'Quick ticket creation' is not present" );
+$m->get_ok( $url . "Dashboards/Queries.html?id=$id" );
+
# add back the previously removed portlets
push(
@{$args->{body}},
- ( "system-My Tickets", "system-Bookmarked Tickets", "component-QuickCreate" )
+ "saved-" . $m->dom->find('[data-description="My Tickets"]')->first->attr('data-name'),
+ "saved-" . $m->dom->find('[data-description="Bookmarked Tickets"]')->first->attr('data-name'),
+ "component-QuickCreate",
);
push(
@@ -73,13 +105,13 @@ push(
);
$res = $m->post(
- $url . 'Prefs/MyRT.html',
+ $url . "Dashboards/Queries.html?id=$id",
$args,
);
is( $res->code, 200, 'add back previously removed portlets' );
like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' );
-$m->content_contains( 'Preferences saved' );
+$m->content_contains( 'Dashboard updated' );
$m->get( $url );
$m->content_contains( 'newest unowned tickets', "'newest unowned tickets' is present" );
@@ -95,7 +127,8 @@ $m->field( "SavedSearchDescription" => 'special chars [test] [_1] ~[_1~]' );
$m->click_button( name => 'SavedSearchSave' );
my ($name) = $m->content =~ /value="(RT::User-\d+-SavedSearch-\d+)"/;
ok( $name, 'saved search name' );
-$m->get( $url . 'Prefs/MyRT.html' );
+
+$m->get_ok( $url . "Dashboards/Queries.html?id=$id" );
$m->content_contains( 'special chars [test] [_1] ~[_1~]',
'saved search listed in rt at a glance items' );
@@ -106,13 +139,13 @@ push(
);
$res = $m->post(
- $url . 'Prefs/MyRT.html',
+ $url . "Dashboards/Queries.html?id=$id",
$args,
);
is( $res->code, 200, 'add saved search to body' );
like( $m->uri, qr/results=[A-Za-z0-9]{32}/, 'URL redirected for results' );
-$m->content_contains( 'Preferences saved' );
+$m->content_contains( 'Dashboard updated' );
$m->get($url);
$m->content_like( qr/special chars \[test\] \d+ \[_1\]/,
@@ -161,7 +194,7 @@ $m->submit_form(
# We don't show saved message on page :/
$m->content_contains("Save as New", 'saved first txn search' );
-$m->get_ok( $url . 'Prefs/MyRT.html' );
+$m->get_ok( $url . "Dashboards/Queries.html?id=$id" );
push(
@{$args->{body}},
"saved-" . $m->dom->find('[data-description="first chart"]')->first->attr('data-name'),
@@ -169,12 +202,12 @@ push(
);
$res = $m->post(
- $url . 'Prefs/MyRT.html',
+ $url . "Dashboards/Queries.html?id=$id",
$args,
);
is( $res->code, 200, 'add system saved searches to body' );
-$m->text_contains( 'Preferences saved' );
+$m->content_contains( 'Dashboard updated' );
$m->get_ok($url);
$m->text_contains('first chart');
commit 17a375ecb20e2ca72e36c79c8c74fc519e5b4987
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri May 14 22:20:53 2021 +0800
Add docs for "RT at a glance" migration to use dashboard
diff --git a/docs/UPGRADING-5.0 b/docs/UPGRADING-5.0
index 5abf895152..50b18af29d 100644
--- a/docs/UPGRADING-5.0
+++ b/docs/UPGRADING-5.0
@@ -283,4 +283,18 @@ configuration setting.
=back
+=head1 UPGRADING FROM 5.0.1 AND EARLIER
+
+=over 4
+
+=item *
+
+"RT at a glance" now uses dashboard as backend, thus users can switch
+different homepages very easily. Users who want to customize homepage need
+"ModifySelf", "SeeOwnDashboard", "ModifyOwnDashboard" and
+"DeleteOwnDashboard" rights granted. "SeeDashboard" is optional but required
+if they want to check out system dashboards too.
+
+=back
+
=cut
commit f759f624acbd204ee3906197d614f04f41e48324
Merge: 287d6649d5 17a375ecb2
Author: Steven Burr <steve at bestpractical.com>
Date: Tue May 25 14:34:16 2021 -0400
Merge branch '5.0/use-dashboard-for-homepage' into siemens-gui
commit ef71d493e435d91080a69600e46a61ff3f74ca27
Author: Steven Burr <steve at bestpractical.com>
Date: Tue Jun 1 13:32:24 2021 -0400
WIP - help updates
diff --git a/share/html/Elements/PopupHelp b/share/html/Elements/PopupHelp
index 1053a95a78..67cf857aeb 100644
--- a/share/html/Elements/PopupHelp
+++ b/share/html/Elements/PopupHelp
@@ -7,7 +7,7 @@ my $help_class;
my $help_content;
# TODO: put in RT::Site::Siemens
-# TODO: add support for langs (cf?)
+# TODO: add support for langs
sub GetArticleContent {
my $article_class = shift || return ''; # required
my $article_name = shift || return ''; # required
@@ -36,7 +36,7 @@ sub GetArticleContent {
if ($cf->Name eq 'Content') {
my $ocfvs = $Article->CustomFieldValues($cf->Id);
my $ocfv = $ocfvs->First;
- return HTML::Mason::Commands::ScrubHTML($ocfv->Content);
+ return $ocfv->Content;
}
}
} else {
@@ -46,95 +46,6 @@ sub GetArticleContent {
return;
}
-# TODO: put in RT::Site::Siemens
-sub GetArticleContentNext {
- my $article_class = shift || return ''; # required
- my $article_name = shift || return ''; # required
- my $article_context = shift || ""; # optional
- my $article_locales = shift || []; # optional
-
- # lookup the article class
- my $cls = RT::Class->new( RT->SystemUser );
- my ($ret, $msg) = $cls->Load( $article_class );
- my $article_classid = $cls->Id;
- if ($ret and $article_classid) {
- RT::Logger->debug("Found article class id: $article_classid");
- } else {
- RT::Logger->debug("No article class found for '$article_class' : $msg");
- return '';
- }
-
- # find the article of the given class
- my $art = RT::Article->new( RT->SystemUser );
- ($ret, $msg) = $art->LoadByCols( Name => $article_name, Class => $article_classid );
- my $article_id = $art->Id;
- if ( $ret and $article_id ) {
- RT::Logger->debug("Found article id: $article_id");
- my $cls = $art->ClassObj;
- my $cfs = $cls->ArticleCustomFields;
- while (my $cf = $cfs->Next) {
- if ($cf->Name eq 'Content') {
- my $ocfvs = $art->CustomFieldValues($cf->Id);
- my $ocfv = $ocfvs->First;
- return HTML::Mason::Commands::ScrubHTML($ocfv->Content);
- }
- }
- } else {
- RT::Logger->debug("No article found for '$article_name'");
- return '';
- }
- return;
-}
-# TODO: put in RT::Site::Siemens
-#sub GetArticleContentWithClass {
-# my $article_class = shift || return ''; # required
-# my $article_name = shift || return ''; # required
-# my $article_context = shift || ""; # optional
-# my $article_locales = shift || []; # optional
-#
-# # lookup the article class
-# my $cls = RT::Class->new( RT->SystemUser );
-# my ($ret, $msg) = $cls->Load( $article_class );
-# my $article_classid = $cls->Id;
-# if ($ret and $cls->Id) {
-# RT::Logger->debug("Found article class id: " . $cls->Id);
-# } else {
-# RT::Logger->debug("No article class found for '$article_class' : $msg");
-# return '';
-# }
-#
-# # find the article of the given class
-# my $articles = RT::Articles->new( RT->SystemUser );
-# $articles->Limit({ FIELD => 'Name', OPERATOR => '=', VALUE => $article_name });
-# $articles->Limit({ FIELD => 'Class', OPERATOR => '=', VALUE => $cls->Id, ENTRYAGGREGATOR => 'AND' });
-# if ($article_context) {
-# my $ctx_cf = RT::CustomField->new( RT->SystemUser );
-# my ($ret, $msg) = $ctx_cf->Load( 'Context' );
-# $articles->LimitCustomField({ FIELD => $ctx_cf->Name, OPERATOR => '=', VALUE => $article_context, ENTRYAGGREGATOR => 'AND' });
-# }
-# if ($article_locales->length) {
-# my $loc_cf = RT::CustomField->new( RT->SystemUser );
-# my ($ret, $msg) = $loc_cf->Load( 'Locale' );
-# $articles->LimitCustomField({ FIELD => $loc_cf->Name, OPERATOR => '=', VALUE => $loc, ENTRYAGGREGATOR => 'AND' });
-# }
-#
-# $Articles->LimitCustomField({ FIELD => '' })
-# if ( $Article and $Article->Id ) {
-# RT::Logger->debug("Found article id: " . $Article->Id);
-# my $class = $Article->ClassObj;
-# my $cfs = $class->ArticleCustomFields;
-# while (my $cf = $cfs->Next) {
-# my $ocfvs = $Article->CustomFieldValues($cf->Id);
-# my $ocfv = $ocfvs->First;
-# HTML::Mason::Commands::ScrubHTML($ocfv->Content);
-# }
-# } else {
-# RT::Logger->debug("No article found for " . $article_name);
-# return '';
-# }
-# return;
-#}
-
if ($key) {
my $lh = $session{'CurrentUser'}->LanguageHandle;
my @locs = [ $lh->language_tag(), $lh->fallback_languages() ];
diff --git a/share/html/Elements/ShowCustomFields b/share/html/Elements/ShowCustomFields
index eff8fba8e3..307b656d76 100644
--- a/share/html/Elements/ShowCustomFields
+++ b/share/html/Elements/ShowCustomFields
@@ -66,10 +66,7 @@
% $m->callback( CallbackName => 'ModifyFieldClasses', CustomField => $CustomField,
% Object => $Object, Classes => \@classes, Grouping => $Grouping );
<div class="form-row <% join(' ', @classes) %>" id="CF-<%$CustomField->id%>-ShowRow">
- <div class="label col-<% $LabelCols %>"><% $CustomField->Name %>:
-% if ( $CustomField->EntryHint ) {
- <span class="far fa-question-circle icon-helper" data-toggle="tooltip" data-placement="top" data-original-title="<% $CustomField->EntryHint %>"></span>
-% }
+ <div class="label col-<% $LabelCols %>" title="<% $CustomField->EntryHint || '' %>"><% $CustomField->Name %><& /Elements/PopupHelp, key => $CustomField->Name &>:
</div>
<div class="value col-<% $ValueCols %> <% $count ? '' : ' no-value' %>">
<span class="current-value">
diff --git a/share/html/Ticket/Elements/EditBasics b/share/html/Ticket/Elements/EditBasics
index 376b9e9749..0319633fe3 100644
--- a/share/html/Ticket/Elements/EditBasics
+++ b/share/html/Ticket/Elements/EditBasics
@@ -186,10 +186,7 @@ for my $field (@fields) {
% for my $field (@fields) {
%# Prefer input name as css class, e.g. "FinalPriority" instead of "Final_Priority"
<div class="form-row <% CSSClass( $field->{args}{Name} || $field->{'name'} ) %>">
- <div class="label col-3"><% loc($field->{'name'}) %>:
-% if ( my $hint = $field->{hint} ) {
- <span class="far fa-question-circle icon-helper" data-toggle="tooltip" data-placement="top" data-original-title="<% $hint %>"></span>
-% }
+ <div class="label col-3" title="<% $field->{hint} || '' %>"><% loc($field->{'name'}) %><& /Elements/PopupHelp, key => $field->{'name'} &> :
</div>
<div class="value col-9"><% $field->{'html'} |n %></div>
</div>
-----------------------------------------------------------------------
More information about the rt-commit
mailing list