[Rt-commit] rt branch, 4.2/migrator-fixes, created. rt-4.2.12-123-g37e7c98

Shawn Moore shawn at bestpractical.com
Mon Jun 27 18:24:27 EDT 2016


The branch, 4.2/migrator-fixes has been created
        at  37e7c98e37f7ad7ac6ad8983fbc3c4702211d23d (commit)

- Log -----------------------------------------------------------------
commit 50dea853e373a5c3d3fafe3f201d8b726fda1099
Author: Dustin Graves <dustin at bestpractical.com>
Date:   Wed Feb 17 20:16:50 2016 +0000

    add AddRight calls to 4.0.1 upgrade step to prevent error message due to failure to canonicalize rights
    
    Fixes: I#31721

diff --git a/etc/upgrade/4.0.1/content b/etc/upgrade/4.0.1/content
index cc3b5f1..851c502 100644
--- a/etc/upgrade/4.0.1/content
+++ b/etc/upgrade/4.0.1/content
@@ -50,6 +50,11 @@ our @Initial = (
     sub {
         RT->Logger->debug('Removing all Delegate and PersonalGroup rights');
 
+        # this temporarily tells the system that the rights exist so it can properly canonicalize them
+        RT::System->AddRight(Admin => AdminOwnPersonalGroups  => 'Add right for 4.0.1 upgrade steps');
+        RT::System->AddRight(Admin => AdminAllPersonalGroups  => 'Add right for 4.0.1 upgrade steps');
+        RT::System->AddRight(Admin => DelegateRights          => 'Add right for 4.0.1 upgrade steps');
+
         my $acl = RT::ACL->new(RT->SystemUser);
         for my $right (qw/AdminOwnPersonalGroups AdminAllPersonalGroups DelegateRights/) {
             $acl->Limit( FIELD => 'RightName', VALUE => $right );

commit 3b8ae5b6697ffd26734c9db46b30002af9460193
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Wed Feb 17 23:19:49 2016 +0000

    Update detection of find version in configure
    
    Commit 4028723 added code to detect the version of find
    because Debian is changing to a newer version of GNU
    find with different syntax than the traditional find.
    However, the change in that commit no longer worked on
    Mac OS X, a BSD-type system.
    
    Modify the find check to use the --version option supported
    on GNU and use GNU syntax if found. Otherwise default to the
    previous BSD syntax which was the previous setting for all
    systems.

diff --git a/configure.ac b/configure.ac
index 44f5384..c671138 100755
--- a/configure.ac
+++ b/configure.ac
@@ -36,21 +36,13 @@ fi
 
 dnl BSD find uses -perm +xxxx, GNU find has deprecated this syntax in favour of
 dnl -perm /xxx.
-AC_MSG_CHECKING([whether find supports -perm /x or find -perm +x])
-if find -perm /0100 -not -perm /0100
-then
-	FINDPERM="/"
-elif
-	find -perm +0100 -not -perm +0100
-then
-	FINDPERM="+"
-else
-	FINDPERM="na"
-fi
-AC_MSG_RESULT([${FINDPERM}])
-if test "x$FINDPERM" = "xna" ; then
-	AC_MSG_WARN([local find program supports neither -perm /0111 nor -perm +0111, make fixperms will not work])
-fi
+AC_MSG_CHECKING([checking version of find])
+AS_IF([find --version 2>&1 | grep 'GNU'],
+      [   FINDPERM="/"
+          AC_MSG_RESULT([configuring for GNU find]) ],
+      [   FINDPERM="+"
+          AC_MSG_RESULT([configuring for BSD find]) ])
+
 AC_SUBST([FINDPERM])
 
 dnl WEB_HANDLER

commit 1cac7d3a64b1b3513610069f7b3895e359585475
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Sat Mar 12 16:27:00 2016 +0000

    Serialize ObjectClass ObjectId using indirection
    
        Otherwise article classes get applied to the wrong queue (the
        one which happens to have had the same ID as one in the database
        being serialized). This method is mostly lifted from
        ObjectCustomField.
    
    Fixes: I#31804

diff --git a/lib/RT/ObjectClass.pm b/lib/RT/ObjectClass.pm
index c51d9d4..f5a5da6 100644
--- a/lib/RT/ObjectClass.pm
+++ b/lib/RT/ObjectClass.pm
@@ -229,6 +229,18 @@ sub FindDependencies {
     $deps->Add( out => $obj );
 }
 
+sub Serialize {
+    my $self = shift;
+    my %args = (@_);
+    my %store = $self->SUPER::Serialize(@_);
+
+    if ($store{ObjectId}) {
+        my $obj = $self->ObjectType->new( RT->SystemUser );
+        $obj->Load( $store{ObjectId} );
+        $store{ObjectId} = \($obj->UID);
+    }
+    return %store;
+}
 
 RT::Base->_ImportOverlays();
 

commit a8bf0f391208c8429feb66fad34dcd71f16d47a5
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Sat Mar 12 16:43:53 2016 +0000

    Serialize ObjectScrip ObjectId using indirection
    
        Otherwise scrip gets applied to the wrong queue (the one which
        happens to have had the same ID as one in the database being
        serialized). This method is mostly lifted from
        ObjectCustomField.
    
    Fixes: I#31805

diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index 8399398..b270d25 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -272,6 +272,19 @@ sub FindDependencies {
     }
 }
 
+sub Serialize {
+    my $self = shift;
+    my %args = (@_);
+    my %store = $self->SUPER::Serialize(@_);
+
+    if ($store{ObjectId}) {
+        my $obj = RT::Queue->new( RT->SystemUser );
+        $obj->Load( $store{ObjectId} );
+        $store{ObjectId} = \($obj->UID);
+    }
+    return %store;
+}
+
 RT::Base->_ImportOverlays();
 
 1;

commit a8b281e18e04197f2ac7206129d7fba26e44ae8a
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Sat Mar 12 16:51:44 2016 +0000

    Serialize ObjectTopic ObjectId using indirection
    
        Otherwise a topic gets applied to the wrong article (the one
        which happens to have had the same ID as one in the database
        being serialized). This method is mostly lifted from
        ObjectCustomField.
    
    Fixes: I#31803

diff --git a/lib/RT/ObjectTopic.pm b/lib/RT/ObjectTopic.pm
index c9429fd..dfe2bfd 100644
--- a/lib/RT/ObjectTopic.pm
+++ b/lib/RT/ObjectTopic.pm
@@ -212,6 +212,19 @@ sub FindDependencies {
     $deps->Add( out => $obj );
 }
 
+sub Serialize {
+    my $self = shift;
+    my %args = (@_);
+    my %store = $self->SUPER::Serialize(@_);
+
+    if ($store{ObjectId}) {
+        my $obj = $self->ObjectType->new( RT->SystemUser );
+        $obj->Load( $store{ObjectId} );
+        $store{ObjectId} = \($obj->UID);
+    }
+    return %store;
+}
+
 RT::Base->_ImportOverlays();
 
 1;

commit aea531f3f5383a53b3ef8114b501b75c7fa7e84e
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Sat Mar 12 18:03:31 2016 +0000

    Avoid breaking rights granted to users when using rt-importer
    
        The "user rights" admin pages issue a SQL query where each
        user's ACLEquivalence group name is expected to be "UserEquiv".
        However, before this commit, the importer system changed each
        group's name from the expected "UserEquiv" to be e.g. "User 99",
        since the user ID changes during import. So because the group
        name was no longer "UserEquiv", the admin UI was failing to find
        the existant but malformed ACL entries.
    
        ACLEquivalence UserEquiv groups stopped using the user's ID in
        their name as of 71fcde32, which is exactly when this regression
        was introduced. Previously, user rights importing worked because
        both the migrator and the ACL subsytems correctly used the Type
        of the group, which was "UserEquiv". (The regression was
        introduced because ACLs started using Name in place of Type)
    
    Fixes: I#31806

diff --git a/lib/RT/Group.pm b/lib/RT/Group.pm
index c412ba6..81ac325 100644
--- a/lib/RT/Group.pm
+++ b/lib/RT/Group.pm
@@ -1741,13 +1741,12 @@ sub PreInflate {
         return;
     };
 
-    # Go looking for the pre-existing version of the it
+    # Go looking for the pre-existing version of it
     if ($data->{Domain} eq "ACLEquivalence") {
         $obj->LoadACLEquivalenceGroup( $data->{Instance} );
         return $duplicated->() if $obj->Id;
 
-        # Update the name and description for the new ID
-        $data->{Name} = 'User '. $data->{Instance};
+        # Update description for the new ID
         $data->{Description} = 'ACL equiv. for user '.$data->{Instance};
     } elsif ($data->{Domain} eq "UserDefined") {
         $data->{Name} = $importer->Qualify($data->{Name});

commit 037839bc7e05a3fcaa9bd9bf9e8efb0d06f9549b
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 21 19:37:23 2016 +0000

    Serialize dashboards in menu preference using indirection
    
        Otherwise the old dashboard IDs will be kept in the user's
        preferences, causing the menu to (in most cases) be empty.
    
    Fixes: I#31810

diff --git a/lib/RT/Attribute.pm b/lib/RT/Attribute.pm
index 9943b57..9f69179 100644
--- a/lib/RT/Attribute.pm
+++ b/lib/RT/Attribute.pm
@@ -632,6 +632,18 @@ sub FindDependencies {
 
     $self->SUPER::FindDependencies($walker, $deps);
     $deps->Add( out => $self->Object );
+
+    # dashboards in menu attribute has dependencies on each of its dashboards
+    if ($self->Name eq RT::User::_PrefName("DashboardsInMenu")) {
+        my $content = $self->Content;
+        for my $pane (values %{ $content || {} }) {
+            for my $dash_id (@$pane) {
+                my $attr = RT::Attribute->new($self->CurrentUser);
+                $attr->LoadById($dash_id);
+                $deps->Add( out => $attr );
+            }
+        }
+    }
 }
 
 sub PreInflate {
@@ -642,9 +654,40 @@ sub PreInflate {
         my $on_uid = ${ $data->{Object} };
         return if $importer->ShouldSkipTransaction($on_uid);
     }
+
+    # decode UIDs to be raw dashboard IDs
+    if ($data->{Name} eq RT::User::_PrefName("DashboardsInMenu")) {
+        my $content = $class->_DeserializeContent($data->{Content});
+        for my $pane (values %{ $content || {} }) {
+            @$pane = map { $importer->LookupObj($$_)->Id } @$pane;
+        }
+        $data->{Content} = $class->_SerializeContent($content);
+    }
+
     return $class->SUPER::PreInflate( $importer, $uid, $data );
 }
 
+sub Serialize {
+    my $self = shift;
+    my %args = (@_);
+    my %store = $self->SUPER::Serialize(@_);
+
+    # encode raw dashboard IDs to be UIDs
+    if ($store{Name} eq RT::User::_PrefName("DashboardsInMenu")) {
+        my $content = $self->_DeserializeContent($store{Content});
+        for my $pane (values %{ $content || {} }) {
+            for (@$pane) {
+                my $attr = RT::Attribute->new($self->CurrentUser);
+                $attr->LoadById($_);
+                $_ = \($attr->UID);
+            }
+        }
+        $store{Content} = $self->_SerializeContent($content);
+    }
+
+    return %store;
+}
+
 RT::Base->_ImportOverlays();
 
 1;

commit 1e9287ae1dd235b9426524c00cebe7898251db46
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 21 23:11:30 2016 +0000

    Add a way to register a postpone callback in importer
    
        This is meant for inflating saved searches and dashboards (see followup
        commits)

diff --git a/lib/RT/Migrate/Importer.pm b/lib/RT/Migrate/Importer.pm
index 7897434..0e6456a 100644
--- a/lib/RT/Migrate/Importer.pm
+++ b/lib/RT/Migrate/Importer.pm
@@ -179,6 +179,9 @@ sub Resolve {
             Field => $ref->{uri},
             Value => $self->LookupObj($uid)->URI,
         ) if defined $ref->{uri};
+        if (my $method = $ref->{method}) {
+            $obj->$method($self, $ref, $class, $id);
+        }
     }
     delete $self->{Pending}{$uid};
 }
@@ -332,7 +335,7 @@ sub Create {
     # Load it back to get real values into the columns
     $obj = $class->new( RT->SystemUser );
     $obj->Load( $id );
-    $obj->PostInflate( $self );
+    $obj->PostInflate( $self, $uid );
 
     return $obj;
 }

commit 7d6f9d6b408efa4c767fecf3418c04e267db0acf
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 21 23:12:52 2016 +0000

    Don't skip importing RT->System searches
    
        While this may duplicate the three builtin searches (bookmarked,
        unowned, my tickets), we definitely do not want to lose any user-defined
        searches saved at the system level.
    
        Furthermore, when merging two RT instances, we may want each user to
        continue using their system's customization of those three builtin
        searches.

diff --git a/lib/RT/Attribute.pm b/lib/RT/Attribute.pm
index 9f69179..c493441 100644
--- a/lib/RT/Attribute.pm
+++ b/lib/RT/Attribute.pm
@@ -652,7 +652,12 @@ sub PreInflate {
 
     if ($data->{Object} and ref $data->{Object}) {
         my $on_uid = ${ $data->{Object} };
-        return if $importer->ShouldSkipTransaction($on_uid);
+
+        # skip attributes of objects we're not inflating
+        # exception: we don't inflate RT->System, but we want RT->System's searches
+        unless ($on_uid eq RT->System->UID && $data->{Name} =~ /Search/) {
+            return if $importer->ShouldSkipTransaction($on_uid);
+        }
     }
 
     # decode UIDs to be raw dashboard IDs

commit 0a408adb24ada26229bea91f6b08a13258e0d2dc
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 21 23:23:07 2016 +0000

    Serialize RT at a Glance preferences using indirection
    
        Otherwise the old search IDs will be kept in the user's preferences,
        causing the homepage to render with "search not found" type errors.
    
    Fixes: I#31809

diff --git a/lib/RT/Attribute.pm b/lib/RT/Attribute.pm
index c493441..9cf42f6 100644
--- a/lib/RT/Attribute.pm
+++ b/lib/RT/Attribute.pm
@@ -644,6 +644,33 @@ sub FindDependencies {
             }
         }
     }
+    # homepage settings attribute has dependencies on each of the searches in it
+    elsif ($self->Name eq RT::User::_PrefName("HomepageSettings")) {
+        my $content = $self->Content;
+        for my $pane (values %{ $content || {} }) {
+            for my $component (@$pane) {
+                # this hairy code mirrors what's in the saved search loader
+                # in /Elements/ShowSearch
+                if ($component->{type} eq 'saved') {
+                    if ($component->{name} =~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/) {
+                        my $attr = RT::Attribute->new($self->CurrentUser);
+                        $attr->LoadById($3);
+                        $deps->Add( out => $attr );
+                    }
+                }
+                elsif ($component->{type} eq 'system') {
+                    my ($search) = RT::System->new($self->CurrentUser)->Attributes->Named( 'Search - ' . $component->{name} );
+                    unless ( $search && $search->Id ) {
+                        my (@custom_searches) = RT::System->new($self->CurrentUser)->Attributes->Named('SavedSearch');
+                        foreach my $custom (@custom_searches) {
+                            if ($custom->Description eq $component->{name}) { $search = $custom; last }
+                        }
+                    }
+                    $deps->Add( out => $search ) if $search;
+                }
+            }
+        }
+    }
 }
 
 sub PreInflate {
@@ -660,16 +687,89 @@ sub PreInflate {
         }
     }
 
+    return $class->SUPER::PreInflate( $importer, $uid, $data );
+}
+
+# this method will be called repeatedly to fix up this attribute's contents
+# (a list of searches, dashboards) during the import process, as the
+# ordinary dependency resolution system can't quite handle the subtlety
+# involved (e.g. a user simply declares out-dependencies on all of her
+# attributes, but those attributes (e.g. dashboards, saved searches,
+# dashboards in menu preferences) have dependencies amongst themselves).
+# if this attribute (e.g. a user's dashboard) fails to load an attribute
+# (e.g. a user's saved search) then it postpones and repeats the postinflate
+# process again when that user's saved search has been imported
+# this method updates Content each time through, each time getting closer and
+# closer to the fully inflated attribute
+sub PostInflateFixup {
+    my $self     = shift;
+    my $importer = shift;
+    my $spec     = shift;
+
     # decode UIDs to be raw dashboard IDs
-    if ($data->{Name} eq RT::User::_PrefName("DashboardsInMenu")) {
-        my $content = $class->_DeserializeContent($data->{Content});
+    if ($self->Name eq RT::User::_PrefName("DashboardsInMenu")) {
+        my $content = $self->Content;
+
         for my $pane (values %{ $content || {} }) {
-            @$pane = map { $importer->LookupObj($$_)->Id } @$pane;
+            for (@$pane) {
+                if (ref($_) eq 'SCALAR') {
+                    my $attr = $importer->LookupObj($$_);
+                    if ($attr) {
+                        $_ = $attr->Id;
+                    }
+                    else {
+                        $importer->Postpone(
+                            for    => $$_,
+                            uid    => $spec->{uid},
+                            method => 'PostInflateFixup',
+                        );
+                    }
+                }
+            }
         }
-        $data->{Content} = $class->_SerializeContent($content);
+        $self->SetContent($content);
     }
+    # decode UIDs to be saved searches
+    elsif ($self->Name eq RT::User::_PrefName("HomepageSettings")) {
+        my $content = $self->Content;
 
-    return $class->SUPER::PreInflate( $importer, $uid, $data );
+        for my $pane (values %{ $content || {} }) {
+            for (@$pane) {
+                if (ref($_->{uid}) eq 'SCALAR') {
+                    my $uid = $_->{uid};
+                    my $attr = $importer->LookupObj($$uid);
+
+                    if ($attr) {
+                        if ($_->{type} eq 'saved') {
+                            $_->{name} = join '-', $attr->ObjectType, $attr->ObjectId, 'SavedSearch', $attr->id;
+                        }
+                        # if type is system, name doesn't need to change
+                        # if type is anything else, pass it through as is
+                        delete $_->{uid};
+                    }
+                    else {
+                        $importer->Postpone(
+                            for    => $$uid,
+                            uid    => $spec->{uid},
+                            method => 'PostInflateFixup',
+                        );
+                    }
+                }
+            }
+        }
+        $self->SetContent($content);
+    }
+}
+
+sub PostInflate {
+    my $self = shift;
+    my ($importer, $uid) = @_;
+
+    $self->SUPER::PostInflate( $importer, $uid );
+
+    # this method is separate because it needs to be callable multple times,
+    # and we can't guarantee that SUPER::PostInflate can deal with that
+    $self->PostInflateFixup($importer, { uid => $uid });
 }
 
 sub Serialize {
@@ -689,6 +789,39 @@ sub Serialize {
         }
         $store{Content} = $self->_SerializeContent($content);
     }
+    # encode saved searches to be UIDs
+    elsif ($store{Name} eq RT::User::_PrefName("HomepageSettings")) {
+        my $content = $self->_DeserializeContent($store{Content});
+        for my $pane (values %{ $content || {} }) {
+            for (@$pane) {
+                # this hairy code mirrors what's in the saved search loader
+                # in /Elements/ShowSearch
+                if ($_->{type} eq 'saved') {
+                    if ($_->{name} =~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/) {
+                        my $attr = RT::Attribute->new($self->CurrentUser);
+                        $attr->LoadById($3);
+                        $_->{uid} = \($attr->UID);
+                    }
+                    # if we can't parse the name, just pass it through
+                }
+                elsif ($_->{type} eq 'system') {
+                    my ($search) = RT::System->new($self->CurrentUser)->Attributes->Named( 'Search - ' . $_->{name} );
+                    unless ( $search && $search->Id ) {
+                        my (@custom_searches) = RT::System->new($self->CurrentUser)->Attributes->Named('SavedSearch');
+                        foreach my $custom (@custom_searches) {
+                            if ($custom->Description eq $_->{name}) { $search = $custom; last }
+                        }
+                    }
+                    # if we can't load the search, just pass it through
+                    if ($search) {
+                        $_->{uid} = \($search->UID);
+                    }
+                }
+                # pass through everything else (e.g. component)
+            }
+        }
+        $store{Content} = $self->_SerializeContent($content);
+    }
 
     return %store;
 }

commit fce4f0831afd4ea8c7f82bfed105db876407675e
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 21 23:42:27 2016 +0000

    Serialize dashboards using indirection
    
        Otherwise the old search and dashboard IDs will be kept in the
        component list, causing the dashboard to render with "search not
        found" type errors.
    
    Fixes: I#31808

diff --git a/lib/RT/Attribute.pm b/lib/RT/Attribute.pm
index 9cf42f6..74c6be9 100644
--- a/lib/RT/Attribute.pm
+++ b/lib/RT/Attribute.pm
@@ -671,6 +671,19 @@ sub FindDependencies {
             }
         }
     }
+    # dashboards have dependencies on all the searches and dashboards they use
+    elsif ($self->Name eq 'Dashboard') {
+        my $content = $self->Content;
+        for my $pane (values %{ $content->{Panes} || {} }) {
+            for my $component (@$pane) {
+                if ($component->{portlet_type} eq 'search' || $component->{portlet_type} eq 'dashboard') {
+                    my $attr = RT::Attribute->new($self->CurrentUser);
+                    $attr->LoadById($component->{id});
+                    $deps->Add( out => $attr );
+                }
+            }
+        }
+    }
 }
 
 sub PreInflate {
@@ -759,6 +772,33 @@ sub PostInflateFixup {
         }
         $self->SetContent($content);
     }
+    elsif ($self->Name eq 'Dashboard') {
+        my $content = $self->Content;
+
+        for my $pane (values %{ $content->{Panes} || {} }) {
+            for (@$pane) {
+                if (ref($_->{uid}) eq 'SCALAR') {
+                    my $uid = $_->{uid};
+                    my $attr = $importer->LookupObj($$uid);
+
+                    if ($attr) {
+                        # update with the new id numbers assigned to us
+                        $_->{id} = $attr->Id;
+                        $_->{privacy} = join '-', $attr->ObjectType, $attr->ObjectId;
+                        delete $_->{uid};
+                    }
+                    else {
+                        $importer->Postpone(
+                            for    => $$uid,
+                            uid    => $spec->{uid},
+                            method => 'PostInflateFixup',
+                        );
+                    }
+                }
+            }
+        }
+        $self->SetContent($content);
+    }
 }
 
 sub PostInflate {
@@ -822,6 +862,21 @@ sub Serialize {
         }
         $store{Content} = $self->_SerializeContent($content);
     }
+    # encode saved searches and dashboards to be UIDs
+    elsif ($store{Name} eq 'Dashboard') {
+        my $content = $self->_DeserializeContent($store{Content}) || {};
+        for my $pane (values %{ $content->{Panes} || {} }) {
+            for (@$pane) {
+                if ($_->{portlet_type} eq 'search' || $_->{portlet_type} eq 'dashboard') {
+                    my $attr = RT::Attribute->new($self->CurrentUser);
+                    $attr->LoadById($_->{id});
+                    $_->{uid} = \($attr->UID);
+                }
+                # pass through everything else (e.g. component)
+            }
+        }
+        $store{Content} = $self->_SerializeContent($content);
+    }
 
     return %store;
 }

commit 5d01baa9207ac93e02a7078c5da777f3d7805389
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 21 23:43:03 2016 +0000

    Serialize dashboard subscriptions using indirection
    
        Otherwise the old dashboard ID will be kept in the subscription,
        causing it to go missing.

diff --git a/lib/RT/Attribute.pm b/lib/RT/Attribute.pm
index 74c6be9..eddd37e 100644
--- a/lib/RT/Attribute.pm
+++ b/lib/RT/Attribute.pm
@@ -684,6 +684,13 @@ sub FindDependencies {
             }
         }
     }
+    # each subscription depends on its dashboard
+    elsif ($self->Name eq 'Subscription') {
+        my $content = $self->Content;
+        my $attr = RT::Attribute->new($self->CurrentUser);
+        $attr->LoadById($content->{DashboardId});
+        $deps->Add( out => $attr );
+    }
 }
 
 sub PreInflate {
@@ -799,6 +806,23 @@ sub PostInflateFixup {
         }
         $self->SetContent($content);
     }
+    elsif ($self->Name eq 'Subscription') {
+        my $content = $self->Content;
+        if (ref($content->{DashboardId}) eq 'SCALAR') {
+            my $attr = $importer->LookupObj(${ $content->{DashboardId} });
+            if ($attr) {
+                $content->{DashboardId} = $attr->Id;
+            }
+            else {
+                $importer->Postpone(
+                    for    => ${ $content->{DashboardId} },
+                    uid    => $spec->{uid},
+                    method => 'PostInflateFixup',
+                );
+            }
+        }
+        $self->SetContent($content);
+    }
 }
 
 sub PostInflate {
@@ -877,6 +901,14 @@ sub Serialize {
         }
         $store{Content} = $self->_SerializeContent($content);
     }
+    # encode subscriptions to have dashboard UID
+    elsif ($store{Name} eq 'Subscription') {
+        my $content = $self->_DeserializeContent($store{Content});
+        my $attr = RT::Attribute->new($self->CurrentUser);
+        $attr->LoadById($content->{DashboardId});
+        $content->{DashboardId} = \($attr->UID);
+        $store{Content} = $self->_SerializeContent($content);
+    }
 
     return %store;
 }

commit 330623dc5e3253be01227ed8053c8ad7c2a1227d
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri Mar 25 01:29:47 2016 +0000

    --exclude-organization option for rt-importer
    
        When records are already known to be unique this is unwanted noise.
    
    Fixes: I#31813
    Fixes: I#31812

diff --git a/lib/RT/Migrate/Importer.pm b/lib/RT/Migrate/Importer.pm
index 0e6456a..5c25d9a 100644
--- a/lib/RT/Migrate/Importer.pm
+++ b/lib/RT/Migrate/Importer.pm
@@ -65,17 +65,20 @@ sub new {
 sub Init {
     my $self = shift;
     my %args = (
-        OriginalId  => undef,
-        Progress    => undef,
-        Statefile   => undef,
-        DumpObjects => undef,
-        HandleError => undef,
+        OriginalId          => undef,
+        Progress            => undef,
+        Statefile           => undef,
+        DumpObjects         => undef,
+        HandleError         => undef,
+        ExcludeOrganization => undef,
         @_,
     );
 
     # Should we attempt to preserve record IDs as they are created?
     $self->{OriginalId} = $args{OriginalId};
 
+    $self->{ExcludeOrganization} = $args{ExcludeOrganization};
+
     $self->{Progress} = $args{Progress};
 
     $self->{HandleError} = sub { 0 };
@@ -294,6 +297,7 @@ sub Qualify {
     my ($string) = @_;
     return $string if $self->{Clone};
     return $string if not defined $self->{Organization};
+    return $string if $self->{ExcludeOrganization};
     return $string if $self->{Organization} eq $RT::Organization;
     return $self->{Organization}.": $string";
 }
@@ -402,9 +406,13 @@ sub ReadStream {
     # If it's a ticket, we might need to create a
     # TicketCustomField for the previous ID
     if ($class eq "RT::Ticket" and $self->{OriginalId}) {
+        my $value = $self->{ExcludeOrganization}
+                  ? $origid
+                  : $self->Organization . ":$origid";
+
         my ($id, $msg) = $obj->AddCustomFieldValue(
             Field             => $self->{OriginalId},
-            Value             => $self->Organization . ":$origid",
+            Value             => $value,
             RecordTransaction => 0,
         );
         warn "Failed to add custom field to $uid: $msg"
diff --git a/lib/RT/Migrate/Importer/File.pm b/lib/RT/Migrate/Importer/File.pm
index cfad9ae..3d36f3d 100644
--- a/lib/RT/Migrate/Importer/File.pm
+++ b/lib/RT/Migrate/Importer/File.pm
@@ -192,7 +192,7 @@ sub SaveState {
            NewQueues NewCFs
            SkipTransactions Pending Invalid
            UIDs
-           OriginalId Clone
+           OriginalId ExcludeOrganization Clone
           /;
     Storable::nstore(\%data, $self->{Statefile});
 
diff --git a/sbin/rt-importer.in b/sbin/rt-importer.in
index 6d3cda5..cc1a426 100644
--- a/sbin/rt-importer.in
+++ b/sbin/rt-importer.in
@@ -95,6 +95,7 @@ GetOptions(
 
     "resume!",
     "originalid|i=s",
+    "exclude-organization",
 
     "ask",
     "ignore-errors",
@@ -142,11 +143,12 @@ elsif ($OPT{'ignore-errors'}) {
 }
 
 my $import = RT::Migrate::Importer::File->new(
-    Directory   => $dir,
-    OriginalId  => $OPT{originalid},
-    DumpObjects => $OPT{dump},
-    Resume      => $OPT{resume},
-    HandleError => $error_handler,
+    Directory           => $dir,
+    OriginalId          => $OPT{originalid},
+    ExcludeOrganization => $OPT{'exclude-organization'},
+    DumpObjects         => $OPT{dump},
+    Resume              => $OPT{resume},
+    HandleError         => $error_handler,
 );
 
 if ($import->Metadata and -t STDOUT and not $OPT{quiet}) {
@@ -222,6 +224,13 @@ current database; this may include users, queues, and tickets.
 It is possible to stop the import process with ^C; it can be later
 resumed by re-running the importer.
 
+Certain records (notably queues and groups) will have their original
+Organization name prepended to them on import. This is primarily to avoid
+duplicate names (for example importing a General queue into an RT that
+already has one would otherwise cause a name collision error). You may
+suppress this behavior by passing the B<--exclude-organization> flag to
+C<rt-importer>.
+
 =head2 OPTIONS
 
 =over
@@ -236,6 +245,12 @@ Places the original ticket organization and ID into a global custom
 field with the given name.  If no global ticket custom field with that
 name is found in the current database, it will create one.
 
+=item B<--exclude-organization>
+
+Ordinarily certain records (groups, queues, the B<--originalid> custom field)
+include the organization name of the original RT instance. Use this option to
+suppress that behavior and use the original name directly.
+
 =item B<--ask>
 
 Prompt for action when an error occurs inserting a record into the

commit 37e7c98e37f7ad7ac6ad8983fbc3c4702211d23d
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Sun May 1 16:17:14 2016 -0400

    Serialize Transaction OldReference and NewReference using indirection
    
        Otherwise each transaction which records a custom field update would
        point to the wrong ObjectCustomFieldValue records, causing RT to
        display incorrect old and new values in, especially, ticket history.

diff --git a/lib/RT/Transaction.pm b/lib/RT/Transaction.pm
index d73bf38..907d1d8 100644
--- a/lib/RT/Transaction.pm
+++ b/lib/RT/Transaction.pm
@@ -2041,6 +2041,9 @@ sub Serialize {
         my $cf = RT::CustomField->new( RT->SystemUser );
         $cf->Load( $store{Field} );
         $store{Field} = \($cf->UID);
+
+        $store{OldReference} = \($self->OldReferenceObject->UID) if $self->OldReference;
+        $store{NewReference} = \($self->NewReferenceObject->UID) if $self->NewReference;
     } elsif ($type =~ /^(Take|Untake|Force|Steal|Give)$/) {
         for my $field (qw/OldValue NewValue/) {
             my $user = RT::User->new( RT->SystemUser );

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


More information about the rt-commit mailing list