diff --git a/platform/features/table/src/TelemetryCollection.js b/platform/features/table/src/TelemetryCollection.js index ddd91377d3..048ea7ae29 100644 --- a/platform/features/table/src/TelemetryCollection.js +++ b/platform/features/table/src/TelemetryCollection.js @@ -112,22 +112,6 @@ define( this.lastBounds = bounds; }; - /** - * Determines is a given telemetry datum is within the bounds currently - * defined for this telemetry collection. - * @private - * @param datum - * @returns {boolean} - */ - TelemetryCollection.prototype.inBounds = function (datum) { - var noBoundsDefined = !this.lastBounds || (this.lastBounds.start === undefined && this.lastBounds.end === undefined); - var withinBounds = - _.get(datum, this.sortField) >= this.lastBounds.start && - _.get(datum, this.sortField) <= this.lastBounds.end; - - return noBoundsDefined || withinBounds; - }; - /** * Adds an individual item to the collection. Used internally only * @private @@ -173,9 +157,10 @@ define( // based on time stamp because the array is guaranteed ordered due // to sorted insertion. var startIx = _.sortedIndex(array, item, this.sortField); + var endIx; if (startIx !== array.length) { - var endIx = _.sortedLastIndex(array, item, this.sortField); + endIx = _.sortedLastIndex(array, item, this.sortField); // Create an array of potential dupes, based on having the // same time stamp @@ -185,7 +170,7 @@ define( } if (!isDuplicate) { - array.splice(startIx, 0, item); + array.splice(endIx || startIx, 0, item); //Return true if it was added and in bounds return array === this.telemetry; diff --git a/platform/features/table/src/controllers/MCTTableController.js b/platform/features/table/src/controllers/MCTTableController.js index d9f3a9f680..272df15ab2 100644 --- a/platform/features/table/src/controllers/MCTTableController.js +++ b/platform/features/table/src/controllers/MCTTableController.js @@ -425,6 +425,38 @@ define( } }; + /** + * Finds the correct insertion point for a new row, which takes into + * account duplicates to make sure new rows are inserted in a way that + * maintains arrival order. + * + * @private + * @param {Array} searchArray + * @param {Object} searchElement Object to find the insertion point for + */ + MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) { + //First, use a binary search to find the correct insertion point + var index = this.binarySearch(searchArray, searchElement, 0, searchArray.length - 1); + var testIndex = index; + + //It's possible that the insertion point is a duplicate of the element to be inserted + var isDupe = function () { + return this.sortComparator(searchElement, + searchArray[testIndex][this.$scope.sortColumn].text) === 0; + }.bind(this); + + // In the event of a duplicate, scan left or right (depending on + // sort order) to find an insertion point that maintains order received + while (testIndex >= 0 && testIndex < searchArray.length && isDupe()) { + if (this.$scope.sortDirection === 'asc') { + index = ++testIndex; + } else { + index = testIndex--; + } + } + return index; + }; + /** * @private */ @@ -439,9 +471,9 @@ define( case -1: return this.binarySearch(searchArray, searchElement, min, sampleAt - 1); - case 0 : + case 0: return sampleAt; - case 1 : + case 1: return this.binarySearch(searchArray, searchElement, sampleAt + 1, max); } @@ -458,7 +490,7 @@ define( index = array.length; } else { //Sort is enabled, perform binary search to find insertion point - index = this.binarySearch(array, element[this.$scope.sortColumn].text, 0, array.length - 1); + index = this.findInsertionPoint(array, element[this.$scope.sortColumn].text); } if (index === -1) { array.unshift(element); diff --git a/platform/features/table/src/controllers/TelemetryTableController.js b/platform/features/table/src/controllers/TelemetryTableController.js index daf042893e..db6f71ceb6 100644 --- a/platform/features/table/src/controllers/TelemetryTableController.js +++ b/platform/features/table/src/controllers/TelemetryTableController.js @@ -364,11 +364,8 @@ define( var telemetryApi = this.openmct.telemetry; var telemetryCollection = this.telemetry; //Set table max length to avoid unbounded growth. - //var maxRows = 100000; - var maxRows = Number.MAX_VALUE; var limitEvaluator; var added = false; - var scope = this.$scope; var table = this.table; this.subscriptions.forEach(function (subscription) { @@ -379,16 +376,6 @@ define( function newData(domainObject, datum) { limitEvaluator = telemetryApi.limitEvaluator(domainObject); added = telemetryCollection.add([table.getRowValues(limitEvaluator, datum)]); - - //Inform table that a new row has been added - if (scope.rows.length > maxRows) { - scope.$broadcast('remove:rows', scope.rows[0]); - scope.rows.shift(); - } - if (!scope.loading && added) { - scope.$broadcast('add:row', - scope.rows.length - 1); - } } objects.forEach(function (object) { diff --git a/platform/features/table/test/TelemetryCollectionSpec.js b/platform/features/table/test/TelemetryCollectionSpec.js index 014e5c684e..c844dadca0 100644 --- a/platform/features/table/test/TelemetryCollectionSpec.js +++ b/platform/features/table/test/TelemetryCollectionSpec.js @@ -138,6 +138,27 @@ define( }; collection.add([addedObjectB, addedObjectA]); + expect(collection.telemetry[11]).toBe(addedObjectB); + } + ); + it("maintains insertion order in the case of duplicate time stamps", + function () { + var addedObjectA = { + timestamp: 10000, + value: { + integer: 10, + text: integerTextMap[10] + } + }; + var addedObjectB = { + timestamp: 10000, + value: { + integer: 11, + text: integerTextMap[11] + } + }; + collection.add([addedObjectA, addedObjectB]); + expect(collection.telemetry[11]).toBe(addedObjectB); } ); diff --git a/platform/features/table/test/controllers/MCTTableControllerSpec.js b/platform/features/table/test/controllers/MCTTableControllerSpec.js index f57b981c50..a0220d93c5 100644 --- a/platform/features/table/test/controllers/MCTTableControllerSpec.js +++ b/platform/features/table/test/controllers/MCTTableControllerSpec.js @@ -459,14 +459,14 @@ define( beforeEach(function () { row4 = { - 'col1': {'text': 'row5 col1'}, + 'col1': {'text': 'row4 col1'}, 'col2': {'text': 'xyz'}, - 'col3': {'text': 'row5 col3'} + 'col3': {'text': 'row4 col3'} }; row5 = { - 'col1': {'text': 'row6 col1'}, + 'col1': {'text': 'row5 col1'}, 'col2': {'text': 'aaa'}, - 'col3': {'text': 'row6 col3'} + 'col3': {'text': 'row5 col3'} }; row6 = { 'col1': {'text': 'row6 col1'}, @@ -490,6 +490,71 @@ define( expect(mockScope.displayRows[3].col2.text).toEqual('ggg'); }); + it('Inserts duplicate values for sort column in order received when sorted descending', function () { + mockScope.sortColumn = 'col2'; + mockScope.sortDirection = 'desc'; + + mockScope.displayRows = controller.sortRows(testRows.slice(0)); + + var row6b = { + 'col1': {'text': 'row6b col1'}, + 'col2': {'text': 'ggg'}, + 'col3': {'text': 'row6b col3'} + }; + var row6c = { + 'col1': {'text': 'row6c col1'}, + 'col2': {'text': 'ggg'}, + 'col3': {'text': 'row6c col3'} + }; + + controller.addRows(undefined, [row4, row5]); + controller.addRows(undefined, [row6, row6b, row6c]); + expect(mockScope.displayRows[0].col2.text).toEqual('xyz'); + expect(mockScope.displayRows[7].col2.text).toEqual('aaa'); + + // Added duplicate rows + expect(mockScope.displayRows[2].col2.text).toEqual('ggg'); + expect(mockScope.displayRows[3].col2.text).toEqual('ggg'); + expect(mockScope.displayRows[4].col2.text).toEqual('ggg'); + + // Check that original order is maintained with dupes + expect(mockScope.displayRows[2].col3.text).toEqual('row6c col3'); + expect(mockScope.displayRows[3].col3.text).toEqual('row6b col3'); + expect(mockScope.displayRows[4].col3.text).toEqual('row6 col3'); + }); + + it('Inserts duplicate values for sort column in order received when sorted ascending', function () { + mockScope.sortColumn = 'col2'; + mockScope.sortDirection = 'asc'; + + mockScope.displayRows = controller.sortRows(testRows.slice(0)); + + var row6b = { + 'col1': {'text': 'row6b col1'}, + 'col2': {'text': 'ggg'}, + 'col3': {'text': 'row6b col3'} + }; + var row6c = { + 'col1': {'text': 'row6c col1'}, + 'col2': {'text': 'ggg'}, + 'col3': {'text': 'row6c col3'} + }; + + controller.addRows(undefined, [row4, row5, row6]); + controller.addRows(undefined, [row6b, row6c]); + expect(mockScope.displayRows[0].col2.text).toEqual('aaa'); + expect(mockScope.displayRows[7].col2.text).toEqual('xyz'); + + // Added duplicate rows + expect(mockScope.displayRows[3].col2.text).toEqual('ggg'); + expect(mockScope.displayRows[4].col2.text).toEqual('ggg'); + expect(mockScope.displayRows[5].col2.text).toEqual('ggg'); + // Check that original order is maintained with dupes + expect(mockScope.displayRows[3].col3.text).toEqual('row6 col3'); + expect(mockScope.displayRows[4].col3.text).toEqual('row6b col3'); + expect(mockScope.displayRows[5].col3.text).toEqual('row6c col3'); + }); + it('Adds new rows at the correct sort position when' + ' sorted and filtered', function () { mockScope.sortColumn = 'col2';