diff --git a/horizon/static/framework/conf/resource-type-registry.service.js b/horizon/static/framework/conf/resource-type-registry.service.js
index 122df04591..f4f282fbdf 100644
--- a/horizon/static/framework/conf/resource-type-registry.service.js
+++ b/horizon/static/framework/conf/resource-type-registry.service.js
@@ -94,7 +94,9 @@
// type, with the data as a result in a promise. For example, Images code
// would register a list function that returns a promise that will resolve
// to all the Images data in list form.
- this.listFunction = angular.noop;
+ this.listFunction = function def() {
+ return Promise.resolve({data: {items: []}});
+ };
this.setListFunction = setListFunction;
// The table columns are an extensible registration of columns of data
@@ -506,10 +508,6 @@
}
var resourceTypes = {};
- // The slugs are only used to align Django routes with heat
- // type names. In a context without Django routing this is
- // not needed.
- var slugs = {};
var defaultSummaryTemplateUrl = false;
var defaultDetailsTemplateUrl = false;
var registry = {
@@ -519,20 +517,9 @@
setDefaultSummaryTemplateUrl: setDefaultSummaryTemplateUrl,
getDefaultSummaryTemplateUrl: getDefaultSummaryTemplateUrl,
setDefaultDetailsTemplateUrl: setDefaultDetailsTemplateUrl,
- getDefaultDetailsTemplateUrl: getDefaultDetailsTemplateUrl,
- setSlug: setSlug,
- getTypeNameBySlug: getTypeNameBySlug
+ getDefaultDetailsTemplateUrl: getDefaultDetailsTemplateUrl
};
- function getTypeNameBySlug(slug) {
- return slugs[slug];
- }
-
- function setSlug(slug, typeName) {
- slugs[slug] = typeName;
- return this;
- }
-
function getDefaultSummaryTemplateUrl() {
return defaultSummaryTemplateUrl;
}
diff --git a/horizon/static/framework/conf/resource-type-registry.service.spec.js b/horizon/static/framework/conf/resource-type-registry.service.spec.js
index 6b93420b17..c8131d85b0 100644
--- a/horizon/static/framework/conf/resource-type-registry.service.spec.js
+++ b/horizon/static/framework/conf/resource-type-registry.service.spec.js
@@ -166,11 +166,6 @@
});
});
- it("sets and retrieves slugs", function() {
- service.setSlug('image', 'OS::Glance::Image');
- expect(service.getTypeNameBySlug('image')).toBe('OS::Glance::Image');
- });
-
describe('getName', function() {
it('returns nothing if names not provided', function() {
var type = service.getResourceType('something');
diff --git a/horizon/static/framework/widgets/panel/hz-resource-panel.controller.js b/horizon/static/framework/widgets/panel/hz-resource-panel.controller.js
new file mode 100644
index 0000000000..59ff9c220d
--- /dev/null
+++ b/horizon/static/framework/widgets/panel/hz-resource-panel.controller.js
@@ -0,0 +1,34 @@
+/*
+ * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
+ *
+ * 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';
+
+ angular
+ .module('horizon.framework.widgets.panel')
+ .controller('horizon.framework.widgets.panel.HzResourcePanelController', controller);
+
+ controller.$inject = [
+ 'horizon.framework.conf.resource-type-registry.service'
+ ];
+
+ function controller(registry) {
+ var ctrl = this;
+
+ ctrl.resourceType = registry.getResourceType(ctrl.resourceTypeName);
+ ctrl.pageName = ctrl.resourceType.getName();
+ }
+
+})();
diff --git a/horizon/static/framework/widgets/panel/hz-resource-panel.controller.spec.js b/horizon/static/framework/widgets/panel/hz-resource-panel.controller.spec.js
new file mode 100644
index 0000000000..52010575ef
--- /dev/null
+++ b/horizon/static/framework/widgets/panel/hz-resource-panel.controller.spec.js
@@ -0,0 +1,57 @@
+/*
+ * (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
+ *
+ * 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('hz-resource-panel controller', function() {
+ var ctrl;
+
+ var resourceType = {
+ getName: function() {
+ return 'MyType';
+ }
+ };
+
+ beforeEach(module('horizon.framework.conf'));
+ beforeEach(module('horizon.framework.widgets.panel'));
+
+ beforeEach(inject(function($controller) {
+ var registry = {
+ getResourceType: angular.noop
+ };
+
+ spyOn(registry, 'getResourceType').and.returnValue(resourceType);
+
+ ctrl = $controller('horizon.framework.widgets.panel.HzResourcePanelController', {
+ 'horizon.framework.conf.resource-type-registry.service': registry,
+ tableResourceType: 'OS::Test::Example'});
+ }));
+
+ it('exists', function() {
+ expect(ctrl).toBeDefined();
+ });
+
+ it('sets resourceType to the resource type', function() {
+ expect(ctrl.resourceType).toBe(resourceType);
+ });
+
+ it('sets resourceTypeName to the resource type name', function() {
+ expect(ctrl.pageName).toEqual('MyType');
+ });
+ });
+
+})();
diff --git a/horizon/static/framework/widgets/panel/hz-resource-panel.directive.js b/horizon/static/framework/widgets/panel/hz-resource-panel.directive.js
new file mode 100644
index 0000000000..c1d2e56dd0
--- /dev/null
+++ b/horizon/static/framework/widgets/panel/hz-resource-panel.directive.js
@@ -0,0 +1,57 @@
+/**
+ * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
+ *
+ * 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';
+
+ angular
+ .module('horizon.framework.widgets.panel')
+ .directive('hzResourcePanel', directive);
+
+ directive.$inject = ['horizon.framework.widgets.basePath'];
+
+ /**
+ * @ngdoc directive
+ * @name hzResourcePanel
+ * @description
+ * This directive takes in a resource type name, e.g. 'OS::Glance::Image'
+ * as a String and produces the shell of a panel for that given resource
+ * type. This primarily includes a header and allows content to be
+ * transcluded.
+ *
+ * @example
+ ```
+
+ Here is my content!
+
+
+ ```
+ */
+ function directive(basePath) {
+
+ var directive = {
+ restrict: 'E',
+ scope: {
+ resourceTypeName: '@'
+ },
+ transclude: true,
+ bindToController: true,
+ templateUrl: basePath + 'panel/hz-resource-panel.html',
+ controller: "horizon.framework.widgets.panel.HzResourcePanelController as ctrl"
+ };
+
+ return directive;
+ }
+})();
diff --git a/horizon/static/framework/widgets/panel/hz-resource-panel.html b/horizon/static/framework/widgets/panel/hz-resource-panel.html
new file mode 100644
index 0000000000..1f650b862e
--- /dev/null
+++ b/horizon/static/framework/widgets/panel/hz-resource-panel.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/horizon/static/framework/widgets/panel/panel.module.js b/horizon/static/framework/widgets/panel/panel.module.js
new file mode 100644
index 0000000000..802e9ef1aa
--- /dev/null
+++ b/horizon/static/framework/widgets/panel/panel.module.js
@@ -0,0 +1,22 @@
+/*
+ * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
+ *
+ * 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';
+
+ angular
+ .module('horizon.framework.widgets.panel', []);
+
+})();
diff --git a/horizon/static/framework/widgets/table/hz-resource-table.controller.js b/horizon/static/framework/widgets/table/hz-resource-table.controller.js
new file mode 100644
index 0000000000..f8b490176f
--- /dev/null
+++ b/horizon/static/framework/widgets/table/hz-resource-table.controller.js
@@ -0,0 +1,126 @@
+/*
+ * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
+ *
+ * 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';
+
+ angular
+ .module('horizon.framework.widgets.table')
+ .controller('horizon.framework.widgets.table.ResourceTableController', controller);
+
+ controller.$inject = [
+ '$q',
+ '$scope',
+ 'horizon.framework.util.actions.action-result.service',
+ 'horizon.framework.conf.resource-type-registry.service'
+ ];
+
+ function controller($q, $scope, actionResultService, registry) {
+ var ctrl = this;
+
+ // 'Public' Controller members
+
+ ctrl.resourceType = registry.getResourceType(ctrl.resourceTypeName);
+ ctrl.items = [];
+ ctrl.itemsSrc = [];
+ ctrl.searchFacets = [];
+ ctrl.config = {
+ detailsTemplateUrl: ctrl.resourceType.summaryTemplateUrl,
+ selectAll: true,
+ expand: true,
+ trackId: 'id',
+ searchColumnSpan: 6,
+ actionColumnSpan: 6,
+ columns: ctrl.resourceType.getTableColumns()
+ };
+ ctrl.batchActions = ctrl.resourceType.globalActions
+ .concat(ctrl.resourceType.batchActions);
+
+ ctrl.actionResultHandler = actionResultHandler;
+
+ // Controller Initialization/Loading
+
+ ctrl.resourceType.listFunction().then(onLoad);
+ registry.initActions(ctrl.resourceType.type, $scope);
+
+ // Local functions
+
+ function onLoad(response) {
+ ctrl.itemsSrc = response.data.items;
+ }
+
+ function actionResultHandler(returnValue) {
+ return $q.when(returnValue, actionSuccessHandler);
+ }
+
+ function actionSuccessHandler(result) { // eslint-disable-line no-unused-vars
+
+ // The action has completed (for whatever "complete" means to that
+ // action. Notice the view doesn't really need to know the semantics of the
+ // particular action because the actions return data in a standard form.
+ // That return includes the id and type of each created, updated, deleted
+ // and failed item.
+ //
+ // This handler is also careful to check the type of each item. This
+ // is important because actions which create non-items are launched from
+ // the items page (like create "volume" from image).
+ var deletedIds, updatedIds, createdIds, failedIds;
+
+ if ( result ) {
+ // Reduce the results to just item ids ignoring other types the action
+ // may have produced
+ deletedIds = actionResultService.getIdsOfType(result.deleted, ctrl.resourceType.type);
+ updatedIds = actionResultService.getIdsOfType(result.updated, ctrl.resourceType.type);
+ createdIds = actionResultService.getIdsOfType(result.created, ctrl.resourceType.type);
+ failedIds = actionResultService.getIdsOfType(result.failed, ctrl.resourceType.type);
+
+ // Handle deleted items
+ if (deletedIds.length) {
+ ctrl.itemsSrc = difference(ctrl.itemsSrc, deletedIds,'id');
+ }
+
+ // Handle updated and created items
+ if ( updatedIds.length || createdIds.length ) {
+ // Ideally, get each created item individually, but
+ // this is simple and robust for the common use case.
+ // TODO: If we want more detailed updates, we could do so here.
+ ctrl.resourceType.listFunction().then(onLoad);
+ }
+
+ // Handle failed items
+ if (failedIds.length) {
+ // Do nothing for now. Please note, actions may (and probably
+ // should) provide toast messages when something goes wrong.
+ }
+
+ } else {
+ // promise resolved, but no result returned. Because the action didn't
+ // tell us what happened...reload the displayed items just in case.
+ ctrl.resourceType.listFunction().then(onLoad);
+ }
+ }
+
+ function difference(currentList, otherList, key) {
+ return currentList.filter(filter);
+
+ function filter(elem) {
+ return otherList.filter(function filterDeletedItem(deletedItem) {
+ return deletedItem === elem[key];
+ }).length === 0;
+ }
+ }
+ }
+
+})();
diff --git a/horizon/static/framework/widgets/table/hz-resource-table.controller.spec.js b/horizon/static/framework/widgets/table/hz-resource-table.controller.spec.js
new file mode 100644
index 0000000000..0dd99d69c8
--- /dev/null
+++ b/horizon/static/framework/widgets/table/hz-resource-table.controller.spec.js
@@ -0,0 +1,122 @@
+/*
+ * (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
+ *
+ * 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('hz-generic-table controller', function() {
+ var ctrl, listFunctionDeferred, $timeout, actionResultDeferred;
+
+ beforeEach(module('horizon.framework.util'));
+ beforeEach(module('horizon.framework.conf'));
+ beforeEach(module('horizon.framework.widgets.table'));
+
+ var resourceType = {
+ type: 'OS::Test::Example',
+ getTableColumns: angular.noop,
+ listFunction: angular.noop,
+ globalActions: [],
+ batchActions: []
+ };
+
+ beforeEach(inject(function($controller, $q, _$timeout_) {
+ $timeout = _$timeout_;
+ var registry = {
+ getTypeNameBySlug: angular.noop,
+ getResourceType: angular.noop,
+ initActions: angular.noop
+ };
+
+ listFunctionDeferred = $q.defer();
+ actionResultDeferred = $q.defer();
+ spyOn(resourceType, 'listFunction').and.returnValue(listFunctionDeferred.promise);
+ spyOn(registry, 'getResourceType').and.returnValue(resourceType);
+
+ ctrl = $controller('horizon.framework.widgets.table.ResourceTableController', {
+ $scope: {},
+ 'horizon.framework.conf.resource-type-registry.service': registry},
+ {resourceTypeName: 'OS::Test::Example'});
+ }));
+
+ it('exists', function() {
+ expect(ctrl).toBeDefined();
+ });
+
+ it('sets itemsSrc to the response data', function() {
+ listFunctionDeferred.resolve({data: {items: [1,2,3]}});
+ $timeout.flush();
+ expect(ctrl.itemsSrc).toEqual([1,2,3]);
+ });
+
+ describe('actionResultHandler', function() {
+ beforeEach(function() {
+ ctrl.itemsSrc = [{type: 'Something', id: -1}, {type: 'OS::Test::Example', id: 1}];
+ });
+
+ it('handles deleted items', function() {
+ actionResultDeferred.resolve({deleted: [{type: 'ignored', id: 0},
+ {type: 'OS::Test::Example', id: 1}]});
+ var promise = ctrl.actionResultHandler(actionResultDeferred.promise);
+ promise.then(function() {
+ expect(ctrl.itemsSrc).toEqual([{type: 'Something', id: -1}]);
+ });
+ $timeout.flush();
+ });
+
+ it('handles updated items', function() {
+ actionResultDeferred.resolve({updated: [{type: 'OS::Test::Example', id: 1}]});
+ var promise = ctrl.actionResultHandler(actionResultDeferred.promise);
+ resourceType.listFunction.calls.reset();
+ promise.then(function() {
+ expect(resourceType.listFunction).toHaveBeenCalled();
+ });
+ $timeout.flush();
+ });
+
+ it('handles created items', function() {
+ actionResultDeferred.resolve({created: [{type: 'OS::Test::Example', id: 1}]});
+ var promise = ctrl.actionResultHandler(actionResultDeferred.promise);
+ resourceType.listFunction.calls.reset();
+ promise.then(function() {
+ expect(resourceType.listFunction).toHaveBeenCalled();
+ });
+ $timeout.flush();
+ });
+
+ it('handles failed items', function() {
+ actionResultDeferred.resolve({failed: [{type: 'OS::Test::Example', id: 1}]});
+ var promise = ctrl.actionResultHandler(actionResultDeferred.promise);
+ resourceType.listFunction.calls.reset();
+ promise.then(function() {
+ expect(resourceType.listFunction).not.toHaveBeenCalled();
+ });
+ $timeout.flush();
+ });
+
+ it('handles falsy results', function() {
+ actionResultDeferred.resolve(false);
+ var promise = ctrl.actionResultHandler(actionResultDeferred.promise);
+ resourceType.listFunction.calls.reset();
+ promise.then(function() {
+ expect(resourceType.listFunction).toHaveBeenCalled();
+ });
+ $timeout.flush();
+ });
+ });
+
+ });
+
+})();
diff --git a/horizon/static/framework/widgets/table/hz-resource-table.directive.js b/horizon/static/framework/widgets/table/hz-resource-table.directive.js
new file mode 100644
index 0000000000..49c60f6daa
--- /dev/null
+++ b/horizon/static/framework/widgets/table/hz-resource-table.directive.js
@@ -0,0 +1,59 @@
+/**
+ * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
+ *
+ * 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';
+
+ angular
+ .module('horizon.framework.widgets.table')
+ .directive('hzResourceTable', directive);
+
+ directive.$inject = ['horizon.framework.widgets.basePath'];
+
+ /**
+ * @ngdoc directive
+ * @name hzResourceTable
+ * @description
+ * This directive produces a table and accompanying components that describe
+ * a list of resources of the given type. Based on information in the
+ * registry, the batch, global, and item-level actions are presented as
+ * appropriate. Search capabilities are also provided. The table contents
+ * are responsive to actions' promise resolutions, updating contents when
+ * they are likely to have changed. This directive allows for the rapid
+ * development of standard resource tables without having to rewrite
+ * boilerplate controllers, markup, etc.
+ * @example
+ ```
+ Here's some content above the table.
+
+ Here's some content below the table.
+ ```
+ */
+
+ function directive(basePath) {
+
+ var directive = {
+ restrict: 'E',
+ scope: {
+ resourceTypeName: '@'
+ },
+ bindToController: true,
+ templateUrl: basePath + 'table/hz-resource-table.html',
+ controller: "horizon.framework.widgets.table.ResourceTableController as ctrl"
+ };
+
+ return directive;
+ }
+})();
diff --git a/horizon/static/framework/widgets/table/hz-resource-table.html b/horizon/static/framework/widgets/table/hz-resource-table.html
new file mode 100644
index 0000000000..2ca4e71e72
--- /dev/null
+++ b/horizon/static/framework/widgets/table/hz-resource-table.html
@@ -0,0 +1,10 @@
+
diff --git a/horizon/static/framework/widgets/widgets.module.js b/horizon/static/framework/widgets/widgets.module.js
index 8384fe80a9..2c0969a47d 100644
--- a/horizon/static/framework/widgets/widgets.module.js
+++ b/horizon/static/framework/widgets/widgets.module.js
@@ -26,6 +26,7 @@
'horizon.framework.widgets.table',
'horizon.framework.widgets.modal',
'horizon.framework.widgets.modal-wait-spinner',
+ 'horizon.framework.widgets.panel',
'horizon.framework.widgets.transfer-table',
'horizon.framework.widgets.charts',
'horizon.framework.widgets.action-list',
diff --git a/releasenotes/notes/resource-directives-44629f1116545141.yaml b/releasenotes/notes/resource-directives-44629f1116545141.yaml
new file mode 100644
index 0000000000..e7b29438f3
--- /dev/null
+++ b/releasenotes/notes/resource-directives-44629f1116545141.yaml
@@ -0,0 +1,14 @@
+---
+prelude: >
+ Angular components now exist to provide simple-to-
+ configure panels and tables, based off of registry
+ information about resources (e.g. Instances).
+features:
+ - The hz-resource-table directive takes in a Heat
+ resource name (e.g. 'OS::Nova::Server') and uses
+ the Angular registry to provide actions, columns,
+ and summary views.
+ - The hz-resource-panel directive takes in a Heat
+ resource name (e.g. 'OS::Nova::Server') and
+ displays an appropriate header and allows content
+ to be transcluded to build the panel page.