[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} 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 {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 * @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 {(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 {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 export default
@@ -58,12 +61,56 @@ export default
return basisFacts.facts.reduce(function(arr, basisFact) { 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 = {}; var searcher = {};
searcher[nameKey] = basisFact[nameKey]; searcher[nameKey] = basisFact[nameKey];
var matchingFact = _.where(comparatorFacts.facts, searcher); var containsValueArray = false;
var matchingFacts = _.where(comparatorFacts.facts, searcher);
var comparisonResults;
var diffs; var diffs;
// If this fact exists more than once in `basisFacts`, then
// we need to process it differently
//
var otherBasisFacts = _.where(basisFacts.facts, searcher);
if (renderOptions.supportsValueArray && otherBasisFacts.length > 1) {
comparisonResults = processFactsForValueArray(basisFacts.position, otherBasisFacts, matchingFacts);
} else {
comparisonResults = processFactsForSingleValue(basisFact, matchingFacts[0] || {});
}
function processFactsForValueArray(basisPosition, basisFacts, comparatorFacts) {
function renderFactValues(facts) {
return facts.map(function(fact) {
return factTemplate.render(fact);
});
}
var basisFactValues = renderFactValues(basisFacts);
var comparatorFactValues = renderFactValues(comparatorFacts);
var slottedValues = slotFactValues(basisPosition, basisFactValues, comparatorFactValues);
if (!_.isEqual(slottedValues.left, slottedValues.right)) {
slottedValues.isDivergent = true;
containsValueArray = true;
} else {
slottedValues.isDivergent = false;
}
return slottedValues;
}
function processFactsForSingleValue(basisFact, comparatorFact) {
// Perform comparison and get back comparisonResults; like: // Perform comparison and get back comparisonResults; like:
// { 'value': // { 'value':
// { leftValue: 'blah', // { leftValue: 'blah',
@@ -71,17 +118,14 @@ export default
// } // }
// }; // };
// //
var comparisonResults = return _.reduce(compareKeys, function(result, compareKey) {
_.reduce(compareKeys, function(result, compareKey) {
var comparatorFact = matchingFact[0] || {};
var isNestedDisplay = false; var isNestedDisplay = false;
var basisFactValue = basisFact[compareKey];
var comparatorFactValue = comparatorFact[compareKey];
var slottedValues;
var slottedValues = slotFactValues(basisFacts.position, if (_.isUndefined(basisFactValue) && _.isUndefined(comparatorFactValue)) {
basisFact[compareKey],
comparatorFact[compareKey]);
if (_.isUndefined(slottedValues.left) && _.isUndefined(slottedValues.right)) {
return result; return result;
} }
@@ -109,13 +153,16 @@ export default
return result; return result;
} }
if (basisFacts.position === 'left') { // if (!renderOptions.supportsValueArray) {
slottedValues.left = template.render(basisFact); // comparatorFact = comparatorFact[0];
slottedValues.right = template.render(comparatorFact); // }
} else {
slottedValues.left = template.render(comparatorFact); basisFactValue = template.render(basisFact);
slottedValues.right = template.render(basisFact); comparatorFactValue = template.render(comparatorFact);
}
slottedValues = slotFactValues(basisFacts.position,
basisFactValue,
comparatorFactValue);
if (slottedValues.left !== slottedValues.right) { if (slottedValues.left !== slottedValues.right) {
slottedValues.isDivergent = true; slottedValues.isDivergent = true;
@@ -131,6 +178,7 @@ export default
return result; return result;
}, {}); }, {});
}
var hasDiffs = var hasDiffs =
_.any(comparisonResults, { isDivergent: true }) || _.any(comparisonResults, { isDivergent: true }) ||
@@ -167,6 +215,7 @@ export default
var descriptor = var descriptor =
{ displayKeyPath: basisFact[nameKey], { displayKeyPath: basisFact[nameKey],
nestingLevel: 0, nestingLevel: 0,
containsValueArray: containsValueArray,
facts: diffs facts: diffs
}; };

View File

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

View File

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

View File

@@ -17,12 +17,27 @@
</h3> </h3>
</div> </div>
<div ng-if="!isNestedDisplay"> <div ng-if="!isNestedDisplay">
<div class="FactDataTable-row" ng-repeat="group in factData | orderBy: 'displayKeyPath'"> <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.keyName}}</span>
<span class="FactDataTable-column">{{group.facts.value1}}</span> <span class="FactDataTable-column">{{group.facts.value1}}</span>
<span class="FactDataTable-column">{{group.facts.value2}}</span> <span class="FactDataTable-column">{{group.facts.value2}}</span>
</div> </div>
</div> </div>
</div>
<div ng-if="isNestedDisplay"> <div ng-if="isNestedDisplay">
<div class="FactDataTable-factGroup FactDataGroup" ng-repeat="group in factData | orderBy: 'displayKeyPath'"> <div class="FactDataTable-factGroup FactDataGroup" ng-repeat="group in factData | orderBy: 'displayKeyPath'">
@@ -35,7 +50,7 @@
<div class="FactDataGroup-facts" data-facts="{{group.facts}}"> <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-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}}"> <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}} {{fact.keyName}}
</span> </span>
<span class="FactDatum-keyName FactDataTable-column" ng-if="fact.isArrayMember"> <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( expect(result).to.deep.equal(
[{ displayKeyPath: 'foo', [{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0, nestingLevel: 0,
facts: facts:
[{ keyName: 'value', [{ keyName: 'value',
@@ -139,6 +140,7 @@ describe('CompareFacts.Flat', function() {
expect(result).to.deep.equal( expect(result).to.deep.equal(
[{ displayKeyPath: 'foo', [{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0, nestingLevel: 0,
facts: facts:
[{ keyName: 'value', [{ keyName: 'value',
@@ -334,6 +336,7 @@ describe('CompareFacts.Flat', function() {
expect(result).to.deep.equal( expect(result).to.deep.equal(
[{ displayKeyPath: 'foo', [{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0, nestingLevel: 0,
facts: facts:
[{ keyName: 'value', [{ keyName: 'value',
@@ -368,6 +371,7 @@ describe('CompareFacts.Flat', function() {
expect(result).to.deep.equal( expect(result).to.deep.equal(
[{ displayKeyPath: 'foo', [{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0, nestingLevel: 0,
facts: facts:
{ keyName: 'foo', { keyName: 'foo',
@@ -389,6 +393,7 @@ describe('CompareFacts.Flat', function() {
expect(result).to.deep.equal( expect(result).to.deep.equal(
[{ displayKeyPath: 'foo', [{ displayKeyPath: 'foo',
containsValueArray: false,
nestingLevel: 0, nestingLevel: 0,
facts: facts:
[{ keyName: 'value', [{ 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() { context('with factTemplate', function() {
var factData; var factData;