Add stub api calls for Mistral server integration
Store the workbooks being edited inside sqlite database of Horizon django app. Now it's possible to: * create a workbook; * see it in the list of workbooks; * edit it; * delete it. To use the models.py DATABASES variable in openstack_dashboard settings needs to be set at least to sqlite3. Change-Id: I9d4c013470e0fc13ef65484c8f6fae69cdad0a05 Implements: blueprint mistral-server-integration
This commit is contained in:
parent
531dc56c64
commit
d9f94958c2
@ -14,56 +14,37 @@
|
|||||||
|
|
||||||
from horizon.test import utils as test_utils
|
from horizon.test import utils as test_utils
|
||||||
|
|
||||||
|
from mistral import models
|
||||||
_workbooks = []
|
|
||||||
|
|
||||||
|
|
||||||
def find_max_id():
|
def create_workbook(request, name, yaml):
|
||||||
max_id = 0
|
wb = models.Workbook.objects.create(name=name, yaml=yaml)
|
||||||
for workbook in _workbooks:
|
wb.save()
|
||||||
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
|
return True
|
||||||
|
|
||||||
|
|
||||||
def modify_workbook(request, json):
|
def modify_workbook(request, id, name, yaml):
|
||||||
id = json['id']
|
try:
|
||||||
for i, workbook in enumerate(_workbooks[:]):
|
wb = models.Workbook.objects.get(id=id)
|
||||||
if unicode(id) == unicode(workbook.id):
|
wb.name = name
|
||||||
_workbooks[i] = test_utils.ObjDictWrapper(**json)
|
wb.yaml = yaml
|
||||||
return True
|
wb.save()
|
||||||
|
except models.Workbook.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
return False
|
return True
|
||||||
|
|
||||||
|
|
||||||
def remove_workbook(request, id):
|
def remove_workbook(request, id):
|
||||||
for i, workbook in enumerate(_workbooks[:]):
|
models.Workbook.objects.get(id=id).delete()
|
||||||
if unicode(id) == unicode(workbook.id):
|
|
||||||
del _workbooks[i]
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def list_workbooks(request):
|
def list_workbooks(request):
|
||||||
return _workbooks
|
return models.Workbook.objects.values('id', 'name')
|
||||||
|
|
||||||
|
|
||||||
def get_workbook(request, id):
|
def get_workbook(request, id):
|
||||||
for workbook in _workbooks:
|
try:
|
||||||
if unicode(id) == unicode(workbook.id):
|
return models.Workbook.objects.get(id=id)
|
||||||
return workbook.__dict__
|
except models.Workbook.DoesNotExist:
|
||||||
|
return None
|
||||||
return None
|
|
||||||
|
7
extensions/mistral/models.py
Normal file
7
extensions/mistral/models.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Workbook(models.Model):
|
||||||
|
name = models.CharField(max_length=50, unique=True)
|
||||||
|
yaml = models.TextField()
|
||||||
|
|
@ -8,41 +8,70 @@
|
|||||||
.value('baseActionID', 'action')
|
.value('baseActionID', 'action')
|
||||||
.value('baseWorkflowID', 'workflow')
|
.value('baseWorkflowID', 'workflow')
|
||||||
.controller('workbookCtrl',
|
.controller('workbookCtrl',
|
||||||
['$scope', 'mistral.workbook.models', 'baseActionID', 'baseWorkflowID',
|
['$scope', 'mistral.workbook.models', '$http',
|
||||||
function($scope, models, baseActionId, baseWorkflowId) {
|
'baseActionID', 'baseWorkflowID',
|
||||||
$scope.workbook = models.Workbook.create({name: 'My Workbook'});
|
function($scope, models, $http, baseActionId, baseWorkflowId) {
|
||||||
|
$scope.init = function(id, yaml, commitUrl, discardUrl) {
|
||||||
|
$scope.workbookID = id;
|
||||||
|
$scope.commitUrl = commitUrl;
|
||||||
|
$scope.discardUrl = discardUrl;
|
||||||
|
if ( id !== undefined ) {
|
||||||
|
$scope.workbook = models.Workbook.create(jsyaml.safeLoad(yaml));
|
||||||
|
} else {
|
||||||
|
$scope.workbook = models.Workbook.create({name: 'My Workbook'});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function getNextIDSuffix(container, regexp) {
|
function getNextIDSuffix(container, regexp) {
|
||||||
var max = Math.max.apply(Math, container.getIDs().map(function(id) {
|
var max = Math.max.apply(Math, container.getIDs().map(function(id) {
|
||||||
var match = regexp.exec(id);
|
var match = regexp.exec(id);
|
||||||
return match && +match[2];
|
return match && +match[2];
|
||||||
}));
|
}));
|
||||||
return max > 0 ? max + 1 : 1;
|
return max > 0 ? max + 1 : 1;
|
||||||
}
|
|
||||||
|
|
||||||
function getWorkbookNextIDSuffix(base) {
|
|
||||||
var containerName = base + 's',
|
|
||||||
regexp = /(workflow|action)([0-9]+)/,
|
|
||||||
container = $scope.workbook.get(containerName);
|
|
||||||
if ( !container ) {
|
|
||||||
throw 'Base should be either "action" or "workflow"!';
|
|
||||||
}
|
}
|
||||||
return getNextIDSuffix(container, regexp);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.addAction = function() {
|
function getWorkbookNextIDSuffix(base) {
|
||||||
var nextSuffix = getWorkbookNextIDSuffix(baseActionId),
|
var containerName = base + 's',
|
||||||
newID = baseActionId + nextSuffix;
|
regexp = /(workflow|action)([0-9]+)/,
|
||||||
$scope.workbook.get('actions').push(
|
container = $scope.workbook.get(containerName);
|
||||||
{name: 'Action ' + nextSuffix}, {id: newID});
|
if ( !container ) {
|
||||||
};
|
throw 'Base should be either "action" or "workflow"!';
|
||||||
|
}
|
||||||
|
return getNextIDSuffix(container, regexp);
|
||||||
|
}
|
||||||
|
|
||||||
$scope.addWorkflow = function() {
|
$scope.addAction = function() {
|
||||||
var nextSuffix = getWorkbookNextIDSuffix(baseWorkflowId),
|
var nextSuffix = getWorkbookNextIDSuffix(baseActionId),
|
||||||
newID = baseWorkflowId + nextSuffix;
|
newID = baseActionId + nextSuffix;
|
||||||
$scope.workbook.get('workflows').push(
|
$scope.workbook.get('actions').push(
|
||||||
{name: 'Workflow ' + nextSuffix}, {id: newID});
|
{name: 'Action ' + nextSuffix}, {id: newID});
|
||||||
};
|
};
|
||||||
|
|
||||||
}])
|
$scope.addWorkflow = function() {
|
||||||
|
var nextSuffix = getWorkbookNextIDSuffix(baseWorkflowId),
|
||||||
|
newID = baseWorkflowId + nextSuffix;
|
||||||
|
$scope.workbook.get('workflows').push(
|
||||||
|
{name: 'Workflow ' + nextSuffix}, {id: newID});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.commitWorkbook = function() {
|
||||||
|
var data = {
|
||||||
|
name: $scope.workbook.get('name').get(),
|
||||||
|
yaml: $scope.workbook.toYAML()
|
||||||
|
};
|
||||||
|
|
||||||
|
$http({
|
||||||
|
url: $scope.commitUrl,
|
||||||
|
method: 'POST',
|
||||||
|
data: data
|
||||||
|
}).success(function(data, status, headers, config) {
|
||||||
|
document.location = $scope.discardUrl;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.discardWorkbook = function() {
|
||||||
|
document.location = $scope.discardUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
}])
|
||||||
})();
|
})();
|
@ -12,6 +12,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse_lazy, reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.template import defaultfilters
|
from django.template import defaultfilters
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
@ -22,10 +23,19 @@ from mistral import api
|
|||||||
class CreateWorkbook(tables.LinkAction):
|
class CreateWorkbook(tables.LinkAction):
|
||||||
name = 'create'
|
name = 'create'
|
||||||
verbose_name = _('Create Workbook')
|
verbose_name = _('Create Workbook')
|
||||||
url = 'horizon:project:mistral:create'
|
url = reverse_lazy('horizon:project:mistral:edit', args=())
|
||||||
icon = 'plus'
|
icon = 'plus'
|
||||||
|
|
||||||
|
|
||||||
|
class ModifyWorkbook(tables.LinkAction):
|
||||||
|
name = 'modify'
|
||||||
|
verbose_name = _('Modify Workbook')
|
||||||
|
|
||||||
|
def get_link_url(self, datum):
|
||||||
|
return reverse('horizon:project:mistral:edit',
|
||||||
|
args=(self.table.get_object_id(datum),))
|
||||||
|
|
||||||
|
|
||||||
class RemoveWorkbook(tables.DeleteAction):
|
class RemoveWorkbook(tables.DeleteAction):
|
||||||
name = 'remove'
|
name = 'remove'
|
||||||
verbose_name = _('Remove Workbook')
|
verbose_name = _('Remove Workbook')
|
||||||
@ -37,9 +47,11 @@ class RemoveWorkbook(tables.DeleteAction):
|
|||||||
|
|
||||||
class WorkbooksTable(tables.DataTable):
|
class WorkbooksTable(tables.DataTable):
|
||||||
name = tables.Column('name', verbose_name=_('Workbook Name'))
|
name = tables.Column('name', verbose_name=_('Workbook Name'))
|
||||||
running = tables.Column('running', verbose_name=_('Running'),
|
|
||||||
filters=(defaultfilters.yesno,))
|
def get_object_id(self, datum):
|
||||||
|
return datum['id']
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table_actions = (CreateWorkbook,)
|
table_actions = (CreateWorkbook,)
|
||||||
|
row_actions = (ModifyWorkbook, RemoveWorkbook)
|
||||||
name = 'workbooks'
|
name = 'workbooks'
|
||||||
|
@ -38,7 +38,8 @@
|
|||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h3>Create Workbook</h3>
|
<h3>Create Workbook</h3>
|
||||||
<div id="create-workbook" class="fluid-container" ng-cloak ng-controller="workbookCtrl">
|
<div id="create-workbook" class="fluid-container" ng-cloak ng-controller="workbookCtrl"
|
||||||
|
ng-init="init({{ id|default:'undefined' }}, '{{ yaml }}', '{{ commit_url }}', '{{ discard_url }}')">
|
||||||
<div class="well">
|
<div class="well">
|
||||||
<div class="two-panels">
|
<div class="two-panels">
|
||||||
<div class="left-panel">
|
<div class="left-panel">
|
||||||
@ -54,8 +55,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
<div class="btn-group btn-toggle pull-right">
|
<div class="btn-group btn-toggle pull-right">
|
||||||
<button class="btn btn-sm btn-default">Graph</button>
|
<button ng-click="isGraphMode = true" class="btn btn-sm"
|
||||||
<button class="btn btn-sm btn-primary active">YAML</button>
|
ng-class="isGraphMode ? 'active btn-primary' : 'btn-default'">Graph</button>
|
||||||
|
<button ng-click="isGraphMode = false" class="btn btn-sm"
|
||||||
|
ng-class="!isGraphMode ? 'active btn-primary' : 'btn-default'">YAML</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -78,9 +81,12 @@
|
|||||||
<!-- YAML Panel -->
|
<!-- YAML Panel -->
|
||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-body">
|
<div class="panel-body" ng-show="!isGraphMode">
|
||||||
<pre>{$ workbook.toYAML() $}</pre>
|
<pre>{$ workbook.toYAML() $}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel-body" ng-show="isGraphMode">
|
||||||
|
Here will be a fancy Graph View as soon as we implement it!
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -88,8 +94,10 @@
|
|||||||
<div class="two-panels">
|
<div class="two-panels">
|
||||||
<div class="full-width">
|
<div class="full-width">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<button class="btn btn-default cancel">Cancel</button>
|
<button ng-click="discardWorkbook()" class="btn btn-default cancel">Cancel</button>
|
||||||
<button class="btn btn-primary">Create</button>
|
<button ng-click="commitWorkbook()" class="btn btn-primary">
|
||||||
|
{$ workbookID ? 'Modify' : 'Create' $}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -305,5 +305,15 @@ describe('workbook model logic', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("'Create'/'Modify'/'Cancel' actions", function() {
|
||||||
|
it('edit causes a request to an api and a return to main page', function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cancel causes just a return to main page', function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -19,6 +19,10 @@ from mistral import views
|
|||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||||
url(r'^create$', views.CreateWorkbookView.as_view(), name='create'),
|
url(r'^edit/(?:(?P<workbook_id>[^/]+))?$',
|
||||||
url(r'^actions/types$', views.ActionTypesView.as_view(), name='action_types')
|
views.EditWorkbookView.as_view(), name='edit'),
|
||||||
|
url(r'^commit/(?:/(?P<workbook_id>[^/]+))?$',
|
||||||
|
views.CommitWorkbookView.as_view(), name='commit'),
|
||||||
|
url(r'^actions/types$', views.ActionTypesView.as_view(),
|
||||||
|
name='action_types')
|
||||||
)
|
)
|
||||||
|
@ -14,9 +14,10 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy, reverse
|
||||||
from django import http
|
from django import http
|
||||||
from django.views.generic import View
|
from django.views import generic as generic_views
|
||||||
|
from horizon import messages
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
from horizon.views import APIView
|
from horizon.views import APIView
|
||||||
import yaml
|
import yaml
|
||||||
@ -26,11 +27,47 @@ from mistral import forms as mistral_forms
|
|||||||
from mistral import tables as mistral_tables
|
from mistral import tables as mistral_tables
|
||||||
|
|
||||||
|
|
||||||
class CreateWorkbookView(APIView):
|
class EditWorkbookView(APIView):
|
||||||
template_name = 'project/mistral/create.html'
|
template_name = 'project/mistral/create.html'
|
||||||
|
|
||||||
|
def get_context_data(self, workbook_id=None, **kwargs):
|
||||||
|
commit_ns = 'horizon:project:mistral:commit'
|
||||||
|
if workbook_id is None:
|
||||||
|
commit_url = reverse(commit_ns, args=())
|
||||||
|
else:
|
||||||
|
commit_url = reverse(commit_ns, args=(workbook_id,))
|
||||||
|
context = {
|
||||||
|
'commit_url': commit_url,
|
||||||
|
'discard_url': reverse('horizon:project:mistral:index')
|
||||||
|
}
|
||||||
|
if workbook_id is not None:
|
||||||
|
context['id'] = workbook_id
|
||||||
|
context['yaml'] = api.get_workbook(self.request, workbook_id).yaml
|
||||||
|
return context
|
||||||
|
|
||||||
class ActionTypesView(View):
|
|
||||||
|
class CommitWorkbookView(generic_views.View):
|
||||||
|
def post(self, request, workbook_id=None, **kwargs):
|
||||||
|
def read_data():
|
||||||
|
data = json.loads(request.read())
|
||||||
|
return data['name'], data['yaml']
|
||||||
|
|
||||||
|
if workbook_id is None:
|
||||||
|
name, yaml = read_data()
|
||||||
|
api.create_workbook(request, name, yaml)
|
||||||
|
message = "The workbook {0} has been successfully created".format(
|
||||||
|
name)
|
||||||
|
else:
|
||||||
|
name, yaml = read_data()
|
||||||
|
api.modify_workbook(request, workbook_id, name, yaml)
|
||||||
|
message = "The workbook {0} has been successfully modified".format(
|
||||||
|
name)
|
||||||
|
messages.success(request, message)
|
||||||
|
return http.HttpResponseRedirect(
|
||||||
|
reverse_lazy('horizon:project:mistral:index'))
|
||||||
|
|
||||||
|
|
||||||
|
class ActionTypesView(generic_views.View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
key = request.GET.get('key')
|
key = request.GET.get('key')
|
||||||
schema = {
|
schema = {
|
||||||
|
Loading…
Reference in New Issue
Block a user