Improve Add Workbook Horizon UX
Show table with all created workbooks (currently stored in memory stub), add 'Add Workbook', 'Edit Workbook' and 'Delete Workbook' actions. Creating/ editing workbook is implemented as modal form (with Workbook Builder inside). Change-Id: I26139f674f7c7f3df2d45a0cd714e53b1d28538c
This commit is contained in:
parent
0019829042
commit
4801a01b93
69
extensions/mistral/api.py
Normal file
69
extensions/mistral/api.py
Normal file
@ -0,0 +1,69 @@
|
||||
# Copyright (c) 2014 Mirantis, Inc.
|
||||
#
|
||||
# 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 horizon.test import utils as test_utils
|
||||
|
||||
|
||||
_workbooks = []
|
||||
|
||||
|
||||
def find_max_id():
|
||||
max_id = 0
|
||||
for workbook in _workbooks:
|
||||
if max_id < int(workbook.id):
|
||||
max_id = int(workbook.id)
|
||||
|
||||
return max_id
|
||||
|
||||
|
||||
def create_workbook(request, json):
|
||||
name = json['name']
|
||||
for workbook in _workbooks:
|
||||
if name == workbook['name']:
|
||||
raise LookupError('Workbook with that name already exists!')
|
||||
|
||||
obj = test_utils.ObjDictWrapper(id=find_max_id()+1, **json)
|
||||
_workbooks.append(obj)
|
||||
return True
|
||||
|
||||
|
||||
def modify_workbook(request, json):
|
||||
id = json['id']
|
||||
for i, workbook in enumerate(_workbooks[:]):
|
||||
if unicode(id) == unicode(workbook.id):
|
||||
_workbooks[i] = test_utils.ObjDictWrapper(**json)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def remove_workbook(request, id):
|
||||
for i, workbook in enumerate(_workbooks[:]):
|
||||
if unicode(id) == unicode(workbook.id):
|
||||
del _workbooks[i]
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def list_workbooks(request):
|
||||
return _workbooks
|
||||
|
||||
|
||||
def get_workbook(request, id):
|
||||
for workbook in _workbooks:
|
||||
if unicode(id) == unicode(workbook.id):
|
||||
return workbook.__dict__
|
||||
|
||||
return None
|
35
extensions/mistral/forms.py
Normal file
35
extensions/mistral/forms.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Copyright (c) 2014 Mirantis, Inc.
|
||||
#
|
||||
# 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 horizon import forms
|
||||
import yaml
|
||||
|
||||
from mistral import api
|
||||
|
||||
|
||||
class BaseWorkbookForm(forms.SelfHandlingForm):
|
||||
workbook = forms.CharField(widget=forms.HiddenInput,
|
||||
required=False)
|
||||
|
||||
|
||||
class CreateWorkbookForm(BaseWorkbookForm):
|
||||
def handle(self, request, data):
|
||||
json = yaml.load(data['workbook'])
|
||||
return api.create_workbook(request, json)
|
||||
|
||||
|
||||
class EditWorkbookForm(BaseWorkbookForm):
|
||||
def handle(self, request, data):
|
||||
json = yaml.load(data['workbook'])
|
||||
return api.modify_workbook(request, json)
|
@ -14,10 +14,7 @@
|
||||
*/
|
||||
|
||||
.left, .right {
|
||||
width: 50%;
|
||||
float: left;
|
||||
padding: 6px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.left {
|
||||
border: 1px solid black;
|
||||
|
@ -301,6 +301,9 @@
|
||||
'@type': Number,
|
||||
'@default': 2
|
||||
},
|
||||
'name': {
|
||||
'@type': String
|
||||
},
|
||||
'description': {
|
||||
'@type': String,
|
||||
'@required': false
|
||||
|
@ -17,6 +17,7 @@ var workbook;
|
||||
|
||||
$(function() {
|
||||
var __counter = 0;
|
||||
var current_workbook_id;
|
||||
|
||||
function getNextCounter() {
|
||||
__counter++;
|
||||
@ -278,16 +279,53 @@ $(function() {
|
||||
});
|
||||
}
|
||||
|
||||
$('button#create-workbook').click(function() {
|
||||
var $controls = $('div#controls'),
|
||||
$label = createNewLabel('Mistral Workbook');
|
||||
$controls.empty();
|
||||
workbook = types.Mistral.Workbook.create();
|
||||
function initWorkbook(recreate) {
|
||||
var $controls = $('div#controls'),
|
||||
$label = createNewLabel('Mistral Workbook'),
|
||||
json = jsyaml.load($('#json-output').val());
|
||||
if ( json === undefined ) {
|
||||
current_workbook_id = null
|
||||
} else {
|
||||
current_workbook_id = json.id;
|
||||
}
|
||||
$controls.empty();
|
||||
if (recreate) {
|
||||
workbook = types.Mistral.Workbook.create();
|
||||
} else {
|
||||
workbook = types.Mistral.Workbook.create(json);
|
||||
}
|
||||
|
||||
drawTypedNode($controls, $label, workbook).find('label').click();
|
||||
drawTypedNode($controls, $label, workbook).find('label').click();
|
||||
}
|
||||
|
||||
$(function() {
|
||||
initWorkbook()
|
||||
});
|
||||
|
||||
$('button#create-workbook').click(function(evt) {
|
||||
initWorkbook(true);
|
||||
evt.preventDefault();
|
||||
});
|
||||
|
||||
$('button#save-workbook').click(function() {
|
||||
$('.right').text(jsyaml.dump(workbook.toJSON()));
|
||||
})
|
||||
function saveWorkbook() {
|
||||
var json = workbook.toJSON(),
|
||||
text;
|
||||
if ( current_workbook_id !== null ) {
|
||||
json.id = current_workbook_id;
|
||||
}
|
||||
text = jsyaml.dump(json);
|
||||
$('.right pre').text(text);
|
||||
$('#json-output').val(text);
|
||||
}
|
||||
|
||||
$('form').submit(saveWorkbook);
|
||||
|
||||
$('button#save-workbook').click(function(evt) {
|
||||
saveWorkbook();
|
||||
evt.preventDefault();
|
||||
});
|
||||
// to prevent modal form submit
|
||||
$('div#controls').click(function(evt) {
|
||||
evt.preventDefault();
|
||||
});
|
||||
});
|
53
extensions/mistral/tables.py
Normal file
53
extensions/mistral/tables.py
Normal file
@ -0,0 +1,53 @@
|
||||
# Copyright (c) 2014 Mirantis, Inc.
|
||||
#
|
||||
# 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 django.template import defaultfilters
|
||||
from horizon import tables
|
||||
|
||||
from mistral import api
|
||||
|
||||
class CreateWorkbook(tables.LinkAction):
|
||||
name = 'create'
|
||||
verbose_name = _('Create Workbook')
|
||||
url = 'horizon:project:mistral:create'
|
||||
classes = ('ajax-modal',)
|
||||
icon = 'plus'
|
||||
|
||||
|
||||
class EditWorkbook(tables.LinkAction):
|
||||
name = 'edit'
|
||||
verbose_name = _('Edit Workbook')
|
||||
url = 'horizon:project:mistral:edit'
|
||||
classes = ('ajax-modal',)
|
||||
|
||||
|
||||
class RemoveWorkbook(tables.DeleteAction):
|
||||
name = 'remove'
|
||||
verbose_name = _('Remove Workbook')
|
||||
data_type_singular = _('Workbook')
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
return api.remove_workbook(request, obj_id)
|
||||
|
||||
|
||||
class WorkbooksTable(tables.DataTable):
|
||||
name = tables.Column('name', verbose_name=_('Workbook Name'))
|
||||
running = tables.Column('running', verbose_name=_('Running'),
|
||||
filters=(defaultfilters.yesno,))
|
||||
|
||||
class Meta:
|
||||
table_actions = (CreateWorkbook, RemoveWorkbook)
|
||||
name = 'workbooks'
|
||||
row_actions = (EditWorkbook, RemoveWorkbook)
|
46
extensions/mistral/templates/mistral/_create.html
Normal file
46
extensions/mistral/templates/mistral/_create.html
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}create_workbook{% endblock %}
|
||||
{% block form_action %}
|
||||
{% if form.initial.workbook_id %}
|
||||
{% url 'horizon:project:mistral:edit' form.initial.workbook_id %}
|
||||
{% else %}
|
||||
{% url 'horizon:project:mistral:create' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-header %}
|
||||
{% if form.initial.workbook_id %}
|
||||
{% trans "Edit Workbook" %}
|
||||
{% else %}
|
||||
{% trans "Create Workbook" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block modal_id %}create_workbook_modal{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<div id="toolbar">
|
||||
<button id="create-workbook">Reset Workbook</button>
|
||||
<button id="save-workbook">Update YAML presentation</button>
|
||||
<div id="controls"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<pre></pre>
|
||||
<input name="workbook" id="json-output" type="hidden" value="{{ form.initial.workbook }}"/>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit"
|
||||
value="{% if form.initial.workbook_id %}{% trans "Edit" %}{% else %}{% trans "Create" %}{% endif %}" />
|
||||
<a href="{% url 'horizon:project:mistral:index' %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-js %}
|
||||
<script src="{{ STATIC_URL }}mistral/js/schema.js"></script>
|
||||
<script src="{{ STATIC_URL }}mistral/js/workbook.js"></script>
|
||||
{% endblock %}
|
@ -1,24 +1,15 @@
|
||||
{% extends "merlin/base.html" %}
|
||||
|
||||
{% block title %}Merlin Project{% endblock %}
|
||||
{% block main %}
|
||||
<div>
|
||||
<div class="left">
|
||||
<div id="toolbar">
|
||||
<button id="create-workbook">New Workbook</button>
|
||||
<button id="save-workbook">Save Workbook</button>
|
||||
</div>
|
||||
<div id="controls"></div>
|
||||
</div>
|
||||
<pre class="right"></pre>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% extends 'merlin/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Workbooks" %}{% endblock %}
|
||||
|
||||
{% block merlin-css %}
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}mistral/css/mistral.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block merlin-js-scripts %}
|
||||
<script src="{{ STATIC_URL }}mistral/js/schema.js"></script>
|
||||
<script src="{{ STATIC_URL }}mistral/js/workbook.js"></script>
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Workbooks") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -19,4 +19,7 @@ from mistral import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^create$', views.CreateWorkbookView.as_view(), name='create'),
|
||||
url(r'^edit/(?P<workbook_id>[^/]+)$', views.EditWorkbookView.as_view(),
|
||||
name='edit')
|
||||
)
|
||||
|
@ -12,8 +12,40 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.views.generic import TemplateView # noqa
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from horizon import tables
|
||||
from horizon.forms import views
|
||||
import yaml
|
||||
|
||||
from mistral import api
|
||||
from mistral import forms as mistral_forms
|
||||
from mistral import tables as mistral_tables
|
||||
|
||||
|
||||
class IndexView(TemplateView):
|
||||
class CreateWorkbookView(views.ModalFormView):
|
||||
form_class = mistral_forms.CreateWorkbookForm
|
||||
template_name = 'project/mistral/create.html'
|
||||
success_url = reverse_lazy('horizon:project:mistral:index')
|
||||
|
||||
|
||||
class EditWorkbookView(views.ModalFormView):
|
||||
form_class = mistral_forms.EditWorkbookForm
|
||||
template_name = 'project/mistral/create.html'
|
||||
success_url = reverse_lazy('horizon:project:mistral:index')
|
||||
|
||||
def get_initial(self):
|
||||
workbook_id = self.kwargs['workbook_id']
|
||||
workbook = api.get_workbook(self.request, workbook_id)
|
||||
if workbook:
|
||||
return {'workbook': yaml.dump(workbook),
|
||||
'workbook_id': workbook_id}
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
template_name = 'project/mistral/index.html'
|
||||
table_class = mistral_tables.WorkbooksTable
|
||||
|
||||
def get_data(self):
|
||||
return api.list_workbooks(self.request)
|
||||
|
1
merlin/templates/merlin/_modal_form.html
Normal file
1
merlin/templates/merlin/_modal_form.html
Normal file
@ -0,0 +1 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
Loading…
x
Reference in New Issue
Block a user