Browse Source

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
Timur Sufiev 4 years ago
parent
commit
d9f94958c2

+ 19
- 38
extensions/mistral/api.py View File

@@ -14,56 +14,37 @@
14 14
 
15 15
 from horizon.test import utils as test_utils
16 16
 
17
+from mistral import models
17 18
 
18
-_workbooks = []
19 19
 
20
-
21
-def find_max_id():
22
-    max_id = 0
23
-    for workbook in _workbooks:
24
-        if max_id < int(workbook.id):
25
-            max_id = int(workbook.id)
26
-
27
-    return max_id
28
-
29
-
30
-def create_workbook(request, json):
31
-    name = json['name']
32
-    for workbook in _workbooks:
33
-        if name == workbook['name']:
34
-            raise LookupError('Workbook with that name already exists!')
35
-
36
-    obj = test_utils.ObjDictWrapper(id=find_max_id()+1, **json)
37
-    _workbooks.append(obj)
20
+def create_workbook(request, name, yaml):
21
+    wb = models.Workbook.objects.create(name=name, yaml=yaml)
22
+    wb.save()
38 23
     return True
39 24
 
40 25
 
41
-def modify_workbook(request, json):
42
-    id = json['id']
43
-    for i, workbook in enumerate(_workbooks[:]):
44
-        if unicode(id) == unicode(workbook.id):
45
-            _workbooks[i] = test_utils.ObjDictWrapper(**json)
46
-            return True
26
+def modify_workbook(request, id, name, yaml):
27
+    try:
28
+        wb = models.Workbook.objects.get(id=id)
29
+        wb.name = name
30
+        wb.yaml = yaml
31
+        wb.save()
32
+    except models.Workbook.DoesNotExist:
33
+        return False
47 34
 
48
-    return False
35
+    return True
49 36
 
50 37
 
51 38
 def remove_workbook(request, id):
52
-    for i, workbook in enumerate(_workbooks[:]):
53
-        if unicode(id) == unicode(workbook.id):
54
-            del _workbooks[i]
55
-            return True
56
-
57
-    return False
39
+    models.Workbook.objects.get(id=id).delete()
58 40
 
59 41
 
60 42
 def list_workbooks(request):
61
-    return _workbooks
43
+    return models.Workbook.objects.values('id', 'name')
62 44
 
63 45
 
64 46
 def get_workbook(request, id):
65
-    for workbook in _workbooks:
66
-        if unicode(id) == unicode(workbook.id):
67
-            return workbook.__dict__
68
-
69
-    return None
47
+    try:
48
+        return models.Workbook.objects.get(id=id)
49
+    except models.Workbook.DoesNotExist:
50
+        return None

+ 7
- 0
extensions/mistral/models.py View File

@@ -0,0 +1,7 @@
1
+from django.db import models
2
+
3
+
4
+class Workbook(models.Model):
5
+    name = models.CharField(max_length=50, unique=True)
6
+    yaml = models.TextField()
7
+

+ 65
- 36
extensions/mistral/static/mistral/js/mistral.workbook.controllers.js View File

@@ -8,41 +8,70 @@
8 8
     .value('baseActionID', 'action')
9 9
     .value('baseWorkflowID', 'workflow')
10 10
     .controller('workbookCtrl',
11
-    ['$scope', 'mistral.workbook.models', 'baseActionID', 'baseWorkflowID',
12
-      function($scope, models, baseActionId, baseWorkflowId) {
13
-      $scope.workbook = models.Workbook.create({name: 'My Workbook'});
14
-
15
-      function getNextIDSuffix(container, regexp) {
16
-        var max = Math.max.apply(Math, container.getIDs().map(function(id) {
17
-          var match = regexp.exec(id);
18
-          return match && +match[2];
19
-        }));
20
-        return max > 0 ? max + 1 : 1;
21
-      }
22
-
23
-      function getWorkbookNextIDSuffix(base) {
24
-        var containerName = base + 's',
25
-          regexp = /(workflow|action)([0-9]+)/,
26
-          container = $scope.workbook.get(containerName);
27
-        if ( !container ) {
28
-          throw 'Base should be either "action" or "workflow"!';
11
+    ['$scope', 'mistral.workbook.models', '$http',
12
+      'baseActionID', 'baseWorkflowID',
13
+      function($scope, models, $http, baseActionId, baseWorkflowId) {
14
+        $scope.init = function(id, yaml, commitUrl, discardUrl) {
15
+          $scope.workbookID = id;
16
+          $scope.commitUrl = commitUrl;
17
+          $scope.discardUrl = discardUrl;
18
+          if ( id !== undefined ) {
19
+            $scope.workbook = models.Workbook.create(jsyaml.safeLoad(yaml));
20
+          } else {
21
+            $scope.workbook = models.Workbook.create({name: 'My Workbook'});
22
+          }
23
+        };
24
+
25
+        function getNextIDSuffix(container, regexp) {
26
+          var max = Math.max.apply(Math, container.getIDs().map(function(id) {
27
+            var match = regexp.exec(id);
28
+            return match && +match[2];
29
+          }));
30
+          return max > 0 ? max + 1 : 1;
31
+        }
32
+
33
+        function getWorkbookNextIDSuffix(base) {
34
+          var containerName = base + 's',
35
+            regexp = /(workflow|action)([0-9]+)/,
36
+            container = $scope.workbook.get(containerName);
37
+          if ( !container ) {
38
+            throw 'Base should be either "action" or "workflow"!';
39
+          }
40
+          return getNextIDSuffix(container, regexp);
29 41
         }
30
-        return getNextIDSuffix(container, regexp);
31
-      }
32
-
33
-      $scope.addAction = function() {
34
-        var nextSuffix = getWorkbookNextIDSuffix(baseActionId),
35
-          newID = baseActionId + nextSuffix;
36
-        $scope.workbook.get('actions').push(
37
-          {name: 'Action ' + nextSuffix}, {id: newID});
38
-      };
39
-
40
-      $scope.addWorkflow = function() {
41
-        var nextSuffix = getWorkbookNextIDSuffix(baseWorkflowId),
42
-          newID = baseWorkflowId + nextSuffix;
43
-        $scope.workbook.get('workflows').push(
44
-          {name: 'Workflow ' + nextSuffix}, {id: newID});
45
-      };
46
-
47
-    }])
42
+
43
+        $scope.addAction = function() {
44
+          var nextSuffix = getWorkbookNextIDSuffix(baseActionId),
45
+            newID = baseActionId + nextSuffix;
46
+          $scope.workbook.get('actions').push(
47
+            {name: 'Action ' + nextSuffix}, {id: newID});
48
+        };
49
+
50
+        $scope.addWorkflow = function() {
51
+          var nextSuffix = getWorkbookNextIDSuffix(baseWorkflowId),
52
+            newID = baseWorkflowId + nextSuffix;
53
+          $scope.workbook.get('workflows').push(
54
+            {name: 'Workflow ' + nextSuffix}, {id: newID});
55
+        };
56
+
57
+        $scope.commitWorkbook = function() {
58
+          var data = {
59
+            name: $scope.workbook.get('name').get(),
60
+            yaml: $scope.workbook.toYAML()
61
+            };
62
+
63
+          $http({
64
+            url: $scope.commitUrl,
65
+            method: 'POST',
66
+            data: data
67
+          }).success(function(data, status, headers, config) {
68
+            document.location = $scope.discardUrl;
69
+          });
70
+        };
71
+
72
+        $scope.discardWorkbook = function() {
73
+          document.location = $scope.discardUrl;
74
+        };
75
+
76
+      }])
48 77
 })();

+ 15
- 3
extensions/mistral/tables.py View File

@@ -12,6 +12,7 @@
12 12
 #    License for the specific language governing permissions and limitations
13 13
 #    under the License.
14 14
 
15
+from django.core.urlresolvers import reverse_lazy, reverse
15 16
 from django.utils.translation import ugettext_lazy as _
16 17
 from django.template import defaultfilters
17 18
 from horizon import tables
@@ -22,10 +23,19 @@ from mistral import api
22 23
 class CreateWorkbook(tables.LinkAction):
23 24
     name = 'create'
24 25
     verbose_name = _('Create Workbook')
25
-    url = 'horizon:project:mistral:create'
26
+    url = reverse_lazy('horizon:project:mistral:edit', args=())
26 27
     icon = 'plus'
27 28
 
28 29
 
30
+class ModifyWorkbook(tables.LinkAction):
31
+    name = 'modify'
32
+    verbose_name = _('Modify Workbook')
33
+
34
+    def get_link_url(self, datum):
35
+        return reverse('horizon:project:mistral:edit',
36
+                       args=(self.table.get_object_id(datum),))
37
+
38
+
29 39
 class RemoveWorkbook(tables.DeleteAction):
30 40
     name = 'remove'
31 41
     verbose_name = _('Remove Workbook')
@@ -37,9 +47,11 @@ class RemoveWorkbook(tables.DeleteAction):
37 47
 
38 48
 class WorkbooksTable(tables.DataTable):
39 49
     name = tables.Column('name', verbose_name=_('Workbook Name'))
40
-    running = tables.Column('running', verbose_name=_('Running'),
41
-                            filters=(defaultfilters.yesno,))
50
+
51
+    def get_object_id(self, datum):
52
+        return datum['id']
42 53
 
43 54
     class Meta:
44 55
         table_actions = (CreateWorkbook,)
56
+        row_actions = (ModifyWorkbook, RemoveWorkbook)
45 57
         name = 'workbooks'

+ 14
- 6
extensions/mistral/templates/mistral/create.html View File

@@ -38,7 +38,8 @@
38 38
 
39 39
 {% block main %}
40 40
 <h3>Create Workbook</h3>
41
-  <div id="create-workbook" class="fluid-container" ng-cloak ng-controller="workbookCtrl">
41
+  <div id="create-workbook" class="fluid-container" ng-cloak ng-controller="workbookCtrl"
42
+       ng-init="init({{ id|default:'undefined' }}, '{{ yaml }}', '{{ commit_url }}', '{{ discard_url }}')">
42 43
     <div class="well">
43 44
       <div class="two-panels">
44 45
         <div class="left-panel">
@@ -54,8 +55,10 @@
54 55
         </div>
55 56
         <div class="right-panel">
56 57
           <div class="btn-group btn-toggle pull-right">
57
-            <button class="btn btn-sm btn-default">Graph</button>
58
-            <button class="btn btn-sm btn-primary active">YAML</button>
58
+            <button ng-click="isGraphMode = true" class="btn btn-sm"
59
+                ng-class="isGraphMode ? 'active btn-primary' : 'btn-default'">Graph</button>
60
+            <button ng-click="isGraphMode = false" class="btn btn-sm"
61
+                ng-class="!isGraphMode ? 'active btn-primary' : 'btn-default'">YAML</button>
59 62
           </div>
60 63
         </div>
61 64
       </div>
@@ -78,9 +81,12 @@
78 81
         <!-- YAML Panel -->
79 82
         <div class="right-panel">
80 83
           <div class="panel panel-default">
81
-            <div class="panel-body">
84
+            <div class="panel-body" ng-show="!isGraphMode">
82 85
               <pre>{$ workbook.toYAML() $}</pre>
83 86
             </div>
87
+            <div class="panel-body" ng-show="isGraphMode">
88
+              Here will be a fancy Graph View as soon as we implement it!
89
+            </div>
84 90
           </div>
85 91
         </div>
86 92
       </div>
@@ -88,8 +94,10 @@
88 94
       <div class="two-panels">
89 95
         <div class="full-width">
90 96
           <div class="pull-right">
91
-            <button class="btn btn-default cancel">Cancel</button>
92
-            <button class="btn btn-primary">Create</button>
97
+            <button ng-click="discardWorkbook()" class="btn btn-default cancel">Cancel</button>
98
+            <button ng-click="commitWorkbook()" class="btn btn-primary">
99
+              {$ workbookID ? 'Modify' : 'Create' $}
100
+              </button>
93 101
           </div>
94 102
         </div>
95 103
       </div>

+ 10
- 0
extensions/mistral/test/js/workbookSpec.js View File

@@ -305,5 +305,15 @@ describe('workbook model logic', function() {
305 305
 
306 306
     });
307 307
 
308
+    describe("'Create'/'Modify'/'Cancel' actions", function() {
309
+      it('edit causes a request to an api and a return to main page', function() {
310
+
311
+      });
312
+
313
+      it('cancel causes just a return to main page', function() {
314
+
315
+      });
316
+    });
317
+
308 318
   })
309 319
 });

+ 6
- 2
extensions/mistral/urls.py View File

@@ -19,6 +19,10 @@ from mistral import views
19 19
 
20 20
 urlpatterns = patterns('',
21 21
     url(r'^$', views.IndexView.as_view(), name='index'),
22
-    url(r'^create$', views.CreateWorkbookView.as_view(), name='create'),
23
-    url(r'^actions/types$', views.ActionTypesView.as_view(), name='action_types')
22
+    url(r'^edit/(?:(?P<workbook_id>[^/]+))?$',
23
+        views.EditWorkbookView.as_view(), name='edit'),
24
+    url(r'^commit/(?:/(?P<workbook_id>[^/]+))?$',
25
+        views.CommitWorkbookView.as_view(), name='commit'),
26
+    url(r'^actions/types$', views.ActionTypesView.as_view(),
27
+        name='action_types')
24 28
 )

+ 41
- 4
extensions/mistral/views.py View File

@@ -14,9 +14,10 @@
14 14
 
15 15
 import json
16 16
 
17
-from django.core.urlresolvers import reverse_lazy
17
+from django.core.urlresolvers import reverse_lazy, reverse
18 18
 from django import http
19
-from django.views.generic import View
19
+from django.views import generic as generic_views
20
+from horizon import messages
20 21
 from horizon import tables
21 22
 from horizon.views import APIView
22 23
 import yaml
@@ -26,11 +27,47 @@ from mistral import forms as mistral_forms
26 27
 from mistral import tables as mistral_tables
27 28
 
28 29
 
29
-class CreateWorkbookView(APIView):
30
+class EditWorkbookView(APIView):
30 31
     template_name = 'project/mistral/create.html'
31 32
 
33
+    def get_context_data(self, workbook_id=None, **kwargs):
34
+        commit_ns = 'horizon:project:mistral:commit'
35
+        if workbook_id is None:
36
+            commit_url = reverse(commit_ns, args=())
37
+        else:
38
+            commit_url = reverse(commit_ns, args=(workbook_id,))
39
+        context = {
40
+            'commit_url': commit_url,
41
+            'discard_url': reverse('horizon:project:mistral:index')
42
+            }
43
+        if workbook_id is not None:
44
+            context['id'] = workbook_id
45
+            context['yaml'] = api.get_workbook(self.request, workbook_id).yaml
46
+        return context
47
+
48
+
49
+class CommitWorkbookView(generic_views.View):
50
+    def post(self, request, workbook_id=None, **kwargs):
51
+        def read_data():
52
+            data = json.loads(request.read())
53
+            return data['name'], data['yaml']
54
+
55
+        if workbook_id is None:
56
+            name, yaml = read_data()
57
+            api.create_workbook(request, name, yaml)
58
+            message = "The workbook {0} has been successfully created".format(
59
+                name)
60
+        else:
61
+            name, yaml = read_data()
62
+            api.modify_workbook(request, workbook_id, name, yaml)
63
+            message = "The workbook {0} has been successfully modified".format(
64
+                name)
65
+        messages.success(request, message)
66
+        return http.HttpResponseRedirect(
67
+            reverse_lazy('horizon:project:mistral:index'))
68
+
32 69
 
33
-class ActionTypesView(View):
70
+class ActionTypesView(generic_views.View):
34 71
     def get(self, request, *args, **kwargs):
35 72
         key = request.GET.get('key')
36 73
         schema = {

Loading…
Cancel
Save