Skip to content

Add ability to rename grouped traces #1919

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Aug 15, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions src/lib/keyed_container.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var NONE = 0;
var NAME = 1;
var VALUE = 2;
var BOTH = 3;
var UNSET = 4;

module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I like it.

@rreusser can you of other situations where this could be useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. It's also used in group styles, for one, though that case might be slightly more complicated. Also frames, though again, slightly more complicated. It's more a matter of realizing that unless we allow custom keys in the schema, abstracting this pattern seems like the only sane way forward.

keyName = keyName || 'name';
Expand All @@ -43,16 +44,18 @@ module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
var obj = {
// NB: this does not actually modify the baseObj
set: function(name, value) {
var changeType = NONE;
var changeType = value === null ? UNSET : NONE;

var idx = indexLookup[name];
if(idx === undefined) {
changeType = BOTH;
changeType = changeType | BOTH;
idx = arr.length;
indexLookup[name] = idx;
} else if(value !== (isSimpleValueProp ? arr[idx][valueName] : nestedProperty(arr[idx], valueName).get())) {
changeType = VALUE;
changeType = changeType | VALUE;
}
var newValue = {};

var newValue = arr[idx] = arr[idx] || {};
newValue[keyName] = name;

if(isSimpleValueProp) {
Expand All @@ -61,7 +64,11 @@ module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
nestedProperty(newValue, valueName).set(value);
}

arr[idx] = newValue;
// If it's not an unset, force that bit to be unset. This is all related to the fact
// that undefined and null are a bit specially implemented in nestedProperties.
if(value !== null) {
changeType = changeType & ~UNSET;
Copy link
Contributor

@etpinard etpinard Aug 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does & ~4 do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm tracking what needs to be updated with a bit mask (which, before adding a bit to track whether it's unset in a way that nestedProperty needs to handle specially, originally made 10% more sense since everything else was strictly an OR operation). & ~4 unsets precisely the 4 bit.

}

changeTypes[idx] = changeTypes[idx] | changeType;

Expand Down Expand Up @@ -93,7 +100,17 @@ module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
},
remove: function(name) {
var idx = indexLookup[name];

if(idx === undefined) return obj;

var object = arr[idx];
if(Object.keys(object).length > 2) {
// This object contains more than just the key/value, so unset
// the value without modifying the entry otherwise:
changeTypes[idx] = changeTypes[idx] | VALUE;
return obj.set(name, null);
}

for(i = idx; i < arr.length; i++) {
changeTypes[i] = changeTypes[i] | BOTH;
}
Expand All @@ -118,9 +135,9 @@ module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
}
if(changeTypes[idx] & VALUE) {
if(isSimpleValueProp) {
update[astr + '.' + valueName] = arr[idx][valueName];
update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : arr[idx][valueName];
} else {
update[astr + '.' + valueName] = nestedProperty(arr[idx], valueName).get();
update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : nestedProperty(arr[idx], valueName).get();
}
}
} else {
Expand Down
13 changes: 7 additions & 6 deletions test/jasmine/tests/legend_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,7 @@ describe('legend interaction', function() {
});
});


describe('editable mode interactions', function() {
var gd;
var mock = {
Expand Down Expand Up @@ -927,15 +928,15 @@ describe('legend interaction', function() {
// Set the name of the third legend item:
return _setValue(3, 'bar');
}).then(function() {
expect(gd.data[1].transforms[0].groupnames).toEqual([
{name: 'bar', group: 3}
expect(gd.data[1].transforms[0].styles).toEqual([
{value: {name: 'bar'}, target: 3}
]);
}).then(function() {
return _setValue(4, 'asdf');
}).then(function() {
expect(gd.data[1].transforms[0].groupnames).toEqual([
{name: 'bar', group: 3},
{name: 'asdf', group: 4}
expect(gd.data[1].transforms[0].styles).toEqual([
{value: {name: 'bar'}, target: 3},
{value: {name: 'asdf'}, target: 4}
]);
}).then(function() {
// Unset the group names:
Expand All @@ -944,7 +945,7 @@ describe('legend interaction', function() {
return _setValue(4, '');
}).then(function() {
// Verify the group names have been cleaned up:
expect(gd.data[1].transforms[0].groupnames).toEqual([]);
expect(gd.data[1].transforms[0].styles).toEqual([]);
}).catch(fail).then(done);
});
});
Expand Down
66 changes: 65 additions & 1 deletion test/jasmine/tests/lib_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1702,16 +1702,46 @@ describe('Test lib.js:', function() {
});

describe('with custom named properties', function() {
it('performs standard operations', function() {
it('performs all of the operations', function() {
var container = {styles: [
{foo: 'name1', bar: 'value1'},
{foo: 'name2', bar: 'value2'}
]};

var carr = Lib.keyedContainer(container, 'styles', 'foo', 'bar');

// SET A VALUE

carr.set('name3', 'value3');

expect(container).toEqual({styles: [
{foo: 'name1', bar: 'value1'},
{foo: 'name2', bar: 'value2'},
{foo: 'name3', bar: 'value3'}
]});

expect(carr.constructUpdate()).toEqual({
'styles[2].foo': 'name3',
'styles[2].bar': 'value3'
});

// REMOVE A VALUE

carr.remove('name2');

expect(container).toEqual({styles: [
{foo: 'name1', bar: 'value1'},
{foo: 'name3', bar: 'value3'}
]});

expect(carr.constructUpdate()).toEqual({
'styles[1].foo': 'name3',
'styles[1].bar': 'value3',
'styles[2]': null
});

// RENAME A VALUE

carr.rename('name1', 'name2');

expect(container).toEqual({styles: [
Expand All @@ -1725,6 +1755,24 @@ describe('Test lib.js:', function() {
'styles[1].bar': 'value3',
'styles[2]': null
});

// SET A VALUE

carr.set('name2', 'value2');

expect(container).toEqual({styles: [
{foo: 'name2', bar: 'value2'},
{foo: 'name3', bar: 'value3'}
]});

expect(carr.constructUpdate()).toEqual({
'styles[0].foo': 'name2',
'styles[0].bar': 'value2',
'styles[1].foo': 'name3',
'styles[1].bar': 'value3',
'styles[2]': null
});

});
});

Expand Down Expand Up @@ -1783,6 +1831,22 @@ describe('Test lib.js:', function() {
'styles[2]': null
});
});

it('unsets but does not remove items with extra top-level data', function() {
var container = {styles: [
{foo: 'name', bar: {value: 'value'}, extra: 'data'}
]};

var carr = Lib.keyedContainer(container, 'styles', 'foo', 'bar.value');

carr.remove('name');

expect(container.styles).toEqual([{foo: 'name', extra: 'data'}]);

expect(carr.constructUpdate()).toEqual({
'styles[0].bar.value': null,
});
});
});
});

Expand Down