Skip to content

Commit

Permalink
Move the periodic bulk updater for tasks to Task
Browse files Browse the repository at this point in the history
  • Loading branch information
iNecas committed Jan 15, 2014
1 parent 54fd9f4 commit 5a7c645
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 128 deletions.
74 changes: 39 additions & 35 deletions app/controllers/katello/api/v2/tasks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def rules
{
:index => test,
:show => test,
:search => dummy,
:bulk_search => dummy,
}
end

Expand All @@ -53,8 +53,12 @@ def show
respond_for_show
end

api :POST, "/tasks/search", "List dynflow tasks for uuids"
param :conditions, Array, :desc => 'List of uuids to fetch info about' do
api :POST, "/tasks/bulk_search", "List dynflow tasks for uuids"
param :searches, Array, :desc => 'List of uuids to fetch info about' do
param :search_id, String, :desc => <<-DESC
Arbitraty value for client to identify the the request parts with results.
It's passed in the results to be able to pair the requests and responses properly.
DESC
param :type, %w[user resource task]
param :task_id, String, :desc => <<-DESC
In case :type = 'task', find the task by the uuid
Expand All @@ -73,72 +77,72 @@ def show
param :per_page, String
end
desc <<-DESC
For every condition it returns the list of tasks that satisfty the condition.
The reason for supporting multiple conditions is the UI that might be ending
needing periodic updates on task status for various conditions at the same time.
For every search it returns the list of tasks that satisfty the condition.
The reason for supporting multiple searches is the UI that might be ending
needing periodic updates on task status for various searches at the same time.
This way, it is possible to get all the task statuses with one request.
DESC
def search
conditions = Array(params[:conditions])
def bulk_search
searches = Array(params[:searches])
@tasks = {}

ret = conditions.map do |condition|
{ condition: condition,
tasks: condition_tasks(condition) }
ret = searches.map do |search_params|
{ search_params: search_params,
results: search_tasks(search_params) }
end
render :json => ret
end

private

def condition_tasks(condition)
def search_tasks(search_params)
scope = DynflowTask
scope = ordering_scope(scope, condition)
scope = search_scope(scope, condition)
scope = active_scope(scope, condition)
scope = pagination_scope(scope, condition)
scope = ordering_scope(scope, search_params)
scope = search_scope(scope, search_params)
scope = active_scope(scope, search_params)
scope = pagination_scope(scope, search_params)
scope.all.map { |task| task_hash(task) }
end

def search_scope(scope, condition)
case condition[:type]
def search_scope(scope, search_params)
case search_params[:type]
when 'user'
if condition[:user_id].blank?
raise HttpErrors::BadRequest, _("User condition requires user_id to be specified")
if search_params[:user_id].blank?
raise HttpErrors::BadRequest, _("User search_params requires user_id to be specified")
end
scope.where(user_id: condition[:user_id])
scope.where(user_id: search_params[:user_id])
when 'resource'
if condition[:resource_type].blank? || condition[:resource_id].blank?
raise HttpErrors::BadRequest, _("Resource condition requires resource_type and resource_id to be specified")
if search_params[:resource_type].blank? || search_params[:resource_id].blank?
raise HttpErrors::BadRequest, _("Resource search_params requires resource_type and resource_id to be specified")
end
scope.joins(:dynflow_locks).where(dynflow_locks:
{ resource_type: condition[:resource_type],
resource_id: condition[:resource_id] })
{ resource_type: search_params[:resource_type],
resource_id: search_params[:resource_id] })
when 'task'
if condition[:task_id].blank?
raise HttpErrors::BadRequest, _("Task condition requires task_id to be specified")
if search_params[:task_id].blank?
raise HttpErrors::BadRequest, _("Task search_params requires task_id to be specified")
end
scope.where(uuid: condition[:task_id])
scope.where(uuid: search_params[:task_id])
else
raise HttpErrors::BadRequest, _("Condition %s not supported") % condition[:type]
raise HttpErrors::BadRequest, _("Search_Params %s not supported") % search_params[:type]
end
end

def active_scope(scope, condition)
if condition[:active_only]
def active_scope(scope, search_params)
if search_params[:active_only]
scope.active
else
scope
end
end

def pagination_scope(scope, condition)
page = condition[:page] || 1
per_page = condition[:per_page] || 10
def pagination_scope(scope, search_params)
page = search_params[:page] || 1
per_page = search_params[:per_page] || 10
scope = scope.limit(per_page).offset((page - 1) * per_page)
end

def ordering_scope(scope, condition)
def ordering_scope(scope, search_params)
scope.joins(:dynflow_execution_plan).
order('dynflow_execution_plans.started_at DESC')
end
Expand Down
2 changes: 1 addition & 1 deletion config/routes/api/v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ class ActionDispatch::Routing::Mapper

api_resources :sync_plans, :only => [:show, :update, :destroy]
api_resources :tasks, :only => [:show] do
post :search, :on => :collection
post :bulk_search, :on => :collection
end
api_resources :about, :only => [:index]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
* Provides the functionality for the repo discovery action pane.
*/
angular.module('Bastion.products').controller('DiscoveryController',
['$scope', '$q', '$timeout', '$http', 'taskListProvider', 'Organization', 'CurrentOrganization',
function ($scope, $q, $timeout, $http, taskListProvider, Organization, CurrentOrganization) {
['$scope', '$q', '$timeout', '$http', 'Task', 'Organization', 'CurrentOrganization',
function ($scope, $q, $timeout, $http, Task, Organization, CurrentOrganization) {
var transformRows, setDiscoveryDetails;

$scope.discovery = {url: ''};
Expand Down Expand Up @@ -84,18 +84,10 @@ angular.module('Bastion.products').controller('DiscoveryController',
});
};

Organization.get({id: CurrentOrganization}, function (org) {
if (org['discovery_task_id']) {
Task.get({id: org['discovery_task_id']}, function (task) {
pollTask(task);
});
}
});

$scope.updateTask = function (task) {
setDiscoveryDetails(task);
if(!task.pending) {
taskListProvider.unregisterScope($scope);
Task.unregisterScope($scope.taskSearchId);
}
}

Expand All @@ -104,7 +96,7 @@ angular.module('Bastion.products').controller('DiscoveryController',
$scope.discoveryTable.rows = [];
$scope.discoveryTable.selectAll(false);
Organization.repoDiscover({id: CurrentOrganization, url: $scope.discovery.url}, function (task) {
taskListProvider.registerScope($scope, { type: 'task', task_id: task.uuid })
$scope.taskSearchId = Task.registerSearch({ type: 'task', task_id: task.uuid }, $scope.updateTask);
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
* Provides the functionality for the details of a system event.
*/
angular.module('Bastion.systems').controller('TaskDetailsController',
['$scope', 'taskListProvider',
function($scope, taskListProvider) {
['$scope', 'Task',
function($scope, Task) {
var taskId, fromState, fromParams;

fromState = 'systems.details.tasks.index';
Expand All @@ -46,10 +46,10 @@ angular.module('Bastion.systems').controller('TaskDetailsController',
$scope.updateTask = function(task) {
$scope.task = task;
if(!$scope.task.pending) {
taskListProvider.unregisterScope($scope);
Task.unregisterSearch($scope.searchId);
}
}

taskListProvider.registerScope($scope, { type: 'task', task_id: taskId })
$scope.searchId = Task.registerSearch({ type: 'task', task_id: taskId }, $scope.updateTask)
}
]);
101 changes: 100 additions & 1 deletion engines/bastion/app/assets/bastion/tasks/task.factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,79 @@ angular.module('Bastion.tasks').factory('Task',
var resource = $resource('/katello/api/tasks/:id/:action',
{id: '@uuid', 'organization_id': CurrentOrganization},
{
query: {method: 'GET', isArray: false}
query: {method: 'GET', isArray: false},
bulkSearch: {method:'POST', isArray: true, params: { action: 'bulk_search'}}
}
);


var bulkSearchRunning = false, searchIdGenerator = 0,
searchParamsById = {}, callbackById = {}, timoutId;

function bulkSearchParams() {
var searches = []
_.each(searchParamsById, function(searchParams, id) {
searchParams['search_id'] = id;
searches.push(searchParams);
});
return { searches: searches };
}

function updateProgress(periodic) {
if(_.keys(searchParamsById).length == 0) {
return;
}
resource.bulkSearch(bulkSearchParams(), function(response) {
try {
_.each(response, function(tasksSearch) {
var searchId = tasksSearch['search_params']['search_id'];
var callback = callbackById[searchId];
try {
if(tasksSearch['search_params']['type'] == 'task') {
callback(tasksSearch['results'][0]);
} else {
callback(tasksSearch['results']);
}
}
catch(e) {
console.log(e);
}
});
}
finally {
// schedule the next update
if(periodic) {
$timeout(function() { updateProgress(periodic); }, 1500);
};
}
});
}

function ensureBulkSearchRunning() {
if(!bulkSearchRunning) {
bulkSearchRunning = true;
updateProgress(true);
}
}

function generateSearchId() {
searchIdGenerator++;
return searchIdGenerator;
}

function addSearch(searchParams, callback) {
var searchId = generateSearchId();
searchParamsById[searchId] = searchParams;
callbackById[searchId] = callback;
ensureBulkSearchRunning();
return searchId;
};

function deleteSearch(searchId) {
delete callbackById[searchId];
delete searchParamsById[searchId];
};

resource.poll = function (task, returnFunction) {
// TODO: remove task.id once we get rid of old TaskStatus code
resource.get({id: (task.id || task.uuid)}, function (data) {
Expand All @@ -48,6 +117,36 @@ angular.module('Bastion.tasks').factory('Task',
returnFunction({'human_readable_result': "Failed to fetch task", failed: true});
});
};

/**
* Registers a search for polling. The polling is
* is performed using bulkSearch for all the searchParamsById at once
* to avoid overloading the server with muptiple requests
* (since it is performed periodically)
*
* @param {Object} searchParams object specifying the params
* of the search.
*
* @param {Function} callback function to reflect the
* results. If searchParams.type == 'task', the
* function is called with a single task, otherwise
* it's passed with array of tasks sattisfying the
* coditions.
*
* @return {Number} the autogenerated id of the condition
* that can be used for unregistering the search later on
*/
resource.registerSearch = function(searchParams, callback) {
return addSearch(searchParams, callback);
}

/**
* Unregisters the search from polling.
*
* @param {Number} id the value returned by the registerSearch
*/
resource.unregisterSearch = function(id) { deleteSearch(id); }

return resource;
}]
);
7 changes: 3 additions & 4 deletions engines/bastion/app/assets/bastion/tasks/tasks.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
* Provides the functionality for the system events list pane.
*/
angular.module('Bastion.tasks').controller('TasksController',
['$scope', '$state', 'Task', 'Nutupane', 'taskListProvider',
function($scope, $state, Task, Nutupane, taskListProvider) {
['$scope', '$state', 'Task', 'Nutupane', 'Task',
function($scope, $state, Task, Nutupane, Task) {
var params, tasksNutupane;
var systemId = 1;
var systemUuid = $scope.$stateParams.systemId;
Expand All @@ -42,8 +42,7 @@ angular.module('Bastion.tasks').controller('TasksController',
self.existingTasks = {}

self.register = function(scope) {
scope.updateTasks = self.updateTasks;
taskListProvider.registerScope(scope, params);
self.searchId = Task.registerSearch(params, self.updateTasks);
};

self.load = function(replace) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
**/

angular.module('Bastion.widgets').directive('currentTasks',
['$document', 'CurrentUser', 'taskListProvider',
function($document, CurrentUser, taskListProvider) {
['$document', 'CurrentUser', 'Task',
function($document, CurrentUser, Task) {

return {
restrict: 'A',
Expand Down Expand Up @@ -46,7 +46,7 @@ angular.module('Bastion.widgets').directive('currentTasks',
});
}],
link: function(scope) {
taskListProvider.registerScope(scope, { active_only: true, type: 'user', user_id: CurrentUser});
Task.registerSearch({ active_only: true, type: 'user', user_id: CurrentUser}, scope.updateTasks);
}
};
}]);
Loading

0 comments on commit 5a7c645

Please sign in to comment.