[Rt-commit] r11245 - in rt/branches/3.8-TESTING: lib/RT/Action

ruz at bestpractical.com ruz at bestpractical.com
Thu Mar 27 17:44:03 EDT 2008


Author: ruz
Date: Thu Mar 27 17:44:02 2008
New Revision: 11245

Added:
   rt/branches/3.8-TESTING/lib/RT/Action/NotifyGroup.pm
   rt/branches/3.8-TESTING/lib/RT/Action/NotifyGroupAsComment.pm
   rt/branches/3.8-TESTING/sbin/rt-notify-group-admin.in   (contents, props changed)

Log:
* add Action::NotifyGroup

Added: rt/branches/3.8-TESTING/lib/RT/Action/NotifyGroup.pm
==============================================================================
--- (empty file)
+++ rt/branches/3.8-TESTING/lib/RT/Action/NotifyGroup.pm	Thu Mar 27 17:44:02 2008
@@ -0,0 +1,147 @@
+=head1 NAME
+
+RT::Action::NotifyGroup - RT Action that sends notifications to groups and/or users
+
+=head1 DESCRIPTION
+
+RT action module that allow you to notify particular groups and/or users.
+Distribution is shipped with C<rt-notify-group-admin> script that
+is command line tool for managing NotifyGroup scrip actions. For more
+more info see its documentation.
+
+=cut
+
+package RT::Action::NotifyGroup;
+
+use strict;
+use warnings;
+use base qw(RT::Action::Notify);
+
+require RT::User;
+require RT::Group;
+
+=head1 METHODS
+
+=head2 SetRecipients
+
+Sets the recipients of this message to Groups and/or Users.
+
+=cut
+
+sub SetRecipients {
+    my $self = shift;
+
+    my $arg = $self->Argument;
+
+    my $old_arg = eval { Storable::thaw( $arg ) };
+    unless( $@ ) {
+        $arg = $self->__ConvertOldArg( $old_arg );
+    }
+
+    foreach( $self->__SplitArg( $arg ) ) {
+        $self->_HandleArgument( $_ );
+    }
+
+    my $creator = $self->TransactionObj->CreatorObj->EmailAddress();
+    unless( $RT::NotifyActor ) {
+        @{ $self->{'To'} } = grep ( !/^\Q$creator\E$/, @{ $self->{'To'} } );
+    }
+
+    $self->{'seen_ueas'} = {};
+
+    return 1;
+}
+
+sub _HandleArgument {
+    my $self = shift;
+    my $instance = shift;
+    
+    my $obj = RT::Principal->new( $RT::SystemUser );
+    $obj->Load( $instance );
+    unless( $obj->id ) {
+        $RT::Logger->error( "Couldn't load principal #$instance" );
+        return;
+    }
+    if( $obj->Disabled ) {
+        $RT::Logger->info( "Principal #$instance is disabled => skip" );
+        return;
+    }
+    if( !$obj->PrincipalType ) {
+        $RT::Logger->crit( "Principal #$instance has empty type" );
+    } elsif( lc $obj->PrincipalType eq 'user' ) {
+        $self->__HandleUserArgument( $obj->Object );
+    } elsif( lc $obj->PrincipalType eq 'group' ) {
+        $self->__HandleGroupArgument( $obj->Object );
+    } else {
+        $RT::Logger->info( "Principal #$instance has unsupported type" );
+    }
+    return;
+}
+
+sub __HandleUserArgument {
+    my $self = shift;
+    my $obj = shift;
+    
+    my $uea = $obj->EmailAddress;
+    unless( $uea ) {
+        $RT::Logger->warning( "User #". $obj->id ." has no email address" );
+        return;
+    }
+    $self->__PushUserAddress( $uea );
+}
+
+sub __HandleGroupArgument {
+    my $self = shift;
+    my $obj = shift;
+
+    my $members = $obj->UserMembersObj;
+    while( my $m = $members->Next ) {
+        $self->__HandleUserArgument( $m );
+    }
+}
+
+sub __SplitArg {
+    return split /[^0-9]+/, $_[1];
+}
+
+sub __ConvertOldArg {
+    my $self = shift;
+    my $arg = shift;
+    my @res;
+    foreach my $r ( @{ $arg } ) {
+        my $obj;
+        next unless $r->{'Type'};
+        if( lc $r->{'Type'} eq 'user' ) {
+            $obj = RT::User->new( $RT::SystemUser );
+        } elsif ( lc $r->{'Type'} eq 'user' ) {
+            $obj = RT::Group->new( $RT::SystemUser );
+        } else {
+            next;
+        }
+        $obj->Load( $r->{'Instance'} );
+        my $id = $obj->id;
+        next unless( $id );
+
+        push @res, $id;
+    }
+
+    return join ';', @res;
+}
+
+sub __PushUserAddress {
+    my $self = shift;
+    my $uea = shift;
+    push @{ $self->{'To'} }, $uea unless $self->{'seen_ueas'}{ $uea }++;
+    return;
+}
+
+
+=head1 AUTHOR
+
+Ruslan U. Zakirov E<lt>ruz at bestpractical.comE<gt>
+
+L<RT::Action::NotifyGroupAsComment>, F<rt-notify-group-admin>
+
+=cut
+
+1;

Added: rt/branches/3.8-TESTING/lib/RT/Action/NotifyGroupAsComment.pm
==============================================================================
--- (empty file)
+++ rt/branches/3.8-TESTING/lib/RT/Action/NotifyGroupAsComment.pm	Thu Mar 27 17:44:02 2008
@@ -0,0 +1,33 @@
+=head1 NAME
+
+RT::Action::NotifyGroupAsComment - RT Action that sends notifications to groups and/or users as comment
+
+=head1 DESCRIPTION
+
+This is subclass of L<RT::Action::NotifyGroup> that send comments instead of replies.
+See C<rt-notify-group-admin> and L<RT::Action::NotifyGroup> docs for more info.
+
+=cut
+
+package RT::Action::NotifyGroupAsComment;
+
+use strict;
+use warnings;
+
+use RT::Action::NotifyGroup;
+
+use base qw(RT::Action::NotifyGroup);
+
+sub SetReturnAddress {
+	my $self = shift;
+	$self->{'comment'} = 1;
+	return $self->SUPER::SetReturnAddress( @_, is_comment => 1 );
+}
+
+=head1 AUTHOR
+
+Ruslan U. Zakirov E<lt>ruz at bestpractical.comE<gt>
+
+=cut
+
+1;

Added: rt/branches/3.8-TESTING/sbin/rt-notify-group-admin.in
==============================================================================
--- (empty file)
+++ rt/branches/3.8-TESTING/sbin/rt-notify-group-admin.in	Thu Mar 27 17:44:02 2008
@@ -0,0 +1,476 @@
+#!@PERL@
+
+=head1 NAME
+
+rt-notify-group-admin - Command line tool for administrating NotifyGroup actions
+
+=head1 SYNOPSIS
+
+    rt-notify-group-admin --list
+    rt-notify-group-admin --create 'Notify foo team' --group Foo
+    rt-notify-group-admin --create 'Notify foo team as comment' --comment --group Foo
+    rt-notify-group-admin --create 'Notify group Foo and Bar' --group Foo --group Bar
+    rt-notify-group-admin --create 'Notify user foo at bar.com' --user foo at bar.com
+    rt-notify-group-admin --create 'Notify VIPs' --user vip1 at bar.com
+    rt-notify-group-admin --add 'Notify VIPs' --user vip2 at bar.com --group vip1 --user vip3 at foo.com
+    rt-notify-group-admin --rename 'Notify VIPs' --newname 'Inform VIPs'
+    rt-notify-group-admin --switch 'Notify VIPs'
+    rt-notify-group-admin --delete 'Notify user foo at bar.com'
+
+=head1 DESCRIPTION
+
+This script list, create, modify or delete scrip actions in the RT DB. Once
+you've created an action you can use it in a scrip.
+
+For example you can create the following action using this script:
+
+    rt-notify-group-admin --create 'Notify developers' --group 'Development Team'
+
+Then you can add the followoing scrip to your Bugs queue:
+    
+    Condition: On Create
+    Action:    Notify developers
+    Template:  Transaction
+    Stage:     TransactionCreate
+
+Your development team will be notified on every new ticket in the queue.
+
+=cut
+
+use warnings;
+use strict;
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+use RT;
+RT::LoadConfig;
+RT::Init;
+
+require RT::Principal;
+require RT::User;
+require RT::Group;
+require RT::ScripActions;
+
+use Getopt::Long qw(GetOptions);
+
+our $cmd = 'usage';
+our $opts = {};
+
+sub parse_args {
+    my $tmp;
+    Getopt::Long::Configure( "pass_through" );
+    if ( GetOptions( 'list' => \$tmp ) && $tmp ) {
+        $cmd = 'list';
+    }
+    elsif ( GetOptions( 'create=s' => \$tmp ) && $tmp ) {
+        $cmd = 'create';
+        $opts->{'name'} = $tmp;
+        $opts->{'groups'} = [];
+        $opts->{'users'} = [];
+        GetOptions( 'comment' => \$opts->{'comment'} );
+        GetOptions( 'group:s@' => $opts->{'groups'} );
+        GetOptions( 'user:s@' => $opts->{'users'} );
+        unless ( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) {
+            usage();
+            exit(-1);
+        }
+    }
+    elsif ( GetOptions( 'add=s' => \$tmp ) && $tmp ) {
+        $cmd = 'add';
+        $opts->{'name'} = $tmp;
+        $opts->{'groups'} = [];
+        $opts->{'users'} = [];
+        GetOptions( 'group:s@' => $opts->{'groups'} );
+        GetOptions( 'user:s@' => $opts->{'users'} );
+        unless ( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) {
+            usage();
+            exit(-1);
+        }
+    }
+    elsif ( GetOptions( 'switch=s' => \$tmp ) && $tmp ) {
+        $cmd = 'switch';
+        $opts->{'name'} = $tmp;
+    }
+    elsif ( GetOptions( 'rename=s' => \$tmp ) && $tmp ) {
+        $cmd = 'rename';
+        $opts->{'name'} = $tmp;
+        GetOptions( 'newname=s' => \$opts->{'newname'} );
+        unless ( $opts->{'newname'} ) {
+            usage();
+            exit(-1);
+        }
+    }
+    elsif ( GetOptions( 'delete=s' => \$tmp ) && $tmp) {
+        $cmd = 'delete';
+        $opts->{'name'} = $tmp;
+    } else {
+        $cmd = 'usage';
+    }
+    
+    return;
+}
+
+sub usage {
+    local $@;
+    eval "require Pod::PlainText;";
+    if ( $@ ) {
+        print "see `perldoc $0`\n";
+    } else {
+        my $parser = Pod::PlainText->new( sentence => 0, width => 78 );
+        $parser->parse_from_file( $0 );
+    }
+}
+
+parse_args();
+
+{
+    eval "main::$cmd()";
+    if ( $@ ) {
+        print STDERR $@ ."\n";
+    }
+}
+
+exit(0);
+
+=head1 USAGE
+
+rt-notify-group-admin --COMMAND ARGS
+
+=head1 COMMANDS
+
+=head2 list
+
+Lists actions and its descriptions.
+
+=cut
+
+sub list {
+    my $actions = _get_our_actions();
+    while( my $a = $actions->Next ) {
+        _list( $a );
+    }
+    return;
+}
+
+sub _list {
+    my $action = shift;
+
+    print "Name: ". $action->Name() ."\n";
+    print "Module: ". $action->ExecModule() ."\n";
+
+    my @princ = argument_to_list( $action );
+
+    print "Members: \n";
+    foreach( @princ ) {
+        my $obj = RT::Principal->new( $RT::SystemUser );
+        $obj->Load( $_ );
+        next unless $obj->id;
+
+        print "\t". $obj->PrincipalType;
+        print "\t=> ". $obj->Object->Name;
+        print "(Disabled!!!)" if $obj->Disabled;
+        print "\n";
+    }
+    print "\n";
+    return;
+}
+
+=head2 create NAME [--comment] [--group GNAME] [--user UNAME]
+
+Creates new action with NAME and adds users and/or groups to its
+recipient list. Would be notify as comment if --comment specified.
+
+=cut
+
+sub create {
+    my $actions = RT::ScripActions->new( $RT::SystemUser );
+    $actions->Limit(
+        FIELD => 'Name',
+        VALUE => $opts->{'name'},
+    );
+    if ( $actions->Count ) {
+        print STDERR "ScripAction '". $opts->{'name'} ."' allready exists\n";
+        exit(-1);
+    }
+
+    my @groups = _check_groups( @{ $opts->{'groups'} } );
+    my @users  = _check_users( @{ $opts->{'users'} } );    
+    unless ( @users + @groups ) {
+        print STDERR "List of groups and users is empty\n";
+        exit(-1);
+    }
+
+    my $action = __create_empty( $opts->{'name'}, $opts->{'comment'} );
+
+    __add( $action, $_ ) foreach( @users );
+    __add( $action, $_ ) foreach( @groups );
+
+    return;
+}
+
+sub __create_empty {
+    my $name = shift;
+    my $as_comment = shift || 0;
+    require RT::ScripAction;
+    my $action = RT::ScripAction->new( $RT::SystemUser );
+    $action->Create(
+        Name => $name,
+        Description => "Created with rt-notify-group-admin script",
+        ExecModule => $as_comment? 'NotifyGroupAsComment': 'NotifyGroup',
+        Argument => '',
+    );
+
+    return $action;
+}
+
+sub _check_groups
+{
+    return grep { $_ ? 1: do { print STDERR "Group '$_' skipped, doesn't exist\n"; 0; } }
+        map { __check_group($_) } @_;
+}
+
+sub __check_group
+{
+    my $instance = shift;
+    require RT::Group;
+    my $obj = RT::Group->new( $RT::SystemUser );
+    $obj->LoadUserDefinedGroup( $instance );
+    return $obj->id ? $obj : undef;
+}
+
+sub _check_users
+{
+    return grep { $_ ? 1: do { print STDERR "User '$_' skipped, doesn't exist\n"; 0; } }
+        map { __check_user($_) } @_;
+}
+
+sub __check_user
+{
+    my $instance = shift;
+    require RT::User;
+    my $obj = RT::User->new( $RT::SystemUser );
+    $obj->Load( $instance );
+    return $obj->id ? $obj : undef;
+}
+
+=head2 add NAME [--group GNAME] [--user UNAME]
+
+Adds groups and/or users to recipients of the action NAME.
+
+=cut
+
+sub add {
+    my $action = _get_action_by_name( $opts->{'name'} );
+    unless ( $action ) {
+        print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
+        exit(-1);
+    }
+
+    my @groups = _check_groups( @{ $opts->{'groups'} } );
+    my @users = _check_users( @{ $opts->{'users'} } );
+    
+    unless ( @users + @groups ) {
+        print STDERR "List of groups and users is empty\n";
+        exit(-1);
+    }
+
+    __add( $action, $_ ) foreach @users;
+    __add( $action, $_ ) foreach @groups;
+
+    return;
+}
+
+sub __add
+{
+    my $action = shift;
+    my $obj = shift;
+
+    my @cur = argument_to_list( $action );
+
+    my $id = $obj->id;
+    return if grep $_ == $id, @cur;
+
+    push @cur, $id;
+
+    return $action->__Set( Field => 'Argument', Value => join(';', @cur) );
+}
+
+=head2 delete NAME
+
+Deletes action NAME if scrips doesn't use it.
+
+=cut
+
+sub delete {
+    my $action = _get_action_by_name( $opts->{'name'} );
+    unless ( $action ) {
+        print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
+        exit(-1);
+    }
+
+    require RT::Scrips;
+    my $scrips = RT::Scrips->new( $RT::SystemUser );
+    $scrips->Limit( FIELD => 'ScripAction', VALUE => $action->id );
+    if ( $scrips->Count ) {
+        my @sid;
+        while( my $s = $scrips->Next ) {
+            push @sid, $s->id;
+        }
+        print STDERR "ScripAction '". $opts->{'name'} ."'"
+            . " is in use by Scrip(s) ". join( ", ", map "#$_", @sid )
+            . "\n";
+        exit(-1);
+    }
+
+    return __delete( $action );
+}
+
+sub __delete {
+    require DBIx::SearchBuilder::Record;
+    return DBIx::SearchBuilder::Record::Delete( shift );
+}
+
+sub _get_action_by_name {
+    my $name = shift;
+    my $actions = _get_our_actions();
+    $actions->Limit(
+        FIELD => 'Name',
+        VALUE => $name
+    );
+
+    if ( $actions->Count > 1 ) {
+        print STDERR "More then one ScripAction with name '$name'\n";
+    }
+
+    return $actions->First;
+}
+
+=head2 switch NAME
+
+Switch action NAME from notify as correspondence to comment and back.
+
+=cut
+
+sub switch {
+    my $action = _get_action_by_name( $opts->{'name'} );
+    unless ( $action ) {
+        print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
+        exit(-1);
+    }
+
+    my %h = (
+        NotifyGroup => 'NotifyGroupAsComment',
+        NotifyGroupAsComment => 'NotifyGroup'
+    );
+
+    return $action->__Set(
+        Field => 'ExecModule',
+        Value => $h{ $action->ExecModule }
+    );
+}
+
+=head2 rename NAME --newname NEWNAME
+
+Renames action NAME to NEWNAME.
+
+=cut
+
+sub rename {
+    my $action = _get_action_by_name( $opts->{'name'} );
+    unless ( $action ) {
+        print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
+        exit(-1);
+    }
+
+    my $actions = RT::ScripActions->new( $RT::SystemUser );
+    $actions->Limit( FIELD => 'Name', VALUE => $opts->{'newname'} );
+    if ( $actions->Count ) {
+        print STDERR "ScripAction '". $opts->{'newname'} ."' allready exists\n";
+        exit(-1);
+    }
+
+    return $action->__Set(
+        Field => 'Name',
+        Value => $opts->{'newname'},
+    );
+}
+
+=head2 NOTES
+
+If command has option --group or --user then you can use it more then once,
+if other is not specified.
+
+=cut
+
+###############
+#### Utils ####
+###############
+
+sub argument_to_list {
+    my $action = shift;
+    my $arg = $action->Argument;
+
+    # old variant via Storable
+    {
+        local $@;
+        require Storable;
+        my $old = eval { Storable::thaw( $arg ) };
+        unless ( $@ ) {
+            $arg = __convert_old($old);
+        }
+    }
+
+    return _split_arg( $arg );
+}
+
+sub _split_arg { return split /[^0-9]+/, $_[0] }
+
+sub __convert_old {
+    my $arg = shift;
+    my @res;
+    foreach my $r ( @{ $arg } ) {
+        my $obj;
+        next unless $r->{'Type'};
+        if ( lc $r->{'Type'} eq 'user' ) {
+            $obj = RT::User->new( $RT::SystemUser );
+        } elsif ( lc $r->{'Type'} eq 'user' ) {
+            $obj = RT::Group->new( $RT::SystemUser );
+        } else {
+            next;
+        }
+        $obj->Load( $r->{'Instance'} );
+        my $id = $obj->id;
+        next unless $id;
+
+        push @res, $id;
+    }
+
+    return join ';', @res;
+}
+
+sub _get_our_actions {
+    my $actions = RT::ScripActions->new( $RT::SystemUser );
+    $actions->Limit(
+        FIELD => 'ExecModule',
+        VALUE => 'NotifyGroup',
+        ENTRYAGGREGATOR => 'OR',
+    );
+    $actions->Limit(
+        FIELD => 'ExecModule',
+        VALUE => 'NotifyGroupAsComment',
+        ENTRYAGGREGATOR => 'OR',
+    );
+
+    return $actions;
+}
+
+=head1 AUTHOR
+
+Ruslan U. Zakirov E<lt>ruz at bestpractical.comE<gt>
+
+=head1 SEE ALSO
+
+L<RT::Action::NotifyGroup>, L<RT::Action::NotifyGroupAsComment>
+
+=cut


More information about the Rt-commit mailing list