[Rt-commit] rt branch, 4.2-on-4.0/search-txn-and-queue-cfs, created. rt-4.0.13-131-gcdf8e9d

Thomas Sibley trs at bestpractical.com
Wed Jul 3 21:02:58 EDT 2013


The branch, 4.2-on-4.0/search-txn-and-queue-cfs has been created
        at  cdf8e9d3c951a6fcc92b697b6017b25e65139ff4 (commit)

- Log -----------------------------------------------------------------
commit 1f982b1249abd38188a5f0ceccbdce580321bdd3
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jun 26 16:53:24 2013 -0700

    Add TxnCF.{Name} and TransactionCF.{Name} syntax to TicketSQL
    
    Reuses all the logic of ticket CF searching.

diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index b2480b0..0aca0b5 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -145,6 +145,8 @@ our %FIELD_METADATA = (
     CustomFieldValue => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair
     CustomField      => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair
     CF               => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair
+    TxnCF            => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair
+    TransactionCF    => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair
     Updated          => [ 'TRANSDATE', ], #loc_left_pair
     RequestorGroup   => [ 'MEMBERSHIPFIELD' => 'Requestor', ], #loc_left_pair
     CCGroup          => [ 'MEMBERSHIPFIELD' => 'Cc', ], #loc_left_pair
@@ -1379,6 +1381,7 @@ Factor out the Join of custom fields so we can use it for sorting too
 
 our %JOIN_ALIAS_FOR_LOOKUP_TYPE = (
     RT::Ticket->CustomFieldLookupType      => sub { "main" },
+    RT::Transaction->CustomFieldLookupType => sub { $_[0]->JoinTransactions },
 );
 
 sub _CustomFieldJoin {

commit 1c6975e81d320e39b55f789d21d00fc93f40efca
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Tue Jun 25 17:05:36 2013 -0700

    Basic tests for searching transaction CFs

diff --git a/t/customfields/transaction_searching.t b/t/customfields/transaction_searching.t
new file mode 100644
index 0000000..b31d4f5
--- /dev/null
+++ b/t/customfields/transaction_searching.t
@@ -0,0 +1,108 @@
+use strict;
+use warnings;
+
+use RT::Test tests => 'no_declare';
+
+my $initialdata = RT::Test::get_relocatable_file("transaction-cfs" => "..", "data", "initialdata");
+my ($rv, $msg) = RT->DatabaseHandle->InsertData( $initialdata, undef, disconnect_after => 0 );
+ok($rv, "Inserted test data from $initialdata")
+    or diag "Error: $msg";
+
+my %ticket = (
+    Spam        => {  },
+    Coffee      => { Billable   => "No", },
+    Phone       => { Billable   => "Yes", Who => ["Telecom", "Information Technology"], When => "2013-06-25", Location => "Geology" },
+    Stacks      => { Billable   => "Yes", Who => "Library", When => "2013-06-01" },
+    Benches     => { Billable   => "Yes", Location => "Outdoors" },
+);
+
+create_tickets();
+
+# Sanity check
+results_are("CF.Location IS NOT NULL", [qw( Phone Benches )]);
+results_are("CF.Location IS NULL",     [qw( Spam Coffee Stacks )]);
+
+results_are("TxnCF.Billable IS NULL", [qw( Spam )]);
+results_are("TxnCF.Billable IS NOT NULL", [qw( Coffee Phone Stacks Benches )]);
+results_are("TxnCF.Billable = 'No'", [qw( Coffee )]);
+results_are("TxnCF.Billable = 'Yes'", [qw( Phone Stacks Benches )]);
+results_are("TxnCF.Billable = 'Yes' AND CF.Location IS NOT NULL", [qw( Phone Benches )]);
+results_are("TxnCF.Billable = 'Yes' AND CF.Location = 'Outdoors'", [qw( Benches )]);
+results_are("TxnCF.Billable = 'Yes' AND CF.Location LIKE 'o'", [qw( Phone Benches )]);
+
+results_are("TxnCF.Who = 'Telecom' OR TxnCF.Who = 'Library'", [qw( Phone Stacks )]);
+results_are("TxnCF.Who != 'Library'", [qw( Spam Coffee Phone Benches )]);
+
+results_are("TxnCF.When > '2013-06-24'", [qw( Phone )]);
+results_are("TxnCF.When < '2013-06-24'", [qw( Stacks )]);
+results_are("TxnCF.When >= '2013-06-01' and TxnCF.When <= '2013-06-30'", [qw( Phone Stacks )]);
+
+results_are("TxnCF.Who LIKE 'e'", [qw( Phone )]);
+results_are("TxnCF.Who NOT LIKE 'e'", [qw( Spam Coffee Stacks Benches )]);
+results_are("TxnCF.Who NOT LIKE 'e' and TxnCF.Who IS NOT NULL", [qw( Stacks )]);
+
+# XXX TODO:
+# Queue-specific txn CFs
+# Multiple transaction CFs by name
+
+done_testing;
+
+sub results_are {
+    my $query    = shift;
+    my $expected = shift;
+    my %expected = map { $_ => 1 } @$expected;
+    my @unexpected;
+
+    my $tickets = RT::Tickets->new(RT->SystemUser);
+    my ($ok, $msg) = $tickets->FromSQL($query);
+    ok($ok, "Searched: $query")
+        or return diag $msg;
+    for my $t (@{$tickets->ItemsArrayRef}) {
+        if (delete $expected{$t->Subject}) {
+            ok(1, "Found expected ticket ".$t->Subject);
+        } else {
+            push @unexpected, $t->Subject;
+        }
+    }
+    ok(0, "Didn't find expected ticket $_")
+        for grep $expected{$_}, @$expected;
+    ok(0, "Found unexpected tickets: ".join ", ", @unexpected)
+        if @unexpected;
+}
+
+sub create_tickets {
+    for my $subject (sort keys %ticket) {
+        my %cfs = %{$ticket{$subject}};
+        my $location = delete $cfs{Location};
+
+        my $ticket = RT::Ticket->new( RT->SystemUser );
+        my ($ok, $msg) = $ticket->Create(
+            Queue   => "General",
+            Subject => $subject,
+        );
+        ok($ticket->id, "Created ticket: $msg") or next;
+
+        if ($location) {
+            ($ok, $msg) = $ticket->AddCustomFieldValue( Field => "Location", Value => $location );
+            ok($ok, "Added Location: $msg") or next;
+        }
+
+        my ($txnid, $txnmsg, $txn) = $ticket->Correspond( Content => "test transaction" );
+        unless ($txnid) {
+            RT->Logger->error("Unable to correspond on ticket $ok: $txnmsg");
+            next;
+        }
+        for my $name (sort keys %cfs) {
+            my $values = ref $cfs{$name} ? $cfs{$name} : [$cfs{$name}];
+            for my $v (@$values) {
+                ($ok, $msg) = $txn->_AddCustomFieldValue(
+                    Field => $name,
+                    Value => $v,
+                    RecordTransaction => 0
+                );
+                RT->Logger->error("Unable to add value '$v' to CF '$name': $msg")
+                    unless $ok;
+            }
+        }
+    }
+}
diff --git a/t/data/initialdata/transaction-cfs b/t/data/initialdata/transaction-cfs
new file mode 100644
index 0000000..e43d986
--- /dev/null
+++ b/t/data/initialdata/transaction-cfs
@@ -0,0 +1,36 @@
+use strict;
+use warnings;
+
+our @CustomFields = (
+    map +{
+        LookupType  => RT::Transaction->CustomFieldLookupType,
+        MaxValues   => 1,
+        Type        => "Freeform",
+        %$_
+    },
+    {   Name    => "Billable",
+        Type    => "Select",
+        Values  => [
+            { Name => "Yes", SortOrder => 1 },
+            { Name => "No",  SortOrder => 2 },
+        ],
+    },
+    {   Name    => "Who",
+        Type    => "SelectMultiple",
+        Values  => [
+            map +{ Name => $_ },
+                "Facilities",
+                "Information Technology",
+                "Library",
+                "Telecom",
+        ],
+    },
+    {   Name    => "When",
+        Type    => "Date",
+    },
+
+    # Some ticket CFs to test mixed searches
+    {   Name        => "Location",
+        LookupType  => RT::Ticket->CustomFieldLookupType,
+    },
+);

commit 74a185e8081133f29628cc384ca929dc39528452
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jun 26 16:40:25 2013 -0700

    Report correct line numbers for failed tests

diff --git a/t/customfields/transaction_searching.t b/t/customfields/transaction_searching.t
index b31d4f5..97a5206 100644
--- a/t/customfields/transaction_searching.t
+++ b/t/customfields/transaction_searching.t
@@ -48,6 +48,8 @@ results_are("TxnCF.Who NOT LIKE 'e' and TxnCF.Who IS NOT NULL", [qw( Stacks )]);
 done_testing;
 
 sub results_are {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
     my $query    = shift;
     my $expected = shift;
     my %expected = map { $_ => 1 } @$expected;

commit 33f781b64b72d8bcb0c6538381d0fa2f606b09c5
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jun 26 16:39:32 2013 -0700

    Meaningful negative searching is hard, comment some tests out as TODO for now
    
    Negative searching is especially hard when there are multiple records
    joining back to a ticket.

diff --git a/t/customfields/transaction_searching.t b/t/customfields/transaction_searching.t
index 97a5206..88b6b28 100644
--- a/t/customfields/transaction_searching.t
+++ b/t/customfields/transaction_searching.t
@@ -22,7 +22,9 @@ create_tickets();
 results_are("CF.Location IS NOT NULL", [qw( Phone Benches )]);
 results_are("CF.Location IS NULL",     [qw( Spam Coffee Stacks )]);
 
-results_are("TxnCF.Billable IS NULL", [qw( Spam )]);
+# TODO: Ideal behaviour of TxnCF IS NULL not yet determined
+#results_are("TxnCF.Billable IS NULL", [qw( Spam )]);
+
 results_are("TxnCF.Billable IS NOT NULL", [qw( Coffee Phone Stacks Benches )]);
 results_are("TxnCF.Billable = 'No'", [qw( Coffee )]);
 results_are("TxnCF.Billable = 'Yes'", [qw( Phone Stacks Benches )]);
@@ -31,14 +33,19 @@ results_are("TxnCF.Billable = 'Yes' AND CF.Location = 'Outdoors'", [qw( Benches
 results_are("TxnCF.Billable = 'Yes' AND CF.Location LIKE 'o'", [qw( Phone Benches )]);
 
 results_are("TxnCF.Who = 'Telecom' OR TxnCF.Who = 'Library'", [qw( Phone Stacks )]);
-results_are("TxnCF.Who != 'Library'", [qw( Spam Coffee Phone Benches )]);
+
+# TODO: Negative searching finds tickets with at least one txn doesn't have the value
+#results_are("TxnCF.Who != 'Library'", [qw( Spam Coffee Phone Benches )]);
 
 results_are("TxnCF.When > '2013-06-24'", [qw( Phone )]);
 results_are("TxnCF.When < '2013-06-24'", [qw( Stacks )]);
 results_are("TxnCF.When >= '2013-06-01' and TxnCF.When <= '2013-06-30'", [qw( Phone Stacks )]);
 
 results_are("TxnCF.Who LIKE 'e'", [qw( Phone )]);
-results_are("TxnCF.Who NOT LIKE 'e'", [qw( Spam Coffee Stacks Benches )]);
+
+# TODO: Negative searching finds tickets with at least one txn doesn't have the value
+#results_are("TxnCF.Who NOT LIKE 'e'", [qw( Spam Coffee Stacks Benches )]);
+
 results_are("TxnCF.Who NOT LIKE 'e' and TxnCF.Who IS NOT NULL", [qw( Stacks )]);
 
 # XXX TODO:

commit 5000efc41079a495a0be95c2529b68e9af45540c
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jun 27 17:14:54 2013 -0700

    Tests for multiple transaction CFs named the same, applied to different queues

diff --git a/t/customfields/transaction_searching.t b/t/customfields/transaction_searching.t
index 88b6b28..0958b5e 100644
--- a/t/customfields/transaction_searching.t
+++ b/t/customfields/transaction_searching.t
@@ -8,7 +8,7 @@ my ($rv, $msg) = RT->DatabaseHandle->InsertData( $initialdata, undef, disconnect
 ok($rv, "Inserted test data from $initialdata")
     or diag "Error: $msg";
 
-my %ticket = (
+create_tickets(
     Spam        => {  },
     Coffee      => { Billable   => "No", },
     Phone       => { Billable   => "Yes", Who => ["Telecom", "Information Technology"], When => "2013-06-25", Location => "Geology" },
@@ -16,8 +16,6 @@ my %ticket = (
     Benches     => { Billable   => "Yes", Location => "Outdoors" },
 );
 
-create_tickets();
-
 # Sanity check
 results_are("CF.Location IS NOT NULL", [qw( Phone Benches )]);
 results_are("CF.Location IS NULL",     [qw( Spam Coffee Stacks )]);
@@ -48,9 +46,26 @@ results_are("TxnCF.Who LIKE 'e'", [qw( Phone )]);
 
 results_are("TxnCF.Who NOT LIKE 'e' and TxnCF.Who IS NOT NULL", [qw( Stacks )]);
 
-# XXX TODO:
+
+# Multiple CFs with same name applied to different queues
+clear_tickets();
+create_tickets(
+    BlueNone    => { Queue => "Blues" },
+    PurpleNone  => { Queue => "Purples" },
+
+    Blue        => { Queue => "Blues",   Color => "Blue" },
+    Purple      => { Queue => "Purples", Color => "Purple" },
+);
+
 # Queue-specific txn CFs
+results_are("TxnCF.Blues.{Color} = 'Blue'", [qw( Blue )]);
+results_are("TxnCF.Blues.{Color} = 'Purple'", []);
+
 # Multiple transaction CFs by name
+results_are("TxnCF.{Color} IS NOT NULL", [qw( Blue Purple )]);
+results_are("TxnCF.{Color} = 'Blue'", [qw( Blue )]);
+results_are("TxnCF.{Color} = 'Purple'", [qw( Purple )]);
+results_are("TxnCF.{Color} LIKE 'e'", [qw( Blue Purple )]);
 
 done_testing;
 
@@ -66,7 +81,7 @@ sub results_are {
     my ($ok, $msg) = $tickets->FromSQL($query);
     ok($ok, "Searched: $query")
         or return diag $msg;
-    for my $t (@{$tickets->ItemsArrayRef}) {
+    for my $t (@{$tickets->ItemsArrayRef || []}) {
         if (delete $expected{$t->Subject}) {
             ok(1, "Found expected ticket ".$t->Subject);
         } else {
@@ -80,13 +95,15 @@ sub results_are {
 }
 
 sub create_tickets {
+    my %ticket = @_;
     for my $subject (sort keys %ticket) {
-        my %cfs = %{$ticket{$subject}};
-        my $location = delete $cfs{Location};
+        my %data = %{$ticket{$subject}};
+        my $location = delete $data{Location};
+        my $queue    = delete $data{Queue} || "General";
 
         my $ticket = RT::Ticket->new( RT->SystemUser );
         my ($ok, $msg) = $ticket->Create(
-            Queue   => "General",
+            Queue   => $queue,
             Subject => $subject,
         );
         ok($ticket->id, "Created ticket: $msg") or next;
@@ -101,17 +118,23 @@ sub create_tickets {
             RT->Logger->error("Unable to correspond on ticket $ok: $txnmsg");
             next;
         }
-        for my $name (sort keys %cfs) {
-            my $values = ref $cfs{$name} ? $cfs{$name} : [$cfs{$name}];
+        for my $name (sort keys %data) {
+            my $values = ref $data{$name} ? $data{$name} : [$data{$name}];
             for my $v (@$values) {
                 ($ok, $msg) = $txn->_AddCustomFieldValue(
                     Field => $name,
                     Value => $v,
                     RecordTransaction => 0
                 );
-                RT->Logger->error("Unable to add value '$v' to CF '$name': $msg")
-                    unless $ok;
+                ok($ok, "Added txn CF $name value '$v'")
+                    or diag $msg;
             }
         }
     }
 }
+
+sub clear_tickets {
+    my $tickets = RT::Tickets->new( RT->SystemUser );
+    $tickets->FromSQL("id > 0");
+    $_->SetStatus("deleted") for @{$tickets->ItemsArrayRef};
+}
diff --git a/t/data/initialdata/transaction-cfs b/t/data/initialdata/transaction-cfs
index e43d986..25c8274 100644
--- a/t/data/initialdata/transaction-cfs
+++ b/t/data/initialdata/transaction-cfs
@@ -1,6 +1,11 @@
 use strict;
 use warnings;
 
+our @Queues = (
+    { Name  => "Blues" },
+    { Name  => "Purples" },
+);
+
 our @CustomFields = (
     map +{
         LookupType  => RT::Transaction->CustomFieldLookupType,
@@ -29,6 +34,17 @@ our @CustomFields = (
         Type    => "Date",
     },
 
+    # Two CFs named the same, but each applied to only one queue
+    # Note: Queue => ref forces RT::Handle to apply rather than
+    # RT::CustomField->Create; the former respects LookupType, the latter
+    # doesn't.
+    {   Name    => "Color",
+        Queue   => ["Blues"],
+    },
+    {   Name    => "Color",
+        Queue   => ["Purples"],
+    },
+
     # Some ticket CFs to test mixed searches
     {   Name        => "Location",
         LookupType  => RT::Ticket->CustomFieldLookupType,

commit 73b1a7e48778efb1468c77f90562c13e54da7129
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jun 27 17:41:39 2013 -0700

    QueueCF.{Name} in TicketSQL
    
    This makes queue CFs suddenly useful for queries such as:
    
        QueueCF.{Type} = 'support'
    
    Currently it joins from Tickets to Queues in order to join to
    OCFs/OCFVs, but a future optimization could remove the Tickets → Queues
    join in favor of limiting on Tickets.Queue directly.

diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 0aca0b5..6a0f66a 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -147,6 +147,7 @@ our %FIELD_METADATA = (
     CF               => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair
     TxnCF            => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair
     TransactionCF    => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair
+    QueueCF          => [ 'CUSTOMFIELD' => 'Queue' ], #loc_left_pair
     Updated          => [ 'TRANSDATE', ], #loc_left_pair
     RequestorGroup   => [ 'MEMBERSHIPFIELD' => 'Requestor', ], #loc_left_pair
     CCGroup          => [ 'MEMBERSHIPFIELD' => 'Cc', ], #loc_left_pair
@@ -1382,6 +1383,14 @@ Factor out the Join of custom fields so we can use it for sorting too
 our %JOIN_ALIAS_FOR_LOOKUP_TYPE = (
     RT::Ticket->CustomFieldLookupType      => sub { "main" },
     RT::Transaction->CustomFieldLookupType => sub { $_[0]->JoinTransactions },
+    RT::Queue->CustomFieldLookupType       => sub {
+        return $_[0]->{_sql_aliases}{queues} ||= $_[0]->Join(
+            ALIAS1 => 'main',
+            FIELD1 => 'Queue',
+            TABLE2 => 'Queues',
+            FIELD2 => 'id',
+        );
+    },
 );
 
 sub _CustomFieldJoin {
@@ -1402,6 +1411,7 @@ sub _CustomFieldJoin {
 
     my ($ObjectCFs, $CFs);
     if ( $cfid ) {
+        # XXX: Could avoid join and use main.Queue instead?
         $ObjectCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
             TYPE   => 'LEFT',
             ALIAS1 => $ObjectAlias,
@@ -1461,6 +1471,7 @@ sub _CustomFieldJoin {
         $self->SUPER::Limit(
             LEFTJOIN        => $ObjectCFs,
             FIELD           => 'ObjectId',
+            # XXX: Could avoid join and use main.Queue instead?
             VALUE           => "$ObjectAlias.id",
             QUOTEVALUE      => 0,
             ENTRYAGGREGATOR => 'AND',

commit cdf8e9d3c951a6fcc92b697b6017b25e65139ff4
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jun 28 14:34:39 2013 -0700

    Transaction and Queue CF pickers in the Query Builder

diff --git a/share/html/NoAuth/css/aileron/ticket-search.css b/share/html/NoAuth/css/aileron/ticket-search.css
index 5b0aa83..039c91c 100644
--- a/share/html/NoAuth/css/aileron/ticket-search.css
+++ b/share/html/NoAuth/css/aileron/ticket-search.css
@@ -49,6 +49,17 @@
     position: relative;
 }
 
+#pick-criteria tr.separator td {
+    position: relative;
+}
+#pick-criteria tr.separator td em {
+    position: absolute;
+    right: 0;
+
+    font-weight: normal;
+    font-variant: italic;
+}
+
 #pick-criteria select {
     width: 8em;
 }
diff --git a/share/html/Search/Elements/PickCriteria b/share/html/Search/Elements/PickCriteria
index 485fceb..9641f79 100644
--- a/share/html/Search/Elements/PickCriteria
+++ b/share/html/Search/Elements/PickCriteria
@@ -53,6 +53,8 @@
 % $m->callback( %ARGS, CallbackName => "BeforeBasics" );
 <& PickBasics, queues => \%queues &>
 <& PickTicketCFs, queues => \%queues &>
+<& PickObjectCFs, Class => 'Transaction', queues => \%queues &>
+<& PickObjectCFs, Class => 'Queue', queues => \%queues &>
 % $m->callback( %ARGS, CallbackName => "AfterCFs" );
 
 <tr class="separator"><td colspan="3"><hr /></td></tr>
diff --git a/share/html/Search/Elements/PickCriteria b/share/html/Search/Elements/PickObjectCFs
similarity index 74%
copy from share/html/Search/Elements/PickCriteria
copy to share/html/Search/Elements/PickObjectCFs
index 485fceb..e5671a5 100644
--- a/share/html/Search/Elements/PickCriteria
+++ b/share/html/Search/Elements/PickObjectCFs
@@ -45,29 +45,30 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<&| /Widgets/TitleBox, title => loc('Add Criteria')&>
-
-<table width="100%" cellspacing="0" cellpadding="0" border="0">
-
-
-% $m->callback( %ARGS, CallbackName => "BeforeBasics" );
-<& PickBasics, queues => \%queues &>
-<& PickTicketCFs, queues => \%queues &>
-% $m->callback( %ARGS, CallbackName => "AfterCFs" );
-
-<tr class="separator"><td colspan="3"><hr /></td></tr>
-<tr>
-<td class="label"><&|/l&>Aggregator</&></td>
-<td class="operator" colspan="2"><& SelectAndOr, Name => "AndOr" &></td>
-
-</tr>
-
-</table>
-
-</&>
-
 <%ARGS>
-$addquery => 0
-$query => undef
+$Class
 %queues => ()
 </%ARGS>
+<%init>
+my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'} );
+$CustomFields->ApplySortOrder;
+$CustomFields->LimitToLookupType( "RT::$Class"->CustomFieldLookupType );
+$CustomFields->LimitToObjectId(0);
+
+foreach my $name (keys %queues) {
+    my $queue = RT::Queue->new($session{'CurrentUser'});
+    $queue->Load($name);
+    $CustomFields->LimitToObjectId($queue->Id) if $queue->Id;
+}
+
+my $has_cf = $CustomFields->First ? 1 : 0;
+$CustomFields->GotoFirstItem;
+</%init>
+% if ($has_cf) {
+<tr class="separator">
+  <td colspan="3">
+    <hr><em><% loc("[_1] CFs", loc($Class)) %></em>
+  </td>
+</tr>
+% }
+<& PickCFs, %ARGS, TicketSQLField => "${Class}CF", CustomFields => $CustomFields &>

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


More information about the Rt-commit mailing list