mistral-dashboard: added action executions screens

* Added 3 new action executions screens: index, overview, and update dialog
 * Added "type" attribute to task screens, followed by a corresponding link
 * Fixed breadcrumbs issue in detail screens following Horizon Newton change
 * Added state label design to detail screens
 * Added design to boolean fields

 * Screenshots: http://pho.to/AXApU

Implements blueprint: mistral-dashboard-action-execution-screens
Implements blueprint: dashboard-detail-screens-improvements
Partially implements blueprint: refactor-execution-link-in-task-executions-screens
Closes-Bug: #1642246

Change-Id: I4c270b2b23d548d4e1cb7e8507e804e44e27c88f
This commit is contained in:
Gal Margalit 2016-11-23 10:35:46 +00:00
parent 1665e7c8a7
commit 2a54532154
31 changed files with 902 additions and 29 deletions

View File

@ -0,0 +1,102 @@
# Copyright 2016 - Nokia.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import forms
from mistraldashboard import api
from mistraldashboard.handle_errors import handle_errors
class UpdateForm(forms.SelfHandlingForm):
action_execution_id = forms.CharField(label=_("Action Execution ID"),
widget=forms.HiddenInput(),
required=False)
output_source = forms.ChoiceField(
label=_('Output'),
help_text=_('Content for output. '
'Select either file, raw content or Null value.'),
choices=[('null', _('<null> (sends empty value)')),
('file', _('File')),
('raw', _('Direct Input'))],
widget=forms.Select(
attrs={'class': 'switchable',
'data-slug': 'outputsource'}
),
required=False
)
output_upload = forms.FileField(
label=_('Output File'),
help_text=_('A local output to upload'),
widget=forms.FileInput(
attrs={'class': 'switched',
'data-switch-on': 'outputsource',
'data-outputsource-file': _('Output File')}
),
required=False
)
output_data = forms.CharField(
label=_('Output Data'),
help_text=_('The raw content for output'),
widget=forms.widgets.Textarea(
attrs={'class': 'switched',
'data-switch-on': 'outputsource',
'data-outputsource-raw': _('Output Data'),
'rows': 4}
),
required=False
)
state = forms.ChoiceField(
label=_('State'),
help_text=_('Select state to update'),
choices=[('null', _('<null> (sends empty value)')),
('SUCCESS', _('Success')),
('ERROR', _('Error'))],
widget=forms.Select(
attrs={'class': 'switchable'}
),
required=False
)
def clean(self):
cleaned_data = super(UpdateForm, self).clean()
cleaned_data['output'] = None
if cleaned_data.get('output_upload'):
files = self.request.FILES
cleaned_data['output'] = files['output_upload'].read()
elif cleaned_data.get('output_data'):
cleaned_data['output'] = cleaned_data['output_data']
elif cleaned_data.get('output_source') == 'null':
cleaned_data['output'] = None
del cleaned_data['output_upload']
del cleaned_data['output_data']
del cleaned_data['output_source']
if cleaned_data['state'] == 'null':
cleaned_data['state'] = None
return cleaned_data
@handle_errors(_("Unable to update Action Execution"), [])
def handle(self, request, data):
return api.action_execution_update(
request,
data['action_execution_id'],
data['state'],
data['output'],
)

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 - Nokia.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
from mistraldashboard import dashboard
class Tasks(horizon.Panel):
name = _("Action Executions")
slug = 'action_executions'
dashboard.MistralDashboard.register(Tasks)

View File

@ -0,0 +1,182 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 - Nokia.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import tables
from mistraldashboard import api
import mistraldashboard.default.SmartCell as SmartCell
from mistraldashboard.default import utils
SmartCell.init()
class UpdateRow(tables.Row):
ajax = True
def get_data(self, request, id):
try:
instance = api.action_execution_get(request, id)
except Exception as e:
msg = _("Unable to get action execution by ID %(id)s: %(e)s.") % {
'id': id, 'e': str(e)
}
exceptions.handle(request, msg)
return instance
class DeleteActionExecution(tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete Action Execution",
u"Delete Action Executions",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted Action Execution",
u"Deleted Action Executions",
count
)
def delete(self, request, action_execution_id):
api.action_execution_delete(request, action_execution_id)
class UpdateActionExecution(tables.LinkAction):
name = "updateAE"
verbose_name = _("Update")
url = "horizon:mistral:action_executions:update"
classes = ("ajax-modal",)
class TaskExecutionIDColumn(tables.Column):
def get_link_url(self, datum):
task_url = "horizon:mistral:tasks:detail"
obj_id = datum.task_execution_id
return reverse(task_url, args=[obj_id])
class WorkflowNameColumn(tables.Column):
def get_link_url(self, datum):
workflow_url = "horizon:mistral:workflows:detail"
obj_id = datum.workflow_name
return reverse(workflow_url, args=[obj_id])
class ActionExecutionsTable(tables.DataTable):
def getHoverHelp(data):
if hasattr(data, 'state_info') and data.state_info:
return {'title': data.state_info}
STATE_STATUS_CHOICES = (
("success", True),
("error", False),
("idle", None),
("running", None),
("canceled", None),
)
id = tables.Column(
"id",
verbose_name=_("ID"),
link="horizon:mistral:action_executions:detail"
)
name = tables.Column(
"name",
verbose_name=_("Name")
)
tags = tables.Column(
"tags",
verbose_name=_("Tags")
)
workflow_name = WorkflowNameColumn(
"workflow_name",
verbose_name=_("Workflow Name"),
link=True
)
task_execution_id = TaskExecutionIDColumn(
"task_execution_id",
verbose_name=_("Task Execution ID"),
link=True
)
task_name = tables.Column(
"task_name",
verbose_name=_("Task name")
)
description = tables.Column(
"description",
verbose_name=_("Description")
)
input = tables.Column(
"",
verbose_name=_("Input"),
empty_value=_("View"),
link="horizon:mistral:action_executions:input",
link_classes=("ajax-modal",)
)
output = tables.Column(
"",
verbose_name=_("Output"),
empty_value=_("View"),
link="horizon:mistral:action_executions:output",
link_classes=("ajax-modal",)
)
created_at = tables.Column(
"created_at",
verbose_name=_("Created at"),
filters=[utils.humantime]
)
updated_at = tables.Column(
"updated_at",
verbose_name=_("Updated at"),
filters=[utils.humantime]
)
accepted = tables.Column(
"accepted",
verbose_name=_("Accepted"),
filters=[utils.booleanfield],
)
state = tables.Column(
"state",
status=True,
status_choices=STATE_STATUS_CHOICES,
verbose_name=_("State"),
filters=[utils.label],
cell_attributes_getter=getHoverHelp
)
class Meta(object):
name = "actionExecutions"
verbose_name = _("Action Executions")
status_columns = ["state"]
row_class = UpdateRow
table_actions = (
tables.FilterAction,
DeleteActionExecution
)
row_actions = (UpdateActionExecution, DeleteActionExecution)

View File

@ -0,0 +1,17 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>
{% trans "Enter new output and or state to update the corresponding Action Execution." %}
</p>
<p>
{% trans "For more info refer to:" %}
<br/>
<a href="http://docs.openstack.org/developer/mistral/developer/webapi/v2.html#action-executions" target="_blank">
{% trans "Mistral documentation - Action Executions" %}
</a>
</p>
{% endblock %}

View File

@ -0,0 +1,96 @@
<span class="mistral-wrapper detail-screen">
{% extends 'mistral/default/base.html' %}
{% load i18n %}
{% block title %}{% trans "Action Execution Details" %}{% endblock %}
{% block page_header %}
<h1>
{% trans "Action Execution Details" %}
</h1>
{% endblock page_header %}
{% block main %}
{% load i18n sizeformat %}
<div class="detail">
<h4>{% trans "Overview" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ action_execution.name }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ action_execution.id }}</dd>
{% if action_execution.description %}
<dt>{% trans "Description" %}</dt>
<dd>{{ action_execution.description }}</dd>
{% endif %}
<div class="line-space">
<dt>{% trans "State" %}</dt>
<dd>{{ action_execution.state }}</dd>
</div>
{% if action_execution.state_info %}
<dt>{% trans "State Info" %}</dt>
<dd>{{ action_execution.state_info }}</dd>
{% endif %}
<dt>{% trans "Accepted" %}</dt>
<dd>{{ action_execution.accepted }}</dd>
<dt>{% trans "Tags" %}</dt>
<dd>{{ action_execution.tags }}</dd>
<br/>
<dt>{% trans "Creation Date" %}</dt>
<dd>{{ action_execution.created_at|parse_isotime}}</dd>
<dt>{% trans "Time Since Created" %}</dt>
<dd>{{ action_execution.created_at|parse_isotime|timesince }}</dd>
<br/>
<dt>{% trans "Update Date" %}</dt>
<dd>{{ action_execution.updated_at|parse_isotime}}</dd>
<dt>{% trans "Time Since Updated" %}</dt>
<dd>{{ action_execution.updated_at|parse_isotime|timesince }}</dd>
<br/>
<dt>{% trans "Input" %}</dt>
<dd>{{ action_execution.input }}</dd>
<dt>{% trans "Output" %}</dt>
<dd>{{ action_execution.output }}</dd>
</dl>
</div>
{% if action_execution.workflow_url %}
<div class="detail">
<h4>{% trans "Workflow Execution" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>
<a href="{{ action_execution.workflow_url }}"
title="{{action_execution.workflow_name}} {% trans "overview" %}">
{{ action_execution.workflow_name }}
</a>
</dd>
</dl>
</div>
{% endif %}
{% if action_execution.task_execution_url %}
<div class="detail">
<h4>{% trans "Task Execution" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>
<a href="{{ action_execution.task_execution_url }}"
title="{{action_execution.task_name}} {% trans "overview" %}">
{{ action_execution.task_name }}
</a>
</dd>
<dt>{% trans "ID" %}</dt>
<dd>
<a href="{{ action_execution.task_execution_url }}"
title="{{ action_execution.task_execution_id }} {% trans "overview" %}">
{{ action_execution.task_execution_id }}
</a>
</dd>
</dl>
</div>
{% endif %}
{% endblock %}
</span>

View File

@ -0,0 +1,13 @@
{% extends 'mistral/default/table.html' %}
{% load i18n %}
{% block title %}
{% trans "Action Executions" %}
{{ task_id }}
{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Action Executions of Task ID:") %}
<h3>
{{ action_execution_id }}
</h3>
{% endblock page_header %}

View File

@ -0,0 +1,9 @@
{% extends 'mistral/default/table.html' %}
{% load i18n %}
{% block title %}
{% trans "Action Executions" %}
{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Action Executions")%}
{% endblock page_header %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" %}
{% endblock page_header %}
{% block main %}
{% include 'mistral/executions/_update.html' %}
{% endblock %}

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 - Nokia.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.conf.urls import url # noqa
from mistraldashboard.action_executions import views
ACTION_EXECUTIONS = r'^(?P<action_execution_id>[^/]+)/%s$'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(ACTION_EXECUTIONS % 'detail', views.OverviewView.as_view(),
name='detail'),
url(ACTION_EXECUTIONS % 'input', views.CodeView.as_view(),
{'column': 'input'}, name='input'),
url(ACTION_EXECUTIONS % 'output', views.CodeView.as_view(),
{'column': 'output'}, name='output'),
url(ACTION_EXECUTIONS % 'update', views.UpdateView.as_view(),
name='update'),
url(ACTION_EXECUTIONS % 'task', views.FilteredByTaskView.as_view(),
name='task')
]

View File

@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 - Nokia.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views import generic
from horizon import exceptions
from horizon import forms
from horizon import tables
from mistraldashboard.action_executions import forms as action_execution_forms
from mistraldashboard.action_executions import tables as mistral_tables
from mistraldashboard import api
from mistraldashboard.default import utils
from mistraldashboard import forms as mistral_forms
def get_single_action_execution_data(request, **kwargs):
try:
action_execution_id = kwargs['action_execution_id']
action_execution = api.action_execution_get(
request,
action_execution_id
)
except Exception:
msg = _('Unable to get action execution "%s".') % action_execution_id
redirect = reverse('horizon:mistral:action_execution:index')
exceptions.handle(request, msg, redirect=redirect)
return action_execution
class OverviewView(generic.TemplateView):
template_name = 'mistral/action_executions/detail.html'
page_title = _("Action Execution Details")
workflow_url = 'horizon:mistral:workflows:detail'
task_execution_url = 'horizon:mistral:tasks:detail'
def get_context_data(self, **kwargs):
context = super(OverviewView, self).get_context_data(**kwargs)
action_execution = get_single_action_execution_data(
self.request,
**kwargs
)
if action_execution.workflow_name:
action_execution.workflow_url = reverse(
self.workflow_url,
args=[action_execution.workflow_name])
if action_execution.task_execution_id:
action_execution.task_execution_url = reverse(
self.task_execution_url,
args=[action_execution.task_execution_id]
)
if action_execution.input:
action_execution.input = utils.prettyprint(action_execution.input)
if action_execution.output:
action_execution.output = utils.prettyprint(
action_execution.output
)
if action_execution.state:
action_execution.state = utils.label(action_execution.state)
if action_execution.accepted:
action_execution.accepted = utils.booleanfield(
action_execution.accepted
)
breadcrumb = [(action_execution.id, reverse(
'horizon:mistral:action_executions:detail',
args=[action_execution.id]
))]
context["custom_breadcrumb"] = breadcrumb
context['action_execution'] = action_execution
return context
class CodeView(forms.ModalFormView):
template_name = 'mistral/default/code.html'
modal_header = _("Code view")
form_id = "code_view"
form_class = mistral_forms.EmptyForm
cancel_label = "OK"
cancel_url = reverse_lazy("horizon:mistral:action_executions:index")
page_title = _("Code view")
def get_context_data(self, **kwargs):
context = super(CodeView, self).get_context_data(**kwargs)
column = self.kwargs['column']
action_execution = get_single_action_execution_data(
self.request,
**self.kwargs
)
io = {}
if column == 'input':
io['name'] = _('Input')
io['value'] = utils.prettyprint(action_execution.input)
elif column == 'output':
io['name'] = _('Output')
io['value'] = (
utils.prettyprint(action_execution.output)
if action_execution.output
else _("No available output yet")
)
context['io'] = io
return context
class IndexView(tables.DataTableView):
table_class = mistral_tables.ActionExecutionsTable
template_name = 'mistral/action_executions/index.html'
def get_data(self):
return api.action_executions_list(self.request)
class UpdateView(forms.ModalFormView):
template_name = 'mistral/action_executions/update.html'
modal_header = _("Update Action Execution")
form_id = "update_action_execution"
form_class = action_execution_forms.UpdateForm
submit_label = _("Update")
success_url = reverse_lazy("horizon:mistral:action_executions:index")
submit_url = "horizon:mistral:action_executions:update"
cancel_url = "horizon:mistral:action_executions:index"
page_title = _("Update Action Execution")
def get_initial(self):
return {"action_execution_id": self.kwargs["action_execution_id"]}
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
context['submit_url'] = reverse(
self.submit_url,
args=[self.kwargs["action_execution_id"]]
)
return context
class FilteredByTaskView(tables.DataTableView):
table_class = mistral_tables.ActionExecutionsTable
template_name = 'mistral/action_executions/filtered.html'
data = {}
def get_data(self, **kwargs):
try:
task_id = self.kwargs['action_execution_id']
data = api.action_executions_list(self.request, task_id)
except Exception:
msg = (
_('Unable to get action execution by task id "%s".') % task_id
)
redirect = reverse('horizon:mistral:action_executions:index')
exceptions.handle(self.request, msg, redirect=redirect)
return data

View File

@ -19,6 +19,7 @@ from horizon import tables
from horizon.utils import filters from horizon.utils import filters
from mistraldashboard import api from mistraldashboard import api
from mistraldashboard.default import utils
class CreateAction(tables.LinkAction): class CreateAction(tables.LinkAction):
@ -92,7 +93,8 @@ class ActionsTable(tables.DataTable):
) )
is_system = tables.Column( is_system = tables.Column(
"is_system", "is_system",
verbose_name=_("Is System") verbose_name=_("Is System"),
filters=[utils.booleanfield]
) )
tags = tables.Column( tags = tables.Column(
tags_to_string, tags_to_string,

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %} <span class="mistral-wrapper detail-screen">
{% extends 'mistral/default/base.html' %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Action Definition" %}{% endblock %} {% block title %}{% trans "Action Definition" %}{% endblock %}
@ -31,8 +32,7 @@
<dd>{{ action.description }}</dd> <dd>{{ action.description }}</dd>
<dt>{% trans "Definition" %}</dt> <dt>{% trans "Definition" %}</dt>
<dd><pre>{{ action.definition }}</pre></dd> <dd><pre>{{ action.definition }}</pre></dd>
</dl> </dl>
</div> </div>
{% endblock %} {% endblock %}
</span>

View File

@ -24,6 +24,7 @@ from horizon import tables
from mistraldashboard.actions import forms as mistral_forms from mistraldashboard.actions import forms as mistral_forms
from mistraldashboard.actions import tables as mistral_tables from mistraldashboard.actions import tables as mistral_tables
from mistraldashboard import api from mistraldashboard import api
from mistraldashboard.default import utils
class CreateView(forms.ModalFormView): class CreateView(forms.ModalFormView):
@ -112,6 +113,14 @@ class DetailView(generic.TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs) context = super(DetailView, self).get_context_data(**kwargs)
action = self.get_data(self.request, **kwargs) action = self.get_data(self.request, **kwargs)
if action.is_system:
action.is_system = utils.booleanfield(action.is_system)
breadcrumb = [(action.name, reverse(
'horizon:mistral:actions:detail',
args=[action.id]
))]
context["custom_breadcrumb"] = breadcrumb
context['action'] = action context['action'] = action
return context return context

View File

@ -330,6 +330,49 @@ def action_delete(request, action_name):
return mistralclient(request).actions.delete(action_name) return mistralclient(request).actions.delete(action_name)
@handle_errors(_("Unable to retrieve action executions list"), [])
def action_executions_list(request, task_execution_id=None):
"""Returns all actions executions.
:param request: Request data
:param task_execution_id: (Optional) Task Execution ID to filter by
"""
return mistralclient(request).action_executions.list(task_execution_id)
@handle_errors(_("Unable to retrieve action execution"), [])
def action_execution_get(request, action_execution_id):
"""Get specific action execution.
:param action_execution_id: Action Execution ID
"""
return mistralclient(request).action_executions.get(action_execution_id)
@handle_errors(_("Unable to delete action execution/s"), [])
def action_execution_delete(request, action_execution_id):
"""Delete action execution.
:param action_execution_id: Action execution ID
"""
return mistralclient(request).action_executions.delete(action_execution_id)
@handle_errors(_("Unable to update action execution"), [])
def action_execution_update(request, id, state=None, output=None):
"""Update action execution output and or state.
:param id: action execution id
:param output: action execution output
:param state: action execution state
"""
return mistralclient(request).action_executions.update(id, state, output)
@handle_errors(_("Unable to retrieve cron trigger list"), []) @handle_errors(_("Unable to retrieve cron trigger list"), [])
def cron_trigger_list(request): def cron_trigger_list(request):
"""Returns all cron triggers. """Returns all cron triggers.

View File

@ -17,7 +17,7 @@
</p> </p>
<p> <p>
{% trans "For more info" %}: {% trans "For more info" %}:
<ul class="list"> <ul class="mistral-wrapper list">
<li> <li>
<a href="http://docs.openstack.org/developer/mistral/developer/webapi/v2.html#cron-triggers" <a href="http://docs.openstack.org/developer/mistral/developer/webapi/v2.html#cron-triggers"
title="{% trans "Mistral Cron Trigger" %}" target="_blank"> title="{% trans "Mistral Cron Trigger" %}" target="_blank">

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %} <span class="mistral-wrapper detail-screen">
{% extends 'mistral/default/base.html' %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Cron Trigger Details" %}{% endblock %} {% block title %}{% trans "Cron Trigger Details" %}{% endblock %}
@ -8,7 +9,7 @@
{% trans "Cron Trigger Details" %} {% trans "Cron Trigger Details" %}
</h1> </h1>
<ol class="breadcrumb"> <ol class="breadcrumb">
<li><a href={{ cron_trigger.list_url }} title="Cron Triggers"> <li><a href="{{ cron_trigger.list_url }}" title="Cron Triggers">
Cron Triggers Cron Triggers
</a> </a>
</li> </li>
@ -97,3 +98,4 @@
</dl> </dl>
</div> </div>
{% endblock %} {% endblock %}
</span>

View File

@ -44,6 +44,12 @@ class OverviewView(generic.TemplateView):
args=[cron_trigger.workflow_name] args=[cron_trigger.workflow_name]
) )
cron_trigger.list_url = reverse_lazy(self.list_url) cron_trigger.list_url = reverse_lazy(self.list_url)
breadcrumb = [(cron_trigger.name, reverse(
'horizon:mistral:cron_triggers:detail',
args=[cron_trigger.name]
))]
context["custom_breadcrumb"] = breadcrumb
context['cron_trigger'] = cron_trigger context['cron_trigger'] = cron_trigger
return context return context

View File

@ -26,10 +26,11 @@ class MistralDashboard(horizon.Dashboard):
'default', 'default',
'workbooks', 'workbooks',
'workflows', 'workflows',
'actions',
'executions', 'executions',
'tasks', 'tasks',
'actions', 'action_executions',
'cron_triggers' 'cron_triggers',
) )
default_panel = 'default' default_panel = 'default'
roles = ('admin',) roles = ('admin',)

View File

@ -0,0 +1,4 @@
<span class="boolfield">
<i class="{{ type.color }} {{ type.icon }}" aria-hidden="true"></i>
{{ bool }}
</span>

View File

@ -26,6 +26,17 @@ TYPES = {
'RUNNING': 'label-info', 'RUNNING': 'label-info',
} }
BOOLEAN_FIELD = {
'True': {
'color': 'green',
'icon': 'fa fa-check'
},
'False': {
'color': 'red',
'icon': 'fa fa-remove'
}
}
def label(x): def label(x):
return render_to_string("mistral/default/_label.html", return render_to_string("mistral/default/_label.html",
@ -33,6 +44,15 @@ def label(x):
"type": TYPES.get(x)}) "type": TYPES.get(x)})
def booleanfield(x):
# todo: check undefined instead of the if blocks in view
# todo: check the red version
return render_to_string("mistral/default/_booleanfield.html",
{"bool": str(x),
"type": BOOLEAN_FIELD.get(str(x))})
def humantime(x): def humantime(x):
return render_to_string("mistral/default/_humantime.html", return render_to_string("mistral/default/_humantime.html",
{"datetime": iso8601.parse_date(x)}) {"datetime": iso8601.parse_date(x)})

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %} <span class="mistral-wrapper detail-screen">
{% extends 'mistral/default/base.html' %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Execution Details" %}{% endblock %} {% block title %}{% trans "Execution Details" %}{% endblock %}
@ -22,7 +23,7 @@
<dd>{{ execution.description }}</dd> <dd>{{ execution.description }}</dd>
{% endif %} {% endif %}
<dt>{% trans "State" %}</dt> <dt>{% trans "State" %}</dt>
<dd>{{ execution.state }}</dd> <dd class="line-space">{{ execution.state }}</dd>
{% if execution.state_info %} {% if execution.state_info %}
<dt>{% trans "State Info" %}</dt> <dt>{% trans "State Info" %}</dt>
<dd>{{ execution.state_info }}</dd> <dd>{{ execution.state_info }}</dd>
@ -66,4 +67,5 @@
</dl> </dl>
</div> </div>
{% endblock %} {% endblock %}
</span>

View File

@ -25,7 +25,7 @@ from horizon import forms
from horizon import tables from horizon import tables
from mistraldashboard import api from mistraldashboard import api
from mistraldashboard.default.utils import prettyprint from mistraldashboard.default import utils
from mistraldashboard.executions import forms as m_forms from mistraldashboard.executions import forms as m_forms
from mistraldashboard.executions import tables as mistral_tables from mistraldashboard.executions import tables as mistral_tables
from mistraldashboard import forms as mistral_forms from mistraldashboard import forms as mistral_forms
@ -160,10 +160,18 @@ class DetailView(generic.TemplateView):
execution.workflow_url = reverse(self.workflow_url, execution.workflow_url = reverse(self.workflow_url,
args=[execution.workflow_name]) args=[execution.workflow_name])
execution.input = prettyprint(execution.input) execution.input = utils.prettyprint(execution.input)
execution.output = prettyprint(execution.output) execution.output = utils.prettyprint(execution.output)
execution.params = prettyprint(execution.params) execution.params = utils.prettyprint(execution.params)
execution.state = utils.label(execution.state)
task.url = reverse(self.task_url, args=[execution.id]) task.url = reverse(self.task_url, args=[execution.id])
breadcrumb = [(execution.id, reverse(
'horizon:mistral:executions:detail',
args=[execution.id]
))]
context["custom_breadcrumb"] = breadcrumb
context['execution'] = execution context['execution'] = execution
context['task'] = task context['task'] = task
@ -189,10 +197,13 @@ class CodeView(forms.ModalFormView):
io = {} io = {}
if column == 'input': if column == 'input':
io['name'] = _('Input') io['name'] = _('Input')
io['value'] = execution.input = prettyprint(execution.input) io['value'] = execution.input = utils.prettyprint(execution.input)
elif column == 'output': elif column == 'output':
io['name'] = _('Output') io['name'] = _('Output')
io['value'] = execution.output = prettyprint(execution.output) io['value'] = execution.output = utils.prettyprint(
execution.output
)
context['io'] = io context['io'] = io
return context return context

View File

@ -1,4 +1,4 @@
.list{ .mistral-wrapper.list{
list-style: inherit; list-style: inherit;
} }
@ -9,3 +9,43 @@
.mistral-wrapper #actions a.btn{ .mistral-wrapper #actions a.btn{
width:initial; width:initial;
} }
.mistral-wrapper.detail-screen .page-breadcrumb ol li{
max-width: inherit;
}
.mistral-wrapper.detail-screen .page-breadcrumb li:last-child{
display:none;
}
.mistral-wrapper .navbar-brand{
padding: 6px 10px;
}
.boolfield{
font-style: italic;
}
.boolfield i{
padding-right: .2em;
}
.boolfield i.green{
color: green;
}
.boolfield i.red{
color: red;
}
.line-space{
margin: .3em 0;
}
.line-space dd{
display:inline-block;
margin-left: 1.5em;
}

View File

@ -14,6 +14,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from django.core.urlresolvers import reverse
from django.template.defaultfilters import title
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from horizon import exceptions from horizon import exceptions
@ -42,6 +44,21 @@ class UpdateRow(tables.Row):
return instance return instance
class TypeColumn(tables.Column):
def get_link_url(self, datum):
obj_id = datum.id
url = ""
action_execution_url = "horizon:mistral:action_executions:task"
if datum.type == "ACTION":
url = action_execution_url
# todo: add missing link to workflow execution
# once available in python mistral client API
# elif datum.type = "WORKFLOW":
# url= "horizon:mistral:workflow:task"
return reverse(url, args=[obj_id])
class TaskTable(tables.DataTable): class TaskTable(tables.DataTable):
def getHoverHelp(data): def getHoverHelp(data):
@ -70,6 +87,12 @@ class TaskTable(tables.DataTable):
verbose_name=_("Workflow Execution ID"), verbose_name=_("Workflow Execution ID"),
link="horizon:mistral:executions:detail_task_id" link="horizon:mistral:executions:detail_task_id"
) )
type = TypeColumn(
"type",
verbose_name=_("Type"),
filters=[title],
link=True
)
result = tables.Column( result = tables.Column(
"", "",
verbose_name=_("Result"), verbose_name=_("Result"),

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %} <span class="mistral-wrapper detail-screen">
{% extends 'mistral/default/base.html' %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Task Details" %}{% endblock %} {% block title %}{% trans "Task Details" %}{% endblock %}
@ -18,12 +19,21 @@
<dd>{{ task.name }}</dd> <dd>{{ task.name }}</dd>
<dt>{% trans "ID" %}</dt> <dt>{% trans "ID" %}</dt>
<dd>{{ task.id }}</dd> <dd>{{ task.id }}</dd>
<dt>{% trans "Type" %}</dt>
<dd>
<a href="{{ task.type_url }}"
title="{{task.type |title}} {% trans "list of the corresponding task" %}">
{{ task.type |title }}
</a>
</dd>
{% if task.state_info %} {% if task.state_info %}
<dt>{% trans "State Info" %}</dt> <dt>{% trans "State Info" %}</dt>
<dd>{{ task.state_info }}</dd> <dd>{{ task.state_info }}</dd>
{% endif %} {% endif %}
<dt>{% trans "State" %}</dt> <div class="line-space">
<dd>{{ task.state }}</dd> <dt>{% trans "State" %}</dt>
<dd>{{ task.state }}</dd>
</div>
<br/> <br/>
<dt>{% trans "Creation Date" %}</dt> <dt>{% trans "Creation Date" %}</dt>
<dd>{{ task.created_at|parse_isotime }}</dd> <dd>{{ task.created_at|parse_isotime }}</dd>
@ -63,3 +73,4 @@
</dl> </dl>
</div> </div>
{% endblock %} {% endblock %}
</span>

View File

@ -25,7 +25,8 @@ from horizon import forms
from horizon import tables from horizon import tables
from mistraldashboard import api from mistraldashboard import api
from mistraldashboard.default.utils import prettyprint from mistraldashboard.default import utils
from mistraldashboard import forms as mistral_forms from mistraldashboard import forms as mistral_forms
from mistraldashboard.tasks import tables as mistral_tables from mistraldashboard.tasks import tables as mistral_tables
@ -63,6 +64,7 @@ class OverviewView(generic.TemplateView):
page_title = _("Task Details") page_title = _("Task Details")
workflow_url = 'horizon:mistral:workflows:detail' workflow_url = 'horizon:mistral:workflows:detail'
execution_url = 'horizon:mistral:executions:detail' execution_url = 'horizon:mistral:executions:detail'
action_execution_url = 'horizon:mistral:action_executions:task'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(OverviewView, self).get_context_data(**kwargs) context = super(OverviewView, self).get_context_data(**kwargs)
@ -71,8 +73,18 @@ class OverviewView(generic.TemplateView):
args=[task.workflow_name]) args=[task.workflow_name])
task.execution_url = reverse(self.execution_url, task.execution_url = reverse(self.execution_url,
args=[task.workflow_execution_id]) args=[task.workflow_execution_id])
task.result = prettyprint(task.result) task.result = utils.prettyprint(task.result)
task.published = prettyprint(task.published) task.published = utils.prettyprint(task.published)
task.state = utils.label(task.state)
if task.type and task.type == "ACTION":
task.type_url = reverse(self.action_execution_url, args=[task.id])
breadcrumb = [(task.id, reverse(
'horizon:mistral:tasks:detail',
args=[task.id]
))]
context["custom_breadcrumb"] = breadcrumb
context['task'] = task context['task'] = task
return context return context
@ -95,10 +107,10 @@ class CodeView(forms.ModalFormView):
if column == 'result': if column == 'result':
io['name'] = _('Result') io['name'] = _('Result')
io['value'] = task.result = prettyprint(task.result) io['value'] = task.result = utils.prettyprint(task.result)
elif column == 'published': elif column == 'published':
io['name'] = _('Published') io['name'] = _('Published')
io['value'] = task.published = prettyprint(task.published) io['value'] = task.published = utils.prettyprint(task.published)
context['io'] = io context['io'] = io

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %} <span class="mistral-wrapper detail-screen">
{% extends 'mistral/default/base.html' %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Workbook Definition" %}{% endblock %} {% block title %}{% trans "Workbook Definition" %}{% endblock %}
@ -11,3 +12,4 @@
<pre>{{ definition }}</pre> <pre>{{ definition }}</pre>
</div> </div>
{% endblock %} {% endblock %}
</span>

View File

@ -43,6 +43,12 @@ class DetailView(generic.TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs) context = super(DetailView, self).get_context_data(**kwargs)
workbook = self.get_data(self.request, **kwargs) workbook = self.get_data(self.request, **kwargs)
breadcrumb = [(workbook.name, reverse(
'horizon:mistral:workbooks:detail',
args=[workbook.name]
))]
context["custom_breadcrumb"] = breadcrumb
context['definition'] = workbook.definition context['definition'] = workbook.definition
return context return context

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %} <span class="mistral-wrapper detail-screen">
{% extends 'mistral/default/base.html' %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Workflow Definition" %}{% endblock %} {% block title %}{% trans "Workflow Definition" %}{% endblock %}
@ -11,3 +12,4 @@
<pre>{{ definition }}</pre> <pre>{{ definition }}</pre>
</div> </div>
{% endblock %} {% endblock %}
</span>

View File

@ -43,10 +43,17 @@ class DetailView(generic.TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs) context = super(DetailView, self).get_context_data(**kwargs)
workflow = self.get_data(self.request, **kwargs) workflow = self.get_data(self.request, **kwargs)
breadcrumb = [(workflow.name, reverse(
'horizon:mistral:workflows:detail',
args=[workflow.name]
))]
context["custom_breadcrumb"] = breadcrumb
context['definition'] = ( context['definition'] = (
workflow.definition or workflow.definition or
'This workflow was created as part of workbook %s' 'This workflow was created as part of workbook %s'
% workflow.name.split('.')[0]) % workflow.name.split('.')[0])
return context return context
def get_data(self, request, **kwargs): def get_data(self, request, **kwargs):