Bug 108088 - Bug change triggers

git-svn-id: svn://svn.office.custis.ru/3rdparty/bugzilla.org/trunk@1624 6955db30-a419-402b-8a0d-67ecbb4d7f56
master
vfilippov 2012-11-14 11:13:39 +00:00
parent db49e899bf
commit c1dfdb5a8f
5 changed files with 191 additions and 28 deletions

View File

@ -52,6 +52,15 @@ if ($params->{save})
($params->{on_update} ? 1 : 0) * CF_UPDATE |
($params->{on_create} ? 1 : 0) * CF_CREATE |
($params->{deny_all} ? 1 : 0) * CF_DENY;
# Триггеры
my $triggers;
for (keys %$params)
{
if ($params->{$_} !~ /^\s*$/so && /^triggers_(.*)$/so)
{
$triggers->{$1} = $params->{$_};
}
}
# Создаём/обновляем
my $ch;
if ($params->{create})
@ -62,6 +71,7 @@ if ($params->{save})
message => $params->{message},
flags => $flags,
except_fields => $except,
triggers => $triggers,
});
}
else
@ -71,6 +81,7 @@ if ($params->{save})
$ch->set_message($params->{message});
$ch->set_flags($flags);
$ch->set_except_fields($except);
$ch->set_triggers($triggers);
$ch->update;
}
delete_token($params->{token});

View File

@ -13,11 +13,18 @@ use Bugzilla::Error;
use constant DB_TABLE => 'checkers';
use constant {
CF_FREEZE => 0x01, # да => заморозка, нет => проверка значений
CF_FATAL => 0x02, # да => ошибка, нет => предупреждение
CF_CREATE => 0x04, # да => проверять при создании бага, нет => не проверять
CF_UPDATE => 0x08, # да => проверять при обновлении бага, нет => не проверять
CF_DENY => 0x10, # да => запрещать изменения всех полей, кроме..., нет => разрешать изменения всех полей, кроме...
# Да => проверка старого состояния бага ("заморозка")
# Нет => проверка нового состояния бага ("проверка новых значений")
CF_FREEZE => 0x01,
# Да => ошибка, нет => предупреждение
CF_FATAL => 0x02,
# Да => проверять при создании бага, нет => не проверять
CF_CREATE => 0x04,
# Да => проверять при обновлении бага, нет => не проверять
CF_UPDATE => 0x08,
# Да => запрещать изменения всех полей, кроме except_fields
# Нет => разрешать изменения всех полей, кроме except_fields
CF_DENY => 0x10,
};
our @EXPORT = qw(CF_FREEZE CF_FATAL CF_CREATE CF_UPDATE CF_DENY);
@ -38,6 +45,8 @@ use constant DB_COLUMNS => (
# если !(flags & CF_DENY), то запретить только их,
# если !(flags & CF_DENY) и их нет, то flags |= CF_DENY
'except_fields',
# Триггеры - действия над полями багов (требует CF_FREEZE и !CF_FATAL)
'triggers',
);
use constant NAME_FIELD => 'message';
use constant ID_FIELD => 'id';
@ -56,6 +65,7 @@ use constant UPDATE_COLUMNS => (
'message',
'sql_code',
'except_fields',
'triggers',
);
# Перепостроение и перекэширование SQL-запроса в базу
@ -95,6 +105,10 @@ sub create
{
$params->{except_fields} = encode_json($params->{except_fields});
}
if ($params->{triggers})
{
$params->{triggers} = encode_json($params->{triggers});
}
my $self = Bugzilla::Object::create($class, $params);
$self->update;
$self->query->set_shared_with_group(Bugzilla::Group->check({ name => 'bz_editcheckers' }));
@ -107,6 +121,10 @@ sub update
my $self = shift;
$self->refresh_sql;
$self->query->set_shared_with_group(Bugzilla::Group->check({ name => 'bz_editcheckers' }));
if ($self->triggers)
{
$self->{flags} |= CF_FREEZE;
}
$self->SUPER::update(@_);
}
@ -147,14 +165,14 @@ sub flags { $_[0]->{flags} }
# Отдельные флаги
sub is_freeze { $_[0]->{flags} & CF_FREEZE }
sub is_fatal { $_[0]->{flags} & CF_FATAL }
sub is_fatal { ($_[0]->{flags} & CF_FATAL) && !$_[0]->triggers }
sub on_create { $_[0]->{flags} & CF_CREATE }
sub on_update { $_[0]->{flags} & CF_UPDATE }
sub deny_all { $_[0]->{flags} & CF_DENY }
# { field_name => value }
# when value is undef, then the change of field with name=field_name to any value is an exception
# when value is not undef, then only the change to value=value is an exception
# Исключать изменения поля field_name на значение value,
# либо на любое значение, если value = undef
sub except_fields
{
my $self = shift;
@ -165,6 +183,21 @@ sub except_fields
return $self->{except_fields_obj};
}
# { field_name => value }
# Изменить значение поля field_name на value. Для полей с множествами значений
# field_name также может быть add_<field_name> или remove_<field_name>, что означает
# добавить значение или удалить значение соответственно.
# FIXME Пока поддерживается только add_cc.
sub triggers
{
my $self = shift;
if (!exists $self->{triggers_obj})
{
$self->{triggers_obj} = $self->{triggers} ? decode_json($self->{triggers}) : undef;
}
return $self->{triggers_obj};
}
sub name
{
my $self = shift;
@ -204,5 +237,12 @@ sub set_except_fields
delete $self->{except_fields_obj};
}
sub set_triggers
{
my ($self, $value) = @_;
$self->set('triggers', $value ? encode_json($value) : undef);
delete $self->{triggers_obj};
}
1;
__END__

View File

@ -75,7 +75,21 @@ sub alert
{
my ($bug, $is_new) = @_;
my (@fatal, @warn);
map { $_->is_fatal ? push(@fatal, $_) : push(@warn, $_) } @{$bug->{failed_checkers} || []};
for (@{$bug->{failed_checkers} || []})
{
if ($_->triggers)
{
# Триггеры нас вообще не расстраивают
}
elsif ($_->is_fatal)
{
push(@fatal, $_);
}
else
{
push(@warn, $_);
}
}
my $force = 1 && Bugzilla->cgi->param('force_checkers');
if (!@fatal && (!@warn || $force))
{
@ -110,15 +124,15 @@ sub show_checker_errors
{
my ($bugs) = @_;
$bugs ||= Bugzilla->request_cache->{failed_checkers};
return if !$bugs || !grep { @{$_->{failed_checkers} || []} } @$bugs;
return if !$bugs || !grep { grep { !$_->triggers } @{$_->{failed_checkers} || []} } @$bugs;
if (Bugzilla->error_mode != ERROR_MODE_WEBPAGE)
{
my $info = [
map { {
bug_id => $_->bug_id,
errors => [ map { $_->message } @{$_->{failed_checkers}} ]
errors => [ map { $_->message } grep { !$_->triggers } @{$_->{failed_checkers}} ]
} }
grep { $_->{failed_checkers} } @$bugs
grep { @{$_->{failed_checkers} || []} } @$bugs
];
ThrowUserError('checks_failed', { bugs => $info });
}
@ -138,7 +152,7 @@ sub freeze_failed_checkers
$failedbugs && @$failedbugs || return undef;
return [
map { [ $_->bug_id, [ map { $_->id } @{$_->{failed_checkers}} ] ] }
grep { $_->{failed_checkers} } @$failedbugs
grep { @{$_->{failed_checkers} || []} } @$failedbugs
];
}
@ -160,10 +174,16 @@ sub unfreeze_failed_checkers
sub filter_failed_checkers
{
my ($checkers, $changes, $bug) = @_;
# фильтруем подошедшие проверки по изменённым полям
# Фильтруем подошедшие проверки по изменённым полям
my @rc;
for (@$checkers)
{
if ($_->triggers)
{
# Не трогаем триггеры
push @rc, $_;
next;
}
my $e = $_->except_fields;
my $ok = 1;
if ($_->deny_all)
@ -220,14 +240,41 @@ sub filter_failed_checkers
@$checkers = @rc;
}
# Запустить триггеры для бага $bug из $bug->{failed_checkers}
sub run_triggers
{
my ($bug) = @_;
my $modified = 0;
for (my $i = $#{$bug->{failed_checkers}}; $i >= 0; $i--)
{
my $checker = $bug->{failed_checkers}->[$i];
if ($checker->triggers)
{
if ($checker->triggers->{add_cc})
{
# FIXME Пока поддерживается только добавление CC, но несложно докрутить произвольные изменения
for (split /[\s,]+/, $checker->triggers->{add_cc})
{
$bug->add_cc($_);
$modified = 1;
}
}
}
# FIXME Нужно показывать информацию о применённом триггере (сейчас оно работает втихаря)
splice @{$bug->{failed_checkers}}, $i, 1;
}
return $modified;
}
# hooks:
sub bug_pre_update
{
my ($args) = @_;
my $bug = $args->{bug};
# запускаем проверки, работающие ДО внесения изменений (заморозка багов)
# запускаем проверки, работающие ДО внесения изменений - заморозку багов и триггеры
$bug->{failed_checkers} = check($bug->bug_id, CF_FREEZE | CF_UPDATE, CF_FREEZE | CF_UPDATE);
run_triggers($bug);
return 1;
}
@ -274,6 +321,11 @@ sub bug_end_of_create
{
alert($bug, 1);
}
# А ещё при создании бага триггеры срабатывают отдельным запросом
if (run_triggers($bug))
{
$bug->update;
}
return 1;
}

View File

@ -122,6 +122,7 @@ sub db_schema_abstract_schema
push @{$schema->{fielddefs}->{FIELDS}}, add_to_deps => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0};
# Bug 68921 - Предикаты корректности из запросов поиска
# Bug 108088 - Триггеры (пока поддерживается только 1 триггер: добавление CC)
$schema->{checkers} = {
FIELDS => [
id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
@ -131,6 +132,7 @@ sub db_schema_abstract_schema
message => {TYPE => 'LONGTEXT', NOTNULL => 1},
sql_code => {TYPE => 'LONGTEXT'},
except_fields => {TYPE => 'LONGBLOB'},
triggers => {TYPE => 'LONGBLOB'},
],
INDEXES => [
checkers_query_id_idx => { FIELDS => ['query_id'] },
@ -406,6 +408,9 @@ sub install_update_fielddefs
$dbh->bz_alter_column('checkers', message => {TYPE => 'LONGTEXT', NOTNULL => 1});
}
# Bug 108088 - Триггеры (пока поддерживается только 1 триггер: добавление CC)
$dbh->bz_add_column('checkers', triggers => {TYPE => 'LONGBLOB'});
# Устанавливаем значение buglist в правильное
my @yes = map { $_->{name} } grep { $_->{buglist} } Bugzilla::Field::DEFAULT_FIELDS;
my @no = map { $_->{name} } grep { !$_->{buglist} } Bugzilla::Field::DEFAULT_FIELDS;

View File

@ -24,11 +24,22 @@
[% FOR c = checkers %]
<tr>
<td><a href="?mode=edit&id=[% c.id %]">[% c.name | html %]</a></td>
<td style="background-color: [% c.is_fatal ? '#fdd' : '#ffd' %]; text-align: center" title="[% c.is_fatal
? 'Жёсткий запрет: При нарушении правила изменения блокируются и выдаётся ошибка'
: 'Мягкий запрет: При нарушении правила выдаётся предупреждение, но изменение не блокируется' %]">
[% c.is_fatal ? "Жёсткий" : "Мягкий" %]
[% IF c.triggers %]
<td style="background-color: #ddf; text-align: center"
title="Вообще не запрет, а триггер: при попадании под условия запроса в баг вносятся изменения" />
Триггер
</td>
[% ELSIF c.is_fatal %]
<td style="background-color: #fdd; text-align: center"
title="Жёсткий запрет: При нарушении правила изменения блокируются и выдаётся ошибка" />
Жёсткий
</td>
[% ELSE %]
<td style="background-color: #ffd; text-align: center"
title="Мягкий запрет: При нарушении правила выдаётся предупреждение, но изменение не блокируется">
Мягкий
</td>
[% END %]
<td style="background-color: [% c.is_freeze ? '#ddf' : '#fdd' %]; text-align: center" title="[% c.is_freeze
? 'Заморозка (защита от изменения)'
: 'Проверка корректности новых значений' %]">
@ -56,14 +67,15 @@
[% ELSE %]
<h3>Редактирование предиката [% checker.name | html %]</h3>
[% END %]
<form action="?save=1&edit=1" method="POST">
<form action="?save=1&edit=1" method="POST" onsubmit="return check_trigger()">
<input type="hidden" name="token" value="[% token | html %]" />
[% IF create %]
<input type="hidden" name="create" value="1" />
[% ELSE %]
<input type="hidden" name="id" value="[% checker.id %]" />
[% END %]
<table>
[% IF create %]
<input type="hidden" name="create" value="1" />
[% ELSE %]
<input type="hidden" name="id" value="[% checker.id %]" />
[% END %]
<tbody>
<tr>
<th>Сохранённый запрос:</th>
<td><select name="query_id">
@ -77,16 +89,31 @@
</select></td>
</tr>
<tr>
<th>Сообщение об ошибке:</th>
<td colspan="2" style="padding-left: 100px">
<input type="radio" id="is_checker" name="is_trigger" value="0" onchange="switch_trigger()" [% " checked='checked'" IF !checker.triggers %] />
<label for="is_checker">Проверка</label>
<input type="radio" id="is_trigger" name="is_trigger" value="1" onchange="switch_trigger()" [% " checked='checked'" IF checker.triggers %] />
<label for="is_trigger">Триггер</label>
</td>
</tr>
<tr>
<th>Описание проверки:</th>
<td><textarea name="message" rows="8" cols="80">[% checker.message | html %]</textarea></td>
</tr>
<tr>
<th>Параметры:</th>
<th>Момент проверки:</th>
<td>
<input type="checkbox" name="on_create" id="on_create" [% " checked='checked'" IF checker.on_create %] onclick="this.blur()" onblur="showhide_allowdeny()" />
<label for="on_create" id="label_for_on_create">Проверять создание новых багов (требуется «Запрещать изменения всех полей»)</label><br />
<input type="checkbox" name="on_update" id="on_update" [% " checked='checked'" IF checker.on_update %] onclick="this.blur()" onblur="showhide_allowdeny()" />
<label for="on_update">Проверять изменения багов</label><br />
<label for="on_update">Проверять изменения багов</label>
</td>
</tr>
</tbody>
<tbody id="tbody_checker" [% " style='display: none'" IF checker.triggers %]>
<tr>
<th>Параметры:</th>
<td>
<input type="checkbox" name="is_freeze" id="is_freeze" [% " checked='checked'" IF checker.is_freeze %] />
<label for="is_freeze" id="label_for_is_freeze">Заморозка багов (защита от изменений, только для режима обновления)</label><br />
<input type="checkbox" name="is_fatal" id="is_fatal" [% " checked='checked'" IF checker.is_fatal %] />
@ -115,7 +142,16 @@
запрет ввода трудозатрат задним числом. Флажок "Запрещать изменения всех полей"
должен быть <b>сброшен</b>.
</td></tr>
</tbody>
<tbody id="tbody_trigger" [% " style='display: none'" IF !checker.triggers %]>
<tr>
<th>Добавить CC:</th>
<td><input type="text" id="triggers_add_cc" name="triggers_add_cc" value="[% checker.triggers.add_cc | html %]" /></td>
</tr>
</tbody>
<tbody>
<tr><td></td><td><input type="submit" value=" Сохранить " /></td></tr>
</tbody>
</table>
</form>
<div id="one_field_copy" style="display:none">
@ -149,6 +185,25 @@ function add_field(fld, val)
}
except_field_index++;
}
function switch_trigger()
{
var f = document.getElementById('is_trigger').checked;
document.getElementById('tbody_checker').style.display = f ? 'none' : '';
document.getElementById('tbody_trigger').style.display = f ? '' : 'none';
}
function check_trigger()
{
var f = document.getElementById('is_trigger').checked;
var cc = document.getElementById('triggers_add_cc');
if (!f)
cc.value = '';
else if (!cc.value)
{
alert('Задайте действие триггера!');
return false;
}
return true;
}
[%# Загружаем текущее состояние дел %]
showhide_allowdeny();
[% IF checker.except_fields %]