Allow keywords to depend on other fields (for example product)
parent
33b65171e8
commit
6425bfe311
|
@ -888,6 +888,12 @@ sub check_dependent_fields
|
|||
{
|
||||
my $vv = $self->get_ids($field_obj->value_field->name);
|
||||
my @bad = grep { !ref $_ || !$field_obj->is_value_enabled($_, $vv) } list($value_objs);
|
||||
if ($fn eq 'keywords')
|
||||
{
|
||||
# Keywords are silently enabled for any new visibility value.
|
||||
$field_obj->add_visibility_values($_->id, [ list $vv ]) for grep { ref $_ } @bad;
|
||||
@bad = grep { !ref $_ } @bad;
|
||||
}
|
||||
if (@bad)
|
||||
{
|
||||
my $n = $field_obj->value_field->name;
|
||||
|
|
|
@ -215,8 +215,8 @@ use constant CAN_TWEAK => {
|
|||
keywords bug_severity priority component assigned_to votes qa_contact dependson blocked target_milestone estimated_time remaining_time see_also) },
|
||||
default_value => { map { $_ => 1 } qw(bug_severity deadline keywords op_sys priority rep_platform short_desc status_whiteboard target_milestone version) },
|
||||
nullable => { map { $_ => 1 } qw(alias bug_severity deadline keywords op_sys priority rep_platform status_whiteboard target_milestone version) },
|
||||
visibility_field_id => { map { $_ => 1 } qw(bug_severity op_sys priority rep_platform status_whiteboard target_milestone version) },
|
||||
value_field_id => { map { $_ => 1 } qw(bug_severity op_sys priority rep_platform) },
|
||||
visibility_field_id => { map { $_ => 1 } qw(bug_severity op_sys priority rep_platform status_whiteboard target_milestone version keywords) },
|
||||
value_field_id => { map { $_ => 1 } qw(bug_severity op_sys priority rep_platform keywords) },
|
||||
default_field_id => { map { $_ => 1 } qw(bug_severity keywords op_sys priority component rep_platform status_whiteboard target_milestone version) },
|
||||
};
|
||||
|
||||
|
@ -1004,20 +1004,52 @@ sub update_visibility_values
|
|||
map { $_->id } @{ $type->new_from_list($visibility_value_ids) }
|
||||
];
|
||||
}
|
||||
Bugzilla->dbh->do(
|
||||
"DELETE FROM fieldvaluecontrol WHERE field_id=? AND value_id=?",
|
||||
undef, $self->id, $controlled_value_id);
|
||||
if (@$visibility_value_ids)
|
||||
my $h = Bugzilla->fieldvaluecontrol->{$vis_field->id};
|
||||
$h = $h->{values}->{$self->id}->{$controlled_value_id} if $controlled_value_id > 0;
|
||||
$h = $h->{fields}->{$self->id} if $controlled_value_id == FLAG_VISIBLE;
|
||||
$h = $h->{null}->{$self->id} if $controlled_value_id == FLAG_NULLABLE;
|
||||
$h = $h->{clone}->{$self->id} if $controlled_value_id == FLAG_CLONED;
|
||||
$h = { %$h };
|
||||
my $add = [];
|
||||
for (@$visibility_value_ids)
|
||||
{
|
||||
$h->{$_} ? delete $h->{$_} : push @$add, $_;
|
||||
}
|
||||
my $del = [ keys %$h ];
|
||||
return 0 if !@$add && !@$del;
|
||||
Bugzilla->dbh->do(
|
||||
"DELETE FROM fieldvaluecontrol WHERE field_id=? AND value_id=?".
|
||||
" AND visibility_value_id IN (".join(", ", @$del).")",
|
||||
undef, $self->id, $controlled_value_id
|
||||
);
|
||||
$self->add_visibility_values($controlled_value_id, $add);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub add_visibility_values
|
||||
{
|
||||
my $self = shift;
|
||||
my ($controlled_value_id, $visibility_value_ids) = @_;
|
||||
my $f = $self->id;
|
||||
for ($controlled_value_id)
|
||||
{
|
||||
$_ eq FLAG_VISIBLE or $_ = int($_) or return 0;
|
||||
}
|
||||
for (@$visibility_value_ids)
|
||||
{
|
||||
($_ = int($_)) > 0 or return 0;
|
||||
}
|
||||
# Ignore duplicate row errors
|
||||
eval
|
||||
{
|
||||
my $f = $self->id;
|
||||
Bugzilla->dbh->do(
|
||||
"INSERT INTO fieldvaluecontrol (field_id, value_id, visibility_value_id) VALUES ".
|
||||
join(",", map { "($f, $controlled_value_id, $_)" } @$visibility_value_ids)
|
||||
);
|
||||
}
|
||||
# Touch the field
|
||||
};
|
||||
my $ok = !$@;
|
||||
$self->touch;
|
||||
return 1;
|
||||
return $ok;
|
||||
}
|
||||
|
||||
sub update_control_lists
|
||||
|
|
|
@ -9,6 +9,7 @@ use strict;
|
|||
use base qw(Bugzilla::WebService);
|
||||
use Bugzilla::Field::Choice;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::WebService::Util qw(validate);
|
||||
use Bugzilla::Error;
|
||||
|
||||
|
@ -46,8 +47,13 @@ sub _get_value
|
|||
return $value;
|
||||
}
|
||||
|
||||
# Get all values for a field. Arguments:
|
||||
# field => <field_name>
|
||||
# Get all or some values for a field. Arguments:
|
||||
# field => <field name>
|
||||
# optional:
|
||||
# name => <name(s) to search for exact match>
|
||||
# match => <string(s) to search in the beginning of value name>
|
||||
# visibility_value_ids => <ID(s) of controlling value in which returned ones should be visible>
|
||||
# limit => maximum number of matches
|
||||
sub get_values
|
||||
{
|
||||
my ($self, $params) = @_;
|
||||
|
@ -56,18 +62,48 @@ sub get_values
|
|||
{
|
||||
return {status => 'field_not_select'};
|
||||
}
|
||||
my $values;
|
||||
my $join = '';
|
||||
my $where = [];
|
||||
my $bind = [];
|
||||
my $type = $field->value_type;
|
||||
my @vv = $field->value_field_id ? list $params->{visibility_value_ids} : ();
|
||||
my @match = list $params->{match};
|
||||
my @name = list $params->{name};
|
||||
if (@match || @name)
|
||||
{
|
||||
my @m;
|
||||
push @m, ('v.'.$type->NAME_FIELD.' LIKE ?') x @match;
|
||||
push @m, 'v.'.$type->NAME_FIELD.' IN ('.join(', ', ('?') x @name).')' if @name;
|
||||
push @$where, '('.join(' OR ', @m).')';
|
||||
push @$bind, (map { $_.'%' } @match), @name;
|
||||
}
|
||||
if (@vv)
|
||||
{
|
||||
$join = " INNER JOIN fieldvaluecontrol fc ON fc.field_id=?".
|
||||
" AND fc.value_id=v.id AND fc.visibility_value_id IN (".join(", ", ("?") x @vv).")";
|
||||
unshift @$bind, $field->id, @vv;
|
||||
}
|
||||
$where = @$where ? join(' AND ', @$where) : '1=1';
|
||||
my $order = $type->LIST_ORDER;
|
||||
$order =~ s/(^|,)\s*(\S)/$1v.$2/gso;
|
||||
trick_taint($_) for @$bind;
|
||||
my $values = Bugzilla->dbh->selectall_arrayref(
|
||||
"SELECT v.* FROM ".$type->DB_TABLE." v $join WHERE $where GROUP BY v.id".
|
||||
" ORDER BY $order ".($params->{limit} ? Bugzilla->dbh->sql_limit(int($params->{limit})) : ''),
|
||||
{Slice=>{}}, @$bind
|
||||
);
|
||||
bless $_, $type for @$values;
|
||||
if ($field->value_field_id)
|
||||
{
|
||||
$values = [ map { {
|
||||
id => $_->id,
|
||||
name => $_->name,
|
||||
visibility_value_ids => [ map { $_->id } @{$_->visibility_values} ],
|
||||
} } @{$field->legal_values} ];
|
||||
} } @$values ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$values = [ map { { id => $_->id, name => $_->name } } @{$field->legal_values} ];
|
||||
$values = [ map { { id => $_->id, name => $_->name } } @$values ];
|
||||
}
|
||||
return {
|
||||
status => 'ok',
|
||||
|
|
|
@ -159,6 +159,10 @@ function handleControllerField(e, controller)
|
|||
// It is more correct to match selected values on name, because a
|
||||
// target_milestone/version/component with the same name may exist for a different product
|
||||
controlled = document.getElementById(controlled_id);
|
||||
if (controlled.nodeName != 'SELECT')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
copt = getSelectedNames(controlled);
|
||||
bz_clearOptions(controlled);
|
||||
if (field_metadata[controlled.id].nullable && !controlled.multiple)
|
||||
|
|
92
js/field.js
92
js/field.js
|
@ -277,12 +277,77 @@ function _value_id(field_name, id)
|
|||
return 'v' + id + '_' + field_name;
|
||||
}
|
||||
|
||||
// Data loader for keyword autocomplete
|
||||
function keywordAutocomplete(hint, emptyOptions)
|
||||
{
|
||||
if (!hint.input.value)
|
||||
{
|
||||
hint.emptyText = 'Type at least 3 letters';
|
||||
if (emptyOptions)
|
||||
hint.replaceItems(convertSimpleList(emptyOptions));
|
||||
else if (field_metadata.keywords.value_field)
|
||||
{
|
||||
var vv = getSelectedIds(document.getElementById(field_metadata.keywords.value_field));
|
||||
var h = field_metadata[field_metadata.keywords.value_field].values.keywords;
|
||||
var o = [];
|
||||
for (var i in field_metadata.keywords.legal)
|
||||
{
|
||||
var controlled_value = field_metadata.keywords.legal[i];
|
||||
if (checkValueVisibility(vv, h[controlled_value[0]]))
|
||||
{
|
||||
o.push([ '<span class="hintRealname">' + htmlspecialchars(controlled_value[1]) + '</span>', controlled_value[1] ]);
|
||||
}
|
||||
}
|
||||
hint.replaceItems(o);
|
||||
}
|
||||
else
|
||||
hint.replaceItems(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var u = window.location.href.replace(/[^\/]+$/, '');
|
||||
u += 'xml.cgi?output=json&method=Field.get_values&field=keywords&limit=20';
|
||||
if (field_metadata.keywords.value_field)
|
||||
{
|
||||
var vv = getSelectedIds(document.getElementById(field_metadata.keywords.value_field));
|
||||
for (var v in vv)
|
||||
{
|
||||
u += '&visibility_value_ids='+v;
|
||||
}
|
||||
}
|
||||
var l = hint.input.value.split(/[\s,]*,[\s,]*/);
|
||||
for (var i = 0; i < l.length; i++)
|
||||
{
|
||||
u += '&match='+encodeURI(l[i]);
|
||||
}
|
||||
|
||||
AjaxLoader(u, function(x)
|
||||
{
|
||||
var r = {};
|
||||
try { eval('r = '+x.responseText+';'); } catch (e) { return; }
|
||||
if (r.status == 'ok')
|
||||
{
|
||||
var data = convertSimpleList(r.values);
|
||||
// FIXME "3" constant, messages: remove hardcode, also in Bugzilla::User::match()
|
||||
if (data.length == 0 && hint.input.value.length < 3)
|
||||
hint.emptyText = 'Type at least 3 letters';
|
||||
else
|
||||
hint.emptyText = 'No keywords found';
|
||||
hint.replaceItems(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addKeywordsAutocomplete()
|
||||
{
|
||||
var emptyKeywordsOptions = [];
|
||||
for (var i = 0; i < field_metadata.keywords.legal.length; i++)
|
||||
var emptyKeywordsOptions = null;
|
||||
if (!field_metadata.keywords.value_field)
|
||||
{
|
||||
emptyKeywordsOptions.push({ name: field_metadata.keywords.legal[i][1] });
|
||||
emptyKeywordsOptions = [];
|
||||
for (var i = 0; i < field_metadata.keywords.legal.length; i++)
|
||||
{
|
||||
emptyKeywordsOptions.push({ name: field_metadata.keywords.legal[i][1] });
|
||||
}
|
||||
}
|
||||
new SimpleAutocomplete("keywords",
|
||||
function(h) { keywordAutocomplete(h, emptyKeywordsOptions); },
|
||||
|
@ -290,27 +355,22 @@ function addKeywordsAutocomplete()
|
|||
);
|
||||
}
|
||||
|
||||
// CustIS bug 66910 - check new keywords and requery description for its
|
||||
// CustIS bug 66910 - check new keywords and requery description for it
|
||||
function check_new_keywords(form)
|
||||
{
|
||||
var non_exist_keywords = [];
|
||||
var cnt_exist_keywords = 0;
|
||||
var input_keywords = form.keywords.value.split(",");
|
||||
var exist_keywords = [];
|
||||
for(var i = 0; i < emptyKeywordsOptions.length; i++)
|
||||
var input_kw = form.keywords.value.trim().split(/[,\s]*,[,\s]*/);
|
||||
var kw_hash = {};
|
||||
for (var i = 0; i < field_metadata.keywords.legal.length; i++)
|
||||
{
|
||||
exist_keywords[i] = emptyKeywordsOptions[i].name.trim();
|
||||
kw_hash[field_metadata.keywords.legal[i][1].toLowerCase()] = true;
|
||||
}
|
||||
|
||||
for(var i = 0; i < input_keywords.length; i++)
|
||||
for (var i = 0; i < input_kw.length; i++)
|
||||
{
|
||||
if (input_keywords[i].trim() != "" && exist_keywords.indexOf(input_keywords[i].trim()) == -1)
|
||||
if (!kw_hash[input_kw[i].toLowerCase()])
|
||||
{
|
||||
non_exist_keywords[cnt_exist_keywords] = input_keywords[i].trim();
|
||||
cnt_exist_keywords++;
|
||||
non_exist_keywords.push(input_kw[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (non_exist_keywords.length > 0)
|
||||
{
|
||||
var keywords_submit = true;
|
||||
|
|
40
js/global.js
40
js/global.js
|
@ -174,51 +174,15 @@ function userAutocomplete(hint, emptyOptions, loadAllOnEmpty)
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
// Convert keyword list from API format for SimpleAutocomplete
|
||||
function convertSimpleList(k)
|
||||
{
|
||||
var data = [];
|
||||
for (var i = 0; i < k.length; i++)
|
||||
data.push([ '<span class="hintRealname">' + k[i].name + '</span>', k[i].name ]);
|
||||
data.push([ '<span class="hintRealname">' + htmlspecialchars(k[i].name) + '</span>', k[i].name ]);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Data loader for keyword autocomplete
|
||||
function keywordAutocomplete(hint, emptyOptions)
|
||||
{
|
||||
if (!hint.input.value)
|
||||
{
|
||||
hint.emptyText = 'Type at least 3 letters';
|
||||
if (emptyOptions)
|
||||
hint.replaceItems(convertSimpleList(emptyOptions));
|
||||
else
|
||||
hint.replaceItems(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var u = window.location.href.replace(/[^\/]+$/, '');
|
||||
u += 'xml.cgi?method=Keyword.get&output=json&maxkeywordmatches=20';
|
||||
var l = hint.input.value.split(/[\s,]*,[\s,]*/);
|
||||
for (var i = 0; i < l.length; i++)
|
||||
u += '&match='+encodeURI(l[i]);
|
||||
|
||||
AjaxLoader(u, function(x) {
|
||||
var r = {};
|
||||
try { eval('r = '+x.responseText+';'); } catch (e) { return; }
|
||||
if (r.status == 'ok')
|
||||
{
|
||||
var data = convertSimpleList(r.keywords);
|
||||
// FIXME "3" constant, messages: remove hardcode, also in Bugzilla::User::match()
|
||||
if (data.length == 0 && hint.input.value.length < 3)
|
||||
hint.emptyText = 'Type at least 3 letters';
|
||||
else
|
||||
hint.emptyText = 'No keywords found';
|
||||
hint.replaceItems(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Data loader for field in buglist autocomplete
|
||||
function fieldBuglistAutocomplete(hint, field, emptyOptions)
|
||||
{
|
||||
|
@ -233,7 +197,7 @@ function fieldBuglistAutocomplete(hint, field, emptyOptions)
|
|||
hint.replaceItems(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showFullComment(oper_id)
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Authors: Vitaliy Filippov <vitalif@mail.ru>, Vladimir Koptev <vladimir.koptev@gmail.com>
|
||||
#%]
|
||||
|
||||
[% SET title = "Select Active " _ field.description _ "s For " _ field.value_field.description _ ' ' _ visibility_value.name | html %]
|
||||
[% SET title = "Select Active " _ field.description _ " Objects For " _ field.value_field.description _ ' ' _ visibility_value.name | html %]
|
||||
|
||||
[% PROCESS global/header.html.tmpl %]
|
||||
|
||||
|
@ -37,7 +37,7 @@
|
|||
</form>
|
||||
|
||||
<p>
|
||||
<a href="editvalues.cgi?field=[% field.name %]">Edit or add [% field.description | html %]s</a> |
|
||||
<a href="editvalues.cgi?field=[% field.name %]">Edit or add [% field.description | html %] objects</a> |
|
||||
[% IF field.value_field.name == 'product' %]
|
||||
<a href="editproducts.cgi?action=edit&product=[% visibility_value.name | uri %]">Edit product [% visibility_value.name | html %]</a>
|
||||
[% ELSIF field.value_field.name == 'classification' %]
|
||||
|
|
|
@ -71,29 +71,7 @@
|
|||
[%+ 'checked="checked"' IF value.isactive || !value.id %] />
|
||||
(this value is selected as default in the parameters for this field)
|
||||
</td>
|
||||
[% IF field.value_field %]
|
||||
<tr>
|
||||
<th colspan="2" align="left">
|
||||
<label for="visibility_value_id">Only appears when [%+ field.value_field.description | html %] is set to:</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<select name="visibility_value_id[]" id="visibility_value_id" multiple="multiple" size="15" style="width: 400px">
|
||||
[% IF field.value_field.nullable %]
|
||||
<option value="0"[% IF field.is_value_enabled(value.id, 0) %] selected[% END %]>---</option>
|
||||
[% END %]
|
||||
[% FOREACH field_value = field.value_field.legal_values %]
|
||||
[% IF field.visibility_field_id != field.value_field_id || field.has_visibility_value(field_value.id) %]
|
||||
<option value="[% field_value.id | none %]" [% ' selected="selected"' IF field.is_value_enabled(value.id, field_value.id) %]>
|
||||
[%- field_value.name | html -%]
|
||||
</option>
|
||||
[% END %]
|
||||
[% END %]
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
</tr>
|
||||
[% IF field.name == "keywords" %]
|
||||
<tr>
|
||||
<th align="left">Description:</th>
|
||||
|
@ -121,8 +99,30 @@
|
|||
</tr>
|
||||
[% END %]
|
||||
[% END %]
|
||||
[% IF field.value_field %]
|
||||
<tr>
|
||||
<th colspan="2" align="left">
|
||||
<label for="visibility_value_id">Only appears when [%+ field.value_field.description | html %] is set to:</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<select name="visibility_value_id[]" id="visibility_value_id" multiple="multiple" size="15" style="width: 400px">
|
||||
[% IF field.value_field.nullable %]
|
||||
<option value="0"[% IF field.is_value_enabled(value.id, 0) %] selected[% END %]>---</option>
|
||||
[% END %]
|
||||
[% FOREACH field_value = field.value_field.legal_values %]
|
||||
[% IF field.visibility_field_id != field.value_field_id || field.has_visibility_value(field_value.id) %]
|
||||
<option value="[% field_value.id | none %]" [% ' selected="selected"' IF field.is_value_enabled(value.id, field_value.id) %]>
|
||||
[%- field_value.name | html -%]
|
||||
</option>
|
||||
[% END %]
|
||||
[% END %]
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
[% INCLUDE "admin/fieldvalues/control-list-common.html.tmpl" this_field=field this_value=value %]
|
||||
</tr>
|
||||
[% Hook.process('fields') %]
|
||||
</table>
|
||||
|
||||
|
|
|
@ -212,13 +212,7 @@ document.write(' <input type="button" name="check_all" value="Check All" onclick
|
|||
<tr>
|
||||
<th>
|
||||
<script type="text/javascript">
|
||||
var emptyKeywordsOptions = "null";
|
||||
addListener(window, 'load', function() {
|
||||
new SimpleAutocomplete("keywords",
|
||||
function(h) { keywordAutocomplete(h, emptyKeywordsOptions); },
|
||||
{ emptyText: 'No keywords found', multipleDelimiter: "," }
|
||||
);
|
||||
});
|
||||
addListener(window, 'load', addKeywordsAutocomplete);
|
||||
</script>
|
||||
<label for="keywords">
|
||||
<a href="describekeywords.cgi">Keywords</a>:
|
||||
|
@ -232,7 +226,6 @@ document.write(' <input type="button" name="check_all" value="Check All" onclick
|
|||
<option value="makeexact">Make the keywords be exactly this list</option>
|
||||
</select>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
[% END %]
|
||||
|
||||
|
|
Loading…
Reference in New Issue