-
Notifications
You must be signed in to change notification settings - Fork 29
Boundary ember code patterns
Add naming conventions for model name, serializer name, model file, serializer file, and look for more.
Add the feature flag to the Environment var's array:
- Admin UI:
ui/admin/config/environment.js
. - Desktop Client:
ui/desktop/config/environment.js
// Declare the feature to enable
let ENV = {
...
featureFlags: {
'featureToEnable': false, // Turn it off by default
}
...
}
// Enables feature flag in development environment.
if (environment === 'development') {
ENV.featureFlags['featureToEnable'] = true;
}
// Enables feature flag in test environment.
if (environment === 'test') {
ENV.featureFlags['featureToEnable'] = true;
}
// Enables feature flag in production environment.
if (environment === 'production') {
ENV.featureFlags['featureToEnable'] = true;
}
Use the feature flag in views:
{{#if (feature-flag 'featureToEnable')}}
... Your hbs code
{{/if}}
Once we decide to roll out the feature, we turn it true
within featureFlags
object. Since we turn it true
by default, we should delete especific environment declarations.
So the previous example will look like:
let ENV = {
...
featureFlags: {
'featureToEnable': true,
}
}
WORK IN PROGRESS Return table of contents.
All the API methods are here. We recommend copying (in raw) the content of controller.swagger.json
within the swagger editor for better visualization.
All the protobuf's files are here very useful for modeling.
Make sure you are: boundary-ui/addons/api
before running any command.
Artifacts involved: model
, serializer
and respective uni tests.
Be aware untitled-library
it is a naming example.
-
Generate model:
ember generate model untitled-library
. And do nothing with it for now. -
Create (manually) a generated model:
boundary-ui/addons/api/addon/generated/models/untitled-library.js
.
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* Describe what this model represents.
*
*/
export default class GeneratedUntitledLibraryModel extends BaseModel {
// =attributes
@attr('string', {
description: 'Optional name for identification purposes',
})
name;
@attr('string', {
description: 'Optional user-set description for identification purposes',
})
description;
@attr('date', {
description: 'The time this resource was created.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time this resource was last updated.',
readOnly: true,
})
updated_time;
@attr('number', {
description: 'Current version number of this resource.',
})
version;
Be aware the attributes marked on the protobuf file as Output only
mean they are readOnly
.
- On the previous generated
untitled-library
model in step 1, we change it to extend from the manually generated model created in step 2. This model has a model fragment for specific attribute, see it as a model to represent an attribute type.
import Model from '@ember-data/model';
export default class UntitledLibrary extends GeneratedModelUntitledModel {
// =attributes
@fragment('fragment-untitled-library-attributes', { defaultValue: {} })
attributes;
}
- Generate the fragment model as we generated the model before:
ember generate model fragment-untitled-library
. Change where it extends from, should beFragment
and then define attributes. Example:
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentUntitledLibraryAttributesModel extends Fragment {
// =attributes
@attr('string', {
description:
'The boolean expression filter to use to determine.',
})
filter;
}
Apart from the model's unit tests, there is not much we can use with models for the sanity check.
-
Generate serializers for the model and the fragment (if necessary):
-
$ ember generate serializer untitled-library
. -
$ ember generate serializer fragment-untitled-library-attributes
.
-
-
Update
serializers/untitled-library.js
to useApplicationSerializer
instead of defaultJSONAPISerializer
import ApplicationSerializer from './application';
export default class UntitledLibrarySerializer extends ApplicationSerializer {
...
}
- Update
serializers/fragment-untitled-library-attributes.js
to useJSONSerializer
instead of defaultJSONAPISerializer
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentUntitledLibraryAttributesSerializer extends JSONSerializer {
...
}
- Update attributes serializer to meet API expectations:
export default class FragmentUntitledLibraryAttributesSerializer extends JSONSerializer {
/**
* If an attribute is annotated as readOnly in the model, don't serialize it.
* Otherwise delegate to default attribute serializer.
* @override
* @method serializeAttribute
* @param {Snapshot} snapshot
* @param {Object} json
* @param {String} key
* @param {Object} attribute
*/
serializeAttribute(snapshot, json, key, attribute) {
const { type, options } = attribute;
let value = super.serializeAttribute(...arguments);
// Convert empty string to null.
if (type === 'string' && json[key] === '') json[key] = null;
// Do not serialize read-only attributes.
if (options.readOnly) delete json[key];
// Version is sent only if it has a non-nullish value
if (key === 'version') {
if (json[key] === null || json[key] === undefined) delete json[key];
}
return value;
}
}
- We do not serialize readOnly values.
- We do serialize empty strings to null.
- We delete some fields if they have a specific value (for example, very common on field
version
).
Think off all the serializations you are performing and test all the cases. Good practice is to look at api/tests/unit/serializers/
for examples and inspiration.
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Serializer | untitled library', function (hooks) {
setupTest(hooks);
test('it serializes correctly on create', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('untitled-library');
const record = store.createRecord('untitled-library', {
name: 'Example',
description: 'Description',
version: 1,
});
const snapshot = record._createSnapshot();
snapshot.adapterOptions = {};
const serializedRecord = serializer.serialize(snapshot);
assert.deepEqual(serializedRecord, {
name: 'Group',
description: 'Description',
version: 1,
attributes: {
description: null,
},
});
});
test('it does not serialize version when it has null or undefined value', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('untitled-library');
store.push({
data: {
id: '1',
type: 'untitled-library',
attributes: {
name: 'Test library',
description: 'Description for the test',
version: undefined,
attributes: {
description: 'Test description as attribute',
},
},
},
});
const record = store.peekRecord('untitled-library', '1');
const snapshot = record._createSnapshot();
const serializedRecord = serializer.serialize(snapshot);
assert.deepEqual(serializedRecord, {
name: 'untitled-library',
description: 'Description for the test',
attributes: {
description: 'Test description as attribute',
},
});
});
});
Artifacts involved config
file, generated factories
, factories
, serializers
, scenarios
- For CRUD operations we will need to mock 5 API methods:
api/mirage/config.js
// Returns the list of all Untitled libraries
this.get(
'/untitled-libraries',
({ untitledLibraries }, { queryParams: { scope_id: scopeId } }) => {
return untitledLibraries.where({ scopeId });
}
);
// Returns a single untitled library
this.get('/untitled-libraries/:id');
// Creates a single untitled library
this.post('/untitled-libraries');
// Deletes a single untitled library
this.del('/untitled-libraries/:id');
// Updates a single untitled library
this.patch('/untitled-libraries/:id');
- We need to add a
generated factory
:
addon/mirage/generated/factories/untitled-library.js
import { Factory } from 'ember-cli-mirage';
import { random, date, datatype } from 'faker';
/**
* GeneratedUntitledLibrary
* A Untitled library is a resource that represents a collection of libraries
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => datatype.number(),
});
- We need to add a
Factory
addon/mirage/factories/untitled-library.js
import factory from '../generated/factories/untitled-library';
import { random, system } from 'faker';
import permissions from '../helpers/permissions';
const types = ['vault'];
export default factory.extend({
id: (i) => `untitled-library-id-${i}`,
// Capabilities
authorized_actions: () =>
permissions.authorizedActionsFor('untitled-library') || [
'no-op',
'read',
'update',
'delete',
],
// In case the factory has also attributes you can generate them within the attributes function
attributes() {
switch (this.type) {
case 'type1':
return {
description: 'This will return a hardcoded string'
};
break;
default:
return {
description: () => random.words();
};
}
},
});
- We need to add a
Model
addon/mirage/models/untitled-library.js
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
});
- We need to add a
Serializer
addon/mirage/serializers/untitled-library.js
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'untitled-library',
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(
this,
arguments
);
return json;
},
});
- Relations when mocking:
For the sake of the example, let's assume to create
untitled-library
we need first alibrary-store
. Let's assumelibrary-store
it is just a list ofuntitled-library
.
Then, we will need to perform the creation of an untitled-library
after library-store
is created.
To do so, let's open library-store
factory:
addon/mirage/factories/library-store.js
import factory from '../generated/factories/library-store';
import { random, system } from 'faker';
import permissions from '../helpers/permissions';
const types = ['vault'];
export default factory.extend({
... factory code
withAssociations: trait({
afterCreate(libraryStore, server) {
const { scope } = credentialStore;
server.createList('untitled-library', 2, {
scope,
credentialStore,
});
},
}),
});
- Last step is to trigger the creation of
library-store
which will generateuntitled-library
with in. To do so we need to add the scenario to mirage:addon/mirage/scenarios/default.js
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
server.createList('library-store', 2, { scope }, 'withAssociations');
});
WORK IN PROGRESS
WORK IN PROGRESS
WORK IN PROGRESS
WORK IN PROGRESS
WORK IN PROGRESS