diff --git a/awx/ui/static/js/system-tracking/compare-facts.js b/awx/ui/static/js/system-tracking/compare-facts.js index fc64465624..092c613361 100644 --- a/awx/ui/static/js/system-tracking/compare-facts.js +++ b/awx/ui/static/js/system-tracking/compare-facts.js @@ -9,11 +9,10 @@ import compareFlatFacts from './compare-facts/flat'; import FactTemplate from './compare-facts/fact-template'; export function compareFacts(module, facts) { - if (module.displayType === 'nested') { - return { factData: compareNestedFacts(facts), - isNestedDisplay: true - }; - } else { + // If the module has a template or includes a list of keys to display, + // then perform a flat comparison, otherwise assume nested + // + if (module.factTemplate || module.nameKey) { // For flat structures we compare left-to-right, then right-to-left to // make sure we get a good comparison between both hosts var compare = _.partialRight(compareFlatFacts, @@ -33,5 +32,9 @@ export function compareFacts(module, facts) { }; }) .value(); + } else { + return { factData: compareNestedFacts(facts), + isNestedDisplay: true + }; } } 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 197abc4540..b9a7ffd7df 100644 --- a/awx/ui/static/js/system-tracking/compare-facts/flat.js +++ b/awx/ui/static/js/system-tracking/compare-facts/flat.js @@ -21,7 +21,14 @@ function slotFactValues(basisPosition, basisValue, comparatorValue) { } export default - function flatCompare(basisFacts, comparatorFacts, nameKey, compareKeys, factTemplate) { + function flatCompare(basisFacts, + comparatorFacts, renderOptions) { + + var nameKey = renderOptions.nameKey; + var compareKeys = renderOptions.compareKey; + var keyNameMap = renderOptions.keyNameMap; + var valueFormatter = renderOptions.valueFormatter; + var factTemplate = renderOptions.factTemplate; return basisFacts.facts.reduce(function(arr, basisFact) { @@ -33,7 +40,44 @@ export default var matchingFact = _.where(comparatorFacts.facts, searcher); var diffs; - if (!_.isUndefined(factTemplate)) { + // Perform comparison and get back comparisonResults; like: + // { 'value': + // { leftValue: 'blah', + // rightValue: 'doo' + // } + // }; + // + var comparisonResults = + _.reduce(compareKeys, function(result, compareKey) { + + if (_.isEmpty(matchingFact)) { + comparatorValue = 'absent'; + } else { + comparatorValue = matchingFact[0][compareKey]; + } + + var slottedValues = slotFactValues(basisFacts.position, + basisFact[compareKey], + comparatorValue); + + if (_.isUndefined(slottedValues.left) && _.isUndefined(slottedValues.right)) { + return result; + } + + if (slottedValues.left !== slottedValues.right) { + slottedValues.isDivergent = true; + } else { + slottedValues.isDivergent = false; + } + + result[compareKey] = slottedValues; + + return result; + }, {}); + + var hasDiffs = _.any(comparisonResults, { isDivergent: true }); + + if (hasDiffs && factTemplate.hasTemplate()) { basisValue = factTemplate.render(basisFact); @@ -43,45 +87,34 @@ export default comparatorValue = factTemplate.render(matchingFact[0]); } - slottedValues = slotFactValues(basisFacts.position, basisValue, comparatorValue); + if (!_.isEmpty(comparisonResults)) { - diffs = - { keyName: basisFact[nameKey], - value1: slottedValues.left, - value2: slottedValues.right - }; + slottedValues = slotFactValues(basisFact.position, basisValue, comparatorValue); - } else { - - if (_.isEmpty(matchingFact)) { - matchingFact = {}; - } else { - matchingFact = matchingFact[0]; + diffs = + { keyName: basisFact[nameKey], + value1: slottedValues.left, + value2: slottedValues.right + }; } + } else if (hasDiffs) { + diffs = - _(basisFact).map(function(value, key) { - var slottedValues = slotFactValues(basisFacts.position, value, matchingFact[key] || 'absent'); - var keyName; + _(comparisonResults).map(function(slottedValues, key) { - if (slottedValues.right !== 'absent') { - if(slottedValues.left === slottedValues.right) { - return; - } + var keyName = key; - if (!_.include(compareKeys, key)) { - return; - } - keyName = basisFact[nameKey]; - } else { - keyName = key; + if (keyNameMap && keyNameMap[key]) { + keyName = keyNameMap[key]; } return { keyName: keyName, value1: slottedValues.left, value1IsAbsent: slottedValues.left === 'absent', value2: slottedValues.right, - value2IsAbsent: slottedValues.right === 'absent' + value2IsAbsent: slottedValues.right === 'absent', + isDivergent: slottedValues.isDivergent }; }).compact() .value(); 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 c82848015d..b55c262d51 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 @@ -2,29 +2,26 @@ var moduleConfig = { 'packages': { compareKey: ['release', 'version'], nameKey: 'name', - displayType: 'flat', sortKey: 1, factTemplate: "{{epoch|append:':'}}{{version}}-{{release}}{{arch|prepend:'.'}}" }, 'services': { compareKey: ['state', 'source'], nameKey: 'name', - displayType: 'flat', factTemplate: '{{state}} ({{source}})', sortKey: 2 }, 'files': { compareKey: ['size', 'mode', 'md5', 'mtime', 'gid', 'uid'], nameKey: 'path', - displayType: 'flat', + displayKeys: ['size', 'mode', 'mtime', 'uid', 'gid', 'md5'], sortKey: 3 }, 'ansible': - { displayType: 'nested', - sortKey: 4 + { sortKey: 4 }, 'custom': - { displayType: 'nested' + { } }; 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 1e5f47822b..076e527f66 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 @@ -1,17 +1,34 @@ +import compareFacts from 'tower/system-tracking/compare-facts/flat'; + /* jshint node: true */ /* globals -expect, -_ */ -import compareFacts from 'tower/system-tracking/compare-facts/flat'; - -var expect = require('chai').expect; +var chai = require('chai'); var _ = require('lodash'); +var chaiThings = require('chai-things'); +chai.use(chaiThings); + +global.expect = chai.expect; global._ = _; describe('CompareFacts.Flat', function() { + function options(overrides) { + return _.merge({}, defaultOptions, overrides); + } + + var defaultTemplate = + { hasTemplate: function() { return false; } + }; + + var defaultOptions = + { factTemplate: defaultTemplate, + nameKey: 'name' + }; + it('returns empty array with empty basis facts', function() { - var result = compareFacts({ facts: [] }, { facts: [] }); + var result = compareFacts({ facts: [] }, { facts: [] }, defaultOptions); expect(result).to.deep.equal([]); }); @@ -27,39 +44,177 @@ describe('CompareFacts.Flat', function() { [{ 'name': 'foo', 'value': 'bar' }] - }, 'name', ['value']); + }, options({ nameKey: 'name', + compareKey: ['value'], + })); + + expect(result).to.deep.equal([]); + }); + + it('returns empty array with multiple compare keys and no differences', function() { + var result = compareFacts( + { facts: + [{ 'name': 'foo', + 'value': 'bar' + }] + }, + { facts: + [{ 'name': 'foo', + 'value': 'bar' + }] + }, options({ compareKey: ['name', 'value'] + })); expect(result).to.deep.equal([]); }); context('when both collections contain facts', function() { - it('includes each fact value when a compareKey differs', function() { + it('includes each compare key value when a compareKey differs', function() { var result = compareFacts( { position: 'left', facts: [{ 'name': 'foo', - 'value': 'bar' + 'value': 'bar', + 'extra': 'doo' }] }, { position: 'right', facts: [{ 'name': 'foo', - 'value': 'baz' + 'value': 'baz', + 'extra': 'doo' }] - }, 'name', ['value']); + }, options({ compareKey: ['value', 'extra'] })); expect(result).to.deep.equal( [{ displayKeyPath: 'foo', nestingLevel: 0, facts: - [{ keyName: 'foo', + [{ keyName: 'value', value1: 'bar', value1IsAbsent: false, value2: 'baz', - value2IsAbsent: false + value2IsAbsent: false, + isDivergent: true + }, + { keyName: 'extra', + value1: 'doo', + value1IsAbsent: false, + value2: 'doo', + value2IsAbsent: false, + isDivergent: false }] }]); }); + + it('ignores compare keys with no values in fact', function() { + var result = compareFacts( + { position: 'left', + facts: + [{ 'name': 'foo', + 'value': 'bar', + 'extra': 'doo' + }] + }, + { position: 'right', + facts: + [{ 'name': 'foo', + 'value': 'baz', + 'extra': 'doo' + }] + }, options({ compareKey: ['value', 'extra', 'blah'] })); + + expect(result).to.deep.equal( + [{ displayKeyPath: 'foo', + nestingLevel: 0, + facts: + [{ keyName: 'value', + value1: 'bar', + value1IsAbsent: false, + value2: 'baz', + value2IsAbsent: false, + isDivergent: true + }, + { keyName: 'extra', + value1: 'doo', + value1IsAbsent: false, + value2: 'doo', + value2IsAbsent: false, + isDivergent: false + }] + }]); + + }); + + it('allows mapping key names with keyNameMap parameter', function() { + var keyNameMap = + { 'extra': 'blah' + }; + + var result = compareFacts( + { position: 'left', + facts: + [{ 'name': 'foo', + 'value': 'bar', + 'extra': 'doo' + }] + }, + { position: 'right', + facts: + [{ 'name': 'foo', + 'value': 'baz', + 'extra': 'doo' + }] + }, options({ compareKey: ['value', 'extra', 'blah'], + keyNameMap: keyNameMap + })); + + expect(result[0].facts).to.include.something.that.deep.equals( + { keyName: 'blah', + value1: 'doo', + value1IsAbsent: false, + value2: 'doo', + value2IsAbsent: false, + isDivergent: false + }); + + }); + + // it('allows formatting values with valueFormat parameter', function() { + // var valueFormat = + // function(key, values) { + // if (key === 'extra') { + // return 'formatted'; + // } + // } + + // var result = compareFacts( + // { position: 'left', + // facts: + // [{ 'name': 'foo', + // 'value': 'bar', + // 'extra': 'doo' + // }] + // }, + // { position: 'right', + // facts: + // [{ 'name': 'foo', + // 'value': 'baz', + // 'extra': 'doo' + // }] + // }, 'name', ['value', 'extra', 'blah'], keyNameMap, defaultTemplate, ); + + // expect(result[0].facts).to.include.something.that.deep.equals( + // { keyName: 'extra', + // value1: 'formatted', + // value1IsAbsent: false, + // value2: 'formatted', + // value2IsAbsent: false, + // isDivergent: false + // }); + + // }); + }); context('when value for nameKey is present in one collection but not the other', function() { @@ -77,46 +232,55 @@ describe('CompareFacts.Flat', function() { it('uses "absent" for the missing value', function() { - var facts = factData([{ 'name': 'foo' - }]); - - var result = compareFacts(facts[0], facts[1], 'name', ['value']); - - expect(result).to.deep.equal( - [{ displayKeyPath: 'foo', - nestingLevel: 0, - facts: - [{ keyName: 'name', - value1: 'foo', - value1IsAbsent: false, - value2: 'absent', - value2IsAbsent: true - }] - }]); - }); - - it('includes all keys from basisFacts', function() { var facts = factData([{ 'name': 'foo', 'value': 'bar' }]); - var result = compareFacts(facts[0], facts[1], 'name', ['value']); + var result = compareFacts(facts[0], facts[1], + options({ compareKey: ['value'] + })); expect(result).to.deep.equal( [{ displayKeyPath: 'foo', nestingLevel: 0, facts: - [{ keyName: 'name', - value1: 'foo', - value1IsAbsent: false, - value2: 'absent', - value2IsAbsent: true - }, - { keyName: 'value', + [{ keyName: 'value', value1: 'bar', value1IsAbsent: false, value2: 'absent', - value2IsAbsent: true + value2IsAbsent: true, + isDivergent: true + }] + }]); + }); + + it('includes given compare keys from basisFacts', function() { + var facts = factData([{ 'name': 'foo', + 'value': 'bar', + 'extra': 'doo' + }]); + + var result = compareFacts(facts[0], facts[1], + options({ compareKey: ['value', 'extra'] + })); + + expect(result).to.deep.equal( + [{ displayKeyPath: 'foo', + nestingLevel: 0, + facts: + [{ keyName: 'value', + value1: 'bar', + value1IsAbsent: false, + value2: 'absent', + value2IsAbsent: true, + isDivergent: true + }, + { keyName: 'extra', + value1: 'doo', + value1IsAbsent: false, + value2: 'absent', + value2IsAbsent: true, + isDivergent: true }] }]); @@ -132,10 +296,14 @@ describe('CompareFacts.Flat', function() { { render: function() { renderCallCount++; }, + hasTemplate: function() { return true; }, template: "" }; - compareFacts(facts[0], facts[1], 'name', ['value'], factTemplate); + compareFacts(facts[0], facts[1], + options({ compareKey: ['value'], + factTemplate: factTemplate + })); expect(renderCallCount).to.equal(1); @@ -168,10 +336,14 @@ describe('CompareFacts.Flat', function() { { render: function(fact) { renderCalledWith.push(fact); }, + hasTemplate: function() { return true; }, template: "" }; - compareFacts(factData[0], factData[1], 'name', ['value'], factTemplate); + compareFacts(factData[0], factData[1], + options({ compareKey: ['value'], + factTemplate: factTemplate + })); expect(renderCalledWith).to.include(factData[0].facts[0]); expect(renderCalledWith).to.include(factData[1].facts[0]);