Showing posts with label RT. Show all posts
Showing posts with label RT. Show all posts

Monday, March 01, 2010

Мастер-класс Request Tracker в Москве, Май 2010

Уникальная возможность лучше познакомиться с системой Request Tracker и узнать то, что Вы еще не знаете. В Москве, в мае пройдет мастер-класс. Это возможности из первых рук, от одного из разработчиков системы, получить ответы на интересующие Вас вопросы. Участие в мастер-классе позволит повысить свою квалификацию и успешно справляться с задачами администрирования и расширения RT под нужды вашей компании.

Заполни анкету прямо сейчас. 15го Марта будет известна стоимость (еще есть возможность повлиять на эту цифру), точные даты и место проведения. Все подробности на странице о мероприятии.

Wednesday, November 25, 2009

New features on rt.cpan.org

We implemented and deployed two new features on rt.cpan.org.

Subject Tags

People with many distributionss will love it, as it allows youto add a custom string into subject of emails per distribution. Subject of emails will be something like [rt.cpan.org YourTokenHere #123].

We decided to leave rt.cpan.org there, but I'm pretty sure therewill be people who will try to abuse the feature. Be smart, don'tuse two short and too long tags, remember that reporters also recieveemails.

Maintainers list with links

It's just UI sugar, each maintainer in the list is now wrapped intoa link that lead to all modules this author maintains.

Saturday, September 12, 2009

Improving usability for people and code reuse

For my long going tisql project I wrote Parse::Boolean module a while ago. Recently found a new application for it and it worked pretty well.

In RT we have scrips - condition, action and a template. When something happens with a ticket, a change checked against conditions of scrips. Only those actions are applied for which conditions returned a true value. Pretty simple, but condition is just a a code and we want code to be re-usable.

Conditions are implemented as a modules and controlled by an argument, usually a parsable string. Strings work good for people. Lots of RT admins can not write a code for conditions and it's stupid to ask them to wrap a code they can not write into a module.

We have 'User Defined' condition module for such cases, where you can write code right in the UI. It helps, but anyway. Here goes another problem. If you have code in a module that nobody except you can write then this module should be help for everybody, but it's not. Often people want to mix complex things with simple conditions and either you have to extend format of argument in the condition or invent a new thing.

I decided to "invent".

There was nothing to invent actually, but connect technologies together and that's what I did. Sometimes I think our work is to connect things together. Recall I said that User Defined allow you to type a condition using Perl right in the UI. Ok. What if we replace perl with custom syntax. Parse::BooleanLogic provides a good parser for pretty random things inside nested parentheses and joined with boolean operators 'AND' and 'OR'. Almost any condition in RT falls into this category and looks like the following even in perl:

    return 1 if ( this OR that OR (that AND that) ) AND else...
    return 0;

In RT we have TicketSQL using which you can search tickets and use the following simple SQL like conditions:

    x = 10 OR y LIKE 'string' OR z IS NULL

I decided that in condition it will work pretty well too. In condition we have the current ticket we check and the change (transaction).

    ( Type = 'Create' AND Ticket.Status = 'resolved' )
    OR ( Type = 'Set' AND Field = 'Status' AND NewValue = 'resolved' )

Looks good and every user can get the syntax, but it's not there yet.

We have modules already that implement conditions and want to reuse them. Pretty easy to solve:

    ModuleName{'argument'} OR !AnotherModule{'argument'}

I'm really proud that my parser allows me to parse syntax like this without much work:

    sub ParseCode {
        my $self = shift;

        my $code = $self->ScripObj->CustomIsApplicableCode;

        my @errors = ();
        my $res = $parser->as_array(
            $code,
            error_cb => sub { push @errors, $_[0]; },
            operand_cb => sub {
                my $op = shift;
                if ( $op =~ /^(!?)($re_exec_module)(?:{$re_module_argument})?$/o ) {
                    return {
                        module => $2,
                        negative => $1,
                        argument => $parser->dq($3),
                    };
                }
                elsif ( $op =~ /^($re_field)\s+($re_bin_op)\s+($re_value)$/o ) {
                    return { op => $2, lhs => $1, rhs => $3 };
                }
                elsif ( $op =~ /^($re_field)\s+($re_un_op)$/o ) {
                    return { op => $2, lhs => $1 };
                }
                else {
                    push @errors, "'$op' is not a check 'Complex' condition knows about";
                    return undef;
                }
            },
        );
        return @errors? (undef, @errors) : ($res);
    }

It's not only parser, but solver as well:

    my $solver = sub {
        my $cond = shift;
        my $self = $_[0];
        if ( $cond->{'op'} ) {
            return $self->OpHandler($cond->{'op'})->(
                $self->GetField( $cond->{'lhs'}, @_ ),
                $self->GetValue( $cond->{'rhs'}, @_ )
            );
        }
        elsif ( $cond->{'module'} ) {
            my $module = 'RT::Condition::'. $cond->{'module'};
            eval "require $module;1" || die "Require of $module failed.\n$@\n";
            my $obj = $module->new (
                TransactionObj => $_[1],
                TicketObj      => $_[2],
                Argument       => $cond->{'argument'},
                CurrentUser    => $RT::SystemUser,
            );
            return $obj->IsApplicable;
        } else {
            die "Boo";
        }
    };

    sub Solve {
        my $self = shift;
        my $tree = shift;

        my $txn = $self->TransactionObj;
        my $ticket = $self->TicketObj;

        return $parser->solve( $tree, $solver, $self, $txn, $ticket );
    }

That's it. Eveything else is grammar regexpes, column handlers and and documentation. Available on the CPAN and on the github.