mirror of
https://github.com/nasa/openmct.git
synced 2025-06-18 15:18:12 +00:00
[Tables] Maintain correct insertion order with duplicates. Fixes #1465
This commit is contained in:
@ -112,22 +112,6 @@ define(
|
|||||||
this.lastBounds = bounds;
|
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
|
* Adds an individual item to the collection. Used internally only
|
||||||
* @private
|
* @private
|
||||||
@ -173,9 +157,10 @@ define(
|
|||||||
// based on time stamp because the array is guaranteed ordered due
|
// based on time stamp because the array is guaranteed ordered due
|
||||||
// to sorted insertion.
|
// to sorted insertion.
|
||||||
var startIx = _.sortedIndex(array, item, this.sortField);
|
var startIx = _.sortedIndex(array, item, this.sortField);
|
||||||
|
var endIx;
|
||||||
|
|
||||||
if (startIx !== array.length) {
|
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
|
// Create an array of potential dupes, based on having the
|
||||||
// same time stamp
|
// same time stamp
|
||||||
@ -185,7 +170,7 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isDuplicate) {
|
if (!isDuplicate) {
|
||||||
array.splice(startIx, 0, item);
|
array.splice(endIx || startIx, 0, item);
|
||||||
|
|
||||||
//Return true if it was added and in bounds
|
//Return true if it was added and in bounds
|
||||||
return array === this.telemetry;
|
return array === this.telemetry;
|
||||||
|
@ -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
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -439,9 +471,9 @@ define(
|
|||||||
case -1:
|
case -1:
|
||||||
return this.binarySearch(searchArray, searchElement, min,
|
return this.binarySearch(searchArray, searchElement, min,
|
||||||
sampleAt - 1);
|
sampleAt - 1);
|
||||||
case 0 :
|
case 0:
|
||||||
return sampleAt;
|
return sampleAt;
|
||||||
case 1 :
|
case 1:
|
||||||
return this.binarySearch(searchArray, searchElement,
|
return this.binarySearch(searchArray, searchElement,
|
||||||
sampleAt + 1, max);
|
sampleAt + 1, max);
|
||||||
}
|
}
|
||||||
@ -458,7 +490,7 @@ define(
|
|||||||
index = array.length;
|
index = array.length;
|
||||||
} else {
|
} else {
|
||||||
//Sort is enabled, perform binary search to find insertion point
|
//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) {
|
if (index === -1) {
|
||||||
array.unshift(element);
|
array.unshift(element);
|
||||||
|
@ -364,11 +364,8 @@ define(
|
|||||||
var telemetryApi = this.openmct.telemetry;
|
var telemetryApi = this.openmct.telemetry;
|
||||||
var telemetryCollection = this.telemetry;
|
var telemetryCollection = this.telemetry;
|
||||||
//Set table max length to avoid unbounded growth.
|
//Set table max length to avoid unbounded growth.
|
||||||
//var maxRows = 100000;
|
|
||||||
var maxRows = Number.MAX_VALUE;
|
|
||||||
var limitEvaluator;
|
var limitEvaluator;
|
||||||
var added = false;
|
var added = false;
|
||||||
var scope = this.$scope;
|
|
||||||
var table = this.table;
|
var table = this.table;
|
||||||
|
|
||||||
this.subscriptions.forEach(function (subscription) {
|
this.subscriptions.forEach(function (subscription) {
|
||||||
@ -379,16 +376,6 @@ define(
|
|||||||
function newData(domainObject, datum) {
|
function newData(domainObject, datum) {
|
||||||
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
||||||
added = telemetryCollection.add([table.getRowValues(limitEvaluator, datum)]);
|
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) {
|
objects.forEach(function (object) {
|
||||||
|
@ -138,6 +138,27 @@ define(
|
|||||||
};
|
};
|
||||||
collection.add([addedObjectB, addedObjectA]);
|
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);
|
expect(collection.telemetry[11]).toBe(addedObjectB);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -459,14 +459,14 @@ define(
|
|||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
row4 = {
|
row4 = {
|
||||||
'col1': {'text': 'row5 col1'},
|
'col1': {'text': 'row4 col1'},
|
||||||
'col2': {'text': 'xyz'},
|
'col2': {'text': 'xyz'},
|
||||||
'col3': {'text': 'row5 col3'}
|
'col3': {'text': 'row4 col3'}
|
||||||
};
|
};
|
||||||
row5 = {
|
row5 = {
|
||||||
'col1': {'text': 'row6 col1'},
|
'col1': {'text': 'row5 col1'},
|
||||||
'col2': {'text': 'aaa'},
|
'col2': {'text': 'aaa'},
|
||||||
'col3': {'text': 'row6 col3'}
|
'col3': {'text': 'row5 col3'}
|
||||||
};
|
};
|
||||||
row6 = {
|
row6 = {
|
||||||
'col1': {'text': 'row6 col1'},
|
'col1': {'text': 'row6 col1'},
|
||||||
@ -490,6 +490,71 @@ define(
|
|||||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
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' +
|
it('Adds new rows at the correct sort position when' +
|
||||||
' sorted and filtered', function () {
|
' sorted and filtered', function () {
|
||||||
mockScope.sortColumn = 'col2';
|
mockScope.sortColumn = 'col2';
|
||||||
|
Reference in New Issue
Block a user