[system_tracking] Support multiple versions of packages with same name

This commit is contained in:
Joe Fiorini 2015-06-18 15:05:00 -04:00
parent 33b6da9bb6
commit bda5a46729
5 changed files with 248 additions and 64 deletions

View File

@ -36,15 +36,18 @@ function slotFactValues(basisPosition, basisValue, comparatorValue) {
* @property {string} nameKey - The name of the key that is used to locate facts from basisFacts in comparatorFacts
* @property {string|string[]} compareKey - A single key or list of keys to compare in the two fact collections
* @property {Tower.SystemTracking.KeyNameMap} keyNameMap - An object mapping existing key names to new key names for display
* @prperty {Tower.SystemTracking.FactTemplate} factTemplate - An optional template used as the string for comparing and displaying a fact
* @property {Tower.SystemTracking.FactTemplate} factTemplate - An optional template used as the string for comparing and displaying a fact
* @property {boolean} supportsValueArray - Indicates that the module we're rendering supports values in an array of strings rather than just a string; iterim measure to make sure modules with multiple facts matching nameKey don't get evaluated because we won't know how to dipslay them
*
* @typedef {(string|boolean)} Tower.SystemTracking.KeyNameMapValue - The name you want to use for the display of a key or "true" to indicate a key should be displayed without changing its name (all keys are hidden by default)
*
* @typedef {Object.<string, Tower.SystemTracking.KeyNameMapValue>} Tower.SystemTracking.KeyNameMap - An object whose keys are the names of keys that exist in a fact and whose values control how that key is displayed
*
* @typedef {{displayKeyPath: string, nestingLevel: number, facts: Array.<Tower.SystemTracking.FactComparisonResult>}} Tower.SystemTracking.FactComparisonDescription
* @typedef {{displayKeyPath: string, nestingLevel: number, facts: Array.<Tower.SystemTracking.FactComparisonResult>}} Tower.SystemTracking.FactComparisonDescriptor
*
* @typedef {{keyName: string, value1, value2, isDivergent: bool}} Tower.SystemTracking.FactComparisonResult
* @typedef {{keyName: string, value1: Tower.SystemTracking.FactComparisonValue, value2: Tower.SystemTracking.FactComparisonValue, isDivergent: boolean}} Tower.SystemTracking.FactComparisonResult
*
* @typedef {(string|string[])} Tower.SystemTracking.FactComparisonValue
*
*/
export default
@ -58,79 +61,124 @@ export default
return basisFacts.facts.reduce(function(arr, basisFact) {
// First, make sure this fact hasn't already been processed, if it
// has don't process it again
//
if (_.any(arr, { displayKeyPath: basisFact[nameKey] })) {
return arr;
}
var searcher = {};
searcher[nameKey] = basisFact[nameKey];
var matchingFact = _.where(comparatorFacts.facts, searcher);
var containsValueArray = false;
var matchingFacts = _.where(comparatorFacts.facts, searcher);
var comparisonResults;
var diffs;
// Perform comparison and get back comparisonResults; like:
// { 'value':
// { leftValue: 'blah',
// rightValue: 'doo'
// }
// };
// If this fact exists more than once in `basisFacts`, then
// we need to process it differently
//
var comparisonResults =
_.reduce(compareKeys, function(result, compareKey) {
var otherBasisFacts = _.where(basisFacts.facts, searcher);
if (renderOptions.supportsValueArray && otherBasisFacts.length > 1) {
comparisonResults = processFactsForValueArray(basisFacts.position, otherBasisFacts, matchingFacts);
} else {
comparisonResults = processFactsForSingleValue(basisFact, matchingFacts[0] || {});
}
var comparatorFact = matchingFact[0] || {};
var isNestedDisplay = false;
function processFactsForValueArray(basisPosition, basisFacts, comparatorFacts) {
var slottedValues = slotFactValues(basisFacts.position,
basisFact[compareKey],
comparatorFact[compareKey]);
function renderFactValues(facts) {
return facts.map(function(fact) {
return factTemplate.render(fact);
});
}
if (_.isUndefined(slottedValues.left) && _.isUndefined(slottedValues.right)) {
return result;
}
var basisFactValues = renderFactValues(basisFacts);
var comparatorFactValues = renderFactValues(comparatorFacts);
var template = factTemplate;
var slottedValues = slotFactValues(basisPosition, basisFactValues, comparatorFactValues);
if (_.isObject(template) && template.hasOwnProperty(compareKey)) {
template = template[compareKey];
if (!_.isEqual(slottedValues.left, slottedValues.right)) {
slottedValues.isDivergent = true;
containsValueArray = true;
} else {
slottedValues.isDivergent = false;
}
// 'true' means render the key without formatting
if (template === true) {
return slottedValues;
}
function processFactsForSingleValue(basisFact, comparatorFact) {
// Perform comparison and get back comparisonResults; like:
// { 'value':
// { leftValue: 'blah',
// rightValue: 'doo'
// }
// };
//
return _.reduce(compareKeys, function(result, compareKey) {
var isNestedDisplay = false;
var basisFactValue = basisFact[compareKey];
var comparatorFactValue = comparatorFact[compareKey];
var slottedValues;
if (_.isUndefined(basisFactValue) && _.isUndefined(comparatorFactValue)) {
return result;
}
var template = factTemplate;
if (_.isObject(template) && template.hasOwnProperty(compareKey)) {
template = template[compareKey];
// 'true' means render the key without formatting
if (template === true) {
template =
{ render: function(fact) { return fact[compareKey]; }
};
}
isNestedDisplay = true;
} else if (typeof template.hasTemplate === 'function' && !template.hasTemplate()) {
template =
{ render: function(fact) { return fact[compareKey]; }
};
isNestedDisplay = true;
} else if (typeof factTemplate.render === 'function') {
template = factTemplate;
} else if (!template.hasOwnProperty(compareKey)) {
return result;
}
// if (!renderOptions.supportsValueArray) {
// comparatorFact = comparatorFact[0];
// }
basisFactValue = template.render(basisFact);
comparatorFactValue = template.render(comparatorFact);
slottedValues = slotFactValues(basisFacts.position,
basisFactValue,
comparatorFactValue);
if (slottedValues.left !== slottedValues.right) {
slottedValues.isDivergent = true;
} else {
slottedValues.isDivergent = false;
}
if (isNestedDisplay) {
result[compareKey] = slottedValues;
} else {
result = slottedValues;
}
isNestedDisplay = true;
} else if (typeof template.hasTemplate === 'function' && !template.hasTemplate()) {
template =
{ render: function(fact) { return fact[compareKey]; }
};
isNestedDisplay = true;
} else if (typeof factTemplate.render === 'function') {
template = factTemplate;
} else if (!template.hasOwnProperty(compareKey)) {
return result;
}
if (basisFacts.position === 'left') {
slottedValues.left = template.render(basisFact);
slottedValues.right = template.render(comparatorFact);
} else {
slottedValues.left = template.render(comparatorFact);
slottedValues.right = template.render(basisFact);
}
if (slottedValues.left !== slottedValues.right) {
slottedValues.isDivergent = true;
} else {
slottedValues.isDivergent = false;
}
if (isNestedDisplay) {
result[compareKey] = slottedValues;
} else {
result = slottedValues;
}
return result;
}, {});
}, {});
}
var hasDiffs =
_.any(comparisonResults, { isDivergent: true }) ||
@ -167,6 +215,7 @@ export default
var descriptor =
{ displayKeyPath: basisFact[nameKey],
nestingLevel: 0,
containsValueArray: containsValueArray,
facts: diffs
};

View File

@ -3,7 +3,8 @@ var moduleConfig =
{ compareKey: ['release', 'version'],
nameKey: 'name',
sortKey: 1,
factTemplate: "{{epoch|append:':'}}{{version}}-{{release}}{{arch|prepend:'.'}}"
factTemplate: "{{epoch|append:':'}}{{version}}-{{release}}{{arch|prepend:'.'}}",
supportsValueArray: true
},
'services':
{ compareKey: ['state', 'source'],

View File

@ -15,6 +15,12 @@
background-color: #ebebeb;
border-color: #adadad;
}
&--flexible {
max-height: initial;
align-items: flex-start;
}
}
&-headingRow {
@ -26,8 +32,21 @@
padding: 8px;
flex: 1 0 33%;
white-space: nowrap;
align-self: flex-start;
&--offsetLeft {
margin-left: 33%;
}
}
&-columnArray {
display: flex;
flex-direction: column;
}
&-columnMember {
margin-bottom: 16px;
&:last-child {
margin-bottom: inherit;
}
}
}

View File

@ -17,10 +17,25 @@
</h3>
</div>
<div ng-if="!isNestedDisplay">
<div class="FactDataTable-row" ng-repeat="group in factData | orderBy: 'displayKeyPath'">
<span class="FactDataTable-column">{{group.facts.keyName}}</span>
<span class="FactDataTable-column">{{group.facts.value1}}</span>
<span class="FactDataTable-column">{{group.facts.value2}}</span>
<div ng-repeat="group in factData | orderBy: 'displayKeyPath'">
<div class="FactDataTable-row FactDataTable-row--flexible" ng-if="group.containsValueArray">
<span class="FactDataTable-column">{{group.facts.keyName}}</span>
<span class="FactDataTable-column FactDataTable-columnArray">
<span class="FactDataTable-columnMember" ng-repeat="value in group.facts.value1">
{{value}}
</span>
</span>
<span class="FactDataTable-column FactDataTable-columnArray">
<span class="FactDataTable-columnMember" ng-repeat="value in group.facts.value2">
{{value}}
</span>
</span>
</div>
<div class="FactDataTable-row" ng-if="!group.containsValueArray">
<span class="FactDataTable-column">{{group.facts.keyName}}</span>
<span class="FactDataTable-column">{{group.facts.value1}}</span>
<span class="FactDataTable-column">{{group.facts.value2}}</span>
</div>
</div>
</div>
<div ng-if="isNestedDisplay">
@ -35,7 +50,7 @@
<div class="FactDataGroup-facts" data-facts="{{group.facts}}">
<div class="FactDataTable-arrayGroup" ng-if="group.isFactArray" ng-repeat="arrayGroup in group.facts" data-array-group="{{arrayGroup}}">
<div class="FactDataTable-row FactDatum" ng-class="{'FactDatum--divergent': fact.isDivergent }" ng-repeat="fact in arrayGroup" data-fact="{{fact}}">
<span class="FactDatum-keyName FactDataTable-column" ng-unless="fact.isArrayMember">
<span class="FactDatum-keyName FactDataTable-column" ng-if="!fact.isArrayMember">
{{fact.keyName}}
</span>
<span class="FactDatum-keyName FactDataTable-column" ng-if="fact.isArrayMember">

View File

@ -105,6 +105,7 @@ describe('CompareFacts.Flat', function() {
expect(result).to.deep.equal(
[{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0,
facts:
[{ keyName: 'value',
@ -139,6 +140,7 @@ describe('CompareFacts.Flat', function() {
expect(result).to.deep.equal(
[{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0,
facts:
[{ keyName: 'value',
@ -334,6 +336,7 @@ describe('CompareFacts.Flat', function() {
expect(result).to.deep.equal(
[{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0,
facts:
[{ keyName: 'value',
@ -368,6 +371,7 @@ describe('CompareFacts.Flat', function() {
expect(result).to.deep.equal(
[{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0,
facts:
{ keyName: 'foo',
@ -389,6 +393,7 @@ describe('CompareFacts.Flat', function() {
expect(result).to.deep.equal(
[{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0,
facts:
[{ keyName: 'value',
@ -407,6 +412,101 @@ describe('CompareFacts.Flat', function() {
});
describe('when value for nameKey exists multiple times in a single collection', function() {
context('with differences between any of the values', function() {
it('includes rendered values as array for value properties', function() {
var factTemplate =
{ hasTemplate:
function() {
return true;
},
render: function(fact) {
return fact.version;
}
};
var result = compareFacts(
{ position: 'left',
facts:
[{ 'name': 'some-package',
'version': 'abcd'
},
{ 'name': 'some-package',
'version': 'efgh'
}]
},
{ position: 'right',
facts:
[{ 'name': 'some-package',
'version': 'abcd'
},
{ 'name': 'some-package',
'version': 'ijkl'
}]
}, options({ compareKey: ['value'],
factTemplate: factTemplate,
supportsValueArray: true
}));
expect(result[0].containsValueArray).to.equal(true);
expect(result[0].facts).to.deep.equal(
{ keyName: 'some-package',
value1: ['abcd', 'efgh'],
value2: ['abcd', 'ijkl']
});
});
});
context('with no differences between any of the values', function() {
it('does not include the property at all', function() {
var expectation;
var factTemplate =
{ hasTemplate:
function() {
return true;
},
render: function(fact) {
return fact.version;
}
};
var result = compareFacts(
{ position: 'left',
facts:
[{ 'name': 'some-package',
'version': 'abcd'
},
{ 'name': 'some-package',
'version': 'efgh'
}]
},
{ position: 'right',
facts:
[{ 'name': 'some-package',
'version': 'abcd'
},
{ 'name': 'some-package',
'version': 'efgh'
}]
}, options({ compareKey: ['value'],
factTemplate: factTemplate,
supportsValueArray: true
}));
// Use assignment to avoid jshint warning
expectation = expect(result).to.be.empty;
});
});
});
context('with factTemplate', function() {
var factData;