Secrets Sync UI: Refactor vercel-project destination to expect array from server (#24628)

* fix vercel project to expect array from server

* add test

* use reduce function!
This commit is contained in:
claire bontempo
2023-12-21 14:39:05 -08:00
committed by GitHub
parent 0ed86eb1a8
commit f2cc80c282
6 changed files with 92 additions and 19 deletions

View File

@@ -47,8 +47,9 @@ export default class SyncDestinationsVercelProjectModel extends SyncDestinationM
})
teamId;
// comma separated string, updated as array using deploymentEnvironmentsArray
@attr({
// commaString transforms param from the server's array type
// to a comma string so changedAttributes() will track changes
@attr('commaString', {
subText: 'Deployment environments where the environment variables are available.',
editType: 'checkboxList',
possibleValues: ['development', 'preview', 'production'],
@@ -56,9 +57,8 @@ export default class SyncDestinationsVercelProjectModel extends SyncDestinationM
})
deploymentEnvironments;
// Instead of using the 'array' attr transform, we keep deploymentEnvironments a string to leverage Ember's changedAttributes()
// which only tracks updates to string types. However, arrays are easier for managing multi-option selection so
// the fieldValue is used to get/set the deploymentEnvironments attribute to/from an array
// Arrays are easier for managing multi-option selection
// these get/set the deploymentEnvironments attribute via arrays
get deploymentEnvironmentsArray() {
// if undefined or an empty string, return empty array
return !this.deploymentEnvironments ? [] : this.deploymentEnvironments.split(',');

View File

@@ -12,17 +12,15 @@ export default class SyncDestinationSerializer extends ApplicationSerializer {
};
serialize(snapshot) {
// special serialization only for PATCH requests
if (snapshot.isNew) return super.serialize(snapshot);
const data = super.serialize(snapshot);
if (snapshot.isNew) return data;
// only send changed values
const data = {};
for (const attr in snapshot.changedAttributes()) {
// first array element is the old value
const [, newValue] = snapshot.changedAttributes()[attr];
data[decamelize(attr)] = newValue;
}
return data;
// only send changed parameters for PATCH requests
const changedKeys = Object.keys(snapshot.changedAttributes()).map((key) => decamelize(key));
return changedKeys.reduce((payload, key) => {
payload[key] = data[key];
return payload;
}, {});
}
// interrupt application's normalizeItems, which is called in normalizeResponse by application serializer

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Transform from '@ember-data/serializer/transform';
/**
* transforms array types from the server to a comma separated string
* useful when using changedAttributes() in the serializer to track attribute changes for PATCH requests
* because arrays are not trackable and strings are!
*/
export default class CommaString extends Transform {
deserialize(serialized) {
if (Array.isArray(serialized)) {
return serialized.join(',');
}
return serialized;
}
serialize(deserialized) {
if (typeof deserialized === 'string') {
return deserialized.split(',');
}
return deserialized;
}
}

View File

@@ -41,6 +41,6 @@ export default Factory.extend({
access_token: '*****',
project_id: 'prj_12345',
team_id: 'team_12345',
deployment_environments: 'development,preview', // 'production' is also an option, but left out for testing to assert form changes value
deployment_environments: ['development', 'preview'], // 'production' is also an option, but left out for testing to assert form changes value
}),
});

View File

@@ -212,6 +212,16 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
gh: ['accessToken'],
'vercel-project': ['accessToken', 'teamId', 'deploymentEnvironments'],
};
const EXPECTED_VALUE = (key) => {
switch (key) {
case 'deployment_environments':
return ['production'];
default:
// for all string type parameters
return `new-${key}-value`;
}
};
for (const destination of SYNC_DESTINATIONS) {
const { type, maskedParams } = destination;
module(`edit destination: ${type}`, function (hooks) {
@@ -237,9 +247,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
const expectedKeys = editable.map((k) => decamelize(k));
assert.propEqual(payloadKeys, expectedKeys, `${type} payload only contains editable attrs`);
expectedKeys.forEach((key) => {
// deploymentEnvironment field fixed possible options
const expectedValue = key === 'deployment_environments' ? 'production' : `new-${key}-value`;
assert.strictEqual(payload[key], expectedValue, `destination: ${type} updates key: ${key}`);
assert.deepEqual(payload[key], EXPECTED_VALUE(key), `destination: ${type} updates key: ${key}`);
});
return { payload };
});

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'vault/tests/helpers';
module('Unit | Transform | comma string', function (hooks) {
setupTest(hooks);
hooks.beforeEach(function () {
this.transform = this.owner.lookup('transform:comma-string');
});
test('it serializes correctly for API', function (assert) {
const serialized = this.transform.serialize('one,two,three');
assert.propEqual(serialized, ['one', 'two', 'three'], 'it serializes from string to array');
assert.propEqual(
this.transform.serialize(['not a string']),
['not a string'],
'it returns original value if not a string'
);
assert.propEqual(
this.transform.serialize('no commas'),
['no commas'],
'it splits a string without commas'
);
});
test('it deserializes correctly from API', function (assert) {
const deserialized = this.transform.deserialize(['one', 'two', 'three']);
assert.strictEqual(deserialized, 'one,two,three', 'it deserializes from array to string');
assert.strictEqual(
this.transform.deserialize('not an array'),
'not an array',
'it returns original value if not an array'
);
});
});