Support post messages and get messages

This patch adds "Post Messages" and "View Messages" actions.
"delete" and "claim" actions for list messages dialog will
be added by subsequent patches.

Implement blueprint: post-get-messages

Change-Id: I42f44e2d8ab9266ba8c21c853956ca69c62ecfce
This commit is contained in:
Fei Long Wang 2017-02-27 15:57:19 +13:00 committed by Shu Muto
parent be8708b36d
commit ad6b606585
15 changed files with 539 additions and 1 deletions

View File

@ -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<queue_name>[^/]+)/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<queue_name>[^/]+)/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"""

View File

@ -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,

View File

@ -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',

View File

@ -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);

View File

@ -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,

View File

@ -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);
}
*/
}
})();

View File

@ -0,0 +1,33 @@
<table class="table tabler-inner"
ng-controller="horizon.dashboard.project.queues.actions.messageController as msgCtrl">
<thead>
<tr>
<th translate>ID</th>
<th translate>Body</th>
<th translate>Time to Live</th>
<th translate>Age</th>
<!--
<th></th>
-->
</tr>
</thead>
<tbody>
<tr ng-if="msgCtrl.messages.length === 0">
<td colspan="100">No message to show.</td>
</tr>
<tr ng-repeat="msg in msgCtrl.messages">
<td>{$ msg.id $}</td>
<td>{$ msg.body $}</td>
<td>{$ msg.ttl $}</td>
<td>{$ msg.age $}</td>
<!--
<td>
<button class="btn btn-xs btn-danger"
ng-click="msgCtrl.deleteMessage(msg)">
<span class="fa fa-trash"></span>
</button>
</td>
-->
</tr>
</tbody>
</table>

View File

@ -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]));
});
}
}
})();

View File

@ -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');
});
});
})();

View File

@ -0,0 +1,28 @@
<div class="hz-section col-sm-6" translate>
<p>
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.
</p>
<p>
The client specifies only the body and TTL for the message. The server
inserts metadata, such as ID and age.
</p>
<p>
See a sample as below:
</p>
<pre>
[
{
"body": {
"event": "BackupProgress",
"current_bytes": "2341134",
"total_bytes": "99614720"
}
}
]
</pre>
</div>

View File

@ -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);
});
}
}
})();

View File

@ -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'
};
}

View File

@ -1,4 +1,8 @@
.subtitle {
margin: 2em 0;
}
textarea#messages {
height: 28em;
}

View File

@ -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);
}
}
})();

View File

@ -109,6 +109,7 @@
</dl>
</div>
<ng-include src="table.subsTemplate"></ng-include>
<ng-include src="table.msgsTemplate"></ng-include>
</td>
</tr>