Resolve fact versions before looking up fact views

This adds a step to the fact version lookup that attempts to resolve
fact versions before looking up their views. Resolving a fact version
means finding another version to use when there is a problem with the
one the user selected.

In this case, the resolution process works as follows:

1. Check if either request returned an empty list

If so, request again, changing the dates to look back a year. This
will almost always return something.

2. If we are requesting multiple facts for the same host, and both of
the resolved versions have the same timestamp, they are duplicates and
we cannot compare against them.

Instead we instruct it to grab the selected and the previous versions for
comparison.

For host-to-host view, this also updates the timestamp in the header to
reflect the actual timestamps. It _does not_ update the date picker
dates yet.
This commit is contained in:
Joe Fiorini
2015-06-03 00:12:00 -04:00
parent 0591812da6
commit 9526975318
7 changed files with 169 additions and 73 deletions

View File

@@ -6,13 +6,36 @@
export default ['Rest', 'GetBasePath', 'ProcessErrors', 'lodashAsPromised', export default ['Rest', 'GetBasePath', 'ProcessErrors', 'lodashAsPromised',
function (Rest, GetBasePath, ProcessErrors, _) { function (Rest, GetBasePath, ProcessErrors, _) {
function buildUrl (host_id, module, startDate, endDate) {
var url = GetBasePath('hosts') + host_id + '/fact_versions/',
params= [["module", module] , ['from', startDate.format()], ['to', endDate.format()]];
params = params.filter(function(p){
return !_.isEmpty(p[1]);
});
params = params.map(function(p){
return p.join("=");
}).join("&");
url = _.compact([url, params]).join("?");
return url;
}
return { return {
getHostFacts: function(host, moduleName, date, fetchScanNumber) { getHostFacts: function(host, moduleName, date, fetchScanNumber) {
var version =this.getVersion(host, moduleName, date.from, date.to, fetchScanNumber); var getVersion = _.partial(this.getVersion, host, moduleName);
var getFacts = this.getFacts; var getFacts = this.getFacts;
return version return getVersion(date.from, date.to, fetchScanNumber)
.then(function(versionData) {
if (_.isEmpty(versionData)) {
var retryStartDate = date.from.clone().subtract(1, 'year');
return getVersion(retryStartDate, date.from, fetchScanNumber);
} else {
return versionData;
}
})
.then(function(versionData) { .then(function(versionData) {
if (_.isEmpty(versionData)) { if (_.isEmpty(versionData)) {
return { fact: [] }; return { fact: [] };
@@ -37,18 +60,21 @@ function (Rest, GetBasePath, ProcessErrors, _) {
}); });
}, },
getVersion: function(host_id, module, startDate, endDate, fetchScanNumber){ getVersion: function(versionParams){
//move the build url into getVersion and have the //move the build url into getVersion and have the
// parameters passed into this // parameters passed into this
var promise, var promise;
url = this.buildUrl(host_id, module, startDate, endDate); var hostId = versionParams.hostId;
var startDate = versionParams.dateRange.from;
var endDate = versionParams.dateRange.to;
var module = versionParams.moduleName;
fetchScanNumber = fetchScanNumber || 0; var url = buildUrl(hostId, module, startDate, endDate);
Rest.setUrl(url); Rest.setUrl(url);
promise = Rest.get(); promise = Rest.get();
return promise.then(function(data) { return promise.then(function(response) {
return data.data.results[fetchScanNumber]; return response.data.results;
}).catch(function (response) { }).catch(function (response) {
ProcessErrors(null, response.data, response.status, null, { ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!', hdr: 'Error!',
@@ -56,20 +82,6 @@ function (Rest, GetBasePath, ProcessErrors, _) {
response.status response.status
}); });
}); });
},
buildUrl: function(host_id, module, startDate, endDate){
var url = GetBasePath('hosts') + host_id + '/fact_versions/',
params= [["module", module] , ['from', startDate.format()], ['to', endDate.format()]];
params = params.filter(function(p){
return !_.isEmpty(p[1]);
});
params = params.map(function(p){
return p.join("=");
}).join("&");
url = _.compact([url, params]).join("?");
return url;
} }
}; };
}]; }];

View File

@@ -7,8 +7,9 @@
export default export default
[ 'factScanDataService', [ 'factScanDataService',
'getModuleOptions', 'getModuleOptions',
'resolveVersions',
'lodashAsPromised', 'lodashAsPromised',
function(factScanDataService, getModuleOptions, _) { function(factScanDataService, getModuleOptions, resolveVersions) {
return function(hostIds, moduleName, leftDate, rightDate) { return function(hostIds, moduleName, leftDate, rightDate) {
var moduleOptions; var moduleOptions;
@@ -17,39 +18,40 @@ export default
hostIds = hostIds.concat(hostIds[0]); hostIds = hostIds.concat(hostIds[0]);
} }
var hostVersionParams =
[{ hostId: hostIds[0],
dateRange: leftDate,
moduleName: moduleName
},
{ hostId: hostIds[1],
dateRange: rightDate,
moduleName: moduleName
}
];
return getModuleOptions(hostIds[0]) return getModuleOptions(hostIds[0])
.then(function(modules) { .then(function(modules) {
moduleOptions = modules; moduleOptions = modules;
return modules; return hostVersionParams;
}).then(function() { }).thenMap(function(versionParam) {
return hostIds; var versionWithRequest =
}).thenMap(function(hostId, index) { [ versionParam,
var date = leftDate; factScanDataService.
var fetchScanNumber; getVersion(versionParam)
if (index === 1) {
date = rightDate;
} else {
if (rightDate.from.isSame(leftDate.from, 'day')) {
fetchScanNumber = 1;
}
}
var params =
[ hostId,
moduleName,
date,
fetchScanNumber
]; ];
return params; return versionWithRequest;
}).thenMap(function(params) { }).thenAll(function(versions) {
var getHostFacts = return resolveVersions(versions);
_.spread(factScanDataService.getHostFacts) }, true)
.bind(factScanDataService); .thenMap(function(versionData) {
if (versionData) {
return getHostFacts(params); return factScanDataService.getFacts(versionData);
}).then(function(hostFacts) { } else {
return { fact: [] };
}
})
.thenAll(function(hostFacts) {
return [moduleOptions, hostFacts]; return [moduleOptions, hostFacts];
}); });
}; };

View File

@@ -0,0 +1,75 @@
function resolveVersions(service, _, results) {
function transformToObjects(versionArray) {
var converted = versionArray[0];
converted.versions = versionArray[1];
return converted;
}
function resolveEmpties(result) {
if (_.isEmpty(result.versions)) {
var originalStartDate = result.dateRange.from;
result.dateRange.from = originalStartDate.clone().subtract(1, 'year');
result.dateRange.to = originalStartDate;
return [result, service.getVersion(result)];
}
return [result, _.promise(result.versions)];
}
function resolveDuplicates(nonEmptyResults) {
var allSameHost =
_.every(nonEmptyResults, { 'hostId': nonEmptyResults[0].hostId });
if (allSameHost) {
var firstTimestamp = nonEmptyResults[0].versions[0].timestamp;
var hostIdsWithDupes =
_(nonEmptyResults)
.pluck('versions[0]')
.filter('timestamp', firstTimestamp)
.map(function(version, index) {
return nonEmptyResults[index].hostId;
})
.value();
if (hostIdsWithDupes.length === 1) {
return _.pluck(nonEmptyResults, 'versions[0]');
}
return nonEmptyResults.map(function(scan, index) {
var hasDupe =
_.include(hostIdsWithDupes, scan.hostId);
if (hasDupe && index === 1) {
return scan.versions[1];
} else {
return scan.versions[0];
}
});
} else {
return _.pluck(nonEmptyResults, 'versions[0]');
}
}
return _(results)
.map(transformToObjects)
.map(resolveEmpties)
.thenAll(function(resolved) {
var versionObjects = resolved.map(transformToObjects);
return resolveDuplicates(versionObjects);
}, true)
.value();
}
export default
[ 'factScanDataService',
'lodashAsPromised',
function(factScanDataService, lodash) {
return _.partial(resolveVersions, factScanDataService, lodash);
}
];

View File

@@ -8,6 +8,7 @@ import route from './system-tracking.route';
import factScanDataService from './data-services/fact-scan-data.service'; import factScanDataService from './data-services/fact-scan-data.service';
import getDataForComparison from './data-services/get-data-for-comparison.factory'; import getDataForComparison from './data-services/get-data-for-comparison.factory';
import getModuleOptions from './data-services/get-module-options.factory'; import getModuleOptions from './data-services/get-module-options.factory';
import resolveVersions from './data-services/resolve-versions.factory';
import controller from './system-tracking.controller'; import controller from './system-tracking.controller';
import stringOrDateFilter from './string-or-date.filter'; import stringOrDateFilter from './string-or-date.filter';
import shared from 'tower/shared/main'; import shared from 'tower/shared/main';
@@ -23,6 +24,7 @@ export default
.service('factScanDataService', factScanDataService) .service('factScanDataService', factScanDataService)
.factory('getDataForComparison', getDataForComparison) .factory('getDataForComparison', getDataForComparison)
.factory('getModuleOptions', getModuleOptions) .factory('getModuleOptions', getModuleOptions)
.factory('resolveVersions', resolveVersions)
.filter('stringOrDate', stringOrDateFilter) .filter('stringOrDate', stringOrDateFilter)
.controller('systemTracking', controller) .controller('systemTracking', controller)
.config(['$routeProvider', function($routeProvider) { .config(['$routeProvider', function($routeProvider) {

View File

@@ -24,8 +24,8 @@ function controller($rootScope,
$scope.hostIds = $routeParams.hosts; $scope.hostIds = $routeParams.hosts;
$scope.inventory = $routeParams.model.inventory; $scope.inventory = $routeParams.model.inventory;
$scope.factModulePickersLabelLeft = "Compare facts collected on"; $scope.factModulePickersLabelLeft = "Compare facts collected on or before";
$scope.factModulePickersLabelRight = "To facts collected on"; $scope.factModulePickersLabelRight = "To facts collected on or before";
$scope.modules = initialFactData.moduleOptions; $scope.modules = initialFactData.moduleOptions;
@@ -40,6 +40,9 @@ function controller($rootScope,
$scope.leftDate = initialFactData.leftSearchRange.from; $scope.leftDate = initialFactData.leftSearchRange.from;
$scope.rightDate = initialFactData.rightSearchRange.from; $scope.rightDate = initialFactData.rightSearchRange.from;
$scope.leftScanDate = initialFactData.leftScanDate;
$scope.rightScanDate = initialFactData.rightScanDate;
function setHeaderValues(viewType) { function setHeaderValues(viewType) {
if (viewType === 'singleHost') { if (viewType === 'singleHost') {
$scope.comparisonLeftHeader = $scope.leftScanDate; $scope.comparisonLeftHeader = $scope.leftScanDate;
@@ -58,7 +61,6 @@ function controller($rootScope,
var leftRange = searchConfig.leftRange; var leftRange = searchConfig.leftRange;
var rightRange = searchConfig.rightRange; var rightRange = searchConfig.rightRange;
var activeModule = searchConfig.module; var activeModule = searchConfig.module;
var leftScanDate, rightScanDate;
if (!factData) { if (!factData) {
@@ -68,12 +70,12 @@ function controller($rootScope,
activeModule.name, activeModule.name,
leftRange, leftRange,
rightRange) rightRange)
.thenAll(function(factDataAndModules) { .then(function(factDataAndModules) {
var responses = factDataAndModules[1]; var responses = factDataAndModules[1];
var data = _.pluck(responses, 'fact'); var data = _.pluck(responses, 'fact');
leftScanDate = moment(responses[0].timestamp); $scope.leftScanDate = moment(responses[0].timestamp);
rightScanDate = moment(responses[1].timestamp); $scope.rightScanDate = moment(responses[1].timestamp);
return data; return data;
}, true); }, true);
@@ -82,7 +84,8 @@ function controller($rootScope,
waitIndicator('start'); waitIndicator('start');
return _(factData) return _(factData)
.thenAll(function(facts) { .promise()
.then(function(facts) {
// Make sure we always start comparison against // Make sure we always start comparison against
// a non-empty array // a non-empty array
// //
@@ -132,7 +135,9 @@ function controller($rootScope,
$scope.factData = info; $scope.factData = info;
setHeaderValues(viewType, leftScanDate, rightScanDate); setHeaderValues(viewType);
return info;
}).finally(function() { }).finally(function() {
waitIndicator('stop'); waitIndicator('stop');

View File

@@ -31,10 +31,10 @@
<section class="FactDataError SystemTrackingContainer-main" ng-if="error" ng-switch="error.name"> <section class="FactDataError SystemTrackingContainer-main" ng-if="error" ng-switch="error.name">
<p class="FactDataError-message" ng-switch-when="NoScanData"> <p class="FactDataError-message" ng-switch-when="NoScanData">
There were no facts collected on the dates you selected ({{error.dateValues.leftDate|amDateFormat:'L'}} and {{error.dateValues.rightDate|amDateFormat:'L'}}). Please pick a different range or module and try again. There were no facts collected on or before the dates you selected ({{error.dateValues.leftDate|amDateFormat:'L'}} and {{error.dateValues.rightDate|amDateFormat:'L'}}). Please pick a different range or module and try again.
</p> </p>
<p class="FactDataError-message" ng-switch-when="InsufficientScanData"> <p class="FactDataError-message" ng-switch-when="InsufficientScanData">
There were no facts collected on one of the dates you selected ({{error.dateValue|amDateFormat:'L'}}). Please select a different date and try again. There were no facts collected on or before one of the dates you selected ({{error.dateValue|amDateFormat:'L'}}). Please select a different date and try again.
</p> </p>
<p class="FactDataError-note"> <p class="FactDataError-note">
To setup or run scan jobs, edit the "<a link-to="inventoryEdit" model="{ inventory_id: inventory }">{{inventory.name}}</a>" inventory and select "Scan Jobs Templates". To setup or run scan jobs, edit the "<a link-to="inventoryEdit" model="{ inventory_id: inventory }">{{inventory.name}}</a>" inventory and select "Scan Jobs Templates".

View File

@@ -32,22 +32,22 @@ export default {
var data = var data =
getDataForComparison(hostIds, moduleParam, leftDate, rightDate). getDataForComparison(hostIds, moduleParam, leftDate, rightDate).
thenAll(function(factDataAndModules) { then(function(factDataAndModules) {
var moduleOptions = factDataAndModules[0]; var moduleOptions = factDataAndModules[0];
var factResponses = factDataAndModules[1]; var factResponses = factDataAndModules[1];
var factData = _.pluck(factResponses, 'fact'); var factData = _.pluck(factResponses, 'fact');
factData.leftSearchRange = leftDate; factData.leftSearchRange = leftDate;
factData.rightSearchRange = rightDate; factData.rightSearchRange = rightDate;
factData.leftScanDate = moment(factResponses[0].timestamp); factData.leftScanDate = moment(factResponses[0].timestamp);
factData.rightScanDate = moment(factResponses[0].timestamp); factData.rightScanDate = moment(factResponses[1].timestamp);
factData.moduleName = moduleParam; factData.moduleName = moduleParam;
factData.moduleOptions = moduleOptions; factData.moduleOptions = moduleOptions;
return factData; return factData;
}, true) }, true)
.value(); .value();
return data; return data;