--- a/taskcluster/actions/hello-action.py
+++ b/taskcluster/actions/hello-action.py
@@ -1,12 +1,13 @@
from .registry import register_callback_action
@register_callback_action(
+ name='hello',
title='Say Hello',
symbol='hw',
description="""
Simple **proof-of-concept** action that prints a hello action.
""",
order=10000, # Put this at the very bottom/end of any menu (default)
context=[{}], # Applies to any task
available=lambda parameters: True, # available regardless decision parameters (default)
--- a/taskcluster/actions/registry.py
+++ b/taskcluster/actions/registry.py
@@ -9,45 +9,47 @@ from taskgraph.parameters import Paramet
GECKO = os.path.realpath(os.path.join(__file__, '..', '..', '..'))
actions = []
callbacks = {}
Action = namedtuple('Action', [
- 'title', 'description', 'order', 'context', 'schema', 'task_template_builder',
+ 'name', 'title', 'description', 'order', 'context', 'schema', 'task_template_builder',
])
def is_json(data):
""" Return ``True``, if ``data`` is a JSON serializable data structure. """
try:
json.dumps(data)
except ValueError:
return False
return True
-def register_task_action(title, description, order, context, schema):
+def register_task_action(name, title, description, order, context, schema):
"""
Register an action task that can be triggered from supporting
user interfaces, such as Treeherder.
Most actions will create intermediate action tasks that call back into
in-tree python code. To write such an action please use
:func:`register_callback_action`.
This function is to be used a decorator for a function that returns a task
template, see :doc:`specification <action-spec>` for details on the
templating features. The decorated function will be given decision task
parameters, which can be embedded in the task template that is returned.
Parameters
----------
+ name : str
+ An identifier for this action, used by UIs to find the action.
title : str
A human readable title for the action to be used as label on a button
or text on a link for triggering the action.
description : str
A human readable description of the action in **markdown**.
This will be display as tooltip and in dialog window when the action
is triggered. This is a good place to describe how to use the action.
order : int
@@ -68,33 +70,35 @@ def register_task_action(title, descript
Returns
-------
function
To be used as decorator for the function that builds the task template.
The decorated function will be given decision parameters and may return
``None`` instead of a task template, if the action is disabled.
"""
+ assert isinstance(name, basestring), 'name must be a string'
assert isinstance(title, basestring), 'title must be a string'
assert isinstance(description, basestring), 'description must be a string'
assert isinstance(order, int), 'order must be an integer'
assert is_json(schema), 'schema must be a JSON compatible object'
mem = {"registered": False} # workaround nonlocal missing in 2.x
def register_task_template_builder(task_template_builder):
assert not mem['registered'], 'register_task_action must be used as decorator'
actions.append(Action(
- title.strip(), description.strip(), order, context, schema, task_template_builder,
+ name.strip(), title.strip(), description.strip(), order, context,
+ schema, task_template_builder,
))
mem['registered'] = True
return register_task_template_builder
-def register_callback_action(title, symbol, description, order=10000, context=[],
- available=lambda parameters: True, schema=None):
+def register_callback_action(name, title, symbol, description, order=10000,
+ context=[], available=lambda parameters: True, schema=None):
"""
Register an action callback that can be triggered from supporting
user interfaces, such as Treeherder.
This function is to be used as a decorator for a callback that takes
parameters as follows:
``parameters``:
@@ -106,16 +110,18 @@ def register_callback_action(title, symb
The id of the task-group this was triggered for.
``task_id`` and `task``:
task identifier and task definition for task the action was triggered
for, ``None`` if no ``context`` parameters was given to
``register_callback_action``.
Parameters
----------
+ name : str
+ An identifier for this action, used by UIs to find the action.
title : str
A human readable title for the action to be used as label on a button
or text on a link for triggering the action.
symbol : str
Treeherder symbol for the action callback, this is the symbol that the
task calling your callback will be displayed as. This is usually 1-3
letters abbreviating the action title.
description : str
@@ -151,17 +157,17 @@ def register_callback_action(title, symb
def register_callback(cb):
assert isinstance(cb, FunctionType), 'callback must be a function'
assert isinstance(symbol, basestring), 'symbol must be a string'
assert 1 <= len(symbol) <= 25, 'symbol must be between 1 and 25 characters'
assert not mem['registered'], 'register_callback_action must be used as decorator'
assert cb.__name__ not in callbacks, 'callback name {} is not unique'.format(cb.__name__)
source_path = os.path.relpath(inspect.stack()[1][1], GECKO)
- @register_task_action(title, description, order, context, schema)
+ @register_task_action(name, title, description, order, context, schema)
def build_callback_action_task(parameters):
if not available(parameters):
return None
match = re.match(r'https://(hg.mozilla.org)/(.*?)/?$', parameters['head_repository'])
if not match:
raise Exception('Unrecognized head_repository')
repo_scope = 'assume:repo:{}/{}:*'.format(
@@ -258,16 +264,17 @@ def render_actions_json(parameters):
assert isinstance(parameters, Parameters), 'requires instance of Parameters'
result = []
for action in sorted(actions, key=lambda action: action.order):
task = action.task_template_builder(parameters)
if task:
assert is_json(task), 'task must be a JSON compatible object'
result.append({
'kind': 'task',
+ 'name': action.name,
'title': action.title,
'description': action.description,
'context': action.context,
'schema': action.schema,
'task': task,
})
return {
'version': 1,
--- a/taskcluster/actions/test-retrigger-action.py
+++ b/taskcluster/actions/test-retrigger-action.py
@@ -13,16 +13,17 @@ from taskgraph.util.time import (
)
TASKCLUSTER_QUEUE_URL = "https://queue.taskcluster.net/v1/task"
logger = logging.getLogger(__name__)
@register_callback_action(
+ name='run-with-options',
title='Schedule test retrigger',
symbol='tr',
description="Retriggers the specified test job with additional options",
context=[{'test-type': 'mochitest'},
{'test-type': 'reftest'}],
order=0,
schema={
'type': 'object',
--- a/taskcluster/docs/action-implementation.rst
+++ b/taskcluster/docs/action-implementation.rst
@@ -12,27 +12,28 @@ action task that will invoke a Python fu
A custom action task is an arbitrary task definition that will be created
directly. In cases where the callback would simply call ``queue.createTask``,
a custom action task can be more efficient.
Creating a Callback Action
--------------------------
A *callback action* is an action that calls back into in-tree logic. That is,
-you register the action with title, description, context, input schema and a
+you register the action with name, title, description, context, input schema and a
python callback. When the action is triggered in a user interface,
input matching the schema is collected, passed to a new task which then calls
your python callback, enabling it to do pretty much anything it wants to.
To create a new action you must create a file
``taskcluster/taskgraph/actions/my-action.py``, that at minimum contains::
from registry import register_callback_action
@register_callback_action(
+ name='hello',
title='Say Hello',
symbol='hw', # Show the callback task in treeherder as 'hw'
description="Simple **proof-of-concept** callback action",
order=10000, # Order in which it should appear relative to other actions
)
def hello_world_action(parameters, input, task_group_id, task_id, task):
# parameters is an instance of taskgraph.parameters.Parameters
# it carries decision task parameters from the original decision task.
@@ -67,16 +68,17 @@ some examples of context parameters and
(i.e., the action is not specific to some task)
The example action below will be shown in the context-menu for tasks with
``task.tags.platform = 'linux'``::
from registry import register_callback_action
@register_callback_action(
+ name='retrigger',
title='Retrigger',
symbol='re-c', # Show the callback task in treeherder as 're-c'
description="Create a clone of the task",
order=1,
context=[{'platform': 'linux'}]
)
def retrigger_action(parameters, input, task_group_id, task_id, task):
# input will be None
@@ -109,16 +111,17 @@ properties to be rendered as markdown be
The example below illustrates how to specify an input schema. Notice that while
this example doesn't specify a ``context`` it is perfectly legal to specify
both ``input`` and ``context``::
from registry import register_callback_action
@register_callback_action(
+ name='run-all',
title='Run All Tasks',
symbol='ra-c', # Show the callback task in treeherder as 'ra-c'
description="**Run all tasks** that have been _optimized_ away.",
order=1,
input={
'title': 'Action Options',
'description': 'Options for how you wish to run all tasks',
'properties': {
@@ -163,16 +166,17 @@ the callback are also available when the
actions to be displayed in the user interface. When registering an action
callback the ``availability`` option can be used to specify a callable
which, given the decision parameters, determines if the action should be available.
The feature is illustrated below::
from registry import register_callback_action
@register_callback_action(
+ name='hello',
title='Say Hello',
symbol='hw', # Show the callback task in treeherder as 'hw'
description="Simple **proof-of-concept** callback action",
order=2,
# Define an action that is only included if this is a push to try
available=lambda parameters: parameters.get('project', None) == 'try',
)
def try_only_action(parameters, input, task_group_id, task_id, task):
@@ -189,16 +193,17 @@ Creating a Custom Action Task
It is possible to define an action that doesn't take a callback. Instead, you'll
then have to provide a task template. For details on how the task template
language works refer to :doc:`the specification for actions.json <action-spec>`,
the example below illustrates how to create such an action::
from registry import register_task_action
@register_task_action(
+ name='retrigger',
title='Retrigger',
description="Create a clone of the task",
order=1,
context=[{'platform': 'linux'}],
input={
'title': 'priority'
'description': 'Priority that should be given to the tasks',
'type': 'string',
--- a/taskcluster/docs/action-spec.rst
+++ b/taskcluster/docs/action-spec.rst
@@ -68,16 +68,17 @@ available:
The following **example** demonstrates how a task template can specify
timestamps and dump input JSON into environment variables::
{
"version": 1,
"actions": [
{
"kind": "task",
+ "name": "thing",
"title: "Do A Thing",
"description": "Do something",
"task": {
"workerType": "my-worker",
"payload": {
"created": {"$fromNow": ""},
"deadline": {"$fromNow": "1 hour 15 minutes"},
"expiration": {"$fromNow": "14 days"},
@@ -94,19 +95,28 @@ timestamps and dump input JSON into envi
],
"variables: {},
}
MetaData
........
-Each action entry must define a ``title`` and ``description``. furthermore,
-the list of actions should be sorted by the order in which actions should
-appear in a menu.
+Each action entry must define a ``name``, ``title`` and ``description``.
+furthermore, the list of actions should be sorted by the order in which actions
+should appear in a menu.
+
+The ``name`` is used by user interfaces to identify the action. For example, a
+retrigger button might look for an action with `name = "retrigger"`.
+
+Action names must be unique for a given task, or for a taskgroup, but the same
+name may be used for actions applying to disjoint sets of tasks. For example,
+it may be helpful to define different "retrigger" actions for build tasks
+`[{jobKind: 'build'}]` and test tasks `[{jobKind: 'test'}]`, and in this case
+only one such action would apply to any given task.
The ``title`` is a human readable string intended to be used as label on the
button, link or menu entry that triggers the action. This should be short and
concise. Ideally, you'll want to avoid duplicates.
The ``description`` property contains a human readable string describing the
action, such as what it does, how it does it, what it is useful for. This string
is to be render as **markdown**, allowing for bullet points, links and other
@@ -170,16 +180,17 @@ An action can take JSON input, the input
specified using a `JSON schema <http://json-schema.org/>`_. This schema is
specified with by the action's ``schema`` property. For example::
{
"version": 1,
"actions": [
{
"kind": "task",
+ "name": "thing",
"title: "Do A Thing",
"description": "Do something",
"schema": {
"description": "The thing to do",
"title": "Thing",
"default": "something",
"type": "string"
"maxLength": 255
--- a/taskcluster/docs/actions-schema.yml
+++ b/taskcluster/docs/actions-schema.yml
@@ -34,16 +34,30 @@ properties:
additionalProperties: true
actions:
type: array
description: |
List of actions that can be triggered.
items:
type: object
properties:
+ name:
+ type: string
+ maxLength: 255
+ description: |
+ The name of this action. This is used by user interfaces to
+ identify the action. For example, a retrigger button might look for
+ an action with `name = "retrigger"`.
+
+ Action names must be unique for a given task, or for a taskgroup,
+ but the same name may be used for actions applying to disjoint sets
+ of tasks. For example, it may be helpful to define different
+ "retrigger" actions for build tasks `[{jobKind: 'build'}]` and test
+ tasks `[{jobKind: 'test'}]`, and in this case only one such action
+ would apply to any given task.
title:
type: string
maxLength: 255
description: |
Title text to be displayed on the button or link triggering the action.
description:
type: string
maxLength: 4096