[Rt-commit] rt branch, 4.4/dashboard-tables, created. rt-4.2.5-200-ge5ac318

? sunnavy sunnavy at bestpractical.com
Mon Sep 29 08:58:41 EDT 2014


The branch, 4.4/dashboard-tables has been created
        at  e5ac318b928c3297c831a46a703a7c8388512e10 (commit)

- Log -----------------------------------------------------------------
commit e5ac318b928c3297c831a46a703a7c8388512e10
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Sep 29 20:53:50 2014 +0800

    split dashboard into its own table

diff --git a/etc/acl.Pg b/etc/acl.Pg
index a659d8e..190ec40 100755
--- a/etc/acl.Pg
+++ b/etc/acl.Pg
@@ -58,6 +58,8 @@ sub acl {
         ObjectTopics
         objectclasses_id_seq
         ObjectClasses
+        dashboards_id_seq
+        Dashboards
     );
 
     my $db_user = RT->Config->Get('DatabaseUser');
diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 58665c7..ad4f88b 100755
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -488,3 +488,16 @@ Created DATE,
 LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
 LastUpdated DATE
 );
+
+CREATE SEQUENCE Dashboards_seq;
+CREATE TABLE Dashboards (
+  id NUMBER(11,0) PRIMARY KEY,
+  Name VARCHAR2(255) NOT NULL,
+  Content CLOB,
+  ObjectType VARCHAR2(25) NOT NULL,
+  ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL,
+  Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
+  Created DATE,
+  LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
+  LastUpdated DATE
+);
diff --git a/etc/schema.Pg b/etc/schema.Pg
index 356441b..078b5da 100755
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -720,3 +720,17 @@ LastUpdated TIMESTAMP NULL,
 PRIMARY KEY (id)
 );
 
+CREATE SEQUENCE dashboards_id_seq;
+CREATE TABLE Dashboards (
+  id INTEGER DEFAULT nextval('dashboards_id_seq'),
+  Name varchar(255) NOT NULL,
+  Content text,
+  ObjectType varchar(64),
+  ObjectId integer,
+  Creator integer NOT NULL DEFAULT 0,
+  Created TIMESTAMP NULL,
+  LastUpdatedBy integer NOT NULL DEFAULT 0,
+  LastUpdated TIMESTAMP NULL,
+  PRIMARY KEY (id)
+);
+
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index 7ba11f7..dfb9245 100755
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -520,3 +520,15 @@ Created TIMESTAMP NULL,
 LastUpdatedBy integer NOT NULL DEFAULT 0,
 LastUpdated TIMESTAMP NULL
 );
+
+CREATE TABLE Dashboards (
+  id INTEGER PRIMARY KEY,
+  Name varchar(255) NOT NULL,
+  Content LONGTEXT NULL,
+  ObjectType varchar(25) NOT NULL,
+  ObjectId INTEGER default 0,
+  Creator integer NULL,
+  Created DATETIME NULL,
+  LastUpdatedBy integer NULL,
+  LastUpdated DATETIME NULL
+);
diff --git a/etc/schema.mysql b/etc/schema.mysql
index 21ff5cb..e77a664 100755
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -509,3 +509,16 @@ CREATE TABLE ObjectClasses (
   LastUpdated datetime default NULL,
   PRIMARY KEY  (id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE Dashboards (
+  id INTEGER NOT NULL AUTO_INCREMENT,
+  Name varchar(255) NULL,
+  Content BLOB,
+  ObjectType varchar(64) CHARACTER SET ascii,
+  ObjectId integer,
+  Creator integer NOT NULL DEFAULT 0,
+  Created DATETIME NULL,
+  LastUpdatedBy integer NOT NULL DEFAULT 0,
+  LastUpdated DATETIME NULL,
+  PRIMARY KEY (id)
+) ENGINE=InnoDB CHARACTER SET utf8;
diff --git a/etc/upgrade/4.3.2/acl.Oracle b/etc/upgrade/4.3.2/acl.Oracle
new file mode 100644
index 0000000..73c16ae
--- /dev/null
+++ b/etc/upgrade/4.3.2/acl.Oracle
@@ -0,0 +1,4 @@
+sub acl {
+    return ();
+}
+1;
diff --git a/etc/upgrade/4.3.2/acl.Pg b/etc/upgrade/4.3.2/acl.Pg
new file mode 100644
index 0000000..b37d760
--- /dev/null
+++ b/etc/upgrade/4.3.2/acl.Pg
@@ -0,0 +1,19 @@
+sub acl {
+    my $dbh = shift;
+
+    my @acls;
+
+    my @tables = qw (
+      dashboards_id_seq
+      Dashboards
+    );
+
+    foreach my $table (@tables) {
+        push @acls,
+          "GRANT SELECT, INSERT, UPDATE, DELETE ON $table to "
+          . RT->Config->Get('DatabaseUser') . ";";
+
+    }
+    return (@acls);
+}
+1;
diff --git a/etc/upgrade/4.3.2/acl.SQLite b/etc/upgrade/4.3.2/acl.SQLite
new file mode 100644
index 0000000..73c16ae
--- /dev/null
+++ b/etc/upgrade/4.3.2/acl.SQLite
@@ -0,0 +1,4 @@
+sub acl {
+    return ();
+}
+1;
diff --git a/etc/upgrade/4.3.2/acl.mysql b/etc/upgrade/4.3.2/acl.mysql
new file mode 100644
index 0000000..73c16ae
--- /dev/null
+++ b/etc/upgrade/4.3.2/acl.mysql
@@ -0,0 +1,4 @@
+sub acl {
+    return ();
+}
+1;
diff --git a/etc/upgrade/4.3.2/content b/etc/upgrade/4.3.2/content
new file mode 100644
index 0000000..098e0ef
--- /dev/null
+++ b/etc/upgrade/4.3.2/content
@@ -0,0 +1,86 @@
+use strict;
+use warnings;
+my %new_id_for;
+
+my $convert_attributes = sub {
+    my $obj = shift;
+
+    for my $attr ($obj->Attributes->Named('Dashboard')) {
+        my $attr_id = $attr->id;
+
+        my $dashboard = RT::Dashboard->new($RT::SystemUser);
+
+        my ($ok, $msg) = $dashboard->Create(
+            Name       => $attr->Description,
+            Content    => $attr->Content,
+            ObjectType => ref($obj),
+            ObjectId   => $obj->id,
+        );
+
+        if (!$ok) {
+            $RT::Logger->error("Unable to reify attribute $attr_id into a dashboard: $msg");
+            next;
+        }
+
+        $new_id_for{$attr_id} = $dashboard->id;
+
+        ($ok, $msg) = $attr->Delete;
+
+        if (!$ok) {
+            $RT::Logger->error("Reified attribute $attr_id into dashboard " . $dashboard->id . " but couldn't delete attribute: $msg");
+            next;
+        }
+
+        $RT::Logger->debug("Reified attribute $attr_id into a dashboard: $msg");
+    }
+};
+
+our @Final = (
+    sub {
+        my $users = RT::Users->new($RT::SystemUser);
+        $users->UnLimit;
+
+        while (my $user = $users->Next) {
+            $convert_attributes->($user);
+        }
+    },
+    sub {
+        my $groups = RT::Groups->new($RT::SystemUser);
+        $groups->UnLimit;
+
+        while (my $group = $groups->Next) {
+            $convert_attributes->($group);
+        }
+    },
+    sub {
+        my $system = RT::System->new($RT::SystemUser);
+        $convert_attributes->($system);
+    },
+
+    sub {
+        my $users = RT::Users->new($RT::SystemUser);
+        $users->UnLimit;
+
+        while (my $user = $users->Next) {
+            for my $subscription ($users->Attributes->Named('Subscription')) {
+                my $old_id = $subscription->SubValue('DashboardId');
+                my $new_id = $new_id_for{$old_id};
+
+                if (!$new_id) {
+                    $RT::Logger->error("Can't update subscription " . $subscription->id . " for old-style dashboard $old_id since it has no new-style ID");
+                    next;
+                }
+
+                my ($ok, $msg) = $subscription->SetSubValue(DashboardId => $new_id);
+
+                if (!$ok) {
+                    $RT::Logger->error("Unable to update subscription " . $subscription->id . " from dashboard $old_id to $new_id: $msg");
+                    next;
+                }
+
+                $RT::Logger->debug("Updated subscription " . $subscription->id . " from dashboard $old_id to $new_id");
+            }
+        }
+    },
+);
+
diff --git a/etc/upgrade/4.3.2/schema.Oracle b/etc/upgrade/4.3.2/schema.Oracle
new file mode 100644
index 0000000..44e6714
--- /dev/null
+++ b/etc/upgrade/4.3.2/schema.Oracle
@@ -0,0 +1,12 @@
+CREATE SEQUENCE Dashboards_seq;
+CREATE TABLE Dashboards (
+  id NUMBER(11,0) PRIMARY KEY,
+  Name VARCHAR2(255) NOT NULL,
+  Content CLOB,
+  ObjectType VARCHAR2(25) NOT NULL,
+  ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL,
+  Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
+  Created DATE,
+  LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
+  LastUpdated DATE
+);
diff --git a/etc/upgrade/4.3.2/schema.Pg b/etc/upgrade/4.3.2/schema.Pg
new file mode 100644
index 0000000..18f6854
--- /dev/null
+++ b/etc/upgrade/4.3.2/schema.Pg
@@ -0,0 +1,13 @@
+CREATE SEQUENCE dashboards_id_seq;
+CREATE TABLE Dashboards (
+  id INTEGER DEFAULT nextval('dashboards_id_seq'),
+  Name varchar(255) NOT NULL,
+  Content text,
+  ObjectType varchar(64),
+  ObjectId integer,
+  Creator integer NOT NULL DEFAULT 0,
+  Created TIMESTAMP NULL,
+  LastUpdatedBy integer NOT NULL DEFAULT 0,
+  LastUpdated TIMESTAMP NULL,
+  PRIMARY KEY (id)
+);
diff --git a/etc/upgrade/4.3.2/schema.SQLite b/etc/upgrade/4.3.2/schema.SQLite
new file mode 100644
index 0000000..ac02600
--- /dev/null
+++ b/etc/upgrade/4.3.2/schema.SQLite
@@ -0,0 +1,11 @@
+CREATE TABLE Dashboards (
+  id INTEGER PRIMARY KEY,
+  Name varchar(255) NOT NULL,
+  Content LONGTEXT NULL,
+  ObjectType varchar(25) NOT NULL,
+  ObjectId INTEGER default 0,
+  Creator integer NULL,
+  Created DATETIME NULL,
+  LastUpdatedBy integer NULL,
+  LastUpdated DATETIME NULL
+);
diff --git a/etc/upgrade/4.3.2/schema.mysql b/etc/upgrade/4.3.2/schema.mysql
new file mode 100644
index 0000000..9a98cef
--- /dev/null
+++ b/etc/upgrade/4.3.2/schema.mysql
@@ -0,0 +1,12 @@
+CREATE TABLE Dashboards (
+  id INTEGER NOT NULL AUTO_INCREMENT,
+  Name varchar(255) NULL,
+  Content BLOB,
+  ObjectType varchar(64) CHARACTER SET ascii,
+  ObjectId integer,
+  Creator integer NOT NULL DEFAULT 0,
+  Created DATETIME NULL,
+  LastUpdatedBy integer NOT NULL DEFAULT 0,
+  LastUpdated DATETIME NULL,
+  PRIMARY KEY (id)
+) ENGINE=InnoDB CHARACTER SET utf8;
diff --git a/lib/RT/Dashboard.pm b/lib/RT/Dashboard.pm
index 6b9244f..bae7ba8 100644
--- a/lib/RT/Dashboard.pm
+++ b/lib/RT/Dashboard.pm
@@ -48,18 +48,14 @@
 
 =head1 NAME
 
-  RT::Dashboard - an API for saving and retrieving dashboards
+  RT::Dashboard - Dashboard
 
 =head1 SYNOPSIS
 
-  use RT::Dashboard
+  use RT::Dashboard;
 
 =head1 DESCRIPTION
 
-  Dashboard is an object that can belong to either an RT::User or an
-  RT::Group.  It consists of an ID, a name, and a number of
-  saved searches and portlets.
-
 =head1 METHODS
 
 
@@ -70,11 +66,16 @@ package RT::Dashboard;
 use strict;
 use warnings;
 
-use base qw/RT::SharedSetting/;
+use base 'RT::Record';
 
 use RT::SavedSearch;
-
 use RT::System;
+use Storable qw/nfreeze thaw/;
+use MIME::Base64;
+use Scalar::Util 'blessed';
+
+sub Table { 'Dashboards' }
+
 'RT::System'->AddRight( Staff   => SubscribeDashboard => 'Subscribe to dashboards'); # loc
 
 'RT::System'->AddRight( General => SeeDashboard       => 'View system dashboards'); # loc
@@ -87,71 +88,30 @@ use RT::System;
 'RT::System'->AddRight( Staff   => ModifyOwnDashboard => 'Modify personal dashboards'); # loc
 'RT::System'->AddRight( Staff   => DeleteOwnDashboard => 'Delete personal dashboards'); # loc
 
+=head2 Create
 
-=head2 ObjectName
-
-An object of this class is called "dashboard"
+Accepts a C<Privacy> instead of an C<ObjectType> and C<ObjectId>.
 
 =cut
 
-sub ObjectName { "dashboard" } # loc
-
-sub SaveAttribute {
-    my $self   = shift;
-    my $object = shift;
-    my $args   = shift;
-
-    return $object->AddAttribute(
-        'Name'        => 'Dashboard',
-        'Description' => $args->{'Name'},
-        'Content'     => {Panes => $args->{'Panes'}},
-    );
-}
-
-sub UpdateAttribute {
+sub Create {
     my $self = shift;
-    my $args = shift;
+    my %args = ( Content => {}, @_ );
 
-    my ($status, $msg) = (1, undef);
-    if (defined $args->{'Panes'}) {
-        ($status, $msg) = $self->{'Attribute'}->SetSubValues(
-            Panes => $args->{'Panes'},
-        );
-    }
 
-    if ($status && $args->{'Name'}) {
-        ($status, $msg) = $self->{'Attribute'}->SetDescription($args->{'Name'})
-            unless $self->Name eq $args->{'Name'};
+    eval  {$args{'Content'} = $self->_SerializeContent($args{'Content'}); };
+    if ($@) {
+        return(0, $@);
     }
 
-    if ($status && $args->{'Privacy'}) {
-        my ($new_obj_type, $new_obj_id) = split /-/, $args->{'Privacy'};
-        my ($obj_type, $obj_id) = split /-/, $self->Privacy;
-
-        my $attr = $self->{'Attribute'};
-        if ($new_obj_type ne $obj_type) {
-            ($status, $msg) = $attr->SetObjectType($new_obj_type);
-        }
-        if ($status && $new_obj_id != $obj_id ) {
-            ($status, $msg) = $attr->SetObjectId($new_obj_id);
-        }
-        $self->{'Privacy'} = $args->{'Privacy'} if $status;
+    # canonicalize Privacy into ObjectType and ObjectId
+    if ($args{Privacy}) {
+        ($args{ObjectType}, $args{ObjectId}) = split '-', delete $args{Privacy};
     }
 
-    return ($status, $msg);
-}
-
-=head2 PostLoadValidate
-
-Ensure that the ID corresponds to an actual dashboard object, since it's all
-attributes under the hood.
-
-=cut
-
-sub PostLoadValidate {
-    my $self = shift;
-    return (0, "Invalid object type") unless $self->{'Attribute'}->Name eq 'Dashboard';
-    return 1;
+    my ( $ret, $msg ) = $self->SUPER::Create(%args);
+    return ( $ret, $msg ) unless $ret;
+    return ( $ret, $self->loc('Dashboard [_1] created',$self->id) );
 }
 
 =head2 Panes
@@ -162,8 +122,13 @@ Returns a hashref of pane name to portlets
 
 sub Panes {
     my $self = shift;
-    return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
-    return $self->{'Attribute'}->SubValue('Panes') || {};
+    return $self->Content->{Panes} || {};
+}
+
+sub SetPanes {
+    my $self = shift;
+    my $panes = shift || {};
+    return $self->SetContent({ %{$self->Content}, Panes => $panes });
 }
 
 =head2 Portlets
@@ -390,22 +355,17 @@ sub ObjectsForLoading {
         Recursively => 1,
         PrincipalId => $CurrentUser->UserObj->PrincipalId
     );
-    my $attrs = $groups->Join(
+    my $dashboards = $groups->Join(
         ALIAS1 => 'main',
         FIELD1 => 'id',
-        TABLE2 => 'Attributes',
+        TABLE2 => 'Dashboards',
         FIELD2 => 'ObjectId',
     );
     $groups->Limit(
-        ALIAS => $attrs,
+        ALIAS => $dashboards,
         FIELD => 'ObjectType',
         VALUE => 'RT::Group',
     );
-    $groups->Limit(
-        ALIAS => $attrs,
-        FIELD => 'Name',
-        VALUE => 'Dashboard',
-    );
     push @objects, @{ $groups->ItemsArrayRef };
 
     # Finally, if you have been granted the SeeDashboard right (which
@@ -449,6 +409,10 @@ Returns a tuple of status and message, where status is true upon success.
 sub Delete {
     my $self = shift;
     my $id = $self->id;
+    unless ($self->CurrentUserCanDelete) {
+        return (0,$self->loc('Permission Denied'));
+    }
+
     my ( $status, $msg ) = $self->SUPER::Delete(@_);
     if ( $status ) {
         # delete all the subscriptions
@@ -464,11 +428,268 @@ sub Delete {
         while ( my $subscription = $subscriptions->Next ) {
             $subscription->Delete();
         }
+        return ( $status, $self->loc('Dashboard [_1] deleted', $id) );
     }
 
     return ( $status, $msg );
 }
 
+sub Object {
+    my $self  = shift;
+    return unless $self->__Value('ObjectId');
+    my $Object = $self->__Value('ObjectType')->new($self->CurrentUser);
+    $Object->Load($self->__Value('ObjectId'));
+    return $Object;
+}
+
+sub Content {
+    my $self = shift;
+    my $content = $self->_Value('Content');
+    return $self->_DeserializeContent($content);
+}
+
+sub SetContent {
+    my $self    = shift;
+    my $content = shift;
+    return $self->_Set( Field => 'Content', Value => $self->_SerializeContent( $content ) );
+}
+
+
+sub _SerializeContent {
+    my $self = shift;
+    my $content = shift;
+    return encode_base64(nfreeze($content));
+}
+
+sub _DeserializeContent {
+    my $self = shift;
+    my $content = shift;
+    return thaw(decode_base64($content));
+}
+
+sub _build_privacy {
+    my $self = shift;
+    my $object = shift || $self->Object;
+    return undef unless $object;
+    return ref($object) . '-' . $object->id;
+}
+
+sub _load_privacy_object {
+    my ($self, $obj_type, $obj_id) = @_;
+    if ( $obj_type eq 'RT::User' ) {
+        if ( $obj_id == $self->CurrentUser->Id ) {
+            return $self->CurrentUser->UserObj;
+        } else {
+            $RT::Logger->warning("User #". $self->CurrentUser->Id ." tried to load container user #". $obj_id);
+            return undef;
+        }
+    }
+    elsif ($obj_type eq 'RT::Group') {
+        my $group = RT::Group->new($self->CurrentUser);
+        $group->Load($obj_id);
+        return $group;
+    }
+    elsif ($obj_type eq 'RT::System') {
+        return RT::System->new($self->CurrentUser);
+    }
+
+    $RT::Logger->error(
+        "Tried to load a ". $self->ObjectName
+        ." belonging to an $obj_type, which is neither a user nor a group"
+    );
+
+    return undef;
+}
+
+sub _GetObject {
+    my $self = shift;
+    my $privacy = shift;
+
+    # short circuit: if they pass the object we want anyway, just return it
+    if (blessed($privacy) && $privacy->isa('RT::Record')) {
+        return $privacy;
+    }
+
+    my ($obj_type, $obj_id) = split(/\-/, ($privacy || ''));
+
+    unless ($obj_type && $obj_id) {
+        $privacy = '(undef)' if !defined($privacy);
+        $RT::Logger->debug("Invalid privacy string '$privacy'");
+        return undef;
+    }
+
+    my $object = $self->_load_privacy_object($obj_type, $obj_id);
+
+    unless (ref($object) eq $obj_type) {
+        $RT::Logger->error("Could not load object of type $obj_type with ID $obj_id, got object of type " . (ref($object) || 'undef'));
+        return undef;
+    }
+
+    # Do not allow the loading of a user object other than the current
+    # user, or of a group object of which the current user is not a member.
+
+    if ($obj_type eq 'RT::User' && $object->Id != $self->CurrentUser->UserObj->Id) {
+        $RT::Logger->debug("Permission denied for user other than self");
+        return undef;
+    }
+
+    if (   $obj_type eq 'RT::Group'
+        && !$object->HasMemberRecursively($self->CurrentUser->PrincipalObj)
+        && !$self->CurrentUser->HasRight( Object => $RT::System, Right => 'SuperUser' ) ) {
+        $RT::Logger->debug("Permission denied, ".$self->CurrentUser->Name.
+                           " is not a member of group");
+        return undef;
+    }
+
+    return $object;
+}
+
+sub Privacy {
+    my $self = shift;
+    return $self->_build_privacy;
+}
+
+sub SetPrivacy {
+    my $self = shift;
+    my $privacy = shift;
+    my ($object_type, $object_id) = split '-', $privacy, 2;
+    $RT::Handle->BeginTransaction();
+    if ( $self->ObjectType ne $object_type ) {
+        my ($ret, $msg) = $self->SetObjectType($object_type);
+        unless ( $ret ) {
+            $RT::Handle->Rollback();
+            return ($ret, $msg);
+        }
+    }
+
+    if ( $self->ObjectId != $object_id ) {
+        my ($ret, $msg) = $self->SetObjectId($object_id);
+        unless ( $ret ) {
+            $RT::Handle->Rollback();
+            return ($ret, $msg);
+        }
+    }
+    $RT::Handle->Commit();
+    return( 1, 'Privacy updated' ); # loc
+}
+
+sub IsVisibleTo {
+    my $self    = shift;
+    my $to      = shift;
+    my $privacy = $self->Privacy || '';
+
+    # if the privacies are the same, then they can be seen. this handles
+    # a personal setting being visible to that user.
+    return 1 if $privacy eq $to;
+
+    # If the setting is systemwide, then any user can see it.
+    return 1 if $privacy =~ /^RT::System/;
+
+    # Only privacies that are RT::System can be seen by everyone.
+    return 0 if $to =~ /^RT::System/;
+
+    # If the setting is group-wide...
+    if ($privacy =~ /^RT::Group-(\d+)$/) {
+        my $setting_group = RT::Group->new($self->CurrentUser);
+        $setting_group->Load($1);
+
+        if ($to =~ /-(\d+)$/) {
+            my $to_id = $1;
+
+            # then any principal that is a member of the setting's group can see
+            # the setting
+            return $setting_group->HasMemberRecursively($to_id);
+        }
+    }
+
+    return 0;
+}
+
+=head2 id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+=head2 Name
+
+Returns the current value of Name.
+(In the database, Name is stored as varchar(255).)
+
+=head2 SetName VALUE
+
+Set Name to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Name will be stored as a varchar(255).)
+
+=head2 Content
+
+Returns the current value of Content.
+(In the database, Content is stored as blob.)
+
+=head2 SetContent VALUE
+
+Set Content to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Content will be stored as a blob.)
+
+=head2 ObjectType
+
+Returns the current value of ObjectType.
+(In the database, ObjectType is stored as varchar(64).)
+
+=head2 SetObjectType VALUE
+
+Set ObjectType to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectType will be stored as a varchar(64).)
+
+=head2 ObjectId
+
+Returns the current value of ObjectId.
+(In the database, ObjectId is stored as int(11).)
+
+=head2 SetObjectId VALUE
+
+Set ObjectId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectId will be stored as a int(11).)
+
+=head2 Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+=head2 Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+=head2 LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+=head2 LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+=cut
+
+sub _CoreAccessible {
+    {
+        id => {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+        Name => {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+        Content => {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
+        ObjectType => {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
+        ObjectId => {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+        Creator => {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+        Created => {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+        LastUpdatedBy => {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+        LastUpdated => {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+ }
+};
+
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/lib/RT/Dashboards.pm b/lib/RT/Dashboards.pm
index 7c8511f..ccdce96 100644
--- a/lib/RT/Dashboards.pm
+++ b/lib/RT/Dashboards.pm
@@ -48,17 +48,11 @@
 
 =head1 NAME
 
-  RT::Dashboards - a pseudo-collection for Dashboard objects.
+  RT::Dashboards - a collection for Dashboard objects
 
 =head1 SYNOPSIS
 
-  use RT::Dashboards
-
-=head1 DESCRIPTION
-
-  Dashboards is an object consisting of a number of Dashboard objects.
-  It works more or less like a DBIx::SearchBuilder collection, although it
-  is not.
+  use RT::Dashboards;
 
 =head1 METHODS
 
@@ -69,43 +63,44 @@ package RT::Dashboards;
 
 use strict;
 use warnings;
-use base 'RT::SharedSettings';
+
+use base 'RT::SearchBuilder';
 
 use RT::Dashboard;
 
-sub RecordClass {
-    return 'RT::Dashboard';
-}
+sub Table { 'Dashboards' }
 
-=head2 LimitToPrivacy
+=head2 LimitToObject
 
-Takes one argument: a privacy string, of the format "<class>-<id>", as produced
-by RT::Dashboard::Privacy(). The Dashboards object will load the dashboards
-belonging to that user or group. Repeated calls to the same object should DTRT.
+The Dashboards object will load the dashboards belonging to the passed-in user
+or group. Repeated calls to the same object should DTRT.
 
 =cut
 
-sub LimitToPrivacy {
+sub LimitToObject {
     my $self = shift;
-    my $privacy = shift;
-
-    my $object = $self->_GetObject($privacy);
-
-    if ($object) {
-        $self->{'objects'} = [];
-        my @dashboard_atts = $object->Attributes->Named('Dashboard');
-        foreach my $att (@dashboard_atts) {
-            my $dashboard = RT::Dashboard->new($self->CurrentUser);
-            $dashboard->Load($privacy, $att->Id);
-            push(@{$self->{'objects'}}, $dashboard);
-        }
-    } else {
-        $RT::Logger->error("Could not load object $privacy");
-    }
+    my $obj  = shift;
+
+    $self->Limit(
+        FIELD => 'ObjectType',
+        VALUE => ref($obj),
+    );
+
+    $self->Limit(
+        FIELD => 'ObjectId',
+        VALUE => $obj->id,
+    );
 }
 
-sub ColumnMapClassName {
-    return 'RT__Dashboard';
+=head2 NewItem
+
+Returns an empty new L<RT::Dashboard> record.
+
+=cut
+
+sub NewItem {
+    my $self = shift;
+    return RT::Dashboard->new($self->CurrentUser);
 }
 
 RT::Base->_ImportOverlays();
diff --git a/share/html/Admin/Global/DashboardsInMenu.html b/share/html/Admin/Global/DashboardsInMenu.html
index 279c022..00bd148 100644
--- a/share/html/Admin/Global/DashboardsInMenu.html
+++ b/share/html/Admin/Global/DashboardsInMenu.html
@@ -69,7 +69,7 @@ my $default_dashboards_in_menu =
 
 use RT::Dashboards;
 my $dashboards = RT::Dashboards->new( $RT::SystemUser );
-$dashboards->LimitToPrivacy('RT::System-' . $sys->id);
+$dashboards->LimitToObject($sys);
 
 my @dashboards;
 while ( my $dashboard = $dashboards->Next ) {
diff --git a/share/html/Dashboards/Elements/DashboardsForObject b/share/html/Dashboards/Elements/DashboardsForObject
index bffdc1a..9439669 100644
--- a/share/html/Dashboards/Elements/DashboardsForObject
+++ b/share/html/Dashboards/Elements/DashboardsForObject
@@ -52,28 +52,21 @@ $Object => undef
 # Returns a hash of dashboards associated on $Object
 
 use RT::Dashboard;
+use RT::Dashboards;
 my %dashboards;
 my $privacy = RT::Dashboard->_build_privacy($Object);
 
-while (my $attr = $Object->Attributes->Next) {
-    if ($attr->Name =~ /^Dashboard\b/) {
-        my $dashboard = RT::Dashboard->new($User);
-        my ($ok, $msg) = $dashboard->Load($privacy, $attr->id);
-
-        if (!$ok) {
-            $RT::Logger->debug("Unable to load dashboard $ok (privacy $privacy): $msg");
-            next;
-        }
-
-        if ($Object->isa('RT::System')) {
-            push @{ $dashboards{system} }, $dashboard;
-        }
-        elsif ($Object->isa('RT::User')) {
-            push @{ $dashboards{personal} }, $dashboard;
-        }
-        elsif ($Object->isa('RT::Group')) {
-            push @{ $dashboards{group}{$Object->Name} }, $dashboard;
-        }
+my $dashboards = RT::Dashboards->new($session{CurrentUser});
+$dashboards->LimitToObject($Object);
+while (my $dashboard = $dashboards->Next) {
+    if ($Object->isa('RT::System')) {
+        push @{ $dashboards{system} }, $dashboard;
+    }
+    elsif ($Object->isa('RT::User')) {
+        push @{ $dashboards{personal} }, $dashboard;
+    }
+    elsif ($Object->isa('RT::Group')) {
+        push @{ $dashboards{group}{$Object->Name} }, $dashboard;
     }
 }
 return \%dashboards;
diff --git a/share/html/Dashboards/Elements/ShowDashboards b/share/html/Dashboards/Elements/ShowDashboards
index e04e149..37d4ca2 100644
--- a/share/html/Dashboards/Elements/ShowDashboards
+++ b/share/html/Dashboards/Elements/ShowDashboards
@@ -47,7 +47,7 @@
 %# END BPS TAGGED BLOCK }}}
 % foreach my $Object (@Objects) {
 %   my $Dashboards = RT::Dashboards->new($session{CurrentUser});
-%   $Dashboards->LimitToPrivacy(join('-',ref($Object),$Object->Id));
+%   $Dashboards->LimitToObject($Object);
 %   my $title;
 %   if (ref $Object eq 'RT::User' && $Object->Id == $session{CurrentUser}->Id) {
 %       $title = loc("My dashboards");
diff --git a/share/html/Dashboards/Modify.html b/share/html/Dashboards/Modify.html
index bba6edf..f8f7376 100644
--- a/share/html/Dashboards/Modify.html
+++ b/share/html/Dashboards/Modify.html
@@ -89,8 +89,7 @@ my $redirect_to ='/Dashboards/Modify.html';
 use RT::Dashboard;
 
 my $Dashboard = RT::Dashboard->new($session{'CurrentUser'});
-my $method = $Create ? 'ObjectsForCreating' : 'ObjectsForModifying';
-my @privacies = $Dashboard->$method;
+my @privacies = grep { $Create ? $Dashboard->CurrentUserCanCreate($_) : $Dashboard->CurrentUserCanModify($_) } $Dashboard->_PrivacyObjects;
 
 Abort(loc("Permission Denied")) if @privacies == 0;
 
@@ -101,7 +100,7 @@ else {
     if ($id eq 'new') {
         $tried_create = 1;
 
-        my ($val, $msg) = $Dashboard->Save(
+        my ($val, $msg) = $Dashboard->Create(
             Name    => $ARGS{'Name'},
             Privacy => $ARGS{'Privacy'},
         );
@@ -133,16 +132,15 @@ else {
 }
 
 if (!$Create && !$tried_create && $id && $ARGS{'Save'}) {
-    my ($ok, $msg) = $Dashboard->Update(Privacy  => $ARGS{'Privacy'},
-                                        Name     => $ARGS{'Name'});
-
-    if ($ok) {
-        push @results, loc("Dashboard [_1] updated", $Dashboard->Name);
+    my ( $ok, $msg );
+    if ( $Dashboard->Privacy ne $ARGS{'Privacy'} ) {
+        ( $ok, $msg ) = $Dashboard->SetPrivacy($ARGS{'Privacy'}) ;
+        push @results, $msg;
     }
-    else {
-        push @results, loc("Dashboard [_1] could not be updated: [_2]", $Dashboard->Name, $msg);
+    if ( $Dashboard->Name ne $ARGS{'Name'} ) {
+        ( $ok, $msg ) = $Dashboard->SetName($ARGS{'Name'});
+        push @results, $msg;
     }
-
 }
 
 
diff --git a/share/html/Dashboards/Queries.html b/share/html/Dashboards/Queries.html
index d84b860..c348947 100644
--- a/share/html/Dashboards/Queries.html
+++ b/share/html/Dashboards/Queries.html
@@ -252,7 +252,7 @@ for my $pane (sort keys %pane_name) {
                 panes        => $panes,
             );
 
-            my ($ok, $msg) = $Dashboard->Update(Panes => $panes);
+            my ($ok, $msg) = $Dashboard->SetPanes($panes);
 
             if ($ok) {
                 push @results, loc("Dashboard updated");
diff --git a/t/web/dashboards-basics.t b/t/web/dashboards-basics.t
index c3533a3..40dc79e 100644
--- a/t/web/dashboards-basics.t
+++ b/t/web/dashboards-basics.t
@@ -68,7 +68,7 @@ $m->form_name('ModifyDashboard');
 $m->field("Name" => 'different dashboard');
 $m->content_lacks('Delete', "Delete button hidden because we are creating");
 $m->click_button(value => 'Create');
-$m->content_contains("Saved dashboard different dashboard");
+$m->content_like(qr/Dashboard \d+ created/);
 $user_obj->PrincipalObj->GrantRight(Right => 'SeeOwnDashboard', Object => $RT::System);
 $m->get($url."Dashboards/index.html");
 $m->follow_link_ok({ text => 'different dashboard'});
@@ -194,13 +194,13 @@ $m->content_contains('Delete', "Delete button shows because we have DeleteOwnDas
 
 $m->form_name('ModifyDashboard');
 $m->click_button(name => 'Delete');
-$m->content_contains("Deleted dashboard");
+$m->content_like(qr/Dashboard \d+ deleted/);
 
 $m->get("/Dashboards/Modify.html?id=$id");
 $m->content_lacks("different dashboard", "dashboard was deleted");
-$m->content_contains("Failed to load dashboard $id");
+$m->text_contains("Couldn't load dashboard $id");
 
-$m->warning_like(qr/Failed to load dashboard.*Couldn't find row/, "the dashboard was deleted");
+$m->warning_like(qr/Couldn't load dashboard.*Couldn't find row/, "the dashboard was deleted");
 
 $user_obj->PrincipalObj->GrantRight(Right => "SuperUser", Object => $RT::System);
 
@@ -225,7 +225,7 @@ $m->field("Privacy" => 'RT::System-1');
 $m->content_lacks('Delete', "Delete button hidden because we are creating");
 $m->click_button(value => 'Create');
 $m->content_lacks("No permission to create dashboards");
-$m->content_contains("Saved dashboard system dashboard");
+$m->content_like(qr/Dashboard \d+ created/);
 
 $m->follow_link_ok({id => 'page-content'});
 
@@ -262,7 +262,7 @@ my ($bad_id) = $personal =~ /^search-(\d+)/;
 
 for my $page (qw/Modify Queries Render Subscription/) {
     $m->get("/Dashboards/$page.html?id=$bad_id");
-    $m->content_like(qr/Couldn.+t load dashboard $bad_id: Invalid object type/);
-    $m->warning_like(qr/Couldn't load dashboard $bad_id: Invalid object type/);
+    $m->text_like(qr/Couldn't load dashboard $bad_id/);
+    $m->warning_like(qr/Couldn't load dashboard $bad_id/);
 }
 
diff --git a/t/web/dashboards-deleted-saved-search.t b/t/web/dashboards-deleted-saved-search.t
index cb96aca..c82f7de 100644
--- a/t/web/dashboards-deleted-saved-search.t
+++ b/t/web/dashboards-deleted-saved-search.t
@@ -37,7 +37,7 @@ $m->submit_form(
     fields    => { Name => 'bar' },
 );
 
-$m->content_contains('Saved dashboard bar', 'dashboard saved' );
+$m->content_like(qr/Dashboard \d+ created/, 'dashboard created' );
 my $dashboard_queries_link = $m->find_link( text_regex => qr/Content/ );
 my ( $dashboard_id ) = $dashboard_queries_link->url =~ /id=(\d+)/;
 
diff --git a/t/web/dashboards-groups.t b/t/web/dashboards-groups.t
index 9f1c37d..05b4711 100644
--- a/t/web/dashboards-groups.t
+++ b/t/web/dashboards-groups.t
@@ -80,7 +80,7 @@ $m->field("Name" => 'inner dashboard');
 $m->field("Privacy" => "RT::Group-" . $inner_group->Id);
 $m->click_button(value => 'Create');
 $m->content_lacks("Permission Denied", "we now have SeeGroupDashboard");
-$m->content_contains("Saved dashboard inner dashboard");
+$m->content_like(qr/Dashboard \d+ created/);
 $m->content_lacks('Delete', "Delete button hidden because we lack DeleteDashboard");
 
 my $dashboard = RT::Dashboard->new($currentuser);
@@ -98,7 +98,6 @@ $m->content_contains("inner dashboard", "we now have SeeGroupDashboard right");
 $m->content_lacks("Permission Denied");
 $m->content_contains('Subscription', "Subscription link not hidden because we have SubscribeDashboard");
 
-
 $m->get_ok("/Dashboards/index.html");
 $m->content_contains("inner dashboard", "We can see the inner dashboard from the UI");
 
diff --git a/t/web/dashboards-in-menu.t b/t/web/dashboards-in-menu.t
index 3126d55..d5acf9f 100644
--- a/t/web/dashboards-in-menu.t
+++ b/t/web/dashboards-in-menu.t
@@ -5,13 +5,13 @@ use RT::Test tests => 31;
 my ($baseurl, $m) = RT::Test->started_ok;
 
 my $system_foo = RT::Dashboard->new($RT::SystemUser);
-$system_foo->Save(
+$system_foo->Create(
     Name    => 'system foo',
     Privacy => 'RT::System-' . $RT::System->id,
 );
 
 my $system_bar = RT::Dashboard->new($RT::SystemUser);
-$system_bar->Save(
+$system_bar->Create(
     Name    => 'system bar',
     Privacy => 'RT::System-' . $RT::System->id,
 );
@@ -42,9 +42,9 @@ diag "setting in admin users";
 my $root = RT::CurrentUser->new( $RT::SystemUser );
 ok( $root->Load('root') );
 my $self_foo = RT::Dashboard->new($root);
-$self_foo->Save( Name => 'self foo', Privacy => 'RT::User-' . $root->id );
+$self_foo->Create( Name => 'self foo', Privacy => 'RT::User-' . $root->id );
 my $self_bar = RT::Dashboard->new($root);
-$self_bar->Save( Name => 'self bar', Privacy => 'RT::User-' . $root->id );
+$self_bar->Create( Name => 'self bar', Privacy => 'RT::User-' . $root->id );
 
 ok( !$m->find_link( text => 'self foo' ), 'no self foo link' );
 $m->get_ok( $baseurl."/Admin/Users/DashboardsInMenu.html?id=" . $root->id);
diff --git a/t/web/dashboards-search-cache.t b/t/web/dashboards-search-cache.t
index 18989d5..6c4fc9f 100644
--- a/t/web/dashboards-search-cache.t
+++ b/t/web/dashboards-search-cache.t
@@ -25,7 +25,7 @@ $m->get_ok("$url/Dashboards/Modify.html?Create=1");
 $m->form_name('ModifyDashboard');
 $m->field('Name' => 'inner dashboard');
 $m->click_button(value => 'Create');
-$m->text_contains('Saved dashboard inner dashboard');
+$m->text_like(qr/Dashboard \d+ created/);
 
 my ($inner_id) = $m->content =~ /name="id" value="(\d+)"/;
 ok($inner_id, "got an ID, $inner_id");
@@ -35,7 +35,7 @@ $m->get_ok("$url/Dashboards/Modify.html?Create=1");
 $m->form_name('ModifyDashboard');
 $m->field('Name' => 'cachey dashboard');
 $m->click_button(value => 'Create');
-$m->text_contains('Saved dashboard cachey dashboard');
+$m->text_like(qr/Dashboard \d+ created/);
 
 my ($dashboard_id) = $m->content =~ /name="id" value="(\d+)"/;
 ok($dashboard_id, "got an ID, $dashboard_id");
@@ -91,7 +91,7 @@ $m->get_ok("/Dashboards/Modify.html?id=$inner_id");
 $m->form_name('ModifyDashboard');
 $m->field('Name' => 'recursive dashboard');
 $m->click_button(value => 'Save Changes');
-$m->text_contains('Dashboard recursive dashboard updated');
+$m->text_contains('Name changed');
 
 # check subscription page again
 $m->get_ok("/Dashboards/Subscription.html?id=$dashboard_id");

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


More information about the rt-commit mailing list