Configurable Dropdown

1. GENERAL DESCRIPTION

The Administrator can create UI elements of the “Dropdown” type with custom behavior, and add them to views. The customization consists in that the Administrator can define, in the form of an API v2 request, the rule by which the Dropdown generates the list of values that it shows when the User clicks on it.

The typical use case of the Configurable Dropdown is having a “dependent selector”. This means having a Dropdown which shows a _limited _list of the respective field’s values, depending on values set for other fields of the same entity. This prevents the User from having to scroll through a long list of all possible values.

Example 1: When the User is linking a Feature to a Program Increment (PI), they want the “Program Increment” Dropdown to list only the PIs of the particular Agile Release Train (ART) assigned to this Feature. The User doesn’t want to scroll through, and guess among, all the ARTs’ PIs.

Example 2: When the User is linking a work item to a Key Result, they want the “Key Result” Dropdown to list only the Key Results of the Objective this work item is linked to. The User doesn’t want to scroll through all the Key Results in the System.

2. HOW TO CONFIGURE AND ADD DROPDOWNS

2.1. What Is a Configuration?

A single particular case of customization of the Dropdown’s behavior is called a “Configuration” of the Dropdown. The Configurable Dropdown currently supports, and its any Configuration can be used in:

  1. Detailed views, as a Dropdown control element,
  2. List views and Inner Lists, as a unit.

Every Configuration can be associated with only one field of an Entity type (types). This field can be a link to a default (native domain) Entity, a link to an Extendable Domain Entity, or a Custom Field of "Targetprocess Entity" type.

A Configuration is in fact a piece of code, a request to the Configurable Controls API which contains a set of attributes. The attributes are explained in the Configuration Attributes section.

2.2. Creating a Dropdown Configuration

A Configuration can be created with a mashup.
Let us create a sample Configuration to solve the problem from Example 1, above.

With the SAFe data model, our Account has, among other things, the following entity types:

  1. Agile Release Train, or ART. This is basically a “team of teams”. It is implemented as an Extendable Domain entity type and is parent to the Team entity type.
  2. Program Increment, or PI. This is basically an iteration of an ART’s work, i.e. a time period linked to an ART. It is a renamed Release, which is a default entity type.

By default, the “Program Increment” Dropdown shows a list of PIs of all the ARTs present in the Account, even if a particular ART has been assigned to the entity in question. It can be difficult for the User to scan through all the PIs most of which are irrelevant. This screenshot illustrates the problem:

602

PIs of all ARTs are available in the Program Increment Dropdown (Detailed view).

981

PIs of all ARTs are available in the Program Increment Dropdown (List view).

Now let’s add a mashup with a Configuration which alleviates the problem – see the code below. This Configuration makes the Dropdown of the “Program Increment” field show only the Program Increments of the ART which is assigned to this particular entity (e.g. Feature). In other words, it prevents the User from having to select from the PIs of all the ARTs in the Account.

This Configuration also sorts the list of PIs in the Dropdown by name in descending order. Given the naming conventions of this particular demo Account, this means that the newest PIs will appear at the top of the Dropdown’s list.

Sample code:

tau.mashups
    .addDependency('tau/api/configurable-controls/controls/v1')
    .addMashup(( controlsApiV1 ) => {
        controlsApiV1.addConfiguration('dropdown', {
          id: 'dd_conf_01',
		  name: 'Program Increments of the assigned ART',
          label: 'Program Increment',
          supportedEntityTypes: ['feature'],
          requiredEntityFields: ['id', 'name', 'release'],
          sampleData: {
            release: { name: 'Program Increment' }
          },
          entityClickBehavior: 'openEntity',
          field: 'release',
          dropdownItemsSource: {
            type: 'interpolatedQuery',
            query: "release?where=(agileReleaseTrain.id==${{agileReleaseTrain.id}}$)&orderBy=name desc"
          }
        })
    })

When setting up the mashup, in the “Placeholder(s)” field you can use any global placeholder, e.g. footerPlaceholder.

After the mashup has been added, you can use this Configuration of the Dropdown in views. Note that you can add any number of Configurations in a single mashup.

(To learn more about how this mashup works, refer to the Configuration Attributes section.)

2.3. Adding a Dropdown Configuration to Detailed Views

On the Detailed view layout editor page, you can find a snippet of the necessary Configuration for use in your layout:

1406

The list of Dropdown Configurations can be accessed in the layout editor.

The snippet should be added as a new property component, or placed instead of an existing property component, in the target entity type’s Detailed view layout:

2068

Substituting a default property component with a custom Dropdown Configuration in Detailed views.

Now in Detailed views, our example Program Increment Dropdown only shows the PIs of the ART which is assigned to the entity:

575

Only the PIs of the selected ART are available in the Dropdown (Detailed view).

2.4. Adding a Dropdown Configuration to a List View

In List views, the Configuration of the Dropdown is reflected in a custom Unit for the target field. Add this custom Unit to the view setup at the “Customize Cards” tab (you’ll probably want to substitute the default unit):

1576

Changing the default “Program Increment” unit to a configured one in a List view.

Now in this List view, the example Program Increment Dropdown only shows the PIs of the ART which is assigned to this particular entity:

975

Only the PIs of the selected ART are available in the Dropdown (List view).

2.5. Adding a Dropdown Configuration to Quick Add

To add configurable Dropdown fields in Quick Add, the first step is to create a configuration. Make sure that "allowedLocations" contains "quickAdd" (see configuration attributes below) or is not set at all.

tau.mashups
    .addDependency('tau/api/configurable-controls/controls/v1')
    .addMashup(( controlsApiV1 ) => {
        controlsApiV1.addConfiguration('dropdown', {
          id: 'dd_conf_01',
          name: 'Program Increments of the assigned ART',
          label: 'Program Increment',
          supportedEntityTypes: ['feature'],
          requiredEntityFields: ['id', 'name', 'release'],
          allowReset:false,
          sampleData: {
            release: { name: 'Program Increment' }
          },
          entityClickBehavior: 'openEntity',
          field: 'release',
          dropdownItemsSource: {
            type: 'custom',
            getItems: async ({entity}) => {
                const artId = entity.agilereleasetrain || (entity.agileReleaseTrain && entity.agileReleaseTrain.id) || null
                const response = await fetch(`/api/v2/release?where=(agileReleaseTrain.id==${artId})&take=1000&orderBy=name desc`);
                const {items} = await response.json();
                return items;
              }
            //type: 'interpolatedQuery',
            //query: "release?where=(agileReleaseTrain.id==${{agileReleaseTrain.id}}$)&orderBy=name desc"
            }
        })
    })

Now go to "Settings", choose "Quick Add" on the left hand side and choose your desired entity.

1500

Configurable Dropdown fields will only be visible in entities specified in supportedEntityTypes field in your configuration.

Choose "Add Field" and select previously configured field. All configurable dropdown fields will be visible at the bottom.

1332

Configuration name is displayed on the right from field name.

After selecting a field, it will be added to the list of configured fields. "Required" flag is controlled by "allowReset" flag in the configuration.

1152

If "allowReset" is either not set or "true", field will not be required.

Now open Quick Add popup by clicking on the button in the top-right corner and choose your configured entity. You should see a configurable dropdown field in the list of entity fields.

800

Example of Program Increment field with allowReset = false.

3. CONFIGURATION ATTRIBUTES

All the possible attributes of a Configuration of a Dropdown are listed and explained below.

3.1. id

Required? Yes.
Text ID specified by the mashup creator. Must be unique among controls of the same type (i.e. “dropdown”).

3.2. name

Required? No.
Friendly text name. Use this to help people understand what this Configuration does.

3.3. label

Required? Yes.
Text rendered as the Dropdown’s label in Detailed views, and as a column header in Lists.

3.4. supportedEntityTypes

Required? No.
Targetprocess entity type names (in lowercase) that support rendering this particular control. If none are provided, the control is considered to be universal.
Example snippet:
supportedEntityTypes: ['userstory', 'bug']

3.5. allowedLocations

Required? No.
Allows to use this Dropdown Configuration in specific view types only, e.g. only Detailed views but not Lists.
Example snippet:

allowedLocations: {
  listUnit: false,
  detailedView: true,
  quickAdd: true,
}

By default a Configuration can be used in all view types currently supported by the Configurable Dropdown.

3.6. requiredEntityFields

Required? No (Yes - for Entity custom field).
A list of fields that should be available in “ConfigurableControlInfo” (see below) and must be requested for this control to render. The fields can belong to the very entity this Dropdown Configuration will be used with (the “target” entity), as well as to entities linked to it.

The fields are aggregated into a single API v2 selector, so it's possible to use field names (i.e. "effort") or aliased API v2 selectors (e.g. "userStoryId:userStory.id" – an ID of a User Story linked to the target entity).

Field names should be written in camel case. E.g. userStory.

Example snippet:

requiredEntityFields: [
    'id', 
    'name', 
    'feature:{id:feature.id,name:feature.name,projectId:feature.project.id}', 
    'tasks:tasks.select({id,name})',
    'customFieldName: {id:CustomValues.Entity("Custom Field Name").id, name:CustomValues.Entity("Custom Field Name").name, resourceType:"entityTypeName"}'
]

3.7. sampleData

Required? No.
A list of dummy values for all fields that are required by the "Required Entity Fields" attribute. These dummy values will be used to render the control in all editors (like Customize Cards) where entity data isn't available, to show a meaningful preview.
Example snippet:

sampleData: {
  id: 5,
  name: 'Some name',
  feature: {id: 10, name: 'Some feature name', projectId: 50},
  tasks: [
    {id: 18, name: 'Task 1'},
    {id: 19, name: 'Task 2'}
  ]
}

3.8. isEnabled

Required? No.
A callback that consists in a particular check to determine if the control must be enabled or disabled (i.e. read only).
Template:

isEnabled?: {
    callback: (info: ConfigurableControlInfo) => boolean | PromiseLike<boolean>;
    rerunOnRequiredFieldsChanged: string[];
  }

Example snippet 1 – the control is only enabled if the target entity’s name starts with an 'S' or 's':

isEnabled: {
    callback: info => info.entity.name.toLowerCase().startsWith('s'),
    rerunOnRequiredFieldsChanged: ['name']
}

Example snippet 2 – the control is always disabled (read only):

isEnabled: { 
    callback: () => false, 
    rerunOnRequiredFieldsChanged: [] 
}

The callback is executed a) prior to the control’s initialization, and b) when one of the entity’s fields listed in "rerunOnRequiredFieldsChanged" is updated.

On initial render, if the callback returns a promise-like, the control will be disabled and will remain disabled until the promise-like resolves with a truthy value; if it rejects or resolves with a falsy value, the control will remain disabled.

On a re-render of the Dropdown, the "rerunOnRequiredFieldsChanged" collection will be checked against the fields that are present in requiredEntityFields and have been updated. If any of them has been modified, the callback is executed.
If the callback returns a promise-like, for UX reasons, the control retains its previous "disabledness" visually (but not functionally) until the promise-like resolves, to prevent flashing on every modification.

NOTE that when registering the control as a unit (i.e. in Lists), this callback will be executed for every visible card. So if the callback performs some heavy and/or asynchronous work to verify visibility (like a network request), this may negatively affect performance when used as a unit.

If "rerunOnRequiredFieldsChanged" is not provided or empty, the callback will only be executed for the initial render.

If isEnabled is not provided, the control is always enabled.

3.9. entityClickBehavior

Required? Yes.
Determines what happens when the Dropdown is not open and the user clicks on the name of the entity selected for this field. "openDropdown" ignores the entity link and opens the Dropdown; "openEntity" opens the entity’s Detailed view.
Example snippet:
entityClickBehavior: 'openEntity'

3.10. field

Required? Yes.
The name of the entity’s field for which the Dropdown is to be used. For Custom Fields, Custom Field name is specified. For non-custom fields, one of the fields present in requiredEntityFields is specified. Field behavior must satisfy the following conditions:

  1. It is null when no value is set.
  2. It has id and name parameters (both required), and an optional resourceType parameter that's used to produce the correct entity label color.

The field will be used both for displaying the value and for updating the value when new dropdown option is chosen.

The field name should be written in camel case. E.g. userStory.

3.11. allowReset

Required? No.
A boolean attribute that determines whether the Dropdown supports an empty value. Is “true” by default.

3.12. dropdownItemsSource

Required? Yes.
A subset of attributes that determines where the items in the Dropdown’s list come from. The items can be provided either by an interpolated query or by a custom function.
Template:

dropdownItemsSource:
    | {
        type: 'interpolatedQuery';
        query: string;
      }
    | {
        type: 'custom';
        getItems: (info: ConfigurableControlInfo) => PromiseLike<{id: any; name: string}[]>;
      };

An interpolated query is a templated request to API v2. It can reference the target entity’s fields with template syntax: ${{}}$ (dollar sign, double open curly braces, expression, double closed curly braces, dollar sign), for example:
"feature?where=(epic.id=='${{epic.id}}$)"
Which means “show the Features that are linked to the same Epic this target entity is linked to”.

In the case of the type: 'custom' option, list items are provided by a custom function which takes the info about the Dropdown’s state (including the requiredEntityFields values) as input, and asynchronously returns an array of objects with 'id' and 'name' fields. In principle, this custom function can make requests to any destination or even return some hardcoded data.
Example:

getItems: async info => {
  const response = await fetch(`https://my-service.com/api/getPossibleProjects?id=${info.entity.id}`);
  const items = await response.json();
  return items;
}

This custom function makes a request to a particular service for an array of entities that satisfy a particular condition, then returns the array.

3.13. showSearch

Required? No.
A boolean attribute that determines whether the Dropdown is always searching. Is “false” by default.
If not set dropdown alows search for more than 10 items.

4. CONFIGURATIONS LIBRARY

This section is an aggregation of useful Dropdown Configuration examples from real Customer Accounts. You can take these examples and modify them to create your new Configurations.

As new useful examples are available, they will be added here.

4.1. Show Value Streams Relevant for the Assigned ARTs

With this Configuration, the Value Stream Dropdown will only show Value Streams relevant for the ARTs assigned to the target entity. If no ARTs are assigned, then the Dropdown shows all the Value Streams in the Account.

tau.mashups
    .addDependency('tau/api/configurable-controls/controls/v1')
    .addMashup(controlsApiV1 => {
        controlsApiV1.addConfiguration('dropdown', {
            id: 'feature_valuestream_filtered_by_assigned_art',
            name: 'Value Stream filtered by ART',
            label: 'Value Stream',
            supportedEntityTypes: ['feature'],
            requiredEntityFields: ['valueStream'],
            sampleData: {
                valueStream: { resourceType: 'valuestream', name: 'Value Stream' }
            },
            entityClickBehavior: 'openDropdown',
            field: 'valueStream',
            dropdownItemsSource: {
                type: 'interpolatedQuery',
                query: "valuestream?where=(valueStreamAgileReleaseTrains.count(${{agileReleaseTrain.id}}$ == null or agileReleaseTrain.id == ${{agileReleaseTrain.id}}$) > 0)&take=999"
            }
        })
    })

4.2. Show Program Increments with Their Corresponding Projects

With this Configuration, the Program Increment (or Release) Dropdown will show its list of possible values, each of them combined with the name of its corresponding Project entity. This is implemented with a custom items source, as an interpolated query won’t work.

Note that the list will only contain the currently running Program Increments (via the &where=(IsCurrent == True) condition).

526

The Dropdown lists PIs with the names of their corresponding Projects.

tau.mashups
    .addDependency('tau/api/configurable-controls/controls/v1')
    .addDependency('tau/configurator')
    .addMashup(( controlsApiV1, configurator ) => {
        const store2 = configurator.getStore2();
        controlsApiV1.addConfiguration('dropdown', {
          id: 'dd_conf_03',
          name: 'PI with Project',
          label: 'PI w/ Project',
          supportedEntityTypes: ['teamiteration'],
          requiredEntityFields: ['release'],
          sampleData: {
            release: {name: 'Program Increment', resourceType: 'release'}
          },
          entityClickBehavior: 'openEntity',
          field: 'release',
          dropdownItemsSource: {
            type: 'custom',
            getItems: async () => {
                const releasesWithProjects = await store2.findAll('release?select={id,name,projectName:project.name}&where=(IsCurrent == True)&orderBy=name desc');
                return releasesWithProjects.map(release => ({
                    id: release.id,
                    name: `${release.name} (${release.projectName})`
                }));
            }
          }
        });
    })

4.3. Show Only the Suitable and Available Users for a Work Allocation

This Configuration limits the Dropdown list of Connected Users in a Work Allocation entity only to those Users who:

  1. have at least one Skill required in this Work Allocation;
  2. have Availabilities in all the Timeframes where this Work Allocation has Demands, and these Availabilities have available capacity equal or greater than the FTE required by this Work Allocation.

In other words, the Dropdown will only show the Users who have a necessary Skill and enough unallocated capacity in the necessary periods.

tau.mashups
    .addDependency('tau/api/configurable-controls/controls/v1')
    .addMashup(( controlsApiV1 ) => {
        controlsApiV1.addConfiguration('dropdown', {
          id: 'dd_conf_04',
          name: 'Connected User',
          label: 'Connected User',
          supportedEntityTypes: ['WorkAllocation'],
          requiredEntityFields: ['connectedUser:connectedUser==null?null:{id:connectedUser.id,name:connectedUser.fullName,resourceType:connectedUser.resourceType}'],
          sampleData: {
            ConnectedUser: { name: 'John Smith' }
          },
          entityClickBehavior: 'openEntity',
          field: 'connectedUser',
          dropdownItemsSource: {
            type: 'interpolatedQuery',
            query: "user?select={id, fullname as name}&where=(Skills.Where(name==\"${{Skill.Name}}$\").Count()>0 and Availabilities.Where(Timeframe.Id in [${{Demands.Select(Timeframe.Id)}}$]).Count()==${{Demands.Count()}}$ and Availabilities.Where(Timeframe.Id in [${{Demands.Select(Timeframe.Id)}}$]).Where((AvailableCapacity/100)<${{CustomValues.Get(\"FTE\").Value}}$).Count()==0)"
          }
        })
    })

For the requiredEntityFields attribute in this particular example it's not possible to use a connectedUser reference in the standard way (connectedUser: {id: connectedUser.id, name: connectedUser.name, resourceType: connectedUser.resourceType}). The selector should be modified because it references the User entity that does not have a name field. For this reason, we alias fullName as name: {id: connectedUser.id, name: connectedUser.fullName, resourceType: connectedUser.resourceType}. However, using this selector alone is now not sufficient, because connectedUser being null will produce an empty object (connectedUser: {}) instead of null. To avoid this, we add an additional check using the ternary operator to evaluate to null whenever the entity's connectedUser also has a null value. For additional reference on API v2 selectors syntax, see the REST API v2 Overview and related documents.

NOTE that Work Allocations, Skills, Availabilities, Timeframes, and Demands are extendable domain entities that might not be present in your particular Targetprocess Account.