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 mistraldashboard import api
from mistraldashboard.default import utils
class CreateAction(tables.LinkAction):
@ -92,7 +93,8 @@ class ActionsTable(tables.DataTable):
)
is_system = tables.Column(
"is_system",
verbose_name=_("Is System")
verbose_name=_("Is System"),
filters=[utils.booleanfield]
)
tags = tables.Column(
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 %}
{% block title %}{% trans "Action Definition" %}{% endblock %}
@ -31,8 +32,7 @@
<dd>{{ action.description }}</dd>
<dt>{% trans "Definition" %}</dt>
<dd><pre>{{ action.definition }}</pre></dd>
</dl>
</div>
{% endblock %}
</span>

View File

@ -24,6 +24,7 @@ from horizon import tables
from mistraldashboard.actions import forms as mistral_forms
from mistraldashboard.actions import tables as mistral_tables
from mistraldashboard import api
from mistraldashboard.default import utils
class CreateView(forms.ModalFormView):
@ -112,6 +113,14 @@ class DetailView(generic.TemplateView):
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**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
return context

View File

@ -330,6 +330,49 @@ def action_delete(request, 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"), [])
def cron_trigger_list(request):
"""Returns all cron triggers.

View File

@ -17,7 +17,7 @@
</p>
<p>
{% trans "For more info" %}:
<ul class="list">
<ul class="mistral-wrapper list">
<li>
<a href="http://docs.openstack.org/developer/mistral/developer/webapi/v2.html#cron-triggers"
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 %}
{% block title %}{% trans "Cron Trigger Details" %}{% endblock %}
@ -8,7 +9,7 @@
{% trans "Cron Trigger Details" %}
</h1>
<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
</a>
</li>
@ -97,3 +98,4 @@
</dl>
</div>
{% endblock %}
</span>

View File

@ -44,6 +44,12 @@ class OverviewView(generic.TemplateView):
args=[cron_trigger.workflow_name]
)
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
return context

View File

@ -26,10 +26,11 @@ class MistralDashboard(horizon.Dashboard):
'default',
'workbooks',
'workflows',
'actions',
'executions',
'tasks',
'actions',
'cron_triggers'
'action_executions',
'cron_triggers',
)
default_panel = 'default'
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',
}
BOOLEAN_FIELD = {
'True': {
'color': 'green',
'icon': 'fa fa-check'
},
'False': {
'color': 'red',
'icon': 'fa fa-remove'
}
}
def label(x):
return render_to_string("mistral/default/_label.html",
@ -33,6 +44,15 @@ def label(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):
return render_to_string("mistral/default/_humantime.html",
{"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 %}
{% block title %}{% trans "Execution Details" %}{% endblock %}
@ -22,7 +23,7 @@
<dd>{{ execution.description }}</dd>
{% endif %}
<dt>{% trans "State" %}</dt>
<dd>{{ execution.state }}</dd>
<dd class="line-space">{{ execution.state }}</dd>
{% if execution.state_info %}
<dt>{% trans "State Info" %}</dt>
<dd>{{ execution.state_info }}</dd>
@ -67,3 +68,4 @@
</div>
{% endblock %}
</span>

View File

@ -25,7 +25,7 @@ from horizon import forms
from horizon import tables
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 tables as mistral_tables
from mistraldashboard import forms as mistral_forms
@ -160,10 +160,18 @@ class DetailView(generic.TemplateView):
execution.workflow_url = reverse(self.workflow_url,
args=[execution.workflow_name])
execution.input = prettyprint(execution.input)
execution.output = prettyprint(execution.output)
execution.params = prettyprint(execution.params)
execution.input = utils.prettyprint(execution.input)
execution.output = utils.prettyprint(execution.output)
execution.params = utils.prettyprint(execution.params)
execution.state = utils.label(execution.state)
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['task'] = task
@ -189,10 +197,13 @@ class CodeView(forms.ModalFormView):
io = {}
if column == 'input':
io['name'] = _('Input')
io['value'] = execution.input = prettyprint(execution.input)
io['value'] = execution.input = utils.prettyprint(execution.input)
elif column == 'output':
io['name'] = _('Output')
io['value'] = execution.output = prettyprint(execution.output)
io['value'] = execution.output = utils.prettyprint(
execution.output
)
context['io'] = io
return context

View File

@ -1,4 +1,4 @@
.list{
.mistral-wrapper.list{
list-style: inherit;
}
@ -9,3 +9,43 @@
.mistral-wrapper #actions a.btn{
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
# 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 horizon import exceptions
@ -42,6 +44,21 @@ class UpdateRow(tables.Row):
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):
def getHoverHelp(data):
@ -70,6 +87,12 @@ class TaskTable(tables.DataTable):
verbose_name=_("Workflow Execution ID"),
link="horizon:mistral:executions:detail_task_id"
)
type = TypeColumn(
"type",
verbose_name=_("Type"),
filters=[title],
link=True
)
result = tables.Column(
"",
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 %}
{% block title %}{% trans "Task Details" %}{% endblock %}
@ -18,12 +19,21 @@
<dd>{{ task.name }}</dd>
<dt>{% trans "ID" %}</dt>
<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 %}
<dt>{% trans "State Info" %}</dt>
<dd>{{ task.state_info }}</dd>
{% endif %}
<div class="line-space">
<dt>{% trans "State" %}</dt>
<dd>{{ task.state }}</dd>
</div>
<br/>
<dt>{% trans "Creation Date" %}</dt>
<dd>{{ task.created_at|parse_isotime }}</dd>
@ -63,3 +73,4 @@
</dl>
</div>
{% endblock %}
</span>

View File

@ -25,7 +25,8 @@ from horizon import forms
from horizon import tables
from mistraldashboard import api
from mistraldashboard.default.utils import prettyprint
from mistraldashboard.default import utils
from mistraldashboard import forms as mistral_forms
from mistraldashboard.tasks import tables as mistral_tables
@ -63,6 +64,7 @@ class OverviewView(generic.TemplateView):
page_title = _("Task Details")
workflow_url = 'horizon:mistral:workflows:detail'
execution_url = 'horizon:mistral:executions:detail'
action_execution_url = 'horizon:mistral:action_executions:task'
def get_context_data(self, **kwargs):
context = super(OverviewView, self).get_context_data(**kwargs)
@ -71,8 +73,18 @@ class OverviewView(generic.TemplateView):
args=[task.workflow_name])
task.execution_url = reverse(self.execution_url,
args=[task.workflow_execution_id])
task.result = prettyprint(task.result)
task.published = prettyprint(task.published)
task.result = utils.prettyprint(task.result)
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
return context
@ -95,10 +107,10 @@ class CodeView(forms.ModalFormView):
if column == 'result':
io['name'] = _('Result')
io['value'] = task.result = prettyprint(task.result)
io['value'] = task.result = utils.prettyprint(task.result)
elif column == 'published':
io['name'] = _('Published')
io['value'] = task.published = prettyprint(task.published)
io['value'] = task.published = utils.prettyprint(task.published)
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 %}
{% block title %}{% trans "Workbook Definition" %}{% endblock %}
@ -11,3 +12,4 @@
<pre>{{ definition }}</pre>
</div>
{% endblock %}
</span>

View File

@ -43,6 +43,12 @@ class DetailView(generic.TemplateView):
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**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
return context

View File

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

View File

@ -43,10 +43,17 @@ class DetailView(generic.TemplateView):
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**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'] = (
workflow.definition or
'This workflow was created as part of workbook %s'
% workflow.name.split('.')[0])
return context
def get_data(self, request, **kwargs):