[Rt-commit] rt branch, 4.2/migrator-fixes, updated. rt-4.2.12-121-g1240499

Shawn Moore shawn at bestpractical.com
Mon Mar 21 19:46:53 EDT 2016


The branch, 4.2/migrator-fixes has been updated
       via  1240499ed3e548486b2a857447c8db990a09a987 (commit)
       via  f05b6682d00d0113225dbcc62f79b8be0c8aa7e9 (commit)
       via  f464c3f2fce0a81a77f0efe54a9311a2fc240b39 (commit)
       via  97cf2096df4b088dbc26de4db0b964e5d16219fd (commit)
       via  69ab5435b21ce782d6114ec985a57ff00ccca29f (commit)
       via  5c2cdaccdac956404b013dcd5b287ad48c929946 (commit)
      from  84b02228763304a20edec35cc8547aa9e9c99862 (commit)

Summary of changes:
 lib/RT/Attribute.pm        | 270 ++++++++++++++++++++++++++++++++++++++++++++-
 lib/RT/Migrate/Importer.pm |   5 +-
 2 files changed, 273 insertions(+), 2 deletions(-)

- Log -----------------------------------------------------------------
commit 5c2cdaccdac956404b013dcd5b287ad48c929946
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 69ab5435b21ce782d6114ec985a57ff00ccca29f
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 97cf2096df4b088dbc26de4db0b964e5d16219fd
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 f464c3f2fce0a81a77f0efe54a9311a2fc240b39
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 f05b6682d00d0113225dbcc62f79b8be0c8aa7e9
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 1240499ed3e548486b2a857447c8db990a09a987
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..a78a6f6 100644
--- a/lib/RT/Attribute.pm
+++ b/lib/RT/Attribute.pm
@@ -684,6 +684,13 @@ sub FindDependencies {
             }
         }
     }
+    # subscriptions have dependencies their 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;
 }

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


More information about the rt-commit mailing list