[Rt-commit] rt branch, 4.6/log-view-db-config-change-history, created. rt-4.4.4-882-g0da6661589

Aaron Trevena ast at bestpractical.com
Mon Mar 30 06:26:44 EDT 2020


The branch, 4.6/log-view-db-config-change-history has been created
        at  0da66615891e65dd13bf2cbc918608d39f851374 (commit)

- Log -----------------------------------------------------------------
commit 039be687752ca9247f6a83d59c4371ea25b436e5
Author: Aaron Trevena <ast at bestpractical.com>
Date:   Fri Mar 20 15:56:42 2020 +0000

    Schema updates for tracking db configuration changes in transactions

diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index b71b41d9d4..2f53b36a94 100644
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -112,7 +112,7 @@ CREATE TABLE Transactions (
         ObjectId                NUMBER(11,0) DEFAULT 0 NOT NULL,
         TimeTaken               NUMBER(11,0) DEFAULT 0 NOT NULL,
         Type                    VARCHAR2(20),
-        Field                   VARCHAR2(40),
+        Field                   VARCHAR2(255),
         OldValue                VARCHAR2(255),
         NewValue                VARCHAR2(255),
         ReferenceType           VARCHAR2(255),
@@ -552,6 +552,5 @@ CREATE TABLE Configurations (
     LastUpdated     DATE
 );
 
-CREATE UNIQUE INDEX Configurations1 ON Configurations (LOWER(Name));
+CREATE INDEX Configurations1 ON Configurations (LOWER(Name), Disabled);
 CREATE INDEX Configurations2 ON Configurations (Disabled);
-
diff --git a/etc/schema.Pg b/etc/schema.Pg
index eda93108df..330eacaa26 100644
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -189,7 +189,7 @@ CREATE TABLE Transactions (
   ObjectId integer NOT NULL DEFAULT 0  ,
   TimeTaken integer NOT NULL DEFAULT 0  ,
   Type varchar(20) NULL  ,
-  Field varchar(40) NULL  ,
+  Field varchar(255) NULL  ,
   OldValue varchar(255) NULL  ,
   NewValue varchar(255) NULL  ,
   ReferenceType varchar(255) NULL,
@@ -793,6 +793,6 @@ CREATE TABLE Configurations (
     PRIMARY KEY (id)
 );
 
-CREATE UNIQUE INDEX Configurations1 ON Configurations (LOWER(Name));
+CREATE INDEX Configurations1 ON Configurations (LOWER(Name), Disabled);
 CREATE INDEX Configurations2 ON Configurations (Disabled);
 
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index b6e0166345..c51070aa87 100644
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -121,7 +121,7 @@ CREATE TABLE Transactions (
   ObjectId integer NULL DEFAULT 0 ,
   TimeTaken integer NULL DEFAULT 0 ,
   Type varchar(20) collate NOCASE NULL  ,
-  Field varchar(40) collate NOCASE NULL  ,
+  Field varchar(255) collate NOCASE NULL  ,
   OldValue varchar(255) collate NOCASE NULL  ,
   NewValue varchar(255) collate NOCASE NULL  ,
   ReferenceType varchar(255) collate NOCASE NULL  ,
@@ -582,6 +582,6 @@ CREATE TABLE Configurations (
     LastUpdated       timestamp                DEFAULT NULL
 );
 
-CREATE UNIQUE INDEX Configurations1 ON Configurations (Name);
+CREATE INDEX Configurations1 ON Configurations (Name, Disabled);
 CREATE INDEX Configurations2 ON Configurations (Disabled);
 
diff --git a/etc/schema.mysql b/etc/schema.mysql
index b0b374c736..eddc72a6b1 100644
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -108,7 +108,7 @@ CREATE TABLE Transactions (
   ObjectId integer NOT NULL DEFAULT 0  ,
   TimeTaken integer NOT NULL DEFAULT 0  ,
   Type varchar(20) CHARACTER SET ascii NULL,
-  Field varchar(40) CHARACTER SET ascii NULL,
+  Field varchar(255) CHARACTER SET ascii NULL,
   OldValue varchar(255) NULL  ,
   NewValue varchar(255) NULL  ,
   ReferenceType varchar(255) CHARACTER SET ascii NULL,
@@ -573,6 +573,6 @@ CREATE TABLE Configurations (
     PRIMARY KEY (id)
 ) ENGINE=InnoDB CHARACTER SET utf8mb4;
 
-CREATE UNIQUE INDEX Configurations1 ON Configurations (Name);
+CREATE INDEX Configurations1 ON Configurations (Name, Disabled);
 CREATE INDEX Configurations2 ON Configurations (Disabled);
 
diff --git a/etc/upgrade/4.5.3/schema.Oracle b/etc/upgrade/4.5.3/schema.Oracle
new file mode 100644
index 0000000000..c4e069a4a7
--- /dev/null
+++ b/etc/upgrade/4.5.3/schema.Oracle
@@ -0,0 +1,5 @@
+-- Add transaction support for new config in database feature
+ALTER TABLE Transactions MODIFY Field VARCHAR2(255);
+
+DROP INDEX Configurations1;
+CREATE INDEX Configurations1 ON Configurations (LOWER(Name), Disabled);
diff --git a/etc/upgrade/4.5.3/schema.Pg b/etc/upgrade/4.5.3/schema.Pg
new file mode 100644
index 0000000000..3df15eacad
--- /dev/null
+++ b/etc/upgrade/4.5.3/schema.Pg
@@ -0,0 +1,5 @@
+-- Add transaction support for new config in database feature
+ALTER TABLE Transactions ALTER COLUMN Field TYPE varchar(255);
+
+DROP INDEX IF EXISTS Configurations1;
+CREATE INDEX Configurations1 ON Configurations (LOWER(Name), Disabled);
diff --git a/etc/upgrade/4.5.3/schema.SQLite b/etc/upgrade/4.5.3/schema.SQLite
new file mode 100644
index 0000000000..88d1ca8dc6
--- /dev/null
+++ b/etc/upgrade/4.5.3/schema.SQLite
@@ -0,0 +1,2 @@
+DROP INDEX IF EXISTS Configurations1;
+CREATE INDEX Configurations1 ON Configurations (Name, Disabled);
diff --git a/etc/upgrade/4.5.3/schema.mysql b/etc/upgrade/4.5.3/schema.mysql
new file mode 100644
index 0000000000..8c3134f25d
--- /dev/null
+++ b/etc/upgrade/4.5.3/schema.mysql
@@ -0,0 +1,5 @@
+-- Add transaction support for new config in database feature
+ALTER TABLE Transactions MODIFY Field VARCHAR(255) CHARACTER SET ascii DEFAULT NULL;
+
+DROP INDEX Configurations1 ON Configurations;
+CREATE INDEX Configurations1 ON Configurations (Name, Disabled);
diff --git a/lib/RT/Transaction.pm b/lib/RT/Transaction.pm
index 914fe51caf..84297f47b4 100644
--- a/lib/RT/Transaction.pm
+++ b/lib/RT/Transaction.pm
@@ -1999,7 +1999,7 @@ sub _CoreAccessible {
         Type =>
                 {read => 1, write => 1, sql_type => 12, length => 20,  is_blob => 0,  is_numeric => 0,  type => 'varchar(20)', default => ''},
         Field =>
-                {read => 1, write => 1, sql_type => 12, length => 40,  is_blob => 0,  is_numeric => 0,  type => 'varchar(40)', default => ''},
+                {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         OldValue =>
                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
         NewValue =>

commit 25315c0903bfd5f68fafc899fa60f0b1af0d2684
Author: Aaron Trevena <ast at bestpractical.com>
Date:   Mon Mar 23 13:21:36 2020 +0000

    Log DB config changes as transactions
    
    Implementation of storing Configuration change history in transactions

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 9362e2a667..8630974ff5 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -2542,7 +2542,7 @@ sub LoadConfigFromDatabase {
     $database_config_cache_time = time;
 
     my $settings = RT::Configurations->new(RT->SystemUser);
-    $settings->UnLimit;
+    $settings->LimitToEnabled;
 
     my %seen;
 
diff --git a/lib/RT/Configuration.pm b/lib/RT/Configuration.pm
index 0d7c3ddf68..4791af3e8e 100644
--- a/lib/RT/Configuration.pm
+++ b/lib/RT/Configuration.pm
@@ -104,62 +104,49 @@ sub Create {
     return (0, $self->loc("Permission Denied"))
         unless $self->CurrentUserHasRight('SuperUser');
 
-    unless ( $args{'Name'} ) {
-        return ( 0, $self->loc("Must specify 'Name' attribute") );
+    if ( $args{'Name'} ) {
+        my ( $ok, $msg ) = $self->ValidateName( $args{'Name'} );
+        unless ($ok) {
+            return ($ok, $msg);
+        }
     }
-
-    my ( $id, $msg ) = $self->ValidateName( $args{'Name'} );
-    return ( 0, $msg ) unless $id;
-
-    my $meta = RT->Config->Meta($args{'Name'});
-    if ($meta->{Immutable}) {
-        return ( 0, $self->loc("You cannot update [_1] using database config; you must edit your site config", $args{'Name'}) );
+    else {
+        return ( 0, $self->loc("Must specify 'Name' attribute") );
     }
 
-    ( $id, $msg ) = $self->ValidateContent( Name => $args{'Name'}, Content => $args{'Content'} );
-    return ( 0, $msg ) unless $id;
 
-    if (ref ($args{'Content'}) ) {
-        ($args{'Content'}, my $error) = $self->_SerializeContent($args{'Content'}, $args{'Name'});
-        if ($error) {
-            return (0, $error);
-        }
-        $args{'ContentType'} = 'perl';
+    $RT::Handle->BeginTransaction;
+    my ( $id, $msg ) = $self->_Create(%args);
+    unless ($id) {
+        $RT::Handle->Rollback;
+        return ($id, $msg);
     }
 
-    my $old_value = RT->Config->Get($args{Name});
-    unless (defined($old_value) && length($old_value)) {
-        $old_value = $self->loc('(no value)');
+    my ($content, $error) = $self->Content;
+    unless (defined($content) && length($content)) {
+        $content = $self->loc('(no value)');
     }
 
-    ( $id, $msg ) = $self->SUPER::Create(
-        map { $_ => $args{$_} } grep {exists $args{$_}}
-            qw(Name Content ContentType),
+    my ( $Trans, $tx_msg, $TransObj ) = $self->_NewTransaction(
+        Type => 'SetConfig',
+        Field => $self->Name,
+        ObjectType => 'RT::Configuration',
+        ObjectId => $self->id,
+        ReferenceType => ref($self),
+        NewReference => $self->id,
     );
-    unless ($id) {
-        return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $args{Name}, $args{Content}, $msg));
+    unless ($Trans) {
+        return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $args{Name}, $content, $tx_msg));
     }
 
+    $RT::Handle->Commit;
     RT->Config->ApplyConfigChangeToAllServerProcesses;
 
-    my ($content, $error) = $self->Content;
-    unless (defined($content) && length($content)) {
-        $content = $self->loc('(no value)');
-    }
-
+    my $old_value = RT->Config->Get($args{Name});
     if ( ref $old_value ) {
         $old_value = $self->_SerializeContent($old_value);
     }
-
-    RT->Logger->info(
-        sprintf(
-            '%s changed %s from "%s" to "%s"',
-            $self->CurrentUser->Name,
-            $self->Name,
-            $old_value // '',
-            $content // ''
-        )
-    );
+    RT->Logger->info($self->CurrentUser->Name . " changed " . $args{Name});
     return ( $id, $self->loc( '[_1] changed from "[_2]" to "[_3]"', $self->Name, $old_value // '', $content // '' ) );
 }
 
@@ -218,8 +205,8 @@ sub ValidateName {
 
     return ( 0, $self->loc('empty name') ) unless defined $name && length $name;
 
-    my $TempSetting = RT::Configuration->new( RT->SystemUser );
-    $TempSetting->Load($name);
+    my $TempSetting  = RT::Configuration->new( RT->SystemUser );
+    $TempSetting->Load(Name => $name, Disabled => 0);
 
     if ( $TempSetting->id && ( !$self->id || $TempSetting->id != $self->id ) ) {
         return ( 0, $self->loc('Name in use') );
@@ -239,10 +226,27 @@ processes.
 sub Delete {
     my $self = shift;
     return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee;
-    my ($ok, $msg) = $self->SUPER::Delete(@_);
-    return ($ok, $msg) if !$ok;
+
+    $RT::Handle->BeginTransaction;
+    my ( $ok, $msg ) = $self->SetDisabled( 1 );
+    unless ($ok) {
+        $RT::Handle->Rollback;
+        return ($ok, $msg);
+    }
+
+    my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+        Type => 'DeleteConfig',
+        Field => $self->Name,
+        ObjectType => 'RT::Configuration',
+        ObjectId => $self->Id,
+        ReferenceType => ref($self),
+        OldReference => $self->id,
+    );
+
+    $RT::Handle->Commit;
     RT->Config->ApplyConfigChangeToAllServerProcesses;
     RT->Logger->info($self->CurrentUser->Name . " removed database setting for " . $self->Name);
+
     return ($ok, $self->loc("Database setting removed."));
 }
 
@@ -276,57 +280,73 @@ sub DecodedContent {
 
 sub SetContent {
     my $self         = shift;
-    my $value        = shift;
+    my $raw_value    = shift;
     my $content_type = shift || '';
 
     return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee;
 
-    my ( $ok, $msg ) = $self->ValidateContent( Content => $value );
+    my ( $ok, $msg ) = $self->ValidateContent( Content => $raw_value );
     return ( 0, $msg ) unless $ok;
 
-    my ($old_value, $error) = $self->Content;
-    unless (defined($old_value) && length($old_value)) {
-        $old_value = $self->loc('(no value)');
-    }
-
+    my $value = $raw_value;
     if (ref $value) {
-        ($value, my $error) = $self->_SerializeContent($value);
-        if ($error) {
-            return (0, $error);
+        ($value, $msg) = $self->_SerializeContent($self->Content, $self->Name);
+        if ($msg) {
+            return (0, $msg);
         }
-        $content_type = 'perl';
+    }
+    if ($self->Content eq $value) {
+        return (0, $self->loc("[_1] update: Nothing changed", ucfirst($self->Name)));
     }
 
     $RT::Handle->BeginTransaction;
+    ( $ok, $msg ) = $self->SetDisabled( 1 );
+    unless ($ok) {
+        $RT::Handle->Rollback;
+        return ($ok, $msg);
+    }
+
+    my ($old_value, $error) = $self->Content;
+    my $old_id = $self->id;
+    my ( $new_id, $new_msg ) = $self->_Create(
+        Name => $self->Name,
+        Content => $raw_value,
+        ContentType => $content_type,
+    );
 
-    ($ok, $msg) = $self->_Set( Field => 'Content', Value => $value );
-    if (!$ok) {
+    unless ($new_id) {
         $RT::Handle->Rollback;
-        return ($ok, $self->loc("Unable to update [_1]: [_2]", $self->Name, $msg));
+        return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $self->Name, $value, $new_msg));
     }
 
-    if ($self->ContentType ne $content_type) {
-        ($ok, $msg) = $self->_Set( Field => 'ContentType', Value => $content_type );
-        if (!$ok) {
-            $RT::Handle->Rollback;
-            return ($ok, $self->loc("Unable to update [_1]: [_2]", $self->Name, $msg));
-        }
+    unless (defined($value) && length($value)) {
+        $value = $self->loc('(no value)');
+    }
+
+    my ( $Trans, $tx_msg, $TransObj ) = $self->_NewTransaction(
+        Type => 'SetConfig',
+        Field => $self->Name,
+        ObjectType => 'RT::Configuration',
+        ObjectId => $new_id,
+        ReferenceType => ref($self),
+        OldReference => $old_id,
+        NewReference => $new_id,
+    );
+    unless ($Trans) {
+        $RT::Handle->Rollback();
+        return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $self->Name, $value, $tx_msg));
     }
 
     $RT::Handle->Commit;
     RT->Config->ApplyConfigChangeToAllServerProcesses;
 
-    unless (defined($value) && length($value)) {
-        $value = $self->loc('(no value)');
+    RT->Logger->info($self->CurrentUser->Name . " changed " . $self->Name);
+    unless (defined($old_value) && length($old_value)) {
+        $old_value = $self->loc('(no value)');
     }
 
-    if (!ref($value) && !ref($old_value)) {
-        RT->Logger->info($self->CurrentUser->Name . " changed " . $self->Name . " from " . $old_value . " to " . $value);
-        return ($ok, $self->loc('[_1] changed from "[_2]" to "[_3]"', $self->Name, $old_value, $value));
-    } else {
-        RT->Logger->info($self->CurrentUser->Name . " changed " . $self->Name);
-        return ($ok, $self->loc("[_1] changed", $self->Name));
-    }
+    return( 1, $self->loc('[_1] changed from "[_2]" to "[_3]"', $self->Name, $old_value // '', $value // '') );
+
 }
 
 =head2 ValidateContent
@@ -363,6 +383,47 @@ sub ValidateContent {
 Documented for internal use only, do not call these from outside
 RT::Configuration itself.
 
+=head2 _Create
+
+Checks that the field being created/updated is not immutable, before calling
+C<SUPER::Create> to save changes in a new row, returning id of new row on success
+ and 0, and message on failure.
+
+=cut
+
+sub _Create {
+    my $self = shift;
+    my %args = (
+        Name => '',
+        Content => '',
+        ContentType => '',
+        @_
+    );
+    my $meta = RT->Config->Meta($args{'Name'});
+    if ($meta->{Immutable}) {
+        return ( 0, $self->loc("You cannot update [_1] using database config; you must edit your site config", $args{'Name'}) );
+    }
+
+    if ( ref( $args{'Content'} ) ) {
+        ( $args{'Content'}, my $error ) =
+          $self->_SerializeContent( $args{'Content'}, $args{'Name'} );
+        if ($error) {
+            return ( 0, $error );
+        }
+        $args{'ContentType'} = 'perl';
+    }
+
+    my ( $id, $msg ) = $self->SUPER::Create(
+        map { $_ => $args{$_} } qw(Name Content ContentType),
+    );
+    unless ($id) {
+        return (0, $self->loc("Setting [_1] to [_2] failed: [_3]", $args{Name}, $args{Content}, $msg));
+    }
+
+    return ($id, $msg);
+}
+
+
 =head2 _Set
 
 Checks if the current user has I<SuperUser> before calling
@@ -403,6 +464,7 @@ sub _SerializeContent {
     my $content = shift;
     require Data::Dumper;
     local $Data::Dumper::Terse = 1;
+    local $Data::Dumper::Sortkeys = 1;
     my $frozen = Data::Dumper::Dumper($content);
     chomp $frozen;
     return $frozen;
diff --git a/share/html/Admin/Tools/EditConfig.html b/share/html/Admin/Tools/EditConfig.html
index 603c46ce8c..a30e0a7c94 100644
--- a/share/html/Admin/Tools/EditConfig.html
+++ b/share/html/Admin/Tools/EditConfig.html
@@ -140,14 +140,6 @@ if (delete $ARGS{Update}) {
             my $setting = RT::Configuration->new($session{CurrentUser});
             $setting->Load($key);
             if ($setting->Id) {
-                if ($setting->Disabled) {
-                    my ($ok, $msg) = $setting->SetDisabled(0);
-                    if (!$ok) {
-                        push @results, $msg;
-                        $has_error++;
-                    }
-                }
-
                 my ($ok, $msg) = $setting->SetContent($val);
                 push @results, $msg;
                 $has_error++ if !$ok;

commit a2d96f55bf210759a2fed37496b3ec8b8af777fe
Author: Aaron Trevena <ast at bestpractical.com>
Date:   Mon Mar 23 13:23:09 2020 +0000

    Remove unused stringify function in configuration edit page

diff --git a/share/html/Admin/Tools/EditConfig.html b/share/html/Admin/Tools/EditConfig.html
index a30e0a7c94..b082750c87 100644
--- a/share/html/Admin/Tools/EditConfig.html
+++ b/share/html/Admin/Tools/EditConfig.html
@@ -62,19 +62,6 @@ my $active_context = {
 
 my @results;
 
-use Data::Dumper;
-my $stringify = sub {
-    my $value = shift;
-    return "" if !defined($value);
-
-    local $Data::Dumper::Terse = 1;
-    local $Data::Dumper::Indent = 2;
-    local $Data::Dumper::Sortkeys = 1;
-    my $output = Dumper $value;
-    chomp $output;
-    return $output;
-};
-
 if (delete $ARGS{Update}) {
     RT->Config->BeginDatabaseConfigChanges;
     $RT::Handle->BeginTransaction;

commit d57d7f1f70d0dad101e1584bfb3340b275638dda
Author: Aaron Trevena <ast at bestpractical.com>
Date:   Mon Mar 23 14:39:30 2020 +0000

    Page to view DB config transaction history
    
    Added page to view transaction log or configurations

diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index 5ce829c461..5731759eed 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -701,9 +701,10 @@ sub BuildMainNav {
         $page->child( edit => raw_html => q[<a id="page-edit" class="menu-item" href="] . RT->Config->Get('WebPath') . qq[/Prefs/MyRT.html"><span class="fas fa-cog" alt="$alt" data-toggle="tooltip" data-placement="top" data-original-title="$alt"></span></a>] );
     }
 
-    if ( $request_path =~ m{^/Admin/Tools/(Configuration|EditConfig)} ) {
+    if ( $request_path =~ m{^/Admin/Tools/(Configuration|EditConfig|ConfigHistory)} ) {
         $page->child( display => title => loc('View'), path => "/Admin/Tools/Configuration.html" );
-        $page->child( history => title => loc('Edit'), path => "/Admin/Tools/EditConfig.html" );
+        $page->child( modify => title => loc('Edit'), path => "/Admin/Tools/EditConfig.html" );
+        $page->child( history => title => loc('History'), path => "/Admin/Tools/ConfigHistory.html" );
     }
 
     # due to historical reasons of always having been in /Elements/Tabs
diff --git a/lib/RT/Transaction.pm b/lib/RT/Transaction.pm
index 84297f47b4..fee1b31d9e 100644
--- a/lib/RT/Transaction.pm
+++ b/lib/RT/Transaction.pm
@@ -1380,6 +1380,30 @@ sub _CanonicalizeRoleName {
         my $self = shift;
         return "Attachment content modified";
     },
+    SetConfig => sub  {
+        my $self = shift;
+        my ($new_value, $old_value);
+
+        # pull in old value from reference if exists
+        if ( $self->OldReference ) {
+            my $oldobj = RT::Configuration->new($self->CurrentUser);
+            $oldobj->Load($self->OldReference);
+            $old_value = $oldobj->Content;
+        }
+
+        # pull in new value from reference if exists
+        if ( $self->NewReference ) {
+            my $newobj = RT::Configuration->new($self->CurrentUser);
+            $newobj->Load($self->NewReference);
+            $new_value = $newobj->Content;
+        }
+        #loc()
+        return ('[_1] changed from "[_2]" to "[_3]"', $self->Field, $old_value // '', $new_value // '');
+    },
+    DeleteConfig => sub  {
+        my $self = shift;
+        return ('[_1] deleted"', $self->Field); #loc()
+    }
 );
 
 
diff --git a/share/html/Admin/Tools/ConfigHistory.html b/share/html/Admin/Tools/ConfigHistory.html
new file mode 100644
index 0000000000..678832430f
--- /dev/null
+++ b/share/html/Admin/Tools/ConfigHistory.html
@@ -0,0 +1,80 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+%#                                          <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%INIT>
+my $title = loc('System Configuration');
+unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser')) {
+  Abort(loc('This feature is only available to system administrators'));
+}
+
+my $Transactions = RT::Transactions->new($session{CurrentUser});
+$Transactions->Limit(FIELD => 'ObjectType', VALUE =>  'RT::Configuration');
+$Transactions->OrderBy(FIELD => 'Created', ORDER => 'DESC');
+</%INIT>
+<& /Admin/Elements/Header, Title => $title &>
+<& /Elements/Tabs &>
+<div class="configuration history">
+ <& /Admin/Elements/ConfigHelp &>
+ <&|/Widgets/TitleBox, title => loc('History')  &>
+   <div class="history-container">
+% my $i = 1;
+% while (my $tx = $Transactions->Next()) {
+    <& /Elements/ShowTransaction,
+    Transaction => $tx,
+    ShowHeaders => 1,
+    ShowDisplayModes => 0,
+    ShowActions => 1,
+    DisplayPath => 'ConfigHistory.html',
+    HasTxnCFs => 0,
+    RowNum => $i
+     &>
+% $i++;
+% }
+  </div>
+ </&>
+</div>
+

commit 0da66615891e65dd13bf2cbc918608d39f851374
Author: Aaron Trevena <ast at bestpractical.com>
Date:   Mon Mar 23 16:42:26 2020 +0000

    Tests for DB config transactions and history
    
    Added unit tests for storing and viewing Configuration change history in transactions

diff --git a/t/web/admin_tools_editconfig.t b/t/web/admin_tools_editconfig.t
index 5b4cbd43ac..889574e37d 100644
--- a/t/web/admin_tools_editconfig.t
+++ b/t/web/admin_tools_editconfig.t
@@ -10,6 +10,7 @@ my ( $url, $m ) = RT::Test->started_ok;
 ok( $m->login(), 'logged in' );
 
 $m->follow_link_ok( { text => 'System Configuration' }, 'followed link to "System Configuration"' );
+$m->follow_link_ok( { text => 'History' }, 'followed link to History page' );
 $m->follow_link_ok( { text => 'Edit' }, 'followed link to Edit page' );
 
 my $tests = [
@@ -41,6 +42,24 @@ my $tests = [
 
 run_test( %{$_} ) for @{$tests};
 
+# check tx log for configuration
+my $transactions = RT::Transactions->new(RT->SystemUser);
+$transactions->Limit(FIELD => 'ObjectType', VALUE =>  'RT::Configuration');
+$transactions->OrderBy(FIELD => 'Created', ORDER => 'ASC');
+my $tx_items = $transactions->ItemsArrayRef;
+
+my $i = 0;
+foreach my $change (@{$tests}) {
+    check_transaction( $tx_items->[$i++], $change );
+}
+
+# check config history page
+$m->get_ok( $url . '/Admin/Tools/ConfigHistory.html');
+$i = 0;
+foreach my $change (@{$tests}) {
+    check_history_page_item($tx_items->[$i++], $change );
+}
+
 sub run_test {
     my %args = @_;
 
@@ -70,17 +89,37 @@ sub run_test {
 
     my $rt_config_value = RT->Config->Get( $args{setting} );
 
-    cmp_deeply( $rt_configuration_value, stringify($args{new_value}), 'value from RT::Configuration->Load matches new value' );
+    is( $rt_configuration_value, stringify($args{new_value}), 'value from RT::Configuration->Load matches new value' );
     cmp_deeply( $rt_config_value, $args{new_value}, 'value from RT->Config->Get matches new value' );
 }
 
+sub check_transaction {
+    my ($tx, $change) = @_;
+    is($tx->ObjectType, 'RT::Configuration', 'tx is config change');
+    is($tx->Field, $change->{setting}, 'tx matches field changed');
+    is($tx->NewValue, stringify($change->{new_value}), 'tx value matches');
+}
+
+sub check_history_page_item {
+    my ($tx, $change) = @_;
+    my $link = sprintf('ConfigHistory.html?id=%d#txn-%d', $tx->ObjectId,$tx->id);
+    ok($m->find_link(url => $link), 'found tx link in history');
+    $m->text_like(regexify($change->{new_value}), 'fetched tx has new value');
+    $m->content_contains( "$change->{setting} changed from", 'fetched tx has changed field');
+}
+
+sub regexify {
+    my $value = stringify(shift);
+    $value =~ s/(\x{0a}|\n|\s+)/\\s\*/g;
+    return qr($value);
+}
+
 sub stringify {
     my $value = shift;
 
     return $value unless ref $value;
 
     local $Data::Dumper::Terse = 1;
-    local $Data::Dumper::Indent = 2;
     local $Data::Dumper::Sortkeys = 1;
 
     my $output = Data::Dumper::Dumper $value;

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


More information about the rt-commit mailing list