Ticketliste als neue Queue Ansicht im Dashboard
Ticketliste als neue Queue Ansicht im Dashboard
Hallo,
ist es möglich, nach eigenen Kritieren zusätzlich eine Ticketliste im Dashboard zu erstellen?
Also wie die Queue Ansicht "Neue Tickets", "Offene Tickets" etc.
Danke!
ist es möglich, nach eigenen Kritieren zusätzlich eine Ticketliste im Dashboard zu erstellen?
Also wie die Queue Ansicht "Neue Tickets", "Offene Tickets" etc.
Danke!
Linux Debian Jessie
DB: postgres
DB: postgres
-
- Znuny guru
- Posts: 2210
- Joined: 13 Mar 2014, 09:16
- Znuny Version: 6.0.14
- Real Name: Rolf Straub
Re: Ticketliste als neue Queue Ansicht im Dashboard
Ja. Wir haben uns z.B. eine "Liste" der offenen WorkOrders im Dashboard erstellt.
Vorgehen wäre:
- Kopiere dir die .pm und .tt Dateien einer Liste die deiner gewünschten möglichst nahe kommt
- Registriere sie mit anderen Namen durch eine .xml Datei in ~otrs/Kernel/Config/Files/
- Passe die kopierten Perl und Template Dateien deinen Wünschen entsprechend an.
Vorgehen wäre:
- Kopiere dir die .pm und .tt Dateien einer Liste die deiner gewünschten möglichst nahe kommt
- Registriere sie mit anderen Namen durch eine .xml Datei in ~otrs/Kernel/Config/Files/
- Passe die kopierten Perl und Template Dateien deinen Wünschen entsprechend an.
Last edited by RStraub on 12 Jun 2015, 12:15, edited 1 time in total.
Currently using: OTRS 6.0.14 -- MariaDB -- Ubuntu 16 LTS
Re: Ticketliste als neue Queue Ansicht im Dashboard
und wie geht das?
Linux Debian Jessie
DB: postgres
DB: postgres
-
- Znuny guru
- Posts: 2210
- Joined: 13 Mar 2014, 09:16
- Znuny Version: 6.0.14
- Real Name: Rolf Straub
Re: Ticketliste als neue Queue Ansicht im Dashboard
Erste Antwort war zu kurz
Hab sie etwas editiert.

Currently using: OTRS 6.0.14 -- MariaDB -- Ubuntu 16 LTS
Re: Ticketliste als neue Queue Ansicht im Dashboard
Super Danke!. Kannst Du mir noch sagen in welchem Pfad die .tt Dateien liegen?
Danke und schönes Wochenende!
Danke und schönes Wochenende!
Linux Debian Jessie
DB: postgres
DB: postgres
-
- Znuny guru
- Posts: 2210
- Joined: 13 Mar 2014, 09:16
- Znuny Version: 6.0.14
- Real Name: Rolf Straub
Re: Ticketliste als neue Queue Ansicht im Dashboard
die liegen unter:
~otrs/Kernel/Output/HTML/Standard/
z.B. AgentDashboardTicketQueueOverview.tt
~otrs/Kernel/Output/HTML/Standard/
z.B. AgentDashboardTicketQueueOverview.tt
Currently using: OTRS 6.0.14 -- MariaDB -- Ubuntu 16 LTS
Re: Ticketliste als neue Queue Ansicht im Dashboard
Super vielen Dank!!!!
Linux Debian Jessie
DB: postgres
DB: postgres
Re: Ticketliste als neue Queue Ansicht im Dashboard
Könntest Du mir vielleicht noch ein Beispiel für die XML Registrierung geben. Das wäre nett. Danke!
Linux Debian Jessie
DB: postgres
DB: postgres
-
- Znuny guru
- Posts: 2210
- Joined: 13 Mar 2014, 09:16
- Znuny Version: 6.0.14
- Real Name: Rolf Straub
Re: Ticketliste als neue Queue Ansicht im Dashboard
Das hier ist das Beispiel für die "WorkOrder" Übersicht.
Eigentlich bau ich die .xml Datein zweigeteilt: Einmal die Registrierung und einmal einen Eintrag für die Konfiguration. Das ist hier beides zusammengeworfen (Sortierung, Status-Filter etc.).
Wichtig ist der Module-Eintrag im Hash und der Name vor dem ###.
Für ein Standalone-File müsstest du das ganze noch einrahmen mit:
Nach Aktualisierung der SysConfig sucht (und erwartet) OTRS dann diese Datei bei jedem Dashboard Aufruf:
~otrs(/Custom/)/Kernel/Output/HTML/DashboardWorkOrderOverview.pm
Eigentlich bau ich die .xml Datein zweigeteilt: Einmal die Registrierung und einmal einen Eintrag für die Konfiguration. Das ist hier beides zusammengeworfen (Sortierung, Status-Filter etc.).
Wichtig ist der Module-Eintrag im Hash und der Name vor dem ###.
Code: Select all
<ConfigItem Name="DashboardBackend###1600-WorkOrderOverview" Required="0" Valid="1">
<Description Translatable="1">Some Description</Description>
<Group>TTO Framework</Group>
<SubGroup>Frontend::Agent::Dashboard</SubGroup>
<Setting>
<Hash>
<Item Key="Module">Kernel::Output::HTML::DashboardWorkOrderOverview</Item>
<Item Key="Title">Workorder Overview</Item>
<Item Key="Description">All workorders</Item>
<Item Key="Permission">rw</Item>
<Item Key="QueuePermissionGroup">users</Item>
<Item Key="Block">ContentLarge</Item>
<Item Key="Group"></Item>
<Item Key="Default">1</Item>
<Item Key="Sort">SortBy=Age;OrderBy=Up</Item>
<Item Key="CacheTTLLocal">0.5</Item>
<Item Key="States">
<Hash>
<Item Key="125">accepted</Item>
<Item Key="129">canceled</Item>
<Item Key="128">closed</Item>
<Item Key="124">created</Item>
<Item Key="127">in progress</Item>
<Item Key="126">ready</Item>
</Hash>
</Item>
</Hash>
</Setting>
</ConfigItem>
Code: Select all
<otrs_config version="1.0" init="Framework">
</otrs_config>
~otrs(/Custom/)/Kernel/Output/HTML/DashboardWorkOrderOverview.pm
Currently using: OTRS 6.0.14 -- MariaDB -- Ubuntu 16 LTS
Re: Ticketliste als neue Queue Ansicht im Dashboard
Danke soweit funktioniert es. Leider ist die Ansicht aber die Falsche. Haben möchte ich:
eine Queue Ansicht alle Tickets mit bestimmten Status. Erhalten habe ich aber eine Ansicht, welche ich mal in der Statistik erstellt habe.
Habe ich evtl. die falsche .tt Datei genommen? Ich hoffe du hast noch Geduld für eine Antwort. Danke!
DashboardTicketQueueProjekt.xml
AgentDashboardTicketQueueProjekt.tt
DashboardTicketQueueProjekt.pm
eine Queue Ansicht alle Tickets mit bestimmten Status. Erhalten habe ich aber eine Ansicht, welche ich mal in der Statistik erstellt habe.
Habe ich evtl. die falsche .tt Datei genommen? Ich hoffe du hast noch Geduld für eine Antwort. Danke!

DashboardTicketQueueProjekt.xml
Code: Select all
<otrs_config version="1.0" init="Framework">
<ConfigItem Name="DashboardBackend###1600-DashboardTicketQueueProjekt" Required="0" Valid="1">
<Description Translatable="1">Projektansicht</Description>
<Group>TTO Framework</Group>
<SubGroup>Frontend::Agent::Dashboard</SubGroup>
<Setting>
<Hash>
<Item Key="Module">Kernel::Output::HTML::DashboardTicketQueueProjekt</Item>
<Item Key="Title">Projekt Overview</Item>
<Item Key="Description">Alle Projekte</Item>
<Item Key="Permission">rw</Item>
<Item Key="QueuePermissionGroup">users</Item>
<Item Key="Block">ContentLarge</Item>
<Item Key="Group"></Item>
<Item Key="Default">1</Item>
<Item Key="Sort">SortBy=Age;OrderBy=Up</Item>
<Item Key="CacheTTLLocal">0.5</Item>
<Item Key="States">
<Hash>
<Item Key="125">accepted</Item>
<Item Key="129">canceled</Item>
<Item Key="128">closed</Item>
<Item Key="124">created</Item>
<Item Key="127">in progress</Item>
<Item Key="126">ready</Item>
</Hash>
</Item>
</Hash>
</Setting>
</ConfigItem>
</otrs_config>
Code: Select all
# --
# AgentDashboardTicketQueueOverview.tt - provides HTML for Ticket Queue Overview
# Copyright (C) 2001-2015 xxx, http://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (AGPL). If you
# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
# --
<table class="DataTable">
<thead>
<tr>
[% RenderBlockStart("ContentLargeTicketQueueOverviewHeaderStatus") %]
<th>[% Translate(Data.Text) | html %]</th>
[% RenderBlockEnd("ContentLargeTicketQueueOverviewHeaderStatus") %]
<th class="QueueOverviewTotals">[% Translate("Totals") | html %]</th>
</tr>
</thead>
<tbody>
[% RenderBlockStart("ContentLargeTicketQueueOverviewQueueName") %]
<tr class="Row">
<td>[% Data.QueueName | html %]</td>
[% RenderBlockStart("ContentLargeTicketQueueOverviewQueueResults") %]
<td><a class="AsBlock" href="[% Env("Baselink") %]Action=AgentTicketSearch;Subaction=Search;[% Env("ChallengeTokenParam") | html %];StateIDs=[% Data.StateID | uri %];QueueIDs=[% Data.QueueID | uri %];[% Data.Sort | html %]">[% Translate(Data.Number) | html %]</a></td>
[% RenderBlockEnd("ContentLargeTicketQueueOverviewQueueResults") %]
[% RenderBlockStart("ContentLargeTicketQueueOverviewQueueTotal") %]
<td><a class="QueueOverviewTotals AsBlock" href="[% Env("Baselink") %]Action=AgentTicketSearch;Subaction=Search;[% Env("ChallengeTokenParam") | html %];QueueIDs=[% Data.QueueID | uri %];[% Data.StateIDs | html %];[% Data.Sort | html %]">[% Translate(Data.Number) | html %]</a></td>
[% RenderBlockEnd("ContentLargeTicketQueueOverviewQueueTotal") %]
</tr>
[% RenderBlockEnd("ContentLargeTicketQueueOverviewQueueName") %]
[% RenderBlockStart("ContentLargeTicketQueueOverviewStatusTotalRow") %]
<tr class="Row">
<td class="QueueOverviewTotals">[% Translate("Totals") | html %]</td>
[% RenderBlockStart("ContentLargeTicketQueueOverviewStatusTotal") %]
<td class="QueueOverviewTotals"><a class="AsBlock" href="[% Env("Baselink") %]Action=AgentTicketSearch;Subaction=Search;[% Env("ChallengeTokenParam") | html %];StateIDs=[% Data.StateID | uri %];[% Data.QueueIDs | html %];[% Data.Sort | html %]">[% Translate(Data.Number) | html %]</a></td>
[% RenderBlockEnd("ContentLargeTicketQueueOverviewStatusTotal") %]
<td class="QueueOverviewTotals"></td>
</tr>
[% RenderBlockEnd("ContentLargeTicketQueueOverviewStatusTotalRow") %]
[% RenderBlockStart("ContentLargeTicketQueueOverviewNone") %]
<tr>
<td colspan="[% Data.ColumnCount | html %]">
[% Translate("No data found.") | html %]
</td>
</tr>
[% RenderBlockEnd("ContentLargeTicketQueueOverviewNone") %]
</tbody>
</table>
[% RenderBlockStart("ContentLargeTicketQueueOverviewRefresh") %]
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
Core.Config.Set('RefreshSeconds_[% Data.NameHTML | html %]', parseInt("[% Data.RefreshTime | html %]", 10) || 0);
if (Core.Config.Get('RefreshSeconds_[% Data.NameHTML | html %]')) {
Core.Config.Set('Timer_[% Data.NameHTML | html %]', window.setTimeout(function() {
// get active filter
var Filter = $('#Dashboard[% Data.Name | html %]-box').find('.Tab.Actions li.Selected a').attr('data-filter');
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';CustomerID=[% Data.CustomerID | html %]', function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
clearTimeout(Core.Config.Get('Timer_[% Data.NameHTML | html %]'));
}, Core.Config.Get('RefreshSeconds_[% Data.NameHTML | html %]') * 1000));
}
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketQueueOverviewRefresh") %]
Code: Select all
# --
# Kernel/Output/HTML/DashboardTicketQueueOverview.pm
# Copyright (C) 2001-2015 xxx, http://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (AGPL). If you
# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
# --
package Kernel::Output::HTML::DashboardTicketQueueOverview;
use strict;
use warnings;
our $ObjectManagerDisabled = 1;
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {%Param};
bless( $Self, $Type );
# get needed parameters
for my $Object (qw( Config Name UserID )) {
die "Got no $Object!" if ( !$Self->{$Object} );
}
$Self->{PrefKey} = 'UserDashboardPref' . $Self->{Name} . '-Shown';
$Self->{CacheKey} = $Self->{Name} . '-' . $Self->{UserID};
return $Self;
}
sub Preferences {
my ( $Self, %Param ) = @_;
return;
}
sub Config {
my ( $Self, %Param ) = @_;
return (
%{ $Self->{Config} },
# remember, do not allow to use page cache
# (it's not working because of internal filter)
CacheKey => undef,
CacheTTL => undef,
);
}
sub Run {
my ( $Self, %Param ) = @_;
my $LimitGroup = $Self->{Config}->{QueuePermissionGroup} || 0;
my $CacheKey = 'User' . '-' . $Self->{UserID} . '-' . $LimitGroup;
my $Content = $Self->{CacheObject}->Get(
Type => 'DashboardQueueOverview',
Key => $CacheKey,
);
return $Content if defined $Content;
# get configured states, get their state ID and test if they exist while we do it
my %States;
my $StateIDURL;
my %ConfiguredStates = %{ $Self->{Config}->{States} };
for my $StateOrder ( sort { $a <=> $b } keys %ConfiguredStates ) {
my $State = $ConfiguredStates{$StateOrder};
# check if state is found, to record StateID
my $StateID = $Kernel::OM->Get('Kernel::System::State')->StateLookup(
State => $State,
) || '';
if ($StateID) {
$States{$State} = $StateID;
# append StateID to URL for search string
$StateIDURL .= "StateIDs=$StateID;";
}
else {
# state does not exist, skipping
delete $ConfiguredStates{$StateOrder};
}
}
# get queue object
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
# get all queues
my %Queues = $QueueObject->GetAllQueues(
UserID => $Self->{UserID},
Type => 'ro',
);
# limit them by QueuePermissionGroup if needed
my $LimitGroupID;
if ($LimitGroup) {
$LimitGroupID = $Self->{GroupObject}->GroupLookup(
Group => $LimitGroup,
);
}
my $Sort = $Self->{Config}->{Sort} || '';
my %QueueToID;
my $QueueIDURL;
# lookup queues, add their QueueID to new hash (needed for Search)
QUEUES:
for my $QueueID ( sort keys %Queues ) {
# see if we have to remove the queue based on LimitGroup
if ($LimitGroup) {
my $GroupID = $QueueObject->GetQueueGroupID(
QueueID => $QueueID,
);
if ( $GroupID != $LimitGroupID ) {
delete $Queues{$QueueID};
next QUEUES;
}
}
# add queue to reverse hash
$QueueToID{ $Queues{$QueueID} } = $QueueID;
# add queue to SearchURL
$QueueIDURL .= "QueueIDs=$QueueID;";
}
my %Results;
for my $QueueID ( sort keys %Queues ) {
my @Results;
for my $StateOrderID ( sort { $a <=> $b } keys %ConfiguredStates ) {
my $QueueTotal = $Kernel::OM->Get('Kernel::System::Ticket')->TicketSearch(
UserID => $Self->{UserID},
Result => 'COUNT',
Queues => [ $Queues{$QueueID} ],
States => [ $ConfiguredStates{$StateOrderID} ],
);
push @Results, $QueueTotal;
}
$Results{ $Queues{$QueueID} } = [@Results];
}
# build header
my @Headers = ( 'Queue', );
for my $StateOrder ( sort { $a <=> $b } keys %ConfiguredStates ) {
push @Headers, $ConfiguredStates{$StateOrder};
}
# get layout object
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
for my $HeaderItem (@Headers) {
$LayoutObject->Block(
Name => 'ContentLargeTicketQueueOverviewHeaderStatus',
Data => {
Text => $HeaderItem,
},
);
}
my $HasContent;
# iterate over all queues, print results;
my @StatusTotal;
QUEUE:
for my $Queue ( sort values %Queues ) {
# Hide empty queues
if ( !grep { defined $_ && $_ > 0 } @{ $Results{$Queue} } ) {
next QUEUE;
}
$HasContent++;
$LayoutObject->Block(
Name => 'ContentLargeTicketQueueOverviewQueueName',
Data => {
QueueName => $Queue,
}
);
# iterate over states
my $Counter = 0;
my $RowTotal;
for my $StateOrderID ( sort { $a <=> $b } keys %ConfiguredStates ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketQueueOverviewQueueResults',
Data => {
Number => $Results{$Queue}->[$Counter],
QueueID => $QueueToID{$Queue},
StateID => $States{ $ConfiguredStates{$StateOrderID} },
State => $ConfiguredStates{$StateOrderID},
Sort => $Sort,
},
);
$RowTotal += $Results{$Queue}->[$Counter] || 0;
$StatusTotal[$StateOrderID] += $Results{$Queue}->[$Counter] || 0;
$Counter++;
}
# print row (queue) total
$LayoutObject->Block(
Name => 'ContentLargeTicketQueueOverviewQueueTotal',
Data => {
Number => $RowTotal,
QueueID => $QueueToID{$Queue},
StateIDs => $StateIDURL,
Sort => $Sort,
},
);
}
if ($HasContent) {
$LayoutObject->Block(
Name => 'ContentLargeTicketQueueOverviewStatusTotalRow',
);
for my $StateOrderID ( sort { $a <=> $b } keys %ConfiguredStates ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketQueueOverviewStatusTotal',
Data => {
Number => $StatusTotal[$StateOrderID],
QueueIDs => $QueueIDURL,
StateID => $States{ $ConfiguredStates{$StateOrderID} },
Sort => $Sort,
},
);
}
}
else {
$LayoutObject->Block(
Name => 'ContentLargeTicketQueueOverviewNone',
Data => {
ColumnCount => ( scalar keys %ConfiguredStates ) + 2,
}
);
}
# check for refresh time
my $Refresh = '';
if ( $Self->{UserRefreshTime} ) {
$Refresh = 60 * $Self->{UserRefreshTime};
my $NameHTML = $Self->{Name};
$NameHTML =~ s{-}{_}xmsg;
$LayoutObject->Block(
Name => 'ContentLargeTicketQueueOverviewRefresh',
Data => {
%{ $Self->{Config} },
Name => $Self->{Name},
NameHTML => $NameHTML,
RefreshTime => $Refresh,
},
);
}
$Content = $LayoutObject->Output(
TemplateFile => 'AgentDashboardTicketQueueProjekt',
Data => {
%{ $Self->{Config} },
Name => $Self->{Name},
},
KeepScriptTags => $Param{AJAX},
);
# cache result
if ( $Self->{Config}->{CacheTTLLocal} ) {
$Self->{CacheObject}->Set(
Type => 'DashboardQueueOverview',
Key => $CacheKey,
Value => $Content || '',
TTL => 2 * 60,
);
}
return $Content;
}
1;
You do not have the required permissions to view the files attached to this post.
Linux Debian Jessie
DB: postgres
DB: postgres
Re: Ticketliste als neue Queue Ansicht im Dashboard
Wenn du eine ähnliche Liste wie "Offene Tickets" auf dem Dashboard darstellen willt, solltest du vllt DashboardTicketGeneric.pm und AgentDashboardTicketGeneric.tt als Basis nehmen.
OTRS 3.3.x (private/testing) on Windows Server 2008 with MSSQL database.
OTRS 3.3.x (private/testing) on CentOS with MySQL database and apache
OTRS 3.3.x (private/testing) on CentOS with MySQL database and apache
Re: Ticketliste als neue Queue Ansicht im Dashboard
Ok jetzt habe ich auf diese beiden Dateien umgestellt. Nun kann ich aber nicht mehr auf das Dashboard zugreifen. Internal Server Error!
Warum? Ich denke es hängt an der xml Datei. Danke!
Warum? Ich denke es hängt an der xml Datei. Danke!
Code: Select all
<otrs_config version="1.0" init="Framework">
<ConfigItem Name="DashboardBackend###1700-DashboardTicketGenericProjekt" Required="0" Valid="1">
<Description Translatable="1">Projektansicht</Description>
<Group>RitterIT Framework</Group>
<SubGroup>Frontend::Agent::Dashboard</SubGroup>
<Setting>
<Hash>
<Item Key="Module">Kernel::Output::HTML::DashboardTicketGenericProjekt</Item>
<Item Key="Title">Projekt Overview</Item>
<Item Key="Description">Alle Projekte</Item>
<Item Key="Permission">rw</Item>
<Item Key="QueuePermissionGroup">users</Item>
<Item Key="Block">ContentLarge</Item>
<Item Key="Group"></Item>
<Item Key="Default">1</Item>
<Item Key="Sort">SortBy=Age;OrderBy=Up</Item>
<Item Key="CacheTTLLocal">0.5</Item>
<Item Key="States">
<Hash>
<Item Key="125">accepted</Item>
<Item Key="129">canceled</Item>
<Item Key="128">closed</Item>
<Item Key="124">created</Item>
<Item Key="127">in progress</Item>
<Item Key="126">ready</Item>
</Hash>
</Item>
</Hash>
</Setting>
</ConfigItem>
</otrs_config>
Linux Debian Jessie
DB: postgres
DB: postgres
-
- Znuny guru
- Posts: 2210
- Joined: 13 Mar 2014, 09:16
- Znuny Version: 6.0.14
- Real Name: Rolf Straub
Re: Ticketliste als neue Queue Ansicht im Dashboard
Internal Server , aha, das ist ja spezifisch 
Zeig uns mal den Apache-Log während einem Dashboard Zugriff bitte.
(und im Zweifel erstmal:
- Apache reload
- RebuildConfig
- DeleteCache)

Zeig uns mal den Apache-Log während einem Dashboard Zugriff bitte.
(und im Zweifel erstmal:
- Apache reload
- RebuildConfig
- DeleteCache)
Currently using: OTRS 6.0.14 -- MariaDB -- Ubuntu 16 LTS
Re: Ticketliste als neue Queue Ansicht im Dashboard
Rebuild und neustart etc. habe ich alles schon gemacht.
Sorry die Apache error.log gibt folgendes aus:
Sorry die Apache error.log gibt folgendes aus:
Code: Select all
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"32"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"46"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"30"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"29"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"108"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"218"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"4"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"216"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"32"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"30"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"30"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"46"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"32"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"108"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"108"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"46"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"108"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"30"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"108"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"29"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"4"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"19"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"19"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"216"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"32"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"4"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"4"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"46"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"4"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"30"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"4"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"29"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"4"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"108"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"4"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: Use of uninitialized value $ValueHash{"218"} in string comparison (cmp) at /opt/otrs//Kernel/Output/HTML/DashboardStats.pm line 231.
[Mon Jun 15 14:35:18 2015] -e: (in cleanup) Can't call method "Get" on an undefined value at /opt/otrs//Kernel/System/AuthSession/DB.pm line 583.
[Mon Jun 15 14:35:18 2015] [error] \t(in cleanup) Can't call method "Get" on an undefined value at /opt/otrs//Kernel/System/AuthSession/DB.pm line 583.\n
Linux Debian Jessie
DB: postgres
DB: postgres
-
- Znuny guru
- Posts: 2210
- Joined: 13 Mar 2014, 09:16
- Znuny Version: 6.0.14
- Real Name: Rolf Straub
Re: Ticketliste als neue Queue Ansicht im Dashboard
Ich vermute das liegt daran das einige Parameter für das Generic-Widget anders gestaltet sein müssen, als für die Queue Ansicht. Für das Generic Widget, kopiere u. modifiziere auch lieber den .xml Code dafür:
Code: Select all
<ConfigItem Name="DashboardBackend###0130-TicketOpen" Required="0" Valid="1">
<Description Translatable="1">Parameters for the dashboard backend of the ticket pending reminder overview of the agent interface. "Limit" is the number of entries shown by default. "Group" is used to restrict the access to the plugin (e. g. Group: admin;group1;group2;). "Default" determines if the plugin is enabled by default or if the user needs to enable it manually. "CacheTTLLocal" is the cache time in minutes for the plugin. Note: Only Ticket attributes and Dynamic Fields (DynamicField_NameX) are allowed for DefaultColumns. Possible settings: 0 = Disabled, 1 = Available, 2 = Enabled by default.</Description>
<Group>Ticket</Group>
<SubGroup>Frontend::Agent::Dashboard</SubGroup>
<Setting>
<Hash>
<Item Key="Module">Kernel::Output::HTML::DashboardTicketGeneric</Item>
<Item Key="Title" Translatable="1">Open Tickets / Need to be answered</Item>
<Item Key="Description" Translatable="1">All open tickets, these tickets have already been worked on, but need a response</Item>
<Item Key="Attributes">StateType=open;</Item>
<Item Key="Filter">All</Item>
<Item Key="Time">Age</Item>
<Item Key="Limit">10</Item>
<Item Key="Permission">rw</Item>
<Item Key="Block">ContentLarge</Item>
<Item Key="Group"></Item>
<Item Key="Default">1</Item>
<Item Key="CacheTTLLocal">0.5</Item>
<Item Key="DefaultColumns">
<Hash>
<Item Key="Age">2</Item>
<Item Key="Changed">1</Item>
<Item Key="CustomerID">1</Item>
<Item Key="CustomerName">1</Item>
<Item Key="CustomerUserID">1</Item>
<Item Key="EscalationResponseTime">1</Item>
<Item Key="EscalationSolutionTime">1</Item>
<Item Key="EscalationTime">1</Item>
<Item Key="EscalationUpdateTime">1</Item>
<Item Key="TicketNumber">2</Item>
<Item Key="Lock">1</Item>
<Item Key="Owner">1</Item>
<Item Key="PendingTime">1</Item>
<Item Key="Queue">1</Item>
<Item Key="Responsible">1</Item>
<Item Key="Priority">1</Item>
<Item Key="Service">1</Item>
<Item Key="State">1</Item>
<Item Key="SLA">1</Item>
<Item Key="Title">2</Item>
<Item Key="Type">1</Item>
</Hash>
</Item>
</Hash>
</Setting>
</ConfigItem>
Currently using: OTRS 6.0.14 -- MariaDB -- Ubuntu 16 LTS
Re: Ticketliste als neue Queue Ansicht im Dashboard
hmm... nachdem ich die XML Datei nun auf Deinen Vorschlag hin umgestellt habe, erscheint die Einstellung auch in der Sysconfig nicht mehr.
Code: Select all
<ConfigItem Name="DashboardBackend###11899-DashboardTicketGenericProjekt" Required="0" Valid="1">
<Description Translatable="1">Parameters for the dashboard backend of the ticket pending reminder overview of the agent interface. "Limit" is the number of entries shown by default. "Group" is used to restrict the access to the plugin (e. g. Group: admin;group1;group2;). "Default" determines if the plugin is enabled by default or if the user needs to enable it manually. "CacheTTLLocal" is the cache time in minutes for the plugin. Note: Only Ticket attributes and Dynamic Fields (DynamicField_NameX) are allowed for DefaultColumns. Possible settings: 0 = Disabled, 1 = Available, 2 = Enabled by default.</Description>
<Group>Ritter Framework</Group>
<SubGroup>Frontend::Agent::DashboardRitter</SubGroup>
<Setting>
<Hash>
<Item Key="Module">Kernel::Output::HTML::DashboardTicketGenericProjekt</Item>
<Item Key="Title" Translatable="1">Offene Projekt Tickets</Item>
<Item Key="Description" Translatable="1">All open tickets, these tickets have already been worked on, but need a response</Item>
<Item Key="Attributes">StateType=open;</Item>
<Item Key="Filter">All</Item>
<Item Key="Time">Age</Item>
<Item Key="Limit">10</Item>
<Item Key="Permission">rw</Item>
<Item Key="Block">ContentLarge</Item>
<Item Key="Group"></Item>
<Item Key="Default">1</Item>
<Item Key="CacheTTLLocal">0.5</Item>
<Item Key="DefaultColumns">
<Hash>
<Item Key="Age">2</Item>
<Item Key="Changed">1</Item>
<Item Key="CustomerID">1</Item>
<Item Key="CustomerName">1</Item>
<Item Key="CustomerUserID">1</Item>
<Item Key="EscalationResponseTime">1</Item>
<Item Key="EscalationSolutionTime">1</Item>
<Item Key="EscalationTime">1</Item>
<Item Key="EscalationUpdateTime">1</Item>
<Item Key="TicketNumber">2</Item>
<Item Key="Lock">1</Item>
<Item Key="Owner">1</Item>
<Item Key="PendingTime">1</Item>
<Item Key="Queue">1</Item>
<Item Key="Responsible">1</Item>
<Item Key="Priority">1</Item>
<Item Key="Service">1</Item>
<Item Key="State">1</Item>
<Item Key="SLA">1</Item>
<Item Key="Title">2</Item>
<Item Key="Type">1</Item>
</Hash>
</Item>
</Hash>
</Setting>
</ConfigItem>
Linux Debian Jessie
DB: postgres
DB: postgres
Re: Ticketliste als neue Queue Ansicht im Dashboard
Da fehlt am Anfangundam Ende
Code: Select all
<?xml version="1.0" encoding="utf-8"?>
<otrs_config version="1.0" init="Application">
Code: Select all
</ConfigItem>
</otrs_config>
OTRS 3.3.x (private/testing) on Windows Server 2008 with MSSQL database.
OTRS 3.3.x (private/testing) on CentOS with MySQL database and apache
OTRS 3.3.x (private/testing) on CentOS with MySQL database and apache
Re: Ticketliste als neue Queue Ansicht im Dashboard
sorry das hatte ich übersehen. Leider funktioniert es aber immer noch nicht. Jetzt kommt wieder der Internal Server Error!
Im error.log von Apache bringt er nun:
Im error.log von Apache bringt er nun:
Code: Select all
[Tue Jun 16 07:14:12 2015] [error] \t(in cleanup) Can't call method "Get" on an undefined value at /opt/otrs//Kernel/System/AuthSession/DB.pm line 583.\n
[Tue Jun 16 07:14:12 2015] [error] [client 172.20.220.151] File does not exist: /var/www/favicon.ico, referer: http://otrs/otrs/index.pl?Action=AgentDashboard
Linux Debian Jessie
DB: postgres
DB: postgres
Re: Ticketliste als neue Queue Ansicht im Dashboard
Internal Server Error trotz rebuilconfig, apache Neustart und delete cache?
Kannst du die entsprechenden config xml, .pm und .tt posten?
Kannst du die entsprechenden config xml, .pm und .tt posten?
OTRS 3.3.x (private/testing) on Windows Server 2008 with MSSQL database.
OTRS 3.3.x (private/testing) on CentOS with MySQL database and apache
OTRS 3.3.x (private/testing) on CentOS with MySQL database and apache
Re: Ticketliste als neue Queue Ansicht im Dashboard
Ja genau habe ich alles gemacht. Rebuild, DeleteCache und apache neu start. Ohne Erfolg. Hier die entsprechenden Dateien. Danke!!!
DashboardTicketGenericProjekt.pm
AgentDashboardTicketGenericProjekt.tt
DashboardTicketGenericProjekt.xml
DashboardTicketGenericProjekt.pm
Code: Select all
# --
# Kernel/Output/HTML/DashboardTicketGeneric.pm
# Copyright (C) 2001-2015 xxx, http://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (AGPL). If you
# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
# --
package Kernel::Output::HTML::DashboardTicketGeneric;
use strict;
use warnings;
use Kernel::System::VariableCheck qw(:all);
our $ObjectManagerDisabled = 1;
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {%Param};
bless( $Self, $Type );
# get needed parameters
for my $Item (qw(Config Name UserID)) {
die "Got no $Item!" if ( !$Self->{$Item} );
}
# get param object
my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
my $RemoveFilters = $ParamObject->GetParam( Param => 'RemoveFilters' )
|| $Param{RemoveFilters}
|| 0;
# get sorting params
for my $Item (qw(SortBy OrderBy)) {
$Self->{$Item} = $ParamObject->GetParam( Param => $Item ) || $Param{$Item};
}
# set filter settings
for my $Item (qw(ColumnFilter GetColumnFilter GetColumnFilterSelect)) {
$Self->{$Item} = $Param{$Item};
}
# save column filters
$Self->{PrefKeyColumnFilters} = 'UserDashboardTicketGenericColumnFilters' . $Self->{Name};
$Self->{PrefKeyColumnFiltersRealKeys} = 'UserDashboardTicketGenericColumnFiltersRealKeys' . $Self->{Name};
# get needed objects
my $JSONObject = $Kernel::OM->Get('Kernel::System::JSON');
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
if ($RemoveFilters) {
$UserObject->SetPreferences(
UserID => $Self->{UserID},
Key => $Self->{PrefKeyColumnFilters},
Value => '',
);
$UserObject->SetPreferences(
UserID => $Self->{UserID},
Key => $Self->{PrefKeyColumnFiltersRealKeys},
Value => '',
);
}
# just in case new filter values arrive
elsif (
IsHashRefWithData( $Self->{GetColumnFilter} )
&& IsHashRefWithData( $Self->{GetColumnFilterSelect} )
&& IsHashRefWithData( $Self->{ColumnFilter} )
)
{
if ( !$ConfigObject->Get('DemoSystem') ) {
# check if the user has filter preferences for this widget
my %Preferences = $UserObject->GetPreferences(
UserID => $Self->{UserID},
);
my $ColumnPrefValues;
if ( $Preferences{ $Self->{PrefKeyColumnFilters} } ) {
$ColumnPrefValues = $JSONObject->Decode(
Data => $Preferences{ $Self->{PrefKeyColumnFilters} },
);
}
PREFVALUES:
for my $Column ( sort keys %{ $Self->{GetColumnFilterSelect} } ) {
if ( $Self->{GetColumnFilterSelect}->{$Column} eq 'DeleteFilter' ) {
delete $ColumnPrefValues->{$Column};
next PREFVALUES;
}
$ColumnPrefValues->{$Column} = $Self->{GetColumnFilterSelect}->{$Column};
}
$UserObject->SetPreferences(
UserID => $Self->{UserID},
Key => $Self->{PrefKeyColumnFilters},
Value => $JSONObject->Encode( Data => $ColumnPrefValues ),
);
# save real key's name
my $ColumnPrefRealKeysValues;
if ( $Preferences{ $Self->{PrefKeyColumnFiltersRealKeys} } ) {
$ColumnPrefRealKeysValues = $JSONObject->Decode(
Data => $Preferences{ $Self->{PrefKeyColumnFiltersRealKeys} },
);
}
REALKEYVALUES:
for my $Column ( sort keys %{ $Self->{ColumnFilter} } ) {
next REALKEYVALUES if !$Column;
my $DeleteFilter = 0;
if ( IsArrayRefWithData( $Self->{ColumnFilter}->{$Column} ) ) {
if ( grep { $_ eq 'DeleteFilter' } @{ $Self->{ColumnFilter}->{$Column} } ) {
$DeleteFilter = 1;
}
}
elsif ( IsHashRefWithData( $Self->{ColumnFilter}->{$Column} ) ) {
if (
grep { $Self->{ColumnFilter}->{$Column}->{$_} eq 'DeleteFilter' }
keys %{ $Self->{ColumnFilter}->{$Column} }
)
{
$DeleteFilter = 1;
}
}
if ($DeleteFilter) {
delete $ColumnPrefRealKeysValues->{$Column};
delete $Self->{ColumnFilter}->{$Column};
next REALKEYVALUES;
}
$ColumnPrefRealKeysValues->{$Column} = $Self->{ColumnFilter}->{$Column};
}
$UserObject->SetPreferences(
UserID => $Self->{UserID},
Key => $Self->{PrefKeyColumnFiltersRealKeys},
Value => $JSONObject->Encode( Data => $ColumnPrefRealKeysValues ),
);
}
}
# check if the user has filter preferences for this widget
my %Preferences = $UserObject->GetPreferences(
UserID => $Self->{UserID},
);
# get column names from Preferences
my $PreferencesColumnFilters;
if ( $Preferences{ $Self->{PrefKeyColumnFilters} } ) {
$PreferencesColumnFilters = $JSONObject->Decode(
Data => $Preferences{ $Self->{PrefKeyColumnFilters} },
);
}
if ($PreferencesColumnFilters) {
$Self->{GetColumnFilterSelect} = $PreferencesColumnFilters;
my @ColumnFilters = keys %{$PreferencesColumnFilters}; ## no critic
for my $Field (@ColumnFilters) {
$Self->{GetColumnFilter}->{ $Field . $Self->{Name} } = $PreferencesColumnFilters->{$Field};
}
}
# get column real names from Preferences
my $PreferencesColumnFiltersRealKeys;
if ( $Preferences{ $Self->{PrefKeyColumnFiltersRealKeys} } ) {
$PreferencesColumnFiltersRealKeys = $JSONObject->Decode(
Data => $Preferences{ $Self->{PrefKeyColumnFiltersRealKeys} },
);
}
if ($PreferencesColumnFiltersRealKeys) {
my @ColumnFiltersReal = keys %{$PreferencesColumnFiltersRealKeys}; ## no critic
for my $Field (@ColumnFiltersReal) {
$Self->{ColumnFilter}->{$Field} = $PreferencesColumnFiltersRealKeys->{$Field};
}
}
# get current filter
my $Name = $ParamObject->GetParam( Param => 'Name' ) || '';
my $PreferencesKey = 'UserDashboardTicketGenericFilter' . $Self->{Name};
if ( $Self->{Name} eq $Name ) {
$Self->{Filter} = $ParamObject->GetParam( Param => 'Filter' ) || '';
}
# remember filter
if ( $Self->{Filter} ) {
# update session
$Kernel::OM->Get('Kernel::System::AuthSession')->UpdateSessionID(
SessionID => $Self->{SessionID},
Key => $PreferencesKey,
Value => $Self->{Filter},
);
# update preferences
if ( !$ConfigObject->Get('DemoSystem') ) {
$UserObject->SetPreferences(
UserID => $Self->{UserID},
Key => $PreferencesKey,
Value => $Self->{Filter},
);
}
}
else {
$Self->{Filter} = $Self->{$PreferencesKey} || $Self->{Config}->{Filter} || 'All';
}
$Self->{PrefKeyShown} = 'UserDashboardPref' . $Self->{Name} . '-Shown';
$Self->{PrefKeyColumns} = 'UserDashboardPref' . $Self->{Name} . '-Columns';
$Self->{PageShown} = $Kernel::OM->Get('Kernel::Output::HTML::Layout')->{ $Self->{PrefKeyShown} }
|| $Self->{Config}->{Limit};
$Self->{StartHit} = int( $ParamObject->GetParam( Param => 'StartHit' ) || 1 );
# define filterable columns
$Self->{ValidFilterableColumns} = {
'Owner' => 1,
'Responsible' => 1,
'CustomerID' => 1,
'CustomerUserID' => 1,
'State' => 1,
'Queue' => 1,
'Priority' => 1,
'Type' => 1,
'Lock' => 1,
'Service' => 1,
'SLA' => 1,
};
# hash with all valid sortable columns (taken from TicketSearch)
# SortBy => 'Age', # Owner|Responsible|CustomerID|State|TicketNumber|Queue
# |Priority|Type|Lock|Title|Service|SLA|Changed|PendingTime|EscalationTime
# | EscalationUpdateTime|EscalationResponseTime|EscalationSolutionTime
$Self->{ValidSortableColumns} = {
'Age' => 1,
'Owner' => 1,
'Responsible' => 1,
'CustomerID' => 1,
'State' => 1,
'TicketNumber' => 1,
'Queue' => 1,
'Priority' => 1,
'Type' => 1,
'Lock' => 1,
'Title' => 1,
'Service' => 1,
'Changed' => 1,
'SLA' => 1,
'PendingTime' => 1,
'EscalationTime' => 1,
'EscalationUpdateTime' => 1,
'EscalationResponseTime' => 1,
'EscalationSolutionTime' => 1,
};
# remove CustomerID if Customer Information Center
if ( $Self->{Action} eq 'AgentCustomerInformationCenter' ) {
delete $Self->{ColumnFilter}->{CustomerID};
delete $Self->{GetColumnFilter}->{CustomerID};
delete $Self->{GetColumnFilterSelect}->{CustomerID};
delete $Self->{ValidFilterableColumns}->{CustomerID};
delete $Self->{ValidSortableColumns}->{CustomerID};
}
$Self->{UseTicketService} = $ConfigObject->Get('Ticket::Service') || 0;
if ( $Self->{Config}->{IsProcessWidget} ) {
# get process management configuration
$Self->{ProcessManagementProcessID}
= $Kernel::OM->Get('Kernel::Config')->Get('Process::DynamicFieldProcessManagementProcessID');
$Self->{ProcessManagementActivityID}
= $Kernel::OM->Get('Kernel::Config')->Get('Process::DynamicFieldProcessManagementActivityID');
# get the list of processes in the system
my $ProcessListHash = $Kernel::OM->Get('Kernel::System::ProcessManagement::Process')->ProcessList(
ProcessState => [ 'Active', 'FadeAway', 'Inactive' ],
Interface => 'all',
Silent => 1,
);
# use only the process EntityIDs
@{ $Self->{ProcessList} } = sort keys %{$ProcessListHash};
}
return $Self;
}
sub Preferences {
my ( $Self, %Param ) = @_;
# configure columns
my @ColumnsEnabled;
my @ColumnsAvailable;
my @ColumnsAvailableNotEnabled;
# check for default settings
if (
$Self->{Config}->{DefaultColumns}
&& IsHashRefWithData( $Self->{Config}->{DefaultColumns} )
)
{
@ColumnsAvailable = grep { $Self->{Config}->{DefaultColumns}->{$_} }
keys %{ $Self->{Config}->{DefaultColumns} };
@ColumnsEnabled = grep { $Self->{Config}->{DefaultColumns}->{$_} eq '2' }
sort { $Self->_DefaultColumnSort() } keys %{ $Self->{Config}->{DefaultColumns} };
}
# check if the user has filter preferences for this widget
my %Preferences = $Kernel::OM->Get('Kernel::System::User')->GetPreferences(
UserID => $Self->{UserID},
);
# get JSON object
my $JSONObject = $Kernel::OM->Get('Kernel::System::JSON');
# if preference settings are available, take them
if ( $Preferences{ $Self->{PrefKeyColumns} } ) {
my $ColumnsEnabled = $JSONObject->Decode(
Data => $Kernel::OM->Get('Kernel::Output::HTML::Layout')->{ $Self->{PrefKeyColumns} },
);
@ColumnsEnabled = grep { $ColumnsEnabled->{Columns}->{$_} == 1 }
keys %{ $ColumnsEnabled->{Columns} };
if ( $ColumnsEnabled->{Order} && @{ $ColumnsEnabled->{Order} } ) {
@ColumnsEnabled = @{ $ColumnsEnabled->{Order} };
}
}
my %Columns;
for my $ColumnName ( sort { $a cmp $b } @ColumnsAvailable ) {
$Columns{Columns}->{$ColumnName} = ( grep { $ColumnName eq $_ } @ColumnsEnabled ) ? 1 : 0;
if ( !grep { $_ eq $ColumnName } @ColumnsEnabled ) {
push @ColumnsAvailableNotEnabled, $ColumnName;
}
}
# remove CustomerID if Customer Information Center
if ( $Self->{Action} eq 'AgentCustomerInformationCenter' ) {
delete $Columns{Columns}->{CustomerID};
@ColumnsEnabled = grep { $_ ne 'CustomerID' } @ColumnsEnabled;
@ColumnsAvailableNotEnabled = grep { $_ ne 'CustomerID' } @ColumnsAvailableNotEnabled;
}
my @Params = (
{
Desc => 'Shown Tickets',
Name => $Self->{PrefKeyShown},
Block => 'Option',
Data => {
5 => ' 5',
10 => '10',
15 => '15',
20 => '20',
25 => '25',
},
SelectedID => $Self->{PageShown},
Translation => 0,
},
{
Desc => 'Shown Columns',
Name => $Self->{PrefKeyColumns},
Block => 'AllocationList',
Columns => $JSONObject->Encode( Data => \%Columns ),
ColumnsEnabled => $JSONObject->Encode( Data => \@ColumnsEnabled ),
ColumnsAvailable => $JSONObject->Encode( Data => \@ColumnsAvailableNotEnabled ),
Translation => 1,
},
);
return @Params;
}
sub Config {
my ( $Self, %Param ) = @_;
# check if frontend module of link is used
if ( $Self->{Config}->{Link} && $Self->{Config}->{Link} =~ /Action=(.+?)([&;].+?|)$/ ) {
my $Action = $1;
if ( !$Kernel::OM->Get('Kernel::Config')->Get('Frontend::Module')->{$Action} ) {
$Self->{Config}->{Link} = '';
}
}
return (
%{ $Self->{Config} },
# Don't cache this globally as it contains JS that is not inside of the HTML.
CacheTTL => undef,
CacheKey => undef,
);
}
sub FilterContent {
my ( $Self, %Param ) = @_;
return if !$Param{FilterColumn};
my $TicketIDs;
my $HeaderColumn = $Param{FilterColumn};
my @OriginalViewableTickets;
if (
$Kernel::OM->Get('Kernel::Config')->Get('OnlyValuesOnTicket')
|| $HeaderColumn eq 'CustomerID'
|| $HeaderColumn eq 'CustomerUserID'
)
{
my %SearchParams = $Self->_SearchParamsGet(%Param);
my %TicketSearch = %{ $SearchParams{TicketSearch} };
my %TicketSearchSummary = %{ $SearchParams{TicketSearchSummary} };
# add process management search terms
if ( $Self->{Config}->{IsProcessWidget} ) {
$TicketSearch{ 'DynamicField_' . $Self->{ProcessManagementProcessID} } = {
Like => $Self->{ProcessList},
};
}
if (
!$Self->{Config}->{IsProcessWidget}
|| IsArrayRefWithData( $Self->{ProcessList} )
)
{
@OriginalViewableTickets = $Kernel::OM->Get('Kernel::System::Ticket')->TicketSearch(
%TicketSearch,
%{ $TicketSearchSummary{ $Self->{Filter} } },
Result => 'ARRAY',
);
}
}
if ( $HeaderColumn =~ m/^DynamicField_/ && !defined $Self->{DynamicField} ) {
# get the dynamic fields for this screen
$Self->{DynamicField} = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
Valid => 0,
ObjectType => ['Ticket'],
);
}
# get column values for to build the filters later
my $ColumnValues = $Self->_GetColumnValues(
OriginalTicketIDs => \@OriginalViewableTickets,
HeaderColumn => $HeaderColumn,
);
# make sure that even a value of 0 is passed as a Selected value, e.g. Unchecked value of a
# check-box dynamic field.
my $SelectedValue = defined $Self->{GetColumnFilter}->{ $HeaderColumn . $Self->{Name} }
? $Self->{GetColumnFilter}->{ $HeaderColumn . $Self->{Name} }
: '';
my $LabelColumn = $HeaderColumn;
if ( $LabelColumn =~ m{ \A DynamicField_ }xms ) {
my $DynamicFieldConfig;
$LabelColumn =~ s{\A DynamicField_ }{}xms;
DYNAMICFIELD:
for my $DFConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DFConfig);
next DYNAMICFIELD if $DFConfig->{Name} ne $LabelColumn;
$DynamicFieldConfig = $DFConfig;
last DYNAMICFIELD;
}
if ( IsHashRefWithData($DynamicFieldConfig) ) {
$LabelColumn = $DynamicFieldConfig->{Label};
}
}
# variable to save the filter's HTML code
my $ColumnFilterJSON = $Self->_ColumnFilterJSON(
ColumnName => $HeaderColumn,
Label => $LabelColumn,
ColumnValues => $ColumnValues->{$HeaderColumn},
SelectedValue => $SelectedValue,
DashboardName => $Self->{Name},
);
return $ColumnFilterJSON;
}
sub Run {
my ( $Self, %Param ) = @_;
my %SearchParams = $Self->_SearchParamsGet(%Param);
my @Columns = @{ $SearchParams{Columns} };
my %TicketSearch = %{ $SearchParams{TicketSearch} };
my %TicketSearchSummary = %{ $SearchParams{TicketSearchSummary} };
my $CacheKey = join '-', $Self->{Name},
$Self->{Action},
$Self->{PageShown},
$Self->{StartHit},
$Self->{UserID};
my $CacheColumns = join(
',',
map {
$_ . '=>' . $Self->{GetColumnFilterSelect}->{$_}
}
sort keys %{ $Self->{GetColumnFilterSelect} }
);
$CacheKey .= '-' . $CacheColumns if $CacheColumns;
$CacheKey .= '-' . $Self->{SortBy} if defined $Self->{SortBy};
$CacheKey .= '-' . $Self->{OrderBy} if defined $Self->{OrderBy};
# CustomerInformationCenter shows data per CustomerID
if ( $Param{CustomerID} ) {
$CacheKey .= '-' . $Param{CustomerID};
}
# get cache object
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# check cache
my $TicketIDs = $CacheObject->Get(
Type => 'Dashboard',
Key => $CacheKey . '-' . $Self->{Filter} . '-List',
);
# find and show ticket list
my $CacheUsed = 1;
# get ticket object
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
if ( !$TicketIDs ) {
# quote all CustomerIDs
if ( $TicketSearch{CustomerID} ) {
$TicketSearch{CustomerID} = $Kernel::OM->Get('Kernel::System::DB')->QueryStringEscape(
QueryString => $TicketSearch{CustomerID},
);
}
# add sort by parameter to the search
if (
!defined $TicketSearch{SortBy}
|| !$Self->{ValidSortableColumns}->{ $TicketSearch{SortBy} }
)
{
if ( $Self->{SortBy} && $Self->{ValidSortableColumns}->{ $Self->{SortBy} } ) {
$TicketSearch{SortBy} = $Self->{SortBy};
}
else {
$TicketSearch{SortBy} = 'Age';
}
}
# add order by parameter to the search
if ( $Self->{OrderBy} ) {
$TicketSearch{OrderBy} = $Self->{OrderBy};
}
# add process management search terms
if ( $Self->{Config}->{IsProcessWidget} ) {
$TicketSearch{ 'DynamicField_' . $Self->{ProcessManagementProcessID} } = {
Like => $Self->{ProcessList},
};
}
$CacheUsed = 0;
my @TicketIDsArray;
if (
!$Self->{Config}->{IsProcessWidget}
|| IsArrayRefWithData( $Self->{ProcessList} )
)
{
@TicketIDsArray = $TicketObject->TicketSearch(
Result => 'ARRAY',
%TicketSearch,
%{ $TicketSearchSummary{ $Self->{Filter} } },
%{ $Self->{ColumnFilter} },
Limit => $Self->{PageShown} + $Self->{StartHit} - 1,
);
}
$TicketIDs = \@TicketIDsArray;
}
# check cache
my $Summary = $CacheObject->Get(
Type => 'Dashboard',
Key => $CacheKey . '-Summary',
);
# if no cache or new list result, do count lookup
if ( !$Summary || !$CacheUsed ) {
TYPE:
for my $Type ( sort keys %TicketSearchSummary ) {
next TYPE if !$TicketSearchSummary{$Type};
# copy original column filter
my %ColumnFilter = %{ $Self->{ColumnFilter} || {} };
# loop through all column filter elements
for my $Element ( sort keys %ColumnFilter ) {
# verify if current column filter element is already present in the ticket search
# summary, to delete it from the column filter hash
if ( $TicketSearchSummary{$Type}->{$Element} ) {
delete $ColumnFilter{$Element};
}
}
# add process management search terms
if ( $Self->{Config}->{IsProcessWidget} ) {
$TicketSearch{ 'DynamicField_' . $Self->{ProcessManagementProcessID} } = {
Like => $Self->{ProcessList},
};
}
$Summary->{$Type} = 0;
if (
!$Self->{Config}->{IsProcessWidget}
|| IsArrayRefWithData( $Self->{ProcessList} )
)
{
$Summary->{$Type} = $TicketObject->TicketSearch(
Result => 'COUNT',
%TicketSearch,
%{ $TicketSearchSummary{$Type} },
%{ $Self->{ColumnFilter} },
%ColumnFilter,
);
}
}
}
# set cache
if ( !$CacheUsed && $Self->{Config}->{CacheTTLLocal} ) {
$CacheObject->Set(
Type => 'Dashboard',
Key => $CacheKey . '-Summary',
Value => $Summary,
TTL => $Self->{Config}->{CacheTTLLocal} * 60,
);
$CacheObject->Set(
Type => 'Dashboard',
Key => $CacheKey . '-' . $Self->{Filter} . '-List',
Value => $TicketIDs,
TTL => $Self->{Config}->{CacheTTLLocal} * 60,
);
}
# set css class
$Summary->{ $Self->{Filter} . '::Selected' } = 'Selected';
# get layout object
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
# get filter ticket counts
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericFilter',
Data => {
%Param,
%{ $Self->{Config} },
Name => $Self->{Name},
%{$Summary},
},
);
# get config object
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# show also watcher if feature is enabled
if ( $ConfigObject->Get('Ticket::Watcher') ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericFilterWatcher',
Data => {
%Param,
%{ $Self->{Config} },
Name => $Self->{Name},
%{$Summary},
},
);
}
# show also responsible if feature is enabled
if ( $ConfigObject->Get('Ticket::Responsible') ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericFilterResponsible',
Data => {
%Param,
%{ $Self->{Config} },
Name => $Self->{Name},
%{$Summary},
},
);
}
# show only myqueues if we have the filter
if ( $TicketSearchSummary{MyQueues} ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericFilterMyQueues',
Data => {
%Param,
%{ $Self->{Config} },
Name => $Self->{Name},
%{$Summary},
},
);
}
# show only myservices if we have the filter
if ( $TicketSearchSummary{MyServices} ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericFilterMyServices',
Data => {
%Param,
%{ $Self->{Config} },
Name => $Self->{Name},
%{$Summary},
},
);
}
# add page nav bar
my $Total = $Summary->{ $Self->{Filter} } || 0;
my %GetColumnFilter = $Self->{GetColumnFilter} ? %{ $Self->{GetColumnFilter} } : ();
my $ColumnFilterLink = '';
COLUMNNAME:
for my $ColumnName ( sort keys %GetColumnFilter ) {
next COLUMNNAME if !$ColumnName;
next COLUMNNAME if !$GetColumnFilter{$ColumnName};
$ColumnFilterLink
.= ';' . $LayoutObject->Ascii2Html( Text => 'ColumnFilter' . $ColumnName )
. '=' . $LayoutObject->Ascii2Html( Text => $GetColumnFilter{$ColumnName} )
}
my $LinkPage =
'Subaction=Element;Name=' . $Self->{Name}
. ';Filter=' . $Self->{Filter}
. ';SortBy=' . ( $Self->{SortBy} || '' )
. ';OrderBy=' . ( $Self->{OrderBy} || '' )
. $ColumnFilterLink
. ';';
if ( $Param{CustomerID} ) {
$LinkPage .= "CustomerID=$Param{CustomerID};";
}
my %PageNav = $LayoutObject->PageNavBar(
StartHit => $Self->{StartHit},
PageShown => $Self->{PageShown},
AllHits => $Total || 1,
Action => 'Action=' . $LayoutObject->{Action},
Link => $LinkPage,
AJAXReplace => 'Dashboard' . $Self->{Name},
IDPrefix => 'Dashboard' . $Self->{Name},
KeepScriptTags => $Param{AJAX},
);
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericFilterNavBar',
Data => {
%{ $Self->{Config} },
Name => $Self->{Name},
%PageNav,
},
);
# show table header
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeader',
Data => {},
);
# define which meta items will be shown
my @MetaItems = $LayoutObject->TicketMetaItemsCount();
# show non-labeled table headers
my $CSS = '';
my $OrderBy;
for my $Item (@MetaItems) {
$CSS = '';
my $Title = $Item;
if ( $Self->{SortBy} && ( $Self->{SortBy} eq $Item ) ) {
if ( $Self->{OrderBy} && ( $Self->{OrderBy} eq 'Up' ) ) {
$OrderBy = 'Down';
$CSS .= ' SortDescendingLarge';
}
else {
$OrderBy = 'Up';
$CSS .= ' SortAscendingLarge';
}
# set title description
my $TitleDesc = $OrderBy eq 'Down' ? 'sorted descending' : 'sorted ascending';
$TitleDesc = $LayoutObject->{LanguageObject}->Translate($TitleDesc);
$Title .= ', ' . $TitleDesc;
}
# add surrounding container
$LayoutObject->Block(
Name => 'GeneralOverviewHeader',
);
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderMeta',
Data => {
CSS => $CSS,
},
);
if ( $Item eq 'New Article' ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderMetaEmpty',
Data => {
HeaderColumnName => $Item,
},
);
}
else {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderMetaLink',
Data => {
%Param,
Name => $Self->{Name},
OrderBy => $OrderBy || 'Up',
HeaderColumnName => $Item,
Title => $Title,
},
);
}
}
# show all needed headers
HEADERCOLUMN:
for my $HeaderColumn (@Columns) {
# skip CustomerID if Customer Information Center
if (
$Self->{Action} eq 'AgentCustomerInformationCenter'
&& $HeaderColumn eq 'CustomerID'
)
{
next HEADERCOLUMN;
}
if ( $HeaderColumn !~ m{\A DynamicField_}xms ) {
$CSS = '';
my $Title = $HeaderColumn;
if ( $Self->{SortBy} && ( $Self->{SortBy} eq $HeaderColumn ) ) {
if ( $Self->{OrderBy} && ( $Self->{OrderBy} eq 'Up' ) ) {
$OrderBy = 'Down';
$CSS .= ' SortDescendingLarge';
}
else {
$OrderBy = 'Up';
$CSS .= ' SortAscendingLarge';
}
# add title description
my $TitleDesc = $OrderBy eq 'Down' ? 'sorted descending' : 'sorted ascending';
$TitleDesc = $LayoutObject->{LanguageObject}->Translate($TitleDesc);
$Title .= ', ' . $TitleDesc;
}
# translate the column name to write it in the current language
my $TranslatedWord;
if ( $HeaderColumn eq 'EscalationTime' ) {
$TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Service Time');
}
elsif ( $HeaderColumn eq 'EscalationResponseTime' ) {
$TranslatedWord = $LayoutObject->{LanguageObject}->Translate('First Response Time');
}
elsif ( $HeaderColumn eq 'EscalationSolutionTime' ) {
$TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Solution Time');
}
elsif ( $HeaderColumn eq 'EscalationUpdateTime' ) {
$TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Update Time');
}
elsif ( $HeaderColumn eq 'PendingTime' ) {
$TranslatedWord = $LayoutObject->{LanguageObject}->Translate('Pending till');
}
else {
$TranslatedWord = $LayoutObject->{LanguageObject}->Translate($HeaderColumn);
}
# add surrounding container
$LayoutObject->Block(
Name => 'GeneralOverviewHeader',
);
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderTicketHeader',
Data => {},
);
if ( $HeaderColumn eq 'TicketNumber' ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderTicketNumberColumn',
Data => {
%Param,
CSS => $CSS || '',
Name => $Self->{Name},
OrderBy => $OrderBy || 'Up',
Filter => $Self->{Filter},
Title => $Title,
},
);
next HEADERCOLUMN;
}
my $FilterTitle = $HeaderColumn;
my $FilterTitleDesc = 'filter not active';
if ( $Self->{GetColumnFilterSelect} && $Self->{GetColumnFilterSelect}->{$HeaderColumn} )
{
$CSS .= ' FilterActive';
$FilterTitleDesc = 'filter active';
}
$FilterTitleDesc = $LayoutObject->{LanguageObject}->Translate($FilterTitleDesc);
$FilterTitle .= ', ' . $FilterTitleDesc;
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumn',
Data => {
HeaderColumnName => $HeaderColumn || '',
HeaderNameTranslated => $TranslatedWord || $HeaderColumn,
CSS => $CSS || '',
},
);
# verify if column is filterable and sortable
if (
$Self->{ValidSortableColumns}->{$HeaderColumn}
&& $Self->{ValidFilterableColumns}->{$HeaderColumn}
)
{
my $Css;
if (
$HeaderColumn eq 'CustomerID'
|| $HeaderColumn eq 'Responsible'
|| $HeaderColumn eq 'Owner'
)
{
$Css = 'Hidden';
}
# variable to save the filter's html code
my $ColumnFilterHTML = $Self->_InitialColumnFilter(
ColumnName => $HeaderColumn,
Css => $Css,
);
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnFilterLink',
Data => {
%Param,
HeaderColumnName => $HeaderColumn,
CSS => $CSS,
HeaderNameTranslated => $TranslatedWord || $HeaderColumn,
ColumnFilterStrg => $ColumnFilterHTML,
OrderBy => $OrderBy || 'Up',
SortBy => $Self->{SortBy} || 'Age',
Name => $Self->{Name},
Title => $Title,
FilterTitle => $FilterTitle,
},
);
if ( $HeaderColumn eq 'CustomerID' ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnFilterLinkCustomerIDSearch',
Data => {
minQueryLength => 2,
queryDelay => 100,
maxResultsDisplayed => 20,
},
);
}
elsif ( $HeaderColumn eq 'Responsible' || $HeaderColumn eq 'Owner' ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnFilterLinkUserSearch',
Data => {
minQueryLength => 2,
queryDelay => 100,
maxResultsDisplayed => 20,
},
);
}
}
# verify if column is just filterable
elsif ( $Self->{ValidFilterableColumns}->{$HeaderColumn} ) {
my $Css;
if ( $HeaderColumn eq 'CustomerUserID' ) {
$Css = 'Hidden';
}
# variable to save the filter's html code
my $ColumnFilterHTML = $Self->_InitialColumnFilter(
ColumnName => $HeaderColumn,
Css => $Css,
);
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnFilter',
Data => {
%Param,
HeaderColumnName => $HeaderColumn,
CSS => $CSS,
HeaderNameTranslated => $TranslatedWord || $HeaderColumn,
ColumnFilterStrg => $ColumnFilterHTML,
Name => $Self->{Name},
Title => $Title,
FilterTitle => $FilterTitle,
},
);
if ( $HeaderColumn eq 'CustomerUserID' ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnFilterLinkCustomerUserSearch',
Data => {
minQueryLength => 2,
queryDelay => 100,
maxResultsDisplayed => 20,
},
);
}
}
# verify if column is just sortable
elsif ( $Self->{ValidSortableColumns}->{$HeaderColumn} ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnLink',
Data => {
%Param,
HeaderColumnName => $HeaderColumn,
CSS => $CSS,
HeaderNameTranslated => $TranslatedWord || $HeaderColumn,
OrderBy => $OrderBy || 'Up',
SortBy => $Self->{SortBy} || $HeaderColumn,
Name => $Self->{Name},
Title => $Title,
},
);
}
else {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnEmpty',
Data => {
%Param,
HeaderNameTranslated => $TranslatedWord || $HeaderColumn,
HeaderColumnName => $HeaderColumn,
CSS => $CSS,
Title => $Title,
},
);
}
}
# Dynamic fields
else {
my $DynamicFieldConfig;
my $DFColumn = $HeaderColumn;
$DFColumn =~ s/DynamicField_//g;
DYNAMICFIELD:
for my $DFConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DFConfig);
next DYNAMICFIELD if $DFConfig->{Name} ne $DFColumn;
$DynamicFieldConfig = $DFConfig;
last DYNAMICFIELD;
}
next HEADERCOLUMN if !IsHashRefWithData($DynamicFieldConfig);
my $Label = $DynamicFieldConfig->{Label};
my $TranslatedLabel = $LayoutObject->{LanguageObject}->Translate($Label);
my $DynamicFieldName = 'DynamicField_' . $DynamicFieldConfig->{Name};
my $CSS = '';
my $FilterTitle = $Label;
my $FilterTitleDesc = 'filter not active';
if (
$Self->{GetColumnFilterSelect}
&& defined $Self->{GetColumnFilterSelect}->{$DynamicFieldName}
)
{
$CSS .= 'FilterActive ';
$FilterTitleDesc = 'filter active';
}
$FilterTitleDesc = $LayoutObject->{LanguageObject}->Translate($FilterTitleDesc);
$FilterTitle .= ', ' . $FilterTitleDesc;
# get field sortable condition
my $IsSortable = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsSortable',
);
# set title
my $Title = $Label;
# add surrounding container
$LayoutObject->Block(
Name => 'GeneralOverviewHeader',
);
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderTicketHeader',
Data => {},
);
if ($IsSortable) {
my $OrderBy;
if (
$Self->{SortBy}
&& ( $Self->{SortBy} eq ( 'DynamicField_' . $DynamicFieldConfig->{Name} ) )
)
{
if ( $Self->{OrderBy} && ( $Self->{OrderBy} eq 'Up' ) ) {
$OrderBy = 'Down';
$CSS .= ' SortDescendingLarge';
}
else {
$OrderBy = 'Up';
$CSS .= ' SortAscendingLarge';
}
# add title description
my $TitleDesc = $OrderBy eq 'Down' ? 'sorted descending' : 'sorted ascending';
$TitleDesc = $LayoutObject->{LanguageObject}->Translate($TitleDesc);
$Title .= ', ' . $TitleDesc;
}
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumn',
Data => {
HeaderColumnName => $DynamicFieldName || '',
CSS => $CSS || '',
},
);
# check if the dynamic field is sortable and filterable (sortable check was made before)
if ( $Self->{ValidFilterableColumns}->{$DynamicFieldName} ) {
# variable to save the filter's HTML code
my $ColumnFilterHTML = $Self->_InitialColumnFilter(
ColumnName => $DynamicFieldName,
Label => $Label,
);
# output sortable and filterable dynamic field
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnFilterLink',
Data => {
%Param,
HeaderColumnName => $DynamicFieldName,
CSS => $CSS,
HeaderNameTranslated => $TranslatedLabel || $DynamicFieldName,
ColumnFilterStrg => $ColumnFilterHTML,
OrderBy => $OrderBy || 'Up',
SortBy => $Self->{SortBy} || 'Age',
Name => $Self->{Name},
Title => $Title,
FilterTitle => $FilterTitle,
},
);
}
# otherwise the dynamic field is only sortable (sortable check was made before)
else {
# output sortable dynamic field
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnLink',
Data => {
%Param,
HeaderColumnName => $DynamicFieldName,
CSS => $CSS,
HeaderNameTranslated => $TranslatedLabel || $DynamicFieldName,
OrderBy => $OrderBy || 'Up',
SortBy => $Self->{SortBy} || $DynamicFieldName,
Name => $Self->{Name},
Title => $Title,
FilterTitle => $FilterTitle,
},
);
}
}
# if the dynamic field was not sortable (check was made and fail before)
# it might be filterable
elsif ( $Self->{ValidFilterableColumns}->{$DynamicFieldName} ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumn',
Data => {
HeaderColumnName => $DynamicFieldName || '',
CSS => $CSS || '',
Title => $Title,
},
);
# variable to save the filter's HTML code
my $ColumnFilterHTML = $Self->_InitialColumnFilter(
ColumnName => $DynamicFieldName,
Label => $Label,
);
# output filtrable (not sortable) dynamic field
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnFilter',
Data => {
%Param,
HeaderColumnName => $DynamicFieldName,
CSS => $CSS,
HeaderNameTranslated => $TranslatedLabel || $DynamicFieldName,
ColumnFilterStrg => $ColumnFilterHTML,
Name => $Self->{Name},
Title => $Title,
FilterTitle => $FilterTitle,
},
);
}
# otherwise the field is not filterable and not sortable
else {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumn',
Data => {
HeaderColumnName => $DynamicFieldName || '',
CSS => $CSS || '',
},
);
# output plain dynamic field header (not filterable, not sortable)
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericHeaderColumnEmpty',
Data => {
%Param,
HeaderNameTranslated => $TranslatedLabel || $DynamicFieldName,
HeaderColumnName => $DynamicFieldName,
CSS => $CSS,
Title => $Title,
},
);
}
}
}
# show tickets
my $Count = 0;
TICKETID:
for my $TicketID ( @{$TicketIDs} ) {
$Count++;
next TICKETID if $Count < $Self->{StartHit};
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
UserID => $Self->{UserID},
DynamicFields => 0,
Silent => 1
);
next TICKETID if !%Ticket;
# set a default title if ticket has no title
if ( !$Ticket{Title} ) {
$Ticket{Title} = $LayoutObject->{LanguageObject}->Translate(
'This ticket has no title or subject'
);
}
my $WholeTitle = $Ticket{Title} || '';
$Ticket{Title} = $TicketObject->TicketSubjectClean(
TicketNumber => $Ticket{TicketNumber},
Subject => $Ticket{Title},
);
# create human age
if ( $Self->{Config}->{Time} ne 'Age' ) {
$Ticket{Time} = $LayoutObject->CustomerAgeInHours(
Age => $Ticket{ $Self->{Config}->{Time} },
Space => ' ',
);
}
else {
$Ticket{Time} = $LayoutObject->CustomerAge(
Age => $Ticket{ $Self->{Config}->{Time} },
Space => ' ',
);
}
# show ticket
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericRow',
Data => \%Ticket,
);
# show ticket flags
my @TicketMetaItems = $LayoutObject->TicketMetaItems(
Ticket => \%Ticket,
);
for my $Item (@TicketMetaItems) {
$LayoutObject->Block(
Name => 'GeneralOverviewRow',
);
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericRowMeta',
Data => {},
);
if ($Item) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericRowMetaImage',
Data => $Item,
);
}
}
# save column content
my $DataValue;
# get needed objects
my $BackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
# show all needed columns
COLUMN:
for my $Column (@Columns) {
# skip CustomerID if Customer Information Center
if (
$Self->{Action} eq 'AgentCustomerInformationCenter'
&& $Column eq 'CustomerID'
)
{
next COLUMN;
}
if ( $Column !~ m{\A DynamicField_}xms ) {
$LayoutObject->Block(
Name => 'GeneralOverviewRow',
);
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericTicketColumn',
Data => {},
);
my $BlockType = '';
my $CSSClass = '';
if ( $Column eq 'TicketNumber' ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericTicketNumber',
Data => {
%Ticket,
Title => $Ticket{Title},
},
);
next COLUMN;
}
elsif ( $Column eq 'EscalationTime' ) {
my %EscalationData;
$EscalationData{EscalationTime} = $Ticket{EscalationTime};
$EscalationData{EscalationDestinationDate} = $Ticket{EscalationDestinationDate};
$EscalationData{EscalationTimeHuman} = $LayoutObject->CustomerAgeInHours(
Age => $EscalationData{EscalationTime},
Space => ' ',
);
$EscalationData{EscalationTimeWorkingTime} = $LayoutObject->CustomerAgeInHours(
Age => $EscalationData{EscalationTimeWorkingTime},
Space => ' ',
);
if ( defined $Ticket{EscalationTime} && $Ticket{EscalationTime} < 60 * 60 * 1 )
{
$EscalationData{EscalationClass} = 'Warning';
}
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericEscalationTime',
Data => {%EscalationData},
);
next COLUMN;
$DataValue = $LayoutObject->CustomerAge(
Age => $Ticket{'EscalationTime'},
Space => ' '
);
}
elsif ( $Column eq 'Age' ) {
$DataValue = $LayoutObject->CustomerAge(
Age => $Ticket{Age},
Space => ' ',
);
}
elsif ( $Column eq 'EscalationSolutionTime' ) {
$BlockType = 'Escalation';
$DataValue = $LayoutObject->CustomerAgeInHours(
Age => $Ticket{SolutionTime} || 0,
Space => ' ',
);
if ( defined $Ticket{SolutionTime} && $Ticket{SolutionTime} < 60 * 60 * 1 ) {
$CSSClass = 'Warning';
}
}
elsif ( $Column eq 'EscalationResponseTime' ) {
$BlockType = 'Escalation';
$DataValue = $LayoutObject->CustomerAgeInHours(
Age => $Ticket{FirstResponseTime} || 0,
Space => ' ',
);
if (
defined $Ticket{FirstResponseTime}
&& $Ticket{FirstResponseTime} < 60 * 60 * 1
)
{
$CSSClass = 'Warning';
}
}
elsif ( $Column eq 'EscalationUpdateTime' ) {
$BlockType = 'Escalation';
$DataValue = $LayoutObject->CustomerAgeInHours(
Age => $Ticket{UpdateTime} || 0,
Space => ' ',
);
if ( defined $Ticket{UpdateTime} && $Ticket{UpdateTime} < 60 * 60 * 1 ) {
$CSSClass = 'Warning';
}
}
elsif ( $Column eq 'PendingTime' ) {
$BlockType = 'Escalation';
$DataValue = $LayoutObject->CustomerAge(
Age => $Ticket{'UntilTime'},
Space => ' '
);
if ( defined $Ticket{UntilTime} && $Ticket{UntilTime} < -1 ) {
$CSSClass = 'Warning';
}
}
elsif ( $Column eq 'Owner' ) {
# get owner info
my %OwnerInfo = $UserObject->GetUserData(
UserID => $Ticket{OwnerID},
);
$DataValue = $OwnerInfo{'UserFirstname'} . ' ' . $OwnerInfo{'UserLastname'};
}
elsif ( $Column eq 'Responsible' ) {
# get responsible info
my %ResponsibleInfo = $UserObject->GetUserData(
UserID => $Ticket{ResponsibleID},
);
$DataValue = $ResponsibleInfo{'UserFirstname'} . ' '
. $ResponsibleInfo{'UserLastname'};
}
elsif (
$Column eq 'State'
|| $Column eq 'Lock'
|| $Column eq 'Priority'
)
{
$BlockType = 'Translatable';
$DataValue = $Ticket{$Column};
}
elsif ( $Column eq 'Created' || $Column eq 'Changed' ) {
$BlockType = 'Time';
$DataValue = $Ticket{$Column};
}
elsif ( $Column eq 'CustomerName' ) {
# get customer name
my $CustomerName;
if ( $Ticket{CustomerUserID} ) {
$CustomerName = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerName(
UserLogin => $Ticket{CustomerUserID},
);
}
$DataValue = $CustomerName;
}
else {
$DataValue = $Ticket{$Column};
}
if ( $Column eq 'Title' ) {
$LayoutObject->Block(
Name => "ContentLargeTicketTitle",
Data => {
Title => "$DataValue " || '',
WholeTitle => $WholeTitle,
Class => $CSSClass || '',
},
);
}
else {
$LayoutObject->Block(
Name => "ContentLargeTicketGenericColumn$BlockType",
Data => {
GenericValue => $DataValue || '',
Class => $CSSClass || '',
},
);
}
}
# Dynamic fields
else {
my $DynamicFieldConfig;
my $DFColumn = $Column;
$DFColumn =~ s/DynamicField_//g;
DYNAMICFIELD:
for my $DFConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DFConfig);
next DYNAMICFIELD if $DFConfig->{Name} ne $DFColumn;
$DynamicFieldConfig = $DFConfig;
last DYNAMICFIELD;
}
next COLUMN if !IsHashRefWithData($DynamicFieldConfig);
# get field value
my $Value = $BackendObject->ValueGet(
DynamicFieldConfig => $DynamicFieldConfig,
ObjectID => $TicketID,
);
my $ValueStrg = $BackendObject->DisplayValueRender(
DynamicFieldConfig => $DynamicFieldConfig,
Value => $Value,
ValueMaxChars => 20,
LayoutObject => $LayoutObject,
);
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericDynamicField',
Data => {
Value => $ValueStrg->{Value},
Title => $ValueStrg->{Title},
},
);
if ( $ValueStrg->{Link} ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericDynamicFieldLink',
Data => {
Value => $ValueStrg->{Value},
Title => $ValueStrg->{Title},
Link => $ValueStrg->{Link},
$DynamicFieldConfig->{Name} => $ValueStrg->{Title},
},
);
}
else {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericDynamicFieldPlain',
Data => {
Value => $ValueStrg->{Value},
Title => $ValueStrg->{Title},
},
);
}
}
}
}
# show "none" if no ticket is available
if ( !$TicketIDs || !@{$TicketIDs} ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericNone',
Data => {},
);
}
# check for refresh time
my $Refresh = '';
if ( $Self->{UserRefreshTime} ) {
$Refresh = 60 * $Self->{UserRefreshTime};
my $NameHTML = $Self->{Name};
$NameHTML =~ s{-}{_}xmsg;
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericRefresh',
Data => {
%{ $Self->{Config} },
Name => $Self->{Name},
NameHTML => $NameHTML,
RefreshTime => $Refresh,
CustomerID => $Param{CustomerID},
%{$Summary},
},
);
}
# check for active filters and add a 'remove filters' button to the widget header
if ( $Self->{GetColumnFilterSelect} && IsHashRefWithData( $Self->{GetColumnFilterSelect} ) ) {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericRemoveFilters',
Data => {
Name => $Self->{Name},
CustomerID => $Param{CustomerID},
},
);
}
else {
$LayoutObject->Block(
Name => 'ContentLargeTicketGenericRemoveFiltersRemove',
Data => {
Name => $Self->{Name},
},
);
}
my $Content = $LayoutObject->Output(
TemplateFile => 'AgentDashboardTicketGenericProjekt',
Data => {
%{ $Self->{Config} },
Name => $Self->{Name},
%{$Summary},
FilterValue => $Self->{Filter},
CustomerID => $Self->{CustomerID},
},
KeepScriptTags => $Param{AJAX},
);
return $Content;
}
sub _InitialColumnFilter {
my ( $Self, %Param ) = @_;
return if !$Param{ColumnName};
return if !$Self->{ValidFilterableColumns}->{ $Param{ColumnName} };
# get layout object
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
my $Label = $Param{Label} || $Param{ColumnName};
$Label = $LayoutObject->{LanguageObject}->Translate($Label);
# set fixed values
my $Data = [
{
Key => '',
Value => uc $Label,
},
];
# define if column filter values should be translatable
my $TranslationOption = 0;
if (
$Param{ColumnName} eq 'State'
|| $Param{ColumnName} eq 'Lock'
|| $Param{ColumnName} eq 'Priority'
)
{
$TranslationOption = 1;
}
my $Class = 'ColumnFilter';
if ( $Param{Css} ) {
$Class .= ' ' . $Param{Css};
}
# build select HTML
my $ColumnFilterHTML = $LayoutObject->BuildSelection(
Name => 'ColumnFilter' . $Param{ColumnName} . $Self->{Name},
Data => $Data,
Class => $Class,
Translation => $TranslationOption,
SelectedID => '',
);
return $ColumnFilterHTML;
}
sub _GetColumnValues {
my ( $Self, %Param ) = @_;
return if !IsStringWithData( $Param{HeaderColumn} );
my $HeaderColumn = $Param{HeaderColumn};
my %ColumnFilterValues;
my $TicketIDs;
if ( IsArrayRefWithData( $Param{OriginalTicketIDs} ) ) {
$TicketIDs = $Param{OriginalTicketIDs};
}
if ( $HeaderColumn !~ m/^DynamicField_/ ) {
my $FunctionName = $HeaderColumn . 'FilterValuesGet';
if ( $HeaderColumn eq 'CustomerID' ) {
$FunctionName = 'CustomerFilterValuesGet';
}
$ColumnFilterValues{$HeaderColumn} = $Kernel::OM->Get('Kernel::System::Ticket::ColumnFilter')->$FunctionName(
TicketIDs => $TicketIDs,
HeaderColumn => $HeaderColumn,
UserID => $Self->{UserID},
);
}
else {
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
my $FieldName = 'DynamicField_' . $DynamicFieldConfig->{Name};
next DYNAMICFIELD if $FieldName ne $HeaderColumn;
# get dynamic field backend object
my $BackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
my $IsFiltrable = $BackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsFiltrable',
);
next DYNAMICFIELD if !$IsFiltrable;
$Self->{ValidFilterableColumns}->{$HeaderColumn} = $IsFiltrable;
if ( IsArrayRefWithData($TicketIDs) ) {
# get the historical values for the field
$ColumnFilterValues{$HeaderColumn} = $BackendObject->ColumnFilterValuesGet(
DynamicFieldConfig => $DynamicFieldConfig,
LayoutObject => $Kernel::OM->Get('Kernel::Output::HTML::Layout'),
TicketIDs => $TicketIDs,
);
}
else {
# get PossibleValues
$ColumnFilterValues{$HeaderColumn} = $BackendObject->PossibleValuesGet(
DynamicFieldConfig => $DynamicFieldConfig,
);
}
last DYNAMICFIELD;
}
}
return \%ColumnFilterValues;
}
=over
=item _ColumnFilterJSON()
creates a JSON select filter for column header
my $ColumnFilterJSON = $TicketOverviewSmallObject->_ColumnFilterJSON(
ColumnName => 'Queue',
Label => 'Queue',
ColumnValues => {
1 => 'PostMaster',
2 => 'Junk',
},
SelectedValue '1',
);
=cut
sub _ColumnFilterJSON {
my ( $Self, %Param ) = @_;
return if !$Param{ColumnName};
return if !$Self->{ValidFilterableColumns}->{ $Param{ColumnName} };
# get layout object
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
my $Label = $Param{Label};
$Label =~ s{ \A DynamicField_ }{}gxms;
$Label = $LayoutObject->{LanguageObject}->Translate($Label);
# set fixed values
my $Data = [
{
Key => 'DeleteFilter',
Value => uc $Label,
},
{
Key => '-',
Value => '-',
Disabled => 1,
},
];
if ( $Param{ColumnValues} && ref $Param{ColumnValues} eq 'HASH' ) {
my %Values = %{ $Param{ColumnValues} };
# set possible values
for my $ValueKey ( sort { lc $Values{$a} cmp lc $Values{$b} } keys %Values ) {
push @{$Data}, {
Key => $ValueKey,
Value => $Values{$ValueKey}
};
}
}
# define if column filter values should be translatable
my $TranslationOption = 0;
if (
$Param{ColumnName} eq 'State'
|| $Param{ColumnName} eq 'Lock'
|| $Param{ColumnName} eq 'Priority'
)
{
$TranslationOption = 1;
}
# build select HTML
my $JSON = $LayoutObject->BuildSelectionJSON(
[
{
Name => 'ColumnFilter' . $Param{ColumnName} . $Param{DashboardName},
Data => $Data,
Class => 'ColumnFilter',
Sort => 'AlphanumericKey',
TreeView => 1,
SelectedID => $Param{SelectedValue},
Translation => $TranslationOption,
AutoComplete => 'off',
},
],
);
return $JSON;
}
sub _SearchParamsGet {
my ( $Self, %Param ) = @_;
# get all search base attributes
my %TicketSearch;
my %DynamicFieldsParameters;
my @Params = split /;/, $Self->{Config}->{Attributes};
# read user preferences and config to get columns that
# should be shown in the dashboard widget (the preferences
# have precedence)
my %Preferences = $Kernel::OM->Get('Kernel::System::User')->GetPreferences(
UserID => $Self->{UserID},
);
# get column names from Preferences
my $PreferencesColumn = $Kernel::OM->Get('Kernel::System::JSON')->Decode(
Data => $Preferences{ $Self->{PrefKeyColumns} },
);
# check for default settings
my @Columns;
if (
$Self->{Config}->{DefaultColumns}
&& IsHashRefWithData( $Self->{Config}->{DefaultColumns} )
)
{
@Columns = grep { $Self->{Config}->{DefaultColumns}->{$_} eq '2' }
sort { $Self->_DefaultColumnSort() } keys %{ $Self->{Config}->{DefaultColumns} };
}
if ($PreferencesColumn) {
if ( $PreferencesColumn->{Columns} && %{ $PreferencesColumn->{Columns} } ) {
@Columns = grep {
defined $PreferencesColumn->{Columns}->{$_}
&& $PreferencesColumn->{Columns}->{$_} eq '1'
} sort { $Self->_DefaultColumnSort() } keys %{ $Self->{Config}->{DefaultColumns} };
}
if ( $PreferencesColumn->{Order} && @{ $PreferencesColumn->{Order} } ) {
@Columns = @{ $PreferencesColumn->{Order} };
}
}
# always set TicketNumber
if ( !grep { $_ eq 'TicketNumber' } @Columns ) {
unshift @Columns, 'TicketNumber';
}
# also always set ProcessID and ActivityID (for process widgets)
if ( $Self->{Config}->{IsProcessWidget} ) {
my @AlwaysColumns = (
'DynamicField_' . $Self->{ProcessManagementProcessID},
'DynamicField_' . $Self->{ProcessManagementActivityID},
);
my $Resort;
for my $AlwaysColumn (@AlwaysColumns) {
if ( !grep { $_ eq $AlwaysColumn } @Columns ) {
push @Columns, $AlwaysColumn;
$Resort = 1;
}
}
if ($Resort) {
@Columns = sort { $Self->_DefaultColumnSort() } @Columns;
}
}
{
# loop through all the dynamic fields to get the ones that should be shown
DYNAMICFIELDNAME:
for my $DynamicFieldName (@Columns) {
next DYNAMICFIELDNAME if $DynamicFieldName !~ m{ DynamicField_ }xms;
# remove dynamic field prefix
my $FieldName = $DynamicFieldName;
$FieldName =~ s/DynamicField_//gi;
$Self->{DynamicFieldFilter}->{$FieldName} = 1;
}
}
# get the dynamic fields for this screen
$Self->{DynamicField} = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
Valid => 1,
ObjectType => ['Ticket'],
FieldFilter => $Self->{DynamicFieldFilter} || {},
);
# get dynamic field backend object
my $BackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
# get filterable Dynamic fields
# cycle trough the activated Dynamic Fields for this screen
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
my $IsFiltrable = $BackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsFiltrable',
);
# if the dynamic field is filterable add it to the ValidFilterableColumns hash
if ($IsFiltrable) {
$Self->{ValidFilterableColumns}->{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = 1;
}
}
# get sortable Dynamic fields
# cycle trough the activated Dynamic Fields for this screen
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
my $IsSortable = $BackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsSortable',
);
# if the dynamic field is sortable add it to the ValidSortableColumns hash
if ($IsSortable) {
$Self->{ValidSortableColumns}->{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = 1;
}
}
# get queue object
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
STRING:
for my $String (@Params) {
next STRING if !$String;
my ( $Key, $Value ) = split /=/, $String;
if ( $Key eq 'CustomerID' ) {
$Key = "CustomerIDRaw";
}
# push ARRAYREF attributes directly in an ARRAYREF
if (
$Key
=~ /^(StateType|StateTypeIDs|Queues|QueueIDs|Types|TypeIDs|States|StateIDs|Priorities|PriorityIDs|Services|ServiceIDs|SLAs|SLAIDs|Locks|LockIDs|OwnerIDs|ResponsibleIDs|WatchUserIDs|ArchiveFlags)$/
)
{
push @{ $TicketSearch{$Key} }, $Value;
}
# check if parameter is a dynamic field and capture dynamic field name (with DynamicField_)
# in $1 and the Operator in $2
# possible Dynamic Fields options include:
# DynamicField_NameX_Equals=123;
# DynamicField_NameX_Like=value*;
# DynamicField_NameX_GreaterThan=2001-01-01 01:01:01;
# DynamicField_NameX_GreaterThanEquals=2001-01-01 01:01:01;
# DynamicField_NameX_SmallerThan=2002-02-02 02:02:02;
# DynamicField_NameX_SmallerThanEquals=2002-02-02 02:02:02;
elsif ( $Key =~ m{\A (DynamicField_.+?) _ (.+?) \z}sxm ) {
# prevent adding ProcessManagement search parameters (for ProcessWidget)
if ( $Self->{Config}->{IsProcessWidget} ) {
next STRING if $2 eq $Self->{ProcessManagementProcessID};
next STRING if $2 eq $Self->{ProcessManagementActivityID};
}
push @{ $DynamicFieldsParameters{$1}->{$2} }, $Value;
}
elsif ( !defined $TicketSearch{$Key} ) {
# change sort by, if needed
if (
$Key eq 'SortBy'
&& $Self->{SortBy}
&& $Self->{ValidSortableColumns}->{ $Self->{SortBy} }
)
{
$Value = $Self->{SortBy};
}
elsif ( $Key eq 'SortBy' && !$Self->{ValidSortableColumns}->{$Value} ) {
$Value = 'Age';
}
$TicketSearch{$Key} = $Value;
}
elsif ( !ref $TicketSearch{$Key} ) {
my $ValueTmp = $TicketSearch{$Key};
$TicketSearch{$Key} = [$ValueTmp];
push @{ $TicketSearch{$Key} }, $Value;
}
else {
push @{ $TicketSearch{$Key} }, $Value;
}
}
%TicketSearch = (
%TicketSearch,
%DynamicFieldsParameters,
Permission => $Self->{Config}->{Permission} || 'ro',
UserID => $Self->{UserID},
);
# CustomerInformationCenter shows data per CustomerID
if ( $Param{CustomerID} ) {
$TicketSearch{CustomerIDRaw} = $Param{CustomerID};
}
# define filter attributes
my @MyQueues = $QueueObject->GetAllCustomQueues(
UserID => $Self->{UserID},
);
if ( !@MyQueues ) {
@MyQueues = (999_999);
}
# get all queues the agent is allowed to see (for my services)
my %ViewableQueues = $QueueObject->GetAllQueues(
UserID => $Self->{UserID},
Type => 'ro',
);
my @ViewableQueueIDs = sort keys %ViewableQueues;
if ( !@ViewableQueueIDs ) {
@ViewableQueueIDs = (999_999);
}
# get the custom services from agent preferences
# set the service ids to an array of non existing service ids (0)
my @MyServiceIDs = (0);
if ( $Self->{UseTicketService} ) {
@MyServiceIDs = $Kernel::OM->Get('Kernel::System::Service')->GetAllCustomServices(
UserID => $Self->{UserID},
);
if ( !defined $MyServiceIDs[0] ) {
@MyServiceIDs = (0);
}
}
my %TicketSearchSummary = (
Locked => {
OwnerIDs => [ $Self->{UserID}, ],
LockIDs => [ '2', '3' ], # 'lock' and 'tmp_lock'
},
Watcher => {
WatchUserIDs => [ $Self->{UserID}, ],
LockIDs => $TicketSearch{LockIDs} // undef,
},
Responsible => {
ResponsibleIDs => [ $Self->{UserID}, ],
LockIDs => $TicketSearch{LockIDs} // undef,
},
MyQueues => {
QueueIDs => \@MyQueues,
LockIDs => $TicketSearch{LockIDs} // undef,
},
MyServices => {
QueueIDs => \@ViewableQueueIDs,
ServiceIDs => \@MyServiceIDs,
LockIDs => $TicketSearch{LockIDs} // undef,
},
All => {
OwnerIDs => undef,
LockIDs => $TicketSearch{LockIDs} // undef,
},
);
if ( defined $TicketSearch{QueueIDs} || defined $TicketSearch{Queues} ) {
delete $TicketSearchSummary{MyQueues};
}
if ( !$Self->{UseTicketService} ) {
delete $TicketSearchSummary{MyServices};
}
return (
Columns => \@Columns,
TicketSearch => \%TicketSearch,
TicketSearchSummary => \%TicketSearchSummary,
);
}
sub _DefaultColumnSort {
my ( $Self, %Param ) = @_;
my %DefaultColumns = (
TicketNumber => 100,
Age => 110,
Changed => 111,
PendingTime => 112,
EscalationTime => 113,
EscalationSolutionTime => 114,
EscalationResponseTime => 115,
EscalationUpdateTime => 116,
Title => 120,
State => 130,
Lock => 140,
Queue => 150,
Owner => 160,
Responsible => 161,
CustomerID => 170,
CustomerName => 171,
CustomerUserID => 172,
Type => 180,
Service => 191,
SLA => 192,
Priority => 193,
);
# set default order of ProcessManagement columns (for process widgets)
if ( $Self->{Config}->{IsProcessWidget} ) {
$DefaultColumns{"DynamicField_$Self->{ProcessManagementProcessID}"} = 101;
$DefaultColumns{"DynamicField_$Self->{ProcessManagementActivityID}"} = 102;
}
# dynamic fields can not be on the DefaultColumns sorting hash
# when comparing 2 dynamic fields sorting must be alphabetical
if ( !$DefaultColumns{$a} && !$DefaultColumns{$b} ) {
return $a cmp $b;
}
# when a dynamic field is compared to a ticket attribute it must be higher
elsif ( !$DefaultColumns{$a} ) {
return 1;
}
# when a ticket attribute is compared to a dynamic field it must be lower
elsif ( !$DefaultColumns{$b} ) {
return -1;
}
# otherwise do a numerical comparison with the ticket attributes
return $DefaultColumns{$a} <=> $DefaultColumns{$b};
}
1;
=back
Code: Select all
# --
# AgentDashboardTicketGeneric.tt - provides HTML for global dashboard
# Copyright (C) 2001-2015 xxx, http://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (AGPL). If you
# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
# --
[% RenderBlockStart("ContentLargeTicketGenericFilter") %]
<ul class="Tab Actions">
<li class="[% Data.item("Locked::Selected") | html %]"><a href="#" id="Dashboard[% Data.Name | html %]Locked" data-filter="Locked">[% Translate("My locked tickets") | html %] ([% Data.Locked | html %])</a></li>
[% RenderBlockStart("ContentLargeTicketGenericFilterWatcher") %]
<li class="[% Data.item("Watcher::Selected") | html %]"><a href="#" id="Dashboard[% Data.Name | html %]Watcher" data-filter="Watcher">[% Translate("My watched tickets") | html %] ([% Data.Watcher | html %])</a></li>
[% RenderBlockEnd("ContentLargeTicketGenericFilterWatcher") %]
[% RenderBlockStart("ContentLargeTicketGenericFilterResponsible") %]
<li class="[% Data.item("Responsible::Selected") | html %]"><a href="#" id="Dashboard[% Data.Name | html %]Responsible" data-filter="Responsible">[% Translate("My responsibilities") | html %] ([% Data.Responsible | html %])</a></li>
[% RenderBlockEnd("ContentLargeTicketGenericFilterResponsible") %]
[% RenderBlockStart("ContentLargeTicketGenericFilterMyQueues") %]
<li class="[% Data.item("MyQueues::Selected") | html %]"><a href="#" id="Dashboard[% Data.Name | html %]MyQueues" data-filter="MyQueues">[% Translate("Tickets in My Queues") | html %] ([% Data.MyQueues | html %])</a></li>
[% RenderBlockEnd("ContentLargeTicketGenericFilterMyQueues") %]
[% RenderBlockStart("ContentLargeTicketGenericFilterMyServices") %]
<li class="[% Data.item("MyServices::Selected") | html %]"><a href="#" id="Dashboard[% Data.Name | html %]MyServices" data-filter="MyServices">[% Translate("Tickets in My Services") | html %] ([% Data.MyServices | html %])</a></li>
[% RenderBlockEnd("ContentLargeTicketGenericFilterMyServices") %]
<li class="[% Data.item("All::Selected") | html %] Last"><a href="#" id="Dashboard[% Data.Name | html %]All" data-filter="All">[% Translate("All tickets") | html %] ([% Data.All | html %])</a></li>
</ul>
[% RenderBlockStart("ContentLargeTicketGenericFilterNavBar") %]
<span class="Pagination">
[% Data.SiteNavBar %]
</span>
[% RenderBlockEnd("ContentLargeTicketGenericFilterNavBar") %]
[% RenderBlockEnd("ContentLargeTicketGenericFilter") %]
#<!-- This form will not be submitted, we need it for the AJAX calls. -->
<form action="[% Env("CGIHandle") %]" method="post" enctype="multipart/form-data">
<input type="hidden" name="CustomerID" value="[% Data.CustomerID | html %]"/>
<input type="hidden" name="Filter[% Data.Name | html %]" id="Filter[% Data.Name | html %]" value="[% Data.FilterValue | html %]" />
<table class="DataTable">
<thead>
[% RenderBlockStart("ContentLargeTicketGenericHeader") %]
<tr>
[% RenderBlockStart("GeneralOverviewHeader") %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderMeta") %]
<th class="DashboardHeader [% Data.CSS | html %]">
[% RenderBlockStart("ContentLargeTicketGenericHeaderMetaLink") %]
<a id="[% Data.HeaderColumnName | html %]OverviewControl[% Data.Name | html %]" name="[% Data.HeaderColumnName | html %]OverviewControl[% Data.Name | html %]" href="#" title="[% Data.Title | html %]" ></a>
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
$('#[% Data.HeaderColumnName | html %]OverviewControl[% Data.Name | html %]').unbind('click').bind('click', function (Event) {
var Filter, LinkPage, ColumnFilterName, CustomerID;
Filter = $('#Filter[% Data.Name | html %]').val() || 'All';
LinkPage = '';
CustomerID = $('input[name=CustomerID]').val() || '';
// get all column filters
$('.ColumnFilter').each(function(){
ColumnFilterName = $(this).attr('name');
// get all options of current column filter
$(this).children().each( function() {
if ( $(this).attr('value') && $(this).attr('selected') ) {
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).attr('value') + ';';
}
});
});
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';' + LinkPage + ';CustomerID=' + CustomerID + ';SortBy=[% Data.HeaderColumnName | html %];OrderBy=[% Data.OrderBy | html %]', function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
Event.preventDefault();
return false;
});
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericHeaderMetaLink") %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderMetaEmpty") %]
<span title="[% Translate(Data.HeaderColumnName) | html %]"></span>
[% RenderBlockEnd("ContentLargeTicketGenericHeaderMetaEmpty") %]
</th>
[% RenderBlockEnd("ContentLargeTicketGenericHeaderMeta") %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderTicketHeader") %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderTicketNumberColumn") %]
<th class="DashboardHeader TicketNumber [% Data.CSS | html %]" data-column="TicketNumber">
<a id="TicketNumberOverviewControl[% Data.Name | html %]" name="TicketNumberOverviewControl[% Data.Name | html %]" href="#" title="[% Data.Title | html %]" >[% Config("Ticket::Hook") %]</a>
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
$('#TicketNumberOverviewControl[% Data.Name | html %]').unbind('click').bind('click', function (Event) {
var Filter, LinkPage, ColumnFilterName, CustomerID;
Filter = $('#Filter[% Data.Name | html %]').val() || 'All';
LinkPage = '';
CustomerID = $('input[name=CustomerID]').val() || '';
// get all column filters
$('.ColumnFilter').each(function(){
ColumnFilterName = $(this).attr('name');
// get all options of current column filter
$(this).children().each( function() {
if ( $(this).attr('value') && $(this).attr('selected') ) {
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).attr('value') + ';';
}
});
});
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';' + LinkPage + ';CustomerID=' + CustomerID + ';SortBy=TicketNumber;OrderBy=[% Data.OrderBy | html %]', function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
Event.preventDefault();
return false;
});
//]]></script>
[% END %]
</th>
[% RenderBlockEnd("ContentLargeTicketGenericHeaderTicketNumberColumn") %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderColumn") %]
<th class="DashboardHeader [% Data.HeaderColumnName | html %] [% Data.CSS | html %]" data-column="[% Data.HeaderColumnName | html %]">
[% RenderBlockStart("ContentLargeTicketGenericHeaderColumnFilterLink") %]
<a href="#" class="ColumnSettingsTrigger" title="[% Data.FilterTitle | html %]">
<i class="fa fa-filter"></i>
</a>
<div class="ColumnSettingsContainer">
<div class="ColumnSettingsBox">
[% Data.ColumnFilterStrg %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderColumnFilterLinkCustomerIDSearch") %]
<input type="text" class="CustomerIDAutoComplete" autocomplete="off" />
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
Core.Config.Set('CustomerIDAutocomplete.QueryDelay', "[% Data.queryDelay | html %]");
Core.Config.Set('CustomerIDAutocomplete.MaxResultsDisplayed', "[% Data.maxResultsDisplayed | html %]");
Core.Config.Set('CustomerIDAutocomplete.MinQueryLength', "[% Data.minQueryLength | html %]");
Core.Agent.Dashboard.InitCustomerIDAutocomplete( $(".CustomerIDAutoComplete") );
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericHeaderColumnFilterLinkCustomerIDSearch") %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderColumnFilterLinkUserSearch") %]
<input type="text" class="UserAutoComplete" autocomplete="off" />
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
Core.Config.Set('UserAutocomplete.QueryDelay', "[% Data.queryDelay | html %]");
Core.Config.Set('UserAutocomplete.MaxResultsDisplayed', "[% Data.maxResultsDisplayed | html %]");
Core.Config.Set('UserAutocomplete.MinQueryLength', "[% Data.minQueryLength | html %]");
Core.Agent.Dashboard.InitUserAutocomplete( $(".UserAutoComplete") );
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericHeaderColumnFilterLinkUserSearch") %]
<a href="#" class="DeleteFilter Hidden"><i class="fa fa-trash-o"></i></a>
</div>
</div>
<a id="[% Data.HeaderColumnName | html %]OverviewControl[% Data.Name | html %]" name="[% Data.HeaderColumnName | html %]OverviewControl[% Data.Name | html %]" href="#" title="[% Data.Title | html %]" >[% Data.HeaderNameTranslated | html %]</a>
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
$('#ColumnFilter[% Data.HeaderColumnName | html %][% Data.Name | html %]').unbind('change').bind('change', function(){
var LinkPage, ColumnFilterName, Filter, ColumnFilterID, CustomerID;
LinkPage = '';
Filter = $('#Filter[% Data.Name | html %]').val() || 'All';
CustomerID = $('input[name=CustomerID]').val() || '';
// set ColumnFilter value for current ColumnFilter
ColumnFilterName = $(this).attr('name');
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).val() + ';';
// remember the current ColumnFilter ID
ColumnFilterID = $(this).attr('ID');
// get all column filters
$('.ColumnFilter').each(function(){
ColumnFilterName = $(this).attr('name');
// exclude current column filter, apparently the selected option is not set to
// selected at this point and uses the old value
if ($(this).attr('ID') !== ColumnFilterID) {
// get all options of current column filter
$(this).children().each( function() {
if ( $(this).attr('value') && $(this).attr('selected') ) {
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).attr('value') + ';';
}
});
}
});
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';CustomerID=' + CustomerID + ';SortBy=[% Data.SortBy | html %];OrderBy=[% Data.OrderBy | html %];' + LinkPage, function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
return false;
});
$('#[% Data.HeaderColumnName | html %]OverviewControl[% Data.Name | html %]').unbind('click').bind('click', function (Event) {
var LinkPage, ColumnFilterName, Filter, ColumnFilterID, CustomerID;
LinkPage = '';
Filter = $('#Filter[% Data.Name | html %]').val() || 'All';
CustomerID = $('input[name=CustomerID]').val() || '';
// set ColumnFilter value for current ColumnFilter
ColumnFilterName = $(this).attr('name');
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).val() + ';';
// remember the current ColumnFilter ID
ColumnFilterID = $(this).attr('ID');
// get all column filters
$('.ColumnFilter').each(function(){
ColumnFilterName = $(this).attr('name');
// exclude current column filter, apparently the selected option is not set to
// selected at this point and uses the old value
if ($(this).attr('ID') !== ColumnFilterID) {
// get all options of current column filter
$(this).children().each( function() {
if ( $(this).attr('value') && $(this).attr('selected') ) {
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).attr('value') + ';';
}
});
}
});
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';CustomerID=' + CustomerID + ';SortBy=[% Data.HeaderColumnName | html %];OrderBy=[% Data.OrderBy | html %];' + LinkPage, function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
Event.preventDefault();
return false;
});
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericHeaderColumnFilterLink") %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderColumnLink") %]
<a id="[% Data.HeaderColumnName | html %]OverviewControl[% Data.Name | html %]" name="[% Data.HeaderColumnName | html %]OverviewControl[% Data.Name | html %]" href="#" title="[% Data.Title | html %]" >[% Data.HeaderNameTranslated | html %]</a>
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
$('#[% Data.HeaderColumnName | html %]OverviewControl[% Data.Name | html %]').unbind('click').bind('click', function (Event) {
var LinkPage, ColumnFilterName, Filter, ColumnFilterID, CustomerID;
LinkPage = '';
Filter = $('#Filter[% Data.Name | html %]').val() || 'All';
CustomerID = $('input[name=CustomerID]').val() || '';
// set ColumnFilter value for current ColumnFilter
ColumnFilterName = $(this).attr('name');
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).val() + ';';
// remember the current ColumnFilter ID
ColumnFilterID = $(this).attr('ID');
// get all column filters
$('.ColumnFilter').each(function(){
ColumnFilterName = $(this).attr('name');
// exclude current column filter, apparently the selected option is not set to
// selected at this point and uses the old value
if ($(this).attr('ID') !== ColumnFilterID) {
// get all options of current column filter
$(this).children().each( function() {
if ( $(this).attr('value') && $(this).attr('selected') ) {
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).attr('value') + ';';
}
});
}
});
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';CustomerID=' + CustomerID +';SortBy=[% Data.HeaderColumnName | html %];OrderBy=[% Data.OrderBy | html %];' + LinkPage, function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
Event.preventDefault();
return false;
});
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericHeaderColumnLink") %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderColumnFilter") %]
<a href="#" class="ColumnSettingsTrigger" title="[% Data.FilterTitle | html %]">
<i class="fa fa-filter"></i>
</a>
<div class="ColumnSettingsContainer">
<div class="ColumnSettingsBox">
[% Data.ColumnFilterStrg %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderColumnFilterLinkCustomerUserSearch") %]
<input type="text" class="CustomerUserAutoComplete" autocomplete="off" />
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
Core.Config.Set('CustomerUserAutocomplete.QueryDelay', "[% Data.queryDelay | html %]");
Core.Config.Set('CustomerUserAutocomplete.MaxResultsDisplayed', "[% Data.maxResultsDisplayed | html %]");
Core.Config.Set('CustomerUserAutocomplete.MinQueryLength', "[% Data.minQueryLength | html %]");
Core.Agent.Dashboard.InitCustomerUserAutocomplete( $(".CustomerUserAutoComplete") );
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericHeaderColumnFilterLinkCustomerUserSearch") %]
<a href="#" class="DeleteFilter Hidden"><i class="fa fa-trash-o"></i></a>
</div>
</div>
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
$('#ColumnFilter[% Data.HeaderColumnName | html %][% Data.Name | html %]').unbind('change').bind('change', function(){
var LinkPage, ColumnFilterName, Filter, ColumnFilterID, CustomerID;
LinkPage = '';
Filter = $('#Filter[% Data.Name | html %]').val() || 'All';
CustomerID = $('input[name=CustomerID]').val() || '';
// set ColumnFilter value for current ColumnFilter
ColumnFilterName = $(this).attr('name');
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).val() + ';';
// remember the current ColumnFilter ID
ColumnFilterID = $(this).attr('ID');
// get all column filters
$('.ColumnFilter').each(function(){
ColumnFilterName = $(this).attr('name');
// exclude current column filter, apparently the selected option is not set to
// selected at this point and uses the old value
if ($(this).attr('ID') !== ColumnFilterID) {
// get all options of current column filter
$(this).children().each( function() {
if ( $(this).attr('value') && $(this).attr('selected') ) {
LinkPage = LinkPage + ColumnFilterName + '=' + $(this).attr('value') + ';';
}
});
}
});
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';CustomerID=' + CustomerID + ';SortBy=[% Data.SortBy | html %];OrderBy=[% Data.OrderBy | html %];' + LinkPage, function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
return false;
});
//]]></script>
[% END %]
<span class="Gray" title="[% Data.Title | html %]" >[% Data.HeaderNameTranslated | html %]</span>
[% RenderBlockEnd("ContentLargeTicketGenericHeaderColumnFilter") %]
[% RenderBlockStart("ContentLargeTicketGenericHeaderColumnEmpty") %]
<span class="Gray" title="[% Data.Title | html %]" >[% Data.HeaderNameTranslated %]</span>
[% RenderBlockEnd("ContentLargeTicketGenericHeaderColumnEmpty") %]
</th>
[% RenderBlockEnd("ContentLargeTicketGenericHeaderColumn") %]
[% RenderBlockEnd("ContentLargeTicketGenericHeaderTicketHeader") %]
[% RenderBlockEnd("GeneralOverviewHeader") %]
</tr>
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
$('.DashboardHeader').off('click').on('click', '.ColumnSettingsTrigger', function() {
var $TriggerObj = $(this),
$ColumnSettingsContainer = $TriggerObj.next('.ColumnSettingsContainer'),
FilterName;
if ($TriggerObj.hasClass('Active')) {
$TriggerObj
.next('.ColumnSettingsContainer')
.find('.ColumnSettingsBox')
.fadeOut('fast', function() {
$TriggerObj.removeClass('Active');
});
}
else {
// slide up all open settings widgets
$('.ColumnSettingsTrigger')
.next('.ColumnSettingsContainer')
.find('.ColumnSettingsBox')
.fadeOut('fast', function() {
$(this).parent().prev('.ColumnSettingsTrigger').removeClass('Active');
});
// show THIS settings widget
$ColumnSettingsContainer
.find('.ColumnSettingsBox')
.fadeIn('fast', function() {
$TriggerObj.addClass('Active');
// only show and use the delete filter icon in case of autocomplete fields
// because in regular dropdowns we have a different way to delete the filter
if ($TriggerObj.closest('th').hasClass('FilterActive') && $ColumnSettingsContainer.find('select.ColumnFilter').hasClass('Hidden')) {
$ColumnSettingsContainer
.find('.DeleteFilter')
.removeClass('Hidden')
.off()
.on('click', function() {
$(this)
.closest('.ColumnSettingsContainer')
.find('select')
.val('DeleteFilter')
.trigger('change');
return false;
});
}
// refresh filter dropdown
FilterName = $ColumnSettingsContainer
.find('select')
.attr('name');
if ( $TriggerObj.closest('th').hasClass('CustomerID') || $TriggerObj.closest('th').hasClass('CustomerUserID') || $TriggerObj.closest('th').hasClass('Responsible') || $TriggerObj.closest('th').hasClass('Owner') ) {
if (!$TriggerObj.parent().find('.SelectedValue').length) {
Core.AJAX.FormUpdate($TriggerObj.parents('form'), 'AJAXFilterUpdate', FilterName, [ FilterName ], function() {
var AutoCompleteValue = $ColumnSettingsContainer
.find('select')
.val(),
AutoCompleteText = $ColumnSettingsContainer
.find('select')
.find('option:selected')
.text();
if (AutoCompleteValue !== 'DeleteFilter') {
$ColumnSettingsContainer
.find('select')
.after('<span class="SelectedValue Hidden"><span title="' + AutoCompleteText + ' (' + AutoCompleteValue + ')">' + AutoCompleteText + ' (' + AutoCompleteValue + ')</span></span>');
}
});
}
}
else {
Core.AJAX.FormUpdate($TriggerObj.parents('form'), 'AJAXFilterUpdate', FilterName, [ FilterName ]);
}
});
}
return false;
});
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericHeader") %]
</thead>
<tbody>
[% RenderBlockStart("ContentLargeTicketGenericRow") %]
<tr class="MasterAction">
[% RenderBlockStart("GeneralOverviewRow") %]
[% RenderBlockStart("ContentLargeTicketGenericRowMeta") %]
<td class="[% Data.ClassTable | html %] Flags" title="[% Translate(Data.Title) | html %]">
[% RenderBlockStart("ContentLargeTicketGenericRowMetaImage") %]
<div class="[% Data.Class | html %] Small" title="[% Translate(Data.Title) | html %]">
<span class="[% Data.ClassSpan | html %]">
<i class="fa fa-star"></i>
<i class="fa fa-star"></i>
<em>[% Translate(Data.Title) | html %]</em>
</span>
</div>
[% RenderBlockEnd("ContentLargeTicketGenericRowMetaImage") %]
</td>
[% RenderBlockEnd("ContentLargeTicketGenericRowMeta") %]
# <td class="W10pc">
# <a href="[% Env("Baselink") %]Action=AgentTicketZoom;TicketID=[% Data.TicketID %]" title="[% Data.Title | html %]" class="AsBlock MasterActionLink">[% Data.TicketNumber %]</a>
# </td>
# <td class="W50pc">
# <div title="[% Data.Title | html %]">[% Data.Title | truncate(70) | html %]</div>
# </td>
# <td>[% Data.Time | html %]</td>
[% RenderBlockStart("ContentLargeTicketGenericTicketColumn") %]
[% RenderBlockStart("ContentLargeTicketGenericTicketNumber") %]
<td>
<a href="[% Env("Baselink") %]Action=AgentTicketZoom;TicketID=[% Data.TicketID %]" title="[% Data.Title | html %]" class="AsBlock MasterActionLink">[% Data.TicketNumber %]</a>
</td>
[% RenderBlockEnd("ContentLargeTicketGenericTicketNumber") %]
[% RenderBlockStart("ContentLargeTicketGenericEscalationTime") %]
<td>
<div title="[% Translate("Service Time") | html %]: [% Data.EscalationTimeWorkingTime | html %] - [% Data.EscalationDestinationDate | Localize("TimeShort") %]" class="[% Data.EscalationClass | html %]">[% Data.EscalationTimeHuman | html %]<br/>[% Data.UpdateTimeDestinationDate | Localize("TimeShort") %]</div>
</td>
[% RenderBlockEnd("ContentLargeTicketGenericEscalationTime") %]
[% RenderBlockStart("ContentLargeTicketGenericColumn") %]
<td>
<div title="[% Data.GenericValue | html %]">[% Data.GenericValue | truncate(40) | html %]</div>
</td>
[% RenderBlockEnd("ContentLargeTicketGenericColumn") %]
[% RenderBlockStart("ContentLargeTicketTitle") %]
<td>
<div title="[% Data.WholeTitle | html %]">[% Data.Title | html %]</div>
</td>
[% RenderBlockEnd("ContentLargeTicketTitle") %]
[% RenderBlockStart("ContentLargeTicketGenericColumnTranslatable") %]
<td>
<div title="[% Translate(Data.GenericValue) | html %]">[% Translate(Data.GenericValue) | html %]</div>
</td>
[% RenderBlockEnd("ContentLargeTicketGenericColumnTranslatable") %]
[% RenderBlockStart("ContentLargeTicketGenericColumnTime") %]
<td>
<div title="[% Data.GenericValue | Localize("TimeShort") | html %]">[% Data.GenericValue | Localize("TimeShort") %]</div>
</td>
[% RenderBlockEnd("ContentLargeTicketGenericColumnTime") %]
[% RenderBlockStart("ContentLargeTicketGenericColumnEscalation") %]
<td>
<div title="[% Data.GenericValue | html %]" class="[% Data.Class | html %]">[% Data.GenericValue | html %]</div>
</td>
[% RenderBlockEnd("ContentLargeTicketGenericColumnEscalation") %]
[% RenderBlockEnd("ContentLargeTicketGenericTicketColumn") %]
[% RenderBlockStart("ContentLargeTicketGenericDynamicField") %]
<td>
<div title="[% Data.Title | html %]">
[% RenderBlockStart("ContentLargeTicketGenericDynamicFieldLink") %]
<a href="[% Data.Link | Interpolate %]" target="_blank" class="DynamicFieldLink">[% Data.Value %]</a>
[% RenderBlockEnd("ContentLargeTicketGenericDynamicFieldLink") %]
[% RenderBlockStart("ContentLargeTicketGenericDynamicFieldPlain") %]
[% Data.Value %]
[% RenderBlockEnd("ContentLargeTicketGenericDynamicFieldPlain") %]
</div>
</td>
[% RenderBlockEnd("ContentLargeTicketGenericDynamicField") %]
[% RenderBlockEnd("GeneralOverviewRow") %]
</tr>
[% RenderBlockEnd("ContentLargeTicketGenericRow") %]
[% RenderBlockStart("ContentLargeTicketGenericNone") %]
<tr>
<td class="AutoColspan">
[% Translate("none") | html %]
</td>
</tr>
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
$('.AutoColspan').each(function() {
var ColspanCount = $(this).closest('table').find('th').length;
$(this).attr('colspan', ColspanCount);
});
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericNone") %]
</tbody>
</table>
</form>
[% RenderBlockStart("ContentLargeTicketGenericRefresh") %]
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
Core.Config.Set('RefreshSeconds_[% Data.NameHTML | html %]', parseInt("[% Data.RefreshTime | html %]", 10) || 0);
if (Core.Config.Get('RefreshSeconds_[% Data.NameHTML | html %]')) {
Core.Config.Set('Timer_[% Data.NameHTML | html %]', window.setTimeout(function() {
// get active filter
var Filter = $('#Dashboard[% Data.Name | html %]-box').find('.Tab.Actions li.Selected a').attr('data-filter'),
$OrderByObj = $('#Dashboard[% Data.Name | html %]-box').find('th.SortDescendingLarge, th.SortAscendingLarge'),
SortBy = $OrderByObj.attr('data-column') || '',
OrderBy = '';
if ($OrderByObj && $OrderByObj.hasClass('SortDescendingLarge')) {
OrderBy = 'Up';
}
else if ($OrderByObj && $OrderByObj.hasClass('SortAscendingLarge')) {
OrderBy = 'Down';
}
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';CustomerID=[% Data.CustomerID | html %];SortBy=' + SortBy + ';OrderBy=' + OrderBy, function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
clearTimeout(Core.Config.Get('Timer_[% Data.NameHTML | html %]'));
}, Core.Config.Get('RefreshSeconds_[% Data.NameHTML | html %]') * 1000));
}
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericRefresh") %]
[% RenderBlockStart("ContentLargeTicketGenericRemoveFilters") %]
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
if (!$('#Dashboard[% Data.Name | html %]-box').find('.ActionMenu').find('.RemoveFilters').length) {
$('#Dashboard[% Data.Name | html %]-box')
.find('.ActionMenu')
.prepend('<div class="WidgetAction RemoveFilters"><a href="#" id="Dashboard[% Data.Name | html %]-remove-filters" title="[% Translate("Remove active filters for this widget.") | html %]"><i class="fa fa-trash-o"></i></a></div>')
.find('.RemoveFilters')
.on('click', function() {
// get active filter
var Filter = $('#Dashboard[% Data.Name | html %]-box').find('.Tab.Actions li.Selected a').attr('data-filter');
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';CustomerID=[% Data.CustomerID | html %];RemoveFilters=1', function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
return false;
});
}
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericRemoveFilters") %]
[% RenderBlockStart("ContentLargeTicketGenericRemoveFiltersRemove") %]
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
$('#Dashboard[% Data.Name | html %]-box').find('.RemoveFilters').remove();
//]]></script>
[% END %]
[% RenderBlockEnd("ContentLargeTicketGenericRemoveFiltersRemove") %]
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">//<![CDATA[
$('.MasterAction').bind('click', function (Event) {
var $MasterActionLink = $(this).find('.MasterActionLink');
// prevent MasterAction on Dynamic Fields links
if ($(Event.target).hasClass('DynamicFieldLink')) {
return true;
}
// only act if the link was not clicked directly
if (Event.target !== $MasterActionLink.get(0)) {
window.location = $MasterActionLink.attr('href');
return false;
}
});
$('#Dashboard[% Data.Name | html %]-box').find('.Tab.Actions li a').unbind('click').bind('click', function() {
var Filter = $(this).attr('data-filter'),
CustomerID;
CustomerID = $('input[name=CustomerID]').val() || '';
$('#Dashboard[% Data.Name | html %]-box').addClass('Loading');
Core.AJAX.ContentUpdate($('#Dashboard[% Data.Name | html %]'), '[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Element;Name=[% Data.Name | html %];Filter=' + Filter + ';CustomerID=' + encodeURIComponent(CustomerID), function () {
Core.UI.Table.InitCSSPseudoClasses($('#Dashboard[% Data.Name | html %]'));
$('#Dashboard[% Data.Name | html %]-box').removeClass('Loading');
});
return false;
});
//]]></script>
[% END %]
DashboardTicketGenericProjekt.xml
Code: Select all
<?xml version="1.0" encoding="utf-8"?>
<otrs_config version="1.0" init="Application">
<ConfigItem Name="DashboardBackend###11899-DashboardTicketGenericProjekt" Required="0" Valid="1">
<Description Translatable="1">Parameters for the dashboard backend of the ticket pending reminder overview of the agent interface. "Limit" is the number of entries shown by default. "Group" is used to restrict the access to the plugin (e. g. Group: admin;group1;group2;). "Default" determines if the plugin is enabled by default or if the user needs to enable it manually. "CacheTTLLocal" is the cache time in minutes for the plugin. Note: Only Ticket attributes and Dynamic Fields (DynamicField_NameX) are allowed for DefaultColumns. Possible settings: 0 = Disabled, 1 = Available, 2 = Enabled by default.</Description>
<Group>Ritter Framework</Group>
<SubGroup>Frontend::Agent::DashboardRitter</SubGroup>
<Setting>
<Hash>
<Item Key="Module">Kernel::Output::HTML::DashboardTicketGenericProjekt</Item>
<Item Key="Title" Translatable="1">Offene Projekt Tickets</Item>
<Item Key="Description" Translatable="1">All open tickets, these tickets have already been worked on, but need a response</Item>
<Item Key="Attributes">StateType=open;</Item>
<Item Key="Filter">All</Item>
<Item Key="Time">Age</Item>
<Item Key="Limit">10</Item>
<Item Key="Permission">rw</Item>
<Item Key="Block">ContentLarge</Item>
<Item Key="Group"></Item>
<Item Key="Default">1</Item>
<Item Key="CacheTTLLocal">0.5</Item>
<Item Key="DefaultColumns">
<Hash>
<Item Key="Age">2</Item>
<Item Key="Changed">1</Item>
<Item Key="CustomerID">1</Item>
<Item Key="CustomerName">1</Item>
<Item Key="CustomerUserID">1</Item>
<Item Key="EscalationResponseTime">1</Item>
<Item Key="EscalationSolutionTime">1</Item>
<Item Key="EscalationTime">1</Item>
<Item Key="EscalationUpdateTime">1</Item>
<Item Key="TicketNumber">2</Item>
<Item Key="Lock">1</Item>
<Item Key="Owner">1</Item>
<Item Key="PendingTime">1</Item>
<Item Key="Queue">1</Item>
<Item Key="Responsible">1</Item>
<Item Key="Priority">1</Item>
<Item Key="Service">1</Item>
<Item Key="State">1</Item>
<Item Key="SLA">1</Item>
<Item Key="Title">2</Item>
<Item Key="Type">1</Item>
</Hash>
</Item>
</Hash>
</Setting>
</ConfigItem>
</otrs_config>
Linux Debian Jessie
DB: postgres
DB: postgres
Re: Ticketliste als neue Queue Ansicht im Dashboard
Code: Select all
package Kernel::Output::HTML::DashboardTicketGeneric;
Code: Select all
package Kernel::Output::HTML::DashboardTicketGenericProjekt;
Ändere dann auch die Zeilen (nicht zwingend notwendig)
Code: Select all
# Kernel/Output/HTML/DashboardTicketGeneric.pm
Code: Select all
# AgentDashboardTicketGeneric.tt - provides HTML for global dashboard
Code: Select all
# Kernel/Output/HTML/DashboardTicketGenericProjekt.pm
Code: Select all
# AgentDashboardTicketGenericProjekt.tt - provides HTML for global dashboard
OTRS 3.3.x (private/testing) on Windows Server 2008 with MSSQL database.
OTRS 3.3.x (private/testing) on CentOS with MySQL database and apache
OTRS 3.3.x (private/testing) on CentOS with MySQL database and apache
Re: Ticketliste als neue Queue Ansicht im Dashboard
Super jetzt funktioniert es. Vielen vielen Dank an alle!!!!
Linux Debian Jessie
DB: postgres
DB: postgres