From bda5a4672954dc0f58134a16925cfd3d12635cf8 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 18 Jun 2015 15:05:00 -0400 Subject: [PATCH] [system_tracking] Support multiple versions of packages with same name --- .../js/system-tracking/compare-facts/flat.js | 165 ++++++++++++------ .../get-module-options.factory.js | 3 +- .../fact-data-table.block.less | 19 ++ .../fact-data-table.partial.html | 25 ++- .../compare-facts/flat-test.js | 100 +++++++++++ 5 files changed, 248 insertions(+), 64 deletions(-) diff --git a/awx/ui/static/js/system-tracking/compare-facts/flat.js b/awx/ui/static/js/system-tracking/compare-facts/flat.js index 4694f7cbb1..6ce42a3464 100644 --- a/awx/ui/static/js/system-tracking/compare-facts/flat.js +++ b/awx/ui/static/js/system-tracking/compare-facts/flat.js @@ -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.} 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.FactComparisonDescription + * @typedef {{displayKeyPath: string, nestingLevel: number, facts: Array.}} 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 }; diff --git a/awx/ui/static/js/system-tracking/data-services/get-module-options.factory.js b/awx/ui/static/js/system-tracking/data-services/get-module-options.factory.js index 9de3f18031..8f2cfb4ec6 100644 --- a/awx/ui/static/js/system-tracking/data-services/get-module-options.factory.js +++ b/awx/ui/static/js/system-tracking/data-services/get-module-options.factory.js @@ -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'], diff --git a/awx/ui/static/js/system-tracking/fact-data-table/fact-data-table.block.less b/awx/ui/static/js/system-tracking/fact-data-table/fact-data-table.block.less index a13aef9798..16194ebf07 100644 --- a/awx/ui/static/js/system-tracking/fact-data-table/fact-data-table.block.less +++ b/awx/ui/static/js/system-tracking/fact-data-table/fact-data-table.block.less @@ -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; + } + } } diff --git a/awx/ui/static/js/system-tracking/fact-data-table/fact-data-table.partial.html b/awx/ui/static/js/system-tracking/fact-data-table/fact-data-table.partial.html index 255e86538b..e937b95c86 100644 --- a/awx/ui/static/js/system-tracking/fact-data-table/fact-data-table.partial.html +++ b/awx/ui/static/js/system-tracking/fact-data-table/fact-data-table.partial.html @@ -17,10 +17,25 @@
-
- {{group.facts.keyName}} - {{group.facts.value1}} - {{group.facts.value2}} +
+
+ {{group.facts.keyName}} + + + {{value}} + + + + + {{value}} + + +
+
+ {{group.facts.keyName}} + {{group.facts.value1}} + {{group.facts.value2}} +
@@ -35,7 +50,7 @@
- + {{fact.keyName}} diff --git a/awx/ui/tests/unit/system-tracking/compare-facts/flat-test.js b/awx/ui/tests/unit/system-tracking/compare-facts/flat-test.js index 2995be4caa..6e7034d78f 100644 --- a/awx/ui/tests/unit/system-tracking/compare-facts/flat-test.js +++ b/awx/ui/tests/unit/system-tracking/compare-facts/flat-test.js @@ -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;