diff --git a/zaqar_ui/api/rest/zaqar.py b/zaqar_ui/api/rest/zaqar.py index 1385e62..e7f071f 100644 --- a/zaqar_ui/api/rest/zaqar.py +++ b/zaqar_ui/api/rest/zaqar.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import six import yaml @@ -138,7 +139,7 @@ class Queues(generic.View): @urls.register class Subscriptions(generic.View): - """API for queues""" + """API for Subscriptions""" url_regex = r'zaqar/queues/(?P[^/]+)/subscriptions/$' @rest_utils.ajax() @@ -163,6 +164,35 @@ class Subscriptions(generic.View): return zaqar.subscription_create(request, queue_name, request.DATA) +@urls.register +class Messages(generic.View): + """API for messages""" + url_regex = r'zaqar/queues/(?P[^/]+)/messages/$' + + @rest_utils.ajax() + def get(self, request, queue_name): + """Get a list of messages""" + result = zaqar.message_list(request, queue_name) + messages = [] + for m in result: + claim_id = None + if m.claim_id: + claim_id = m.claim_id() + messages.append({'age': m.age, + 'body': m.body, + 'claim_id': claim_id, + 'id': m.id, + 'href': m.href, + 'ttl': m.ttl}) + return messages + + @rest_utils.ajax(data_required=True) + def post(self, request, queue_name): + """Create new messages""" + messages = json.loads(request.DATA.get("messages")) + return zaqar.message_post(request, queue_name, messages) + + @urls.register class Subscription(generic.View): """API for retrieving a single subscription""" diff --git a/zaqar_ui/api/zaqar.py b/zaqar_ui/api/zaqar.py index ca3da35..ecb9222 100644 --- a/zaqar_ui/api/zaqar.py +++ b/zaqar_ui/api/zaqar.py @@ -94,6 +94,14 @@ def queue_purge(request, queue_name, resource_types): queue.purge(resource_types=resource_types) +def message_post(request, queue_name, messages_data): + return zaqarclient(request).queue(queue_name).post(messages_data) + + +def message_list(request, queue_name): + return zaqarclient(request).queue(queue_name).messages() + + def subscription_list(request, queue_name): return [{'subscriber': s.subscriber, 'id': s.id, diff --git a/zaqar_ui/karma.conf.js b/zaqar_ui/karma.conf.js index 19c7cf0..0192378 100644 --- a/zaqar_ui/karma.conf.js +++ b/zaqar_ui/karma.conf.js @@ -65,6 +65,7 @@ module.exports = function (config) { toxPath + 'xstatic/pkg/tv4/data/tv4.js', toxPath + 'xstatic/pkg/objectpath/data/ObjectPath.js', toxPath + 'xstatic/pkg/angular_schema_form/data/schema-form.js', + toxPath + 'xstatic/pkg/angular_fileupload/data/ng-file-upload.js', // TODO: These should be mocked. toxPath + '/horizon/static/horizon/js/horizon.js', diff --git a/zaqar_ui/static/app/core/openstack-service-api/zaqar.service.js b/zaqar_ui/static/app/core/openstack-service-api/zaqar.service.js index a922c70..524a5ca 100644 --- a/zaqar_ui/static/app/core/openstack-service-api/zaqar.service.js +++ b/zaqar_ui/static/app/core/openstack-service-api/zaqar.service.js @@ -29,6 +29,7 @@ function ZaqarAPI(apiService, toast) { var queuePath = '/api/zaqar/queues/'; + var msgPath = '/api/zaqar/queues/%s/messages/'; var subPath = '/api/zaqar/queues/%s/subscriptions/'; var poolPath = '/api/zaqar/pools/'; var flavorPath = '/api/zaqar/flavors/'; @@ -40,6 +41,8 @@ deleteQueue: deleteQueue, updateQueue: updateQueue, purgeQueue: purgeQueue, + postMessages: postMessages, + getMessages: getMessages, getSubscriptions: getSubscriptions, addSubscription: addSubscription, deleteSubscription: deleteSubscription, @@ -92,6 +95,17 @@ return apiService.post(url, form).error(error(msg)); } + function getMessages(queueName) { + var url = interpolate(msgPath, [queueName]); + return apiService.get(url); + } + + function postMessages(queueName, msgs) { + var msg = gettext('Unable to post messages.'); + var url = interpolate(msgPath, [queueName]); + return apiService.post(url, msgs).error(error(msg)); + } + function getSubscriptions(queue) { var url = interpolate(subPath, [queue.name]); return apiService.get(url); diff --git a/zaqar_ui/static/dashboard/project/queues/actions/actions.module.js b/zaqar_ui/static/dashboard/project/queues/actions/actions.module.js index ad6d071..090d4b7 100644 --- a/zaqar_ui/static/dashboard/project/queues/actions/actions.module.js +++ b/zaqar_ui/static/dashboard/project/queues/actions/actions.module.js @@ -33,6 +33,8 @@ 'horizon.dashboard.project.queues.actions.deleteQueueService', 'horizon.dashboard.project.queues.actions.updateQueueService', 'horizon.dashboard.project.queues.actions.purgeQueueService', + 'horizon.dashboard.project.queues.actions.postMessageService', + 'horizon.dashboard.project.queues.actions.listMessageService', 'horizon.dashboard.project.queues.actions.createSubscriptionService', 'horizon.dashboard.project.queues.resourceType' ]; @@ -43,12 +45,28 @@ deleteQueueService, updateQueueService, purgeQueueService, + postMessageService, + listMessageService, createSubscriptionService, resourceType ) { var queueResourceType = registry.getResourceType(resourceType); queueResourceType.itemActions + .append({ + id: 'messagesPost', + service: postMessageService, + template: { + text: gettext('Post Messages') + } + }) + .append({ + id: 'messagesList', + service: listMessageService, + template: { + text: gettext('View Messages') + } + }) .append({ id: 'queuesItemUpdate', service: updateQueueService, diff --git a/zaqar_ui/static/dashboard/project/queues/actions/list-message.controller.js b/zaqar_ui/static/dashboard/project/queues/actions/list-message.controller.js new file mode 100644 index 0000000..aa2d37c --- /dev/null +++ b/zaqar_ui/static/dashboard/project/queues/actions/list-message.controller.js @@ -0,0 +1,62 @@ +/** + * Copyright 2017 Catalyst IT Ltd. + * + * 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. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @name messageController + * @ngController + * + * @description + * Controller for the messages table + */ + angular + .module('horizon.dashboard.project.queues.actions') + .controller('horizon.dashboard.project.queues.actions.messageController', + messageController); + + messageController.$inject = [ + '$scope', + 'horizon.app.core.openstack-service-api.zaqar' + ]; + + function messageController($scope, zaqar) { + + var ctrl = this; + ctrl.queue = $scope.model.id; + ctrl.messages = []; + ctrl.claimMessage = claimMessage; + ctrl.deleteMessage = deleteMessage; + + zaqar.getMessages(ctrl.queue).success(function (response) { + ctrl.messages = response; + }); + + ////////// + + /* TODO: actions will be implemented later. + function claimMessage(message) { + console.info(message); + } + + function deleteMessage(message) { + console.info(message); + } + */ + } +})(); diff --git a/zaqar_ui/static/dashboard/project/queues/actions/list-message.html b/zaqar_ui/static/dashboard/project/queues/actions/list-message.html new file mode 100644 index 0000000..2d0145a --- /dev/null +++ b/zaqar_ui/static/dashboard/project/queues/actions/list-message.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + +
IDBodyTime to LiveAge
No message to show.
{$ msg.id $}{$ msg.body $}{$ msg.ttl $}{$ msg.age $}
\ No newline at end of file diff --git a/zaqar_ui/static/dashboard/project/queues/actions/list-message.service.js b/zaqar_ui/static/dashboard/project/queues/actions/list-message.service.js new file mode 100644 index 0000000..81096eb --- /dev/null +++ b/zaqar_ui/static/dashboard/project/queues/actions/list-message.service.js @@ -0,0 +1,136 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function() { + 'use strict'; + + angular + .module('horizon.dashboard.project.queues') + .factory( + 'horizon.dashboard.project.queues.actions.listMessageService', listMessageService); + + listMessageService.$inject = [ + '$q', + 'horizon.dashboard.project.queues.basePath', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.app.core.openstack-service-api.zaqar', + 'horizon.dashboard.project.queues.events', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service' + ]; + + /** + * @ngdoc factory + * @name horizon.dashboard.project.queues.actions.listMessageService + * @param {Object} $q + * @param {String} basePath + * @param {Object} policy + * @param {Object} zaqar + * @param {Object} events + * @param {Object} gettext + * @param {Object} $qExtensions + * @param {Object} modal + * @param {Object} toast + * @returns {Object} list messages service + * @description Brings up the polling messages modal dialog. + * On submit, poll messages from given queues. + * On cancel, do nothing. + */ + function listMessageService( + $q, basePath, policy, zaqar, events, gettext, $qExtensions, modal, toast + ) { + // schema + var schema = { + type: "object", + properties: { + postMessages: { + title: gettext("List Messages"), + type: "string" + } + } + }; + + // form + var form = [ + { + type: 'section', + htmlClass: 'row', + items: [ + { + type: 'section', + htmlClass: 'col-sm-12', + items: [ + { + type: 'template', + templateUrl: basePath + 'actions/list-message.html' + } + ] + } + ] + } + ]; + + // model + var model; + + var message = { + success: gettext('Messages has been posted to queue %s successfully.') + }; + + var service = { + initAction: initAction, + perform: perform, + allowed: allowed + }; + + return service; + + ////////////// + + function initAction() { + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + function perform(selected) { + model = { + id: selected.id, + name: selected.name + }; + // modal config + var config = { + "title": gettext('List Messages'), + "submitText": gettext('List Messages'), + "schema": schema, + "form": form, + "model": model + }; + return modal.open(config).then(submit); + } + + function submit(context) { + var id = context.model.id; + var name = context.model.name; + delete context.model.id; + delete context.model.name; + return zaqar.postMessages(id, context.model).then(function() { + toast.add('success', interpolate(message.success, [name])); + }); + } + } +})(); diff --git a/zaqar_ui/static/dashboard/project/queues/actions/list-message.spec.js b/zaqar_ui/static/dashboard/project/queues/actions/list-message.spec.js new file mode 100644 index 0000000..2537483 --- /dev/null +++ b/zaqar_ui/static/dashboard/project/queues/actions/list-message.spec.js @@ -0,0 +1,53 @@ +/** + * 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. + */ + +(function() { + 'use strict'; + describe('horizon.dashboard.project.queues.actions.messageController', function() { + var zaqar, controller, $scope, $q, deferred; + + beforeEach(module('horizon.framework')); + beforeEach(module('horizon.app.core.openstack-service-api')); + beforeEach(module('horizon.dashboard.project.queues')); + + beforeEach(inject(function ($injector, _$rootScope_) { + $scope = _$rootScope_.$new(); + $scope.model = { + id: '' + }; + zaqar = $injector.get('horizon.app.core.openstack-service-api.zaqar'); + controller = $injector.get('$controller'); + controller( + 'horizon.dashboard.project.queues.actions.messageController', + { + $scope: $scope, + zaqar: zaqar + }); + deferred = $q.defer(); + deferred.resolve({data: {id: '1'}}); + spyOn(zaqar, 'getMessages').and.returnValue(deferred.promise); + asdf(); + })); + + it('should load messages for queue', function() { + expect(zaqar.getMessages).toHaveBeenCalled(); + }); + + it('should queue_id is provided by scope variable', function() { + $scope.model.id = '1'; + $scope.$apply(); + expect($scope.model.id).toBe('1'); + }); + }); +})(); diff --git a/zaqar_ui/static/dashboard/project/queues/actions/post-message.help.html b/zaqar_ui/static/dashboard/project/queues/actions/post-message.help.html new file mode 100644 index 0000000..5928007 --- /dev/null +++ b/zaqar_ui/static/dashboard/project/queues/actions/post-message.help.html @@ -0,0 +1,28 @@ +
+

+ You can submit up to 10 messages in a single request, but you must always + encapsulate the messages in a collection container (an array in JSON, even + for a single message - without the JSON array, you receive the “Invalid + request body” message). The resulting value of the Location header or + response body might be used to retrieve the created messages for further + processing. +

+

+ The client specifies only the body and TTL for the message. The server + inserts metadata, such as ID and age. +

+

+ See a sample as below: +

+
+[
+  {
+    "body": {
+      "event": "BackupProgress",
+      "current_bytes": "2341134",
+      "total_bytes": "99614720"
+    }
+  }
+]
+
+
\ No newline at end of file diff --git a/zaqar_ui/static/dashboard/project/queues/actions/post-message.service.js b/zaqar_ui/static/dashboard/project/queues/actions/post-message.service.js new file mode 100644 index 0000000..ec8472f --- /dev/null +++ b/zaqar_ui/static/dashboard/project/queues/actions/post-message.service.js @@ -0,0 +1,143 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function() { + 'use strict'; + + angular + .module('horizon.dashboard.project.queues') + .factory( + 'horizon.dashboard.project.queues.actions.postMessageService', postMessageService); + + postMessageService.$inject = [ + '$q', + 'horizon.dashboard.project.queues.basePath', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.app.core.openstack-service-api.zaqar', + 'horizon.dashboard.project.queues.events', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service' + ]; + + /** + * @ngdoc factory + * @name horizon.dashboard.project.queues.actions.postMessageService + * @param {Object} $q + * @param {String} basePath + * @param {Object} policy + * @param {Object} zaqar + * @param {Object} events + * @param {Object} gettext + * @param {Object} $qExtensions + * @param {Object} modal + * @param {Object} toast + * @returns {Object} post messages service + * @description Brings up the post messages modal dialog. + * On submit, post messages to given queues. + * On cancel, do nothing. + */ + function postMessageService( + $q, basePath, policy, zaqar, events, gettext, $qExtensions, modal, toast + ) { + // schema + var schema = { + type: "object", + properties: { + postMessages: { + title: gettext("Post Messages"), + type: "string" + } + } + }; + + // form + var form = [ + { + type: 'section', + htmlClass: 'row', + items: [ + { + type: 'section', + htmlClass: 'col-sm-6', + items: [ + { + key: 'messages', + type: 'textarea' + } + ] + }, + { + type: 'template', + templateUrl: basePath + 'actions/post-message.help.html' + } + ] + } + ]; + + // model + var model = {}; + + var message = { + success: gettext('Messages has been posted to queue %s successfully.') + }; + + var service = { + initAction: initAction, + perform: perform, + allowed: allowed + }; + + var scope; + return service; + + ////////////// + + function initAction() { + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + function perform(selected, $scope) { + scope = $scope; + model = { + id: selected.id, + name: selected.name + }; + // modal config + var config = { + "title": gettext('List Messages'), + "submitText": gettext('Post'), + "schema": schema, + "form": form, + "model": model + }; + return modal.open(config).then(submit); + } + + function submit(context) { + var id = context.model.id; + var name = context.model.name; + delete context.model.id; + delete context.model.name; + return zaqar.postMessages(id, context.model).then(function() { + toast.add('success', interpolate(message.success, [name])); + scope.$emit(events.POST_MESSAGE_SUCCESS, name); + }); + } + } +})(); diff --git a/zaqar_ui/static/dashboard/project/queues/queues.module.js b/zaqar_ui/static/dashboard/project/queues/queues.module.js index f906796..08f60a2 100644 --- a/zaqar_ui/static/dashboard/project/queues/queues.module.js +++ b/zaqar_ui/static/dashboard/project/queues/queues.module.js @@ -50,6 +50,7 @@ DELETE_SUCCESS: 'horizon.dashboard.project.queues.DELETE_SUCCESS', UPDATE_SUCCESS: 'horizon.dashboard.project.queues.UPDATE_SUCCESS', PURGE_SUCCESS: 'horizon.dashboard.project.queues.PURGE_SUCCESS', + POST_MESSAGE_SUCCESS: 'horizon.dashboard.project.queues.POST_MESSAGE_SUCCESS', SUBSCRIPTION_CREATE_SUCCESS: 'horizon.dashboard.project.queues.SUBSCRIPTION_CREATE_SUCCESS' }; } diff --git a/zaqar_ui/static/dashboard/project/queues/queues.scss b/zaqar_ui/static/dashboard/project/queues/queues.scss index a64dba3..71788b6 100644 --- a/zaqar_ui/static/dashboard/project/queues/queues.scss +++ b/zaqar_ui/static/dashboard/project/queues/queues.scss @@ -1,4 +1,8 @@ .subtitle { margin: 2em 0; +} + +textarea#messages { + height: 28em; } \ No newline at end of file diff --git a/zaqar_ui/static/dashboard/project/queues/table/queue.controller.js b/zaqar_ui/static/dashboard/project/queues/table/queue.controller.js index dab5c49..d2677ca 100644 --- a/zaqar_ui/static/dashboard/project/queues/table/queue.controller.js +++ b/zaqar_ui/static/dashboard/project/queues/table/queue.controller.js @@ -55,12 +55,14 @@ var deleteWatcher = $scope.$on(events.DELETE_SUCCESS, onDeleteSuccess); var updateWatcher = $scope.$on(events.UPDATE_SUCCESS, onUpdateSuccess); var purgeWatcher = $scope.$on(events.PURGE_SUCCESS, onPurgeSuccess); + var postMessageWatcher = $scope.$on(events.POST_MESSAGE_SUCCESS, onPostMessageSuccess); var subWatcher = $scope.$on(events.SUBSCRIPTION_CREATE_SUCCESS, broadcastEvents); $scope.$on('$destroy', function destroy() { createWatcher(); deleteWatcher(); updateWatcher(); purgeWatcher(); + postMessageWatcher(); subWatcher(); }); } @@ -146,6 +148,10 @@ refreshSubscriptions(queueName); } + function onPostMessageSuccess(e, queueName) { + e.stopPropagation(); + refreshQueue(queueName); + } } })(); diff --git a/zaqar_ui/static/dashboard/project/queues/table/queue.html b/zaqar_ui/static/dashboard/project/queues/table/queue.html index e894a8e..de08747 100644 --- a/zaqar_ui/static/dashboard/project/queues/table/queue.html +++ b/zaqar_ui/static/dashboard/project/queues/table/queue.html @@ -109,6 +109,7 @@ +