[[PageOutline(2-5,Contents,pullout)]] = Dynamically show and hide fields and options, and enforce user-defined rules on commits == Description This plugin helps manage configurations that contain lots of fields or need to enforce rules for what is and isn't permitted. It can dynamically control what fields appear in the user interface depending on easily-configurable conditions. It can also block commits if specific conditions haven't been met. There are two main components, named 'assistant' and 'warden'. The Assistant manages the user-interface side, hiding and showing fields and options, and filling in fields for the user. The Warden enforces the rules that prevent commits. === Hiding a field The Assistant uses expressions to define what fields are presented to the user in different circumstances. Rule names start with the name of the field, followed by a dot, followed by the attribute "visible". If the rule evaluates to false, the field is hidden. The rule is re-evaluated whenever one of the fields affecting the result changes. For example, let's assume that the tickets contain a field named 'approval' that is to be hidden if the ticket is being created or if it is in the 'closed' state. Write a `kis_assistant` rule for `approval.visible`: {{{#!ini [kis_assistant] approval.visible = !(status == '' || status == 'closed') }}} The expression syntax is similar to that of C or Javascript. An acceptable alternative would be: {{{#!ini [kis_assistant] approval.visible = status != '' && status != 'closed' }}} Because lists of states are common, the Assistant also provides an `in` operator that tests membership of a list, with very high precedence. So another acceptable alternative would be: {{{#!ini [kis_assistant] approval.visible = !status in '', 'closed' }}} By default, fields are hidden only in the "Change Properties" box: the area where the user modifies the fields. To hide them in the main ticket description at the top of the page as well, add the suffix `.all` to the rule name. To hide the fields in the main ticket description //only//, add the suffix `.ticket` to the rule name. To use different rules for hiding the field in the main ticket description and in the "Change Properties" box, give the main ticket description rule the suffix `.ticket` and the "Change Properties" rule the suffix `.property`. === Hiding options Options for a field are grouped into named sets, by adding the attribute ".options" to the field name, followed by a dot, followed by the name of the set. There is then a matching rule that has the attribute ".available" followed by a dot, followed by the name of the set. If the "available" rule evaluates true, the options in the set become available for selection in the interface. For example, let's assume that we have a set of people on our project with the role of 'approver' (i.e. they are members of the Trac permissions group 'approver'). Let's also assume that the 'approval' field mentioned above is a Select or Radio field that has options 'Not assessed', 'Denied' and 'Approved'. The basic set of options 'Not assessed' or 'Denied' are available to all, but the full set of options including 'Approved' is only available if the user is a member of the 'approver' group or if the field already had the value 'Approved' when the page was loaded. {{{#!ini [kis_assistant] approval.options.basic_set = 'Not assessed', 'Denied' approval.available.basic_set = true approval.options.full_set = 'Approved' approval.available.full_set = has_role('approver') || _approval == 'Approved' }}} The condition for the basic set of options being available is therefore just 'true'. The full set of options is available if the function 'has_role' returned 'true' when called with the parameter 'approver', or if the value of 'approval' when the page was loaded was already set to 'Approved'. (The underscore in front of 'approval' means "use the value of this field at the time the page was loaded".) Note that the options are hidden, not removed. The user will still be able to select the option in most browsers by using keyboard shortcuts. Use a Warden rule to restrict the values accepted when a ticket is submitted, if that is what is needed. === Updating the contents of fields automatically The 'update' attribute of a field defines a rule for automatically updating the field's content. Normally, it is re-evaluated whenever one of the fields used to determine the outcome of the rule is changed. For example, take the rule: {{{#!ini [kis_assistant] priority.update = (effort > 5) ? 'high' : 'low' }}} This assumes that a custom field named 'effort' is defined. If the 'effort' field is changed to a value greater than 5, then the priority field is set to 'high'. Otherwise it is set to low. Sometimes it's necessary to update a field only under certain conditions. In that case, the optional 'update.when' attribute can be used to define those conditions. For example: {{{#!ini [kis_assistant] priority.update.when = milestone == 'Build 42' }}} Now the rule stated previously will be applied when the milestone is changed to 'Build 42', not when the 'effort' field is changed. The 'update.when' rule is re-evaluated whenever one of the fields used to determine the outcome of the rule is changed. === Using templates The 'template' attribute on a field assigns a name to a block of template text that could be used to pre-populate the field. The 'available' attribute for that name then defines the condition under which the field will be populated with that text. For example: {{{#!ini [kis_assistant] evaluation.template.change = '=== Description ===\\nDescribe the change fully...' evaluation.available.change = evaluation_template == 'Change' evaluation.template.fault = '=== Description ===\\nDescribe the fault fully...' evaluation.available.fault = evaluation_template == 'Fault' evaluation.template.none = '' evaluation.available.none = evaluation_template == 'None' }}} This assumes that a custom field named 'evaluation_template' is defined (either a Select or a Radio field) with options 'None', 'Change' and 'Fault'. 'evaluation' is a Textarea field. When 'evaluation_template' is set to 'Change', the 'evaluation' field will be initialised with the value of the 'evaluation.template.change' option (shown here in a cut-down form; it would normally contain template entries for all the items of information that might be wanted in a Change evaluation). Similarly for 'evaluation_template' values of 'Fault' or 'None'. A field is only initialised from a template if it is currently either empty or unchanged from one of the alternative template values. Template fields can be preferred over the use of automatically-updated fields because of this behaviour. === Blocking commits The Warden prevents commits from being made if certain conditions aren't met. Warden rules use the same expressions as the Assistant, but they are only evaluated when the ticket is submitted (or previewed). Rule names are arbitrary, but should be descriptive as they are reported to the user if the rule causes a ticket submission to be blocked. For example, take the rules: {{{#!ini [kis_warden] approval required to close = status == 'closed' && approval != 'Approved' only designated approver can approve = !has_role('approver') && approval != _approval && approval == 'Approved' }}} The commit is blocked if any rule evaluates true. Therefore, the first rule means that the ticket cannot be closed if the 'approval' field has not been set to the value 'Approved'. The second rule means that only a user who is a member of the permissions group 'approver' can change the 'approval' field to that value. == Functions The same functions are available in both the Assistant and the Warden. Functions in the Assistant are evaluated once for a given set of parameters, and the result of the function call is cached. Unless the parameters change, the function won't be re-evaluated. === Built-in functions There are four built-in functions: * child_open() Returns true if any other open ticket has a field named 'parent' which contains a Trac link that points at the current ticket, otherwise it returns false. This function is useful in conjunction with the ChildTicketsPlugin. * has_role( [, ]) is a string parameter naming a Trac permissions group. If the parameter is omitted, it defaults to the current user. The function returns true if the user is a member of the named group, otherwise it returns false. * is_parent([ ]) If the parameter is omitted, it defaults to the current ticket. The function returns true if some other ticket has a field named 'parent' which contains a Trac link that points at the , otherwise it returns false. This function is useful in conjunction with the ChildTicketsPlugin. * status_of() contains the number of another ticket, optionally prefixed with '#'. The function returns the current status of the other ticket, or the null value if the other ticket cannot be found. === User-defined functions User functions can be defined by adding a Python file to the Trac plugins folder that implements the `IConfigFunction` interface. For example: {{{#!python from trac.core import * from kis2 import IConfigFunction class MyConfigFunctions(Component): ''' Local functions for use by 'kisplugin' configuration files. ''' implements(IConfigFunction) # Example: implement named string constants def safety(self, req, safety_enum): if safety_enum == 'YES': return 'Safety related' if safety_enum == 'OK': return 'Safety related - OK to close' if safety_enum == 'NO': return 'Not safety related' }}} This example would define a function 'safety()', implementing named constants. 'safety('OK')', for example, returns the string 'Safety related - OK to close'. The 'req' parameter is the HTTP request object; the remaining parameters are the parameters of the function call passed in from the configuration file. == Expression syntax In expressions, field names evaluate to the current value of the corresponding field, except for the special names `status`, which evaluates to the ticket status (or the empty string if the ticket has not yet been created), `authname`, which evaluates to the current username, `true` which evaluates true and `false`, which evaluates false. If the field name is prefixed with an underscore, it evaluates to the value of the field at the time the page was loaded. Text-type fields evaluate to their contents, checkboxes evaluate to true if checked or false if not, and Select or Radio fields evaluate to the selected item if an item is selected or undefined if no item is selected. The full grammar of the expressions is: {{{ expression ::= or_expression | or_expression "?" expression ":" expression or_expression ::= and_expression | and_expression "||" or_expression and_expression ::= equality | equality "&&" and_expression equality ::= comparison | comparison "==" | "!=" | "~=" equality comparison ::= sum | sum "<" | ">" | "<=" | ">=" comparison sum ::= product | product "+" | "-" sum product ::= negation | negation "*" | "/" product negation ::= membership | "-" | "!" negation membership ::= term | term "in" cmp_list cmp_list ::= "(" cmp_list ")" | expression | expression "," cmp_list param_list ::= *empty* | expression | expression "," param_list term ::= "(" expression ")" | | | "(" param_list ")" | "'" "'" }}} `~=` is a pattern-matching operator that returns True only if the value on the left is matched by the regular expression on the right. `in` is an operator that returns true only if the value on the left appears in the list on the right. The operators `!`, `==`, `!=`, `||` and `&&` are negation, equality, inequality, OR and AND respectively. Note that the `&&` and `||` operators evaluate in the same way as the Javascript operators (or the Python `and` and `or` operators). So 'x && y' evaluates to 'x' if 'x' is false; 'y' if 'x' is true. [[span('x || y')]] evaluates to 'x' if 'x' is true; 'y' if 'x' is false. == What's new in version 2 * Function calls and user-defined functions. * Automatic updating of fields. * The operator 'has_role' has been retired - the 'has_role()' function can now be used instead. * Labels and templates now have to be string expressions (in other words, they now need to be surrounded by single-quotes). * New operators: arithmetic and comparison operators, and the ternary operator '?' ':' * Changes to operator precedence: now very similar to that of Javascript. * From version 2.1: fields can be hidden in the main ticket description as well as the Change Properties area. * From version 2.5: the 'status' of a new ticket before it is created evaluates to the empty string rather than 'new'. === Utility for importing TracTicketChainedFieldsPlugin configurations As this plugin can duplicate most of the function of the TracTicketChainedFieldsPlugin, there is a script in the 'utilities' folder that can translate the JSON configuration files used by that plugin into a set of equivalent Assistant rules. == Comparison to DynamicFieldsPlugin The scope of this plugin is similar to that of the DynamicFieldsPlugin plugin. The major differences are: - rules use arbitrary expressions for increased flexibility; - it's possible to manage the options visible in drop-down lists; - it's possible to use the `status` of the ticket in rules (though not particularly consistently, especially during previews); and - users can't set their own preferences or default values. == !Bugs/Feature Requests Existing bugs and feature requests for KeepInterfaceSimplePlugin are [report:9?COMPONENT=KeepInterfaceSimplePlugin here]. If you have any issues, create a [/newticket?component=KeepInterfaceSimplePlugin new ticket]. [[TicketQuery(component=KeepInterfaceSimplePlugin&group=type,format=progress)]] == Download Download the zipped source from [export:keepinterfacesimpleplugin here]. This plugin is also available on [[pypi:KeepInterfaceSimple2Plugin|PyPI]]. {{{ $ pip install KeepInterfaceSimple2Plugin }}} == Source You can check out KeepInterfaceSimplePlugin from [/svn/keepinterfacesimpleplugin here] using Subversion, or [source:keepinterfacesimpleplugin browse the source] with Trac. == Installation General instructions on installing Trac plugins can be found on the [TracPlugins#InstallingaTracplugin TracPlugins] page. == Recent Changes [[ChangeLog(keepinterfacesimpleplugin, 3)]] == !Author/Contributors **Author:** [wiki:ash] [[BR]] **Maintainer:** [[Maintainer]] [[BR]] **Contributors:**