Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #24429 - Module stream host bulk actions #7736

Merged
merged 9 commits into from
Oct 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/controllers/katello/api/v2/hosts_bulk_actions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,18 @@ def available_incremental_updates
respond_for_index :collection => response, :template => :available_incremental_updates
end

api :POST, "/hosts/bulk/module_streams",
N_("Fetch available module streams for hosts.")
param_group :bulk_params
def module_streams
options = {}
options[:group] = [:name, :stream]
options[:resource_class] = Katello::ModuleStream
host_module_streams = Katello::ModuleStream.available_for_hosts(@hosts)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does @hosts get set here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh find_editable_hosts is set to :except =>
nevermind :)

respond_for_index(collection: scoped_search(host_module_streams, :name, :asc, options),
template: '../../../api/v2/module_streams/name_streams')
end

private

def find_errata
Expand Down
15 changes: 10 additions & 5 deletions app/controllers/katello/remote_execution_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module Katello
if Katello.with_remote_execution?
class RemoteExecutionController < JobInvocationsController
include Concerns::Api::V2::BulkHostsExtensions

def new
@composer = prepare_composer
end
Expand Down Expand Up @@ -28,11 +30,14 @@ def prepare_composer
end

def hosts
if params[:scoped_search].present?
params[:scoped_search]
else
::Host.where(:id => params[:host_ids].try(:split, ','))
end
host_ids = params[:host_ids].is_a?(String) ? params[:host_ids].split(',') : params[:host_ids]
find_bulk_hosts('edit_hosts',
included: {
ids: host_ids,
search: params[:scoped_search],
excluded: params[:excluded]
}
)
end

def inputs
Expand Down
1 change: 1 addition & 0 deletions config/routes/overrides.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def matches?(request)
match '/bulk/environment_content_view' => 'hosts_bulk_actions#environment_content_view', :via => :put
match '/bulk/release_version' => 'hosts_bulk_actions#release_version', :via => :put
match '/bulk/available_incremental_updates' => 'hosts_bulk_actions#available_incremental_updates', :via => :post
match '/bulk/module_streams' => 'hosts_bulk_actions#module_streams', :via => :post
match '/subscriptions/' => 'host_subscriptions#create', :via => :post
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
(function () {
'use strict';

/**
* @ngdoc service
* @name Bastion.common.service:ModuleStreamActions
*
* @description
* Provides common list of actions for module streams
*/

function ModuleStreamActions(translate) {
this.getActions = function() {
return [
{ action: 'enable', description: translate("Enable")},
{ action: 'disable', description: translate("Disable")},
{ action: 'install', description: translate("Install")},
{ action: 'update', description: translate("Update")},
{ action: 'remove', description: translate("Remove")}
];
};
}

angular.module('Bastion.common').service('ModuleStreamActions', ModuleStreamActions);
ModuleStreamActions.$inject = ['translate'];
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @ngdoc object
* @name Bastion.content-hosts.controller:ContentHostsBulkModuleStreamsModalController
*
* @requires $scope
* @requires $window
* @requires $timeout
* @requires $uibModalInstance
* @requires HostBulkAction
* @requires Nutupane
* @requires BastionConfig
* @requires hostIds
* @requires ModuleStreamActions
*
* @description
* Provides the functionality for the content host module streams list and actions.
*/

angular.module('Bastion.content-hosts').controller('ContentHostsBulkModuleStreamsModalController',
['$scope', '$window', '$timeout', '$uibModalInstance', 'HostBulkAction', 'Nutupane', 'BastionConfig',
'hostIds', 'ModuleStreamActions',
function ($scope, $window, $timeout, $uibModalInstance, HostBulkAction, Nutupane,
BastionConfig, hostIds, ModuleStreamActions) {
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};

$scope.moduleStreamActions = ModuleStreamActions.getActions();

$scope.working = false;

$scope.moduleStreamsNutupane = new Nutupane(HostBulkAction, hostIds, 'moduleStreams');
$scope.controllerName = 'katello_module_streams';
$scope.moduleStreamsNutupane.masterOnly = true;
$scope.table = $scope.moduleStreamsNutupane.table;
$scope.remoteExecutionPresent = BastionConfig.remoteExecutionPresent;
$scope.remoteExecutionByDefault = BastionConfig.remoteExecutionByDefault;

$scope.moduleStreamActionFormValues = {
authenticityToken: $window.AUTH_TOKEN.replace(/&quot;/g, ''),
remoteAction: 'module_stream_action',
search: hostIds.included.search
};

if (hostIds.included.ids) {
$scope.moduleStreamActionFormValues.hostIds = hostIds.included.ids.join(',');
}

$scope.performViaRemoteExecution = function(moduleSpec, actionType) {
$scope.working = true;
$scope.moduleStreamActionFormValues.moduleSpec = moduleSpec;
$scope.moduleStreamActionFormValues.moduleStreamAction = actionType;

$timeout(function () {
angular.element('#moduleStreamActionForm').submit();
}, 0);
};
}
]
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<div data-extend-template="components/views/bst-modal.html">
<h4 data-block="modal-header">Content Host Module Stream Management</h4>
<div data-block="modal-body">
<div bst-feature-flag="remote_actions">
<h3 translate ng-hide="selectedModuleStreamOption === 'available'">Available Module Streams</h3>
</div>

<form id="moduleStreamActionForm" method="post" action="/katello/remote_execution">
<input type="hidden" name="remote_action" ng-value="moduleStreamActionFormValues.remoteAction"/>
<input type="hidden" name="module_stream_action" ng-value="moduleStreamActionFormValues.moduleStreamAction"/>
<input type="hidden" name="module_spec" ng-value="moduleStreamActionFormValues.moduleSpec"/>
<input type="hidden" name="scoped_search" ng-value="errataActionFormValues.search"/>
<input type="hidden" name="host_ids" ng-value="moduleStreamActionFormValues.hostIds"/>
<input type="hidden" name="customize" ng-value="moduleStreamActionFormValues.customize"/>
<input type="hidden" name="authenticity_token" ng-value="moduleStreamActionFormValues.authenticityToken"/>
</form>

<div data-extend-template="layouts/partials/table.html">

<span data-block="no-rows-message" translate>
There are no Module Streams to display.
</span>

<span data-block="no-search-results-message" translate>
Your search returned zero Module Streams.
</span>
<div data-block="table">
<table class="table table-striped table-bordered" ng-class="{'table-mask': table.working}">
<thead>
<tr bst-table-head>
<th bst-table-column translate>Name</th>
<th bst-table-column translate>Stream</th>
<th bst-table-column translate>Actions</th>
</tr>
</thead>

<tbody>
<tr bst-table-row ng-repeat="module in table.rows">
<td bst-table-cell>
<a href="/module_streams?search=module_spec%3D{{ module.module_spec }}">
{{ module.name }}</a></td>
<td bst-table-cell>{{ module.stream }}</td>
<td bst-table-cell>
<div class="dropdown" ng-hide="!remoteExecutionPresent || denied('edit_content_hosts', contentHost)">
<button class="btn btn-default dropdown-toggle" ng-disabled="$scope.working" type="button" id="dropdownMenu1" data-toggle="dropdown"> Actions
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
<li role="presentation">
<label class="drop-down-check-box">
<input id="customize" name="customize" ng-model="moduleStreamActionFormValues.customize" type="checkbox"/>
<span translate>Customize</span>
</label>
</li>
<li role="presentation" class="divider"></li>

<li role="presentation" ng-repeat="action in moduleStreamActions"><a role="menuitem" tabindex="-1" ng-click="performViaRemoteExecution(module.module_spec, action.action)" href="#">{{ action.description}}</a></li>
</ul>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div data-block="modal-footer">
<button class="btn btn-default" ng-click="cancel()" translate>Cancel</button>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,16 @@ angular.module('Bastion.content-hosts').service('ContentHostsModalHelper', ['$ui
}
});
};

this.openModuleStreamsModal = function() {
$uibModal.open({
templateUrl: 'content-hosts/bulk/views/content-host-bulk-module-streams-modal.html',
controller: 'ContentHostsBulkModuleStreamsModalController',
size: 'lg',
resolve: {
hostIds: this.resolveFunc()
}
});
};
}]
);
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,10 @@ angular.module('Bastion.content-hosts').controller('ContentHostsController',
nutupane.invalidate();
ContentHostsModalHelper.openSubscriptionsModal();
};

$scope.openModuleStreamsModal = function () {
nutupane.invalidate();
ContentHostsModalHelper.openModuleStreamsModal();
};
}]
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,17 @@
* @resource $timeout
* @resource $window
* @requires ModuleStream
* @requires translate
* @requires Nutupane
* @requires ModuleStreamActions
*
* @description
* Provides the functionality for the content host module streams list and actions.
*/
angular.module('Bastion.content-hosts').controller('ContentHostModuleStreamsController',
['$scope', '$timeout', '$window', 'ModuleStream', 'translate', 'Nutupane', 'BastionConfig',
function ($scope, $timeout, $window, ModuleStream, translate, Nutupane, BastionConfig) {
$scope.moduleStreamActions = [
{ action: 'enable', description: translate("Enable")},
{ action: 'disable', description: translate("Disable")},
{ action: 'install', description: translate("Install")},
{ action: 'update', description: translate("Update")},
{ action: 'remove', description: translate("Remove")}
];
['$scope', '$timeout', '$window', 'ModuleStream', 'Nutupane', 'BastionConfig', 'ModuleStreamActions',
function ($scope, $timeout, $window, ModuleStream, Nutupane, BastionConfig, ModuleStreamActions) {
$scope.moduleStreamActions = ModuleStreamActions.getActions();

$scope.working = false;

$scope.moduleStreamsNutupane = new Nutupane(ModuleStream, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<span page-title ng-model="host">{{ 'Module Streams for: ' | translate }} {{ host.name }}</span>

<div bst-feature-flag="remote_actions">
<h3 translate ng-hide="selectedModuleStreamOption === 'available'">Available Module Streams</h3>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ <h2 translate>Content Hosts</h2>
<a ng-click="openRepositorySetsModal()" disable-link="table.numSelected === 0" translate>Manage Repository Sets</a>
</li>

<li role="menuitem" ng-show="permitted('edit_hosts')" ng-class="{disabled: table.numSelected === 0}">
<a ng-click="openModuleStreamsModal()" disable-link="table.numSelected === 0" translate>Manage Module Streams</a>
</li>

<li class="divider"></li>

<li role="menuitem" ng-show="permitted('destroy_hosts')" ng-class="{disabled: table.numSelected === 0}">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ angular.module('Bastion.host-collections').controller('HostCollectionDetailsCont
ContentHostsModalHelper.openSubscriptionsModal();
};

$scope.openModuleStreamsModal = function () {
ContentHostsModalHelper.openModuleStreamsModal();
};

$scope.save = function (hostCollection) {
var deferred = $q.defer();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ <h3 translate>Actions</h3>
</a>
</li>

<li>
<a translate ng-click = "openModuleStreamsModal()">
Module Stream Management
</a>
</li>

<li bst-feature-flag="lifecycle_environments">
<a translate ng-click="openEnvironmentModal()">
Change assigned Lifecycle Environment or Content View
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ angular.module('Bastion.hosts').factory('HostBulkAction',
destroyHosts: {method: 'PUT', params: {action: 'destroy'}},
environmentContentView: {method: 'PUT', params: {action: 'environment_content_view'}},
releaseVersion: {method: 'PUT', params: {action: 'release_version'}},
availableIncrementalUpdates: {method: 'POST', isArray: true, params: {action: 'available_incremental_updates'}}
availableIncrementalUpdates: {method: 'POST', isArray: true, params: {action: 'available_incremental_updates'}},
moduleStreams: {method: 'POST', params: {action: 'module_streams'}}
});

}]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
describe('Controller: ContentHostsBulkModuleStreamsModalController', function() {
var $scope, $controller, $uibModalInstance, hostIds, translate, ModuleStream,
HostBulkAction, CurrentOrganization, Nutupane, formValues;

beforeEach(module('Bastion.content-hosts', 'Bastion.test-mocks'));

beforeEach(function() {
HostBulkAction = {
installContent: function() {}
};
translate = function() {};
CurrentOrganization = 'foo';
selectedModuleStreams = [1, 2, 3, 4];
Nutupane = function() {
this.table = {
showColumns: function () {},
getSelected: function () {return selectedModuleStreams }
};

this.setParams = function () {}
};

$uibModalInstance = {
close: function () {},
dismiss: function () {}
};

hostIds = {included: {ids: [1, 2, 3]}};
ModuleStream = {};
ModuleStreamAction = {};
});

beforeEach(inject(function(_$controller_, $rootScope, $q, $window) {
$window.AUTH_TOKEN = 'secret_token';
$scope = $rootScope.$new();
$controller = _$controller_;

$scope.table = {
rows: [],
numSelected: 5
};

$controller('ContentHostsBulkModuleStreamsModalController', {
$scope: $scope,
$uibModalInstance: $uibModalInstance,
ModuleStream: ModuleStream,
hostIds: hostIds,
Nutupane: Nutupane,
translate: translate,
CurrentOrganization: CurrentOrganization,
ModuleStreamAction: ModuleStreamAction
});
}));

it("provides a function for cancelling the modal", function () {
spyOn($uibModalInstance, 'dismiss');
$scope.cancel();
expect($uibModalInstance.dismiss).toHaveBeenCalled();
});

it("can call module stream actions on multiple content hosts", function() {
formValues = {
authenticityToken: 'secret_token',
remoteAction: 'module_stream_action',
search: undefined,
hostIds: '1,2,3',
moduleSpec: 'django:1.9',
moduleStreamAction: 'enable'
};
$scope.performViaRemoteExecution("django:1.9", "enable");;
expect($scope.moduleStreamActionFormValues).toEqual(formValues);
});
});
Loading