From ae535c607ea485d0b5829b2b53352e66da6347d8 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 19 May 2020 16:33:31 -0400 Subject: [PATCH 01/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20setup=20API=20con?= =?UTF-8?q?cerns=20for=20project=20resource?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/adapters/application.js | 14 ++++ addons/api/addon/adapters/project.js | 14 ++++ addons/api/addon/generated/models/project.js | 36 ++++++++++ addons/api/addon/models/base.js | 3 + addons/api/addon/models/project.js | 3 + addons/api/addon/serializers/application.js | 66 +++++++++++++++++ addons/api/app/adapters/application.js | 1 + addons/api/app/adapters/project.js | 1 + addons/api/app/models/base.js | 1 + addons/api/app/models/project.js | 1 + addons/api/app/serializers/application.js | 1 + addons/api/package.json | 1 + .../api/tests/dummy/app/routes/application.js | 4 +- addons/api/tests/dummy/config/environment.js | 4 ++ .../tests/unit/adapters/application-test.js | 20 ++++++ .../api/tests/unit/adapters/project-test.js | 16 +++++ addons/api/tests/unit/models/base-test.js | 13 ++++ addons/api/tests/unit/models/project-test.js | 12 ++++ .../unit/serializers/application-test.js | 70 +++++++++++++++++++ 19 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 addons/api/addon/adapters/application.js create mode 100644 addons/api/addon/adapters/project.js create mode 100644 addons/api/addon/generated/models/project.js create mode 100644 addons/api/addon/models/base.js create mode 100644 addons/api/addon/models/project.js create mode 100644 addons/api/addon/serializers/application.js create mode 100644 addons/api/app/adapters/application.js create mode 100644 addons/api/app/adapters/project.js create mode 100644 addons/api/app/models/base.js create mode 100644 addons/api/app/models/project.js create mode 100644 addons/api/app/serializers/application.js create mode 100644 addons/api/tests/unit/adapters/application-test.js create mode 100644 addons/api/tests/unit/adapters/project-test.js create mode 100644 addons/api/tests/unit/models/base-test.js create mode 100644 addons/api/tests/unit/models/project-test.js create mode 100644 addons/api/tests/unit/serializers/application-test.js diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js new file mode 100644 index 000000000..a64f443bc --- /dev/null +++ b/addons/api/addon/adapters/application.js @@ -0,0 +1,14 @@ +import RESTAdapter from '@ember-data/adapter/rest'; +import config from 'ember-get-config'; +import { get } from '@ember/object'; + +export default class ApplicationAdapter extends RESTAdapter { + // =attributes + + /** + * Sets namespace to the `api.namespace` string in the application's config, + * if set. + * @type {string} + */ + namespace = get(config, 'api.namespace'); +} diff --git a/addons/api/addon/adapters/project.js b/addons/api/addon/adapters/project.js new file mode 100644 index 000000000..a3a02d3e7 --- /dev/null +++ b/addons/api/addon/adapters/project.js @@ -0,0 +1,14 @@ +import ApplicationAdapter from './application'; +import ScopeAdapterMixin from '../mixins/scope-adapter'; + +export default class ProjectAdapter extends ApplicationAdapter.extend( + ScopeAdapterMixin +) { + // =attributes + + /** + * Project is scoped to org only, technically. + * @type {boolean} + */ + includeProject = false; +} diff --git a/addons/api/addon/generated/models/project.js b/addons/api/addon/generated/models/project.js new file mode 100644 index 000000000..abfe583d4 --- /dev/null +++ b/addons/api/addon/generated/models/project.js @@ -0,0 +1,36 @@ +import BaseModel from '../../models/base'; +import { attr } from '@ember-data/model'; + +/** + * Project contains all fields related to a Project resource + */ +export default class GeneratedProjectModel 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\nOutput only.', + readOnly: true, + }) + created_time; + + @attr('date', { + description: 'The time this resource was last updated\nOutput only.', + readOnly: true, + }) + updated_time; + + @attr('boolean', { + description: 'Whether the resource is disabled', + }) + disabled; +} diff --git a/addons/api/addon/models/base.js b/addons/api/addon/models/base.js new file mode 100644 index 000000000..d8d0d004d --- /dev/null +++ b/addons/api/addon/models/base.js @@ -0,0 +1,3 @@ +import Model from '@ember-data/model'; + +export default class BaseModel extends Model {} diff --git a/addons/api/addon/models/project.js b/addons/api/addon/models/project.js new file mode 100644 index 000000000..6799bf779 --- /dev/null +++ b/addons/api/addon/models/project.js @@ -0,0 +1,3 @@ +import GeneratedProjectModel from '../generated/models/project'; + +export default class OrgModel extends GeneratedProjectModel {} diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js new file mode 100644 index 000000000..a17ee2db3 --- /dev/null +++ b/addons/api/addon/serializers/application.js @@ -0,0 +1,66 @@ +import RESTSerializer from '@ember-data/serializer/rest'; +import { underscore } from '@ember/string'; +import { copy } from '@ember/object/internals'; + +/** + * Manages serialization/normalization of data to/from the API. + */ +export default class ApplicationSerializer extends RESTSerializer { + // =methods + + /** + * Generates an underscored key for the attribute. + * @param {string} attr + * @return {string} + */ + keyForAttribute(attr) { + return underscore(attr); + } + + /** + * If an attribute is annotated as readOnly in the model, don't serialize it. + * Otherwise delegate to default attribute serializer. + * @method serializeAttribute + * @param {Snapshot} snapshot + * @param {Object} json + * @param {String} key + * @param {Object} attribute + */ + serializeAttribute(snapshot, json, key, { options }) { + return options.readOnly ? null : super.serializeAttribute(...arguments); + } + + /** + * In our API, array payloads are always rooted under the same key `items`. + * Ember Data normally expects them to be rooted under their model name, + * e.g. `{hostCatalogs: [...]}`. This method makes this transformation to + * accommodate Ember Data. + * + * @override + * @param {object} store + * @param {object} primaryModelClass + * @param {object} payload + * @param {?string} id + * @param {string} requestType + * @return {object} + */ + normalizeArrayResponse(store, primaryModelClass, payload, id, requestType) { + // Setup a new payload data structure. + const transformedPayload = {}; + // Find the Ember-data-expected root key name. + const payloadKey = this.payloadKeyFromModelName( + primaryModelClass.modelName + ); + // Copy the data rooted under `items` in the existing payload into the new + // payload under the expected root key name. + transformedPayload[payloadKey] = copy(payload.items, true); + // Return the result of normalizing the transformed payload. + return super.normalizeArrayResponse( + store, + primaryModelClass, + transformedPayload, + id, + requestType + ); + } +} diff --git a/addons/api/app/adapters/application.js b/addons/api/app/adapters/application.js new file mode 100644 index 000000000..1e462f594 --- /dev/null +++ b/addons/api/app/adapters/application.js @@ -0,0 +1 @@ +export { default } from 'api/adapters/application'; diff --git a/addons/api/app/adapters/project.js b/addons/api/app/adapters/project.js new file mode 100644 index 000000000..0908e069c --- /dev/null +++ b/addons/api/app/adapters/project.js @@ -0,0 +1 @@ +export { default } from 'api/adapters/project'; diff --git a/addons/api/app/models/base.js b/addons/api/app/models/base.js new file mode 100644 index 000000000..56eb805f8 --- /dev/null +++ b/addons/api/app/models/base.js @@ -0,0 +1 @@ +export { default } from 'api/models/base'; diff --git a/addons/api/app/models/project.js b/addons/api/app/models/project.js new file mode 100644 index 000000000..1fc732fe1 --- /dev/null +++ b/addons/api/app/models/project.js @@ -0,0 +1 @@ +export { default } from 'api/models/project'; diff --git a/addons/api/app/serializers/application.js b/addons/api/app/serializers/application.js new file mode 100644 index 000000000..568fa69b6 --- /dev/null +++ b/addons/api/app/serializers/application.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/application'; diff --git a/addons/api/package.json b/addons/api/package.json index 46be73f6e..d5253ab79 100644 --- a/addons/api/package.json +++ b/addons/api/package.json @@ -41,6 +41,7 @@ "ember-data": "^3.16.0", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", + "ember-get-config": "^0.2.4", "ember-load-initializers": "^2.1.1", "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.6.0", diff --git a/addons/api/tests/dummy/app/routes/application.js b/addons/api/tests/dummy/app/routes/application.js index 62946d66d..036973710 100644 --- a/addons/api/tests/dummy/app/routes/application.js +++ b/addons/api/tests/dummy/app/routes/application.js @@ -4,7 +4,7 @@ import { inject as service } from '@ember/service'; export default class ApplicationRoute extends Route { @service scope; - afterModel() { - //console.log(this.scope); + model() { + this.store.findAll('project'); } } diff --git a/addons/api/tests/dummy/config/environment.js b/addons/api/tests/dummy/config/environment.js index 1b05a0196..b073014e3 100644 --- a/addons/api/tests/dummy/config/environment.js +++ b/addons/api/tests/dummy/config/environment.js @@ -21,6 +21,10 @@ module.exports = function (environment) { // Here you can pass flags/options to your application instance // when it is created }, + + api: { + namespace: '/api/addon/v1', + }, }; if (environment === 'development') { diff --git a/addons/api/tests/unit/adapters/application-test.js b/addons/api/tests/unit/adapters/application-test.js new file mode 100644 index 000000000..16d34c9b1 --- /dev/null +++ b/addons/api/tests/unit/adapters/application-test.js @@ -0,0 +1,20 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import config from 'ember-get-config'; + +module('Unit | Adapter | application', function (hooks) { + setupTest(hooks); + + test('its namespace is equal to the configured namespace', function (assert) { + assert.expect(2); + const adapter = this.owner.lookup('adapter:application'); + assert.ok(adapter.namespace); + assert.equal(adapter.namespace, config.api.namespace); + }); + + test('it generates correct URL prefixes', function (assert) { + assert.expect(1); + const adapter = this.owner.lookup('adapter:application'); + assert.equal(adapter.urlPrefix(), config.api.namespace); + }); +}); diff --git a/addons/api/tests/unit/adapters/project-test.js b/addons/api/tests/unit/adapters/project-test.js new file mode 100644 index 000000000..90576b2bd --- /dev/null +++ b/addons/api/tests/unit/adapters/project-test.js @@ -0,0 +1,16 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import config from 'ember-get-config'; + +module('Unit | Adapter | project', function (hooks) { + setupTest(hooks); + + test('it generates URL prefixes without project scope', function (assert) { + assert.expect(1); + const service = this.owner.lookup('service:scope'); + service.org = { id: 1 }; + service.project = { id: 2 }; + const adapter = this.owner.lookup('adapter:project'); + assert.equal(adapter.urlPrefix(), `${config.api.namespace}/orgs/1`); + }); +}); diff --git a/addons/api/tests/unit/models/base-test.js b/addons/api/tests/unit/models/base-test.js new file mode 100644 index 000000000..d2cb2f2b4 --- /dev/null +++ b/addons/api/tests/unit/models/base-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | base', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('base', {}); + assert.ok(model); + }); +}); diff --git a/addons/api/tests/unit/models/project-test.js b/addons/api/tests/unit/models/project-test.js new file mode 100644 index 000000000..9b2a93d73 --- /dev/null +++ b/addons/api/tests/unit/models/project-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | project', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('project', {}); + assert.ok(model); + }); +}); diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js new file mode 100644 index 000000000..281799a40 --- /dev/null +++ b/addons/api/tests/unit/serializers/application-test.js @@ -0,0 +1,70 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | application', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('application'); + assert.ok(serializer); + }); + + test('it serializes records', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const record = store.createRecord('project', { + name: 'Project', + description: 'Description', + }); + const serializedRecord = record.serialize(); + assert.deepEqual(serializedRecord, { + name: 'Project', + description: 'Description', + disabled: false, + }); + }); + + test('it normalizes array records from an `items` root key', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('project'); + const projectModelClass = store.createRecord('project').constructor; + const payload = { + items: [ + { id: 1, name: 'Project 1' }, + { id: 2, name: 'Project 2' }, + { id: 3, name: 'Project 3' }, + ], + }; + const normalizedArray = serializer.normalizeArrayResponse( + store, + projectModelClass, + payload + ); + assert.deepEqual(normalizedArray, { + included: [], + data: [ + { + id: '1', + type: 'project', + attributes: { name: 'Project 1' }, + relationships: {}, + }, + { + id: '2', + type: 'project', + attributes: { name: 'Project 2' }, + relationships: {}, + }, + { + id: '3', + type: 'project', + attributes: { name: 'Project 3' }, + relationships: {}, + }, + ], + }); + }); +}); From bfb79a51c13767dd567612222e5f33fef7ad8a9e Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 19 May 2020 16:51:45 -0400 Subject: [PATCH 02/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20migrate=20to=20em?= =?UTF-8?q?ber-copy=20addon=20because=20of=20deprecations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/serializers/application.js | 2 +- addons/api/package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index a17ee2db3..660c5992c 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -1,6 +1,6 @@ import RESTSerializer from '@ember-data/serializer/rest'; import { underscore } from '@ember/string'; -import { copy } from '@ember/object/internals'; +import { copy } from 'ember-copy'; /** * Manages serialization/normalization of data to/from the API. diff --git a/addons/api/package.json b/addons/api/package.json index d5253ab79..151c4959b 100644 --- a/addons/api/package.json +++ b/addons/api/package.json @@ -38,6 +38,7 @@ "ember-cli-sri": "^2.1.1", "ember-cli-template-lint": "^1.0.0-beta.3", "ember-cli-uglify": "^3.0.0", + "ember-copy": "^1.0.0", "ember-data": "^3.16.0", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", From 7d9e85b86abb625f8cf90494566e992edd8e3797 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 19 May 2020 18:10:39 -0400 Subject: [PATCH 03/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20initial=20m?= =?UTF-8?q?ocked=20projects=20list=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/models/project.js | 2 +- ui/core/app/router.js | 8 +- ui/core/app/routes/.gitkeep | 0 ui/core/app/routes/application.js | 6 + ui/core/app/routes/orgs.js | 3 + ui/core/app/routes/orgs/org.js | 20 ++ ui/core/app/routes/orgs/org/projects.js | 13 + ui/core/app/templates/orgs.hbs | 1 + ui/core/app/templates/orgs/org.hbs | 1 + ui/core/app/templates/orgs/org/projects.hbs | 9 + ui/core/config/environment.js | 16 +- ui/core/mirage/config.js | 25 ++ ui/core/mirage/factories/project.js | 3 + ui/core/mirage/generated/factories/project.js | 16 ++ ui/core/mirage/scenarios/default.js | 5 + ui/core/mirage/serializers/application.js | 45 ++++ ui/core/package.json | 2 + .../integration/helpers/app-name-test.js | 4 +- ui/core/tests/unit/routes/application-test.js | 11 + ui/core/tests/unit/routes/orgs-test.js | 11 + ui/core/tests/unit/routes/orgs/org-test.js | 11 + .../unit/routes/orgs/org/projects-test.js | 11 + ui/core/translations/en-us.yaml | 6 +- yarn.lock | 231 +++++++++++++++++- 24 files changed, 440 insertions(+), 20 deletions(-) delete mode 100644 ui/core/app/routes/.gitkeep create mode 100644 ui/core/app/routes/application.js create mode 100644 ui/core/app/routes/orgs.js create mode 100644 ui/core/app/routes/orgs/org.js create mode 100644 ui/core/app/routes/orgs/org/projects.js create mode 100644 ui/core/app/templates/orgs.hbs create mode 100644 ui/core/app/templates/orgs/org.hbs create mode 100644 ui/core/app/templates/orgs/org/projects.hbs create mode 100644 ui/core/mirage/config.js create mode 100644 ui/core/mirage/factories/project.js create mode 100644 ui/core/mirage/generated/factories/project.js create mode 100644 ui/core/mirage/scenarios/default.js create mode 100644 ui/core/mirage/serializers/application.js create mode 100644 ui/core/tests/unit/routes/application-test.js create mode 100644 ui/core/tests/unit/routes/orgs-test.js create mode 100644 ui/core/tests/unit/routes/orgs/org-test.js create mode 100644 ui/core/tests/unit/routes/orgs/org/projects-test.js diff --git a/addons/api/addon/models/project.js b/addons/api/addon/models/project.js index 6799bf779..063ed5110 100644 --- a/addons/api/addon/models/project.js +++ b/addons/api/addon/models/project.js @@ -1,3 +1,3 @@ import GeneratedProjectModel from '../generated/models/project'; -export default class OrgModel extends GeneratedProjectModel {} +export default class ProjectModel extends GeneratedProjectModel {} diff --git a/ui/core/app/router.js b/ui/core/app/router.js index 818c8d496..a20befcc5 100644 --- a/ui/core/app/router.js +++ b/ui/core/app/router.js @@ -6,4 +6,10 @@ export default class Router extends EmberRouter { rootURL = config.rootURL; } -Router.map(function () {}); +Router.map(function () { + this.route('orgs', function () { + this.route('org', { path: ':org_id' }, function () { + this.route('projects'); + }); + }); +}); diff --git a/ui/core/app/routes/.gitkeep b/ui/core/app/routes/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/core/app/routes/application.js b/ui/core/app/routes/application.js new file mode 100644 index 000000000..6cc5ca574 --- /dev/null +++ b/ui/core/app/routes/application.js @@ -0,0 +1,6 @@ +import Route from '@ember/routing/route'; + +/** + * Entry route for the application. + */ +export default class ApplicationRoute extends Route {} diff --git a/ui/core/app/routes/orgs.js b/ui/core/app/routes/orgs.js new file mode 100644 index 000000000..b3f86119f --- /dev/null +++ b/ui/core/app/routes/orgs.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class OrgsRoute extends Route {} diff --git a/ui/core/app/routes/orgs/org.js b/ui/core/app/routes/orgs/org.js new file mode 100644 index 000000000..d1fcd307a --- /dev/null +++ b/ui/core/app/routes/orgs/org.js @@ -0,0 +1,20 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default class OrgsOrgRoute extends Route { + // =services + + @service scope; + + // =methods + + /** + * Sets the scope to the specified organization and returns the org. + * @param {object} params + * @return {object} + */ + model({ org_id: id }) { + this.scope.org = { id }; + return this.scope.org; + } +} diff --git a/ui/core/app/routes/orgs/org/projects.js b/ui/core/app/routes/orgs/org/projects.js new file mode 100644 index 000000000..ee50e8f8a --- /dev/null +++ b/ui/core/app/routes/orgs/org/projects.js @@ -0,0 +1,13 @@ +import Route from '@ember/routing/route'; + +export default class OrgsOrgProjectsRoute extends Route { + // =methods + + /** + * Returns all projects from the store. + * @return {Promise[ProjectModel]} + */ + model() { + return this.store.findAll('project'); + } +} diff --git a/ui/core/app/templates/orgs.hbs b/ui/core/app/templates/orgs.hbs new file mode 100644 index 000000000..e2147cab0 --- /dev/null +++ b/ui/core/app/templates/orgs.hbs @@ -0,0 +1 @@ +{{outlet}} \ No newline at end of file diff --git a/ui/core/app/templates/orgs/org.hbs b/ui/core/app/templates/orgs/org.hbs new file mode 100644 index 000000000..c24cd6895 --- /dev/null +++ b/ui/core/app/templates/orgs/org.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/ui/core/app/templates/orgs/org/projects.hbs b/ui/core/app/templates/orgs/org/projects.hbs new file mode 100644 index 000000000..777cc4e17 --- /dev/null +++ b/ui/core/app/templates/orgs/org/projects.hbs @@ -0,0 +1,9 @@ +

{{t 'resources.projects'}}

+ +
    + {{#each @model as |project|}} +
  • {{project.name}}
  • + {{/each}} +
+ +{{outlet}} diff --git a/ui/core/config/environment.js b/ui/core/config/environment.js index fd1d4fdbd..9f1b5f837 100644 --- a/ui/core/config/environment.js +++ b/ui/core/config/environment.js @@ -25,6 +25,10 @@ module.exports = function (environment) { // when it is created }, + api: { + namespace: 'api/v1', + }, + appName: APP_NAME, contentSecurityPolicyHeader: 'Content-Security-Policy', @@ -38,8 +42,8 @@ module.exports = function (environment) { 'img-src': ["'self'"], 'style-src': ["'self'"], 'media-src': ["'self'"], - 'manifest-src': ["'self'"] - } + 'manifest-src': ["'self'"], + }, }; // Unsafe policy is necessary in development and test environments, but should @@ -60,10 +64,10 @@ module.exports = function (environment) { componentOptions: { axeOptions: { checks: { - 'color-contrast': { options: { noScroll: true } } - } - } - } + 'color-contrast': { options: { noScroll: true } }, + }, + }, + }, }; enableUnsafeCSP(); diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js new file mode 100644 index 000000000..7fae775bc --- /dev/null +++ b/ui/core/mirage/config.js @@ -0,0 +1,25 @@ +import config from '../config/environment'; + +export default function() { + + // make this `http://localhost:8080`, for example, if your API is on a different server + // this.urlPrefix = ''; + // make this `/api`, for example, if your API is namespaced + this.namespace = config.api.namespace; + // delay for each request, automatically set to 0 during testing + this.timing = 50; + + /* + Shorthand cheatsheet: + + this.get('/posts'); + this.post('/posts'); + this.get('/posts/:id'); + this.put('/posts/:id'); // or this.patch + this.del('/posts/:id'); + + https://www.ember-cli-mirage.com/docs/route-handlers/shorthands + */ + + this.get('/orgs/:org_id/projects'); +} diff --git a/ui/core/mirage/factories/project.js b/ui/core/mirage/factories/project.js new file mode 100644 index 000000000..2a3aae2be --- /dev/null +++ b/ui/core/mirage/factories/project.js @@ -0,0 +1,3 @@ +import factory from '../generated/factories/project'; + +export default factory.extend(); diff --git a/ui/core/mirage/generated/factories/project.js b/ui/core/mirage/generated/factories/project.js new file mode 100644 index 000000000..69054217d --- /dev/null +++ b/ui/core/mirage/generated/factories/project.js @@ -0,0 +1,16 @@ +import { Factory } from 'ember-cli-mirage'; +import { random, date } from 'faker'; + +/** + * GeneratedProjectModelFactory + * Project contains all fields related to a Project resource + */ +export default Factory.extend({ + + name: () => random.words(), + description: () => random.words(), + created_time: () => date.recent(), + updated_time: () => date.recent(), + disabled: () => random.boolean(), + +}); diff --git a/ui/core/mirage/scenarios/default.js b/ui/core/mirage/scenarios/default.js new file mode 100644 index 000000000..070bbc1ee --- /dev/null +++ b/ui/core/mirage/scenarios/default.js @@ -0,0 +1,5 @@ +export default function(server) { + + server.createList('project', 3); + +} diff --git a/ui/core/mirage/serializers/application.js b/ui/core/mirage/serializers/application.js new file mode 100644 index 000000000..a0b104e4a --- /dev/null +++ b/ui/core/mirage/serializers/application.js @@ -0,0 +1,45 @@ +import { RestSerializer } from 'ember-cli-mirage'; +import { underscore } from '@ember/string'; + +/** + * Manages serialization/normalization of data into and out of the mock API. + */ +export default RestSerializer.extend({ + + // =attributes + + /** + * False to disable automatically rooting payloads under a key. + * This is handled by the `serialize()` method, see below. + * @type {boolean} + */ + root: false, + + /** + * @type {boolean} + */ + embed: true, + + // =methods + + /** + * Generates an underscored key for the attribute. + * @param {string} attr + * @return {string} + */ + keyForAttribute(attr) { + return underscore(attr); + }, + + /** + * Serialize items into a root key `items`. + * @return {object} + */ + serialize() { + let json = RestSerializer.prototype.serialize.apply(this, arguments); + // If array, root it under a standard `items` key + if (json.length) json = {items: json}; + return json; + } + +}); diff --git a/ui/core/package.json b/ui/core/package.json index f93ab9c26..b8ff1d161 100644 --- a/ui/core/package.json +++ b/ui/core/package.json @@ -47,6 +47,7 @@ "ember-cli-eslint": "^5.1.0", "ember-cli-htmlbars": "^4.2.2", "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-mirage": "^1.1.6", "ember-cli-sass": "^10.0.1", "ember-cli-sri": "^2.1.1", "ember-cli-template-lint": "^1.0.0-beta.3", @@ -67,6 +68,7 @@ "emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774", "eslint-plugin-ember": "^7.7.2", "eslint-plugin-node": "^11.0.0", + "faker": "^4.1.0", "loader.js": "^4.7.0", "prettier": "^2.0.5", "qunit-dom": "^1.0.0", diff --git a/ui/core/tests/integration/helpers/app-name-test.js b/ui/core/tests/integration/helpers/app-name-test.js index cf2aac82f..aefbf2a57 100644 --- a/ui/core/tests/integration/helpers/app-name-test.js +++ b/ui/core/tests/integration/helpers/app-name-test.js @@ -3,10 +3,10 @@ import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; -module('Integration | Helper | app-name', function(hooks) { +module('Integration | Helper | app-name', function (hooks) { setupRenderingTest(hooks); - test('it renders', async function(assert) { + test('it renders', async function (assert) { await render(hbs`{{app-name}}`); assert.equal(this.element.textContent.trim(), 'Application Name'); }); diff --git a/ui/core/tests/unit/routes/application-test.js b/ui/core/tests/unit/routes/application-test.js new file mode 100644 index 000000000..a65ec1f35 --- /dev/null +++ b/ui/core/tests/unit/routes/application-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | application', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:application'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/orgs-test.js b/ui/core/tests/unit/routes/orgs-test.js new file mode 100644 index 000000000..72888b83a --- /dev/null +++ b/ui/core/tests/unit/routes/orgs-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | orgs', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:orgs'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/orgs/org-test.js b/ui/core/tests/unit/routes/orgs/org-test.js new file mode 100644 index 000000000..8f1eb4fe2 --- /dev/null +++ b/ui/core/tests/unit/routes/orgs/org-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | orgs/org', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:orgs/org'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects-test.js b/ui/core/tests/unit/routes/orgs/org/projects-test.js new file mode 100644 index 000000000..ce18ada89 --- /dev/null +++ b/ui/core/tests/unit/routes/orgs/org/projects-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | orgs/org/projects', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:orgs/org/projects'); + assert.ok(route); + }); +}); diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index 31983c217..e7823c763 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -1,4 +1,2 @@ -app: - name: 'Application Name' -example: - text: 'Example' +resources: + projects: Projects diff --git a/yarn.lock b/yarn.lock index e888e5e7a..d6e455ee3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1816,6 +1816,11 @@ resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.1.tgz#c1e8480844dfaeecde8b827d0e4bbf8793274659" integrity sha512-A3TBBjg5iVo8S4TTG0VrW8G9YNLob4+M6rALKjY8Sxr9zPExWQ7iTPUSvJVE7YhF9E08EQMubx1vRal3jtpJ9Q== +"@miragejs/pretender-node-polyfill@^0.1.0": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@miragejs/pretender-node-polyfill/-/pretender-node-polyfill-0.1.2.tgz#d26b6b7483fb70cd62189d05c95d2f67153e43f2" + integrity sha512-M/BexG/p05C5lFfMunxo/QcgIJnMT2vDVCd00wNqK2ImZONIlEETZwWJu1QtLxtmYlSHlCFl3JNzp0tLe7OJ5g== + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -3844,6 +3849,13 @@ babel-plugin-ember-modules-api-polyfill@^2.12.0, babel-plugin-ember-modules-api- dependencies: ember-rfc176-data "^0.3.12" +babel-plugin-ember-modules-api-polyfill@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.13.0.tgz#f9d3928d4c40192a44a5b52b67bbdedc4a748ea2" + integrity sha512-Q2i7uZMNSuJFYUV8stEsLQIRfMhQJew62sOnR+ESNjb4vlI7Rj1JlSgymTGwmCTVh+poTBpMidiWCoITDtbiIA== + dependencies: + ember-rfc176-data "^0.3.13" + babel-plugin-emotion@^10.0.20, babel-plugin-emotion@^10.0.27: version "10.0.33" resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz#ce1155dcd1783bbb9286051efee53f4e2be63e03" @@ -7251,7 +7263,7 @@ ember-assign-polyfill@^2.6.0: ember-cli-babel "^6.16.0" ember-cli-version-checker "^2.0.0" -ember-auto-import@^1.2.15, ember-auto-import@^1.5.3: +ember-auto-import@^1.2.15, ember-auto-import@^1.2.19, ember-auto-import@^1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-1.5.3.tgz#b32936f874d1ed7057ad2ed3f6116357820be44b" integrity sha512-7JfdunM1BmLy/lyUXu7uEoi0Gi4+dxkGM23FgIEyW5g7z4MidhP53Fc61t49oPSnq7+J4lLpbH1f6C+mDMgb4A== @@ -7361,6 +7373,38 @@ ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-version-checker "^2.1.2" semver "^5.5.0" +ember-cli-babel@^7.5.0: + version "7.20.0" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.20.0.tgz#aa77eadd15036a053534fc68f7933bb97c5a1e25" + integrity sha512-DwV5Cbn8wlRFpG8zQXxYPRb0NRnnaj/2qVDODeUEjM/doDhoDDbTSSaEI5Pf0kIlkVIimyGB8qpGv1fkgj2IfQ== + dependencies: + "@babel/core" "^7.9.0" + "@babel/helper-compilation-targets" "^7.8.7" + "@babel/plugin-proposal-class-properties" "^7.8.3" + "@babel/plugin-proposal-decorators" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.9.0" + "@babel/plugin-transform-runtime" "^7.9.0" + "@babel/plugin-transform-typescript" "^7.9.0" + "@babel/polyfill" "^7.8.7" + "@babel/preset-env" "^7.9.0" + "@babel/runtime" "^7.9.0" + amd-name-resolver "^1.2.1" + babel-plugin-debug-macros "^0.3.0" + babel-plugin-ember-data-packages-polyfill "^0.1.2" + babel-plugin-ember-modules-api-polyfill "^2.13.0" + babel-plugin-module-resolver "^3.1.1" + broccoli-babel-transpiler "^7.4.0" + broccoli-debug "^0.6.4" + broccoli-funnel "^2.0.1" + broccoli-source "^1.1.0" + clone "^2.1.2" + ember-cli-babel-plugin-helpers "^1.1.0" + ember-cli-version-checker "^4.1.0" + ensure-posix-path "^1.0.2" + fixturify-project "^1.10.0" + rimraf "^3.0.1" + semver "^5.5.0" + ember-cli-code-coverage@^1.0.0-beta.9: version "1.0.0-beta.9" resolved "https://registry.yarnpkg.com/ember-cli-code-coverage/-/ember-cli-code-coverage-1.0.0-beta.9.tgz#4a4ca60e88fbf1895de99e3b439f46c129417ab7" @@ -7503,6 +7547,21 @@ ember-cli-lodash-subset@^2.0.1: resolved "https://registry.yarnpkg.com/ember-cli-lodash-subset/-/ember-cli-lodash-subset-2.0.1.tgz#20cb68a790fe0fde2488ddfd8efbb7df6fe766f2" integrity sha1-IMtop5D+D94kiN39jvu332/nZvI= +ember-cli-mirage@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/ember-cli-mirage/-/ember-cli-mirage-1.1.6.tgz#4092966379a77ed2700ee0255c6426b272ef0607" + integrity sha512-xvW9ziv9JUKv9IA7SnmeCg0/pKiacro2Vg/kv8iqzFTRGMos51QrrsCI/2rXjAloGJM1SnaKL1TWUp25e76RVw== + dependencies: + broccoli-file-creator "^2.1.1" + broccoli-funnel "^2.0.1" + broccoli-merge-trees "^3.0.2" + ember-auto-import "^1.2.19" + ember-cli-babel "^7.5.0" + ember-get-config "^0.2.2" + ember-inflector "^2.0.0 || ^3.0.0" + lodash-es "^4.17.11" + miragejs "^0.1.31" + ember-cli-normalize-entity-name@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ember-cli-normalize-entity-name/-/ember-cli-normalize-entity-name-1.0.0.tgz#0b14f7bcbc599aa117b5fddc81e4fd03c4bad5b7" @@ -7881,7 +7940,7 @@ ember-fetch@^7.0.0: node-fetch "^2.6.0" whatwg-fetch "^3.0.0" -ember-get-config@0.2.4: +ember-get-config@0.2.4, ember-get-config@^0.2.2, ember-get-config@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/ember-get-config/-/ember-get-config-0.2.4.tgz#118492a2a03d73e46004ed777928942021fe1ecd" integrity sha1-EYSSoqA9c+RgBO13eSiUICH+Hs0= @@ -7889,7 +7948,7 @@ ember-get-config@0.2.4: broccoli-file-creator "^1.1.1" ember-cli-babel "^6.3.0" -ember-inflector@^3.0.1: +"ember-inflector@^2.0.0 || ^3.0.0", ember-inflector@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ember-inflector/-/ember-inflector-3.0.1.tgz#04be6df4d7e4000f6d6bd70787cdc995f77be4ab" integrity sha512-fngrwMsnhkBt51KZgwNwQYxgURwV4lxtoHdjxf7RueGZ5zM7frJLevhHw7pbQNGqXZ3N+MRkhfNOLkdDK9kFdA== @@ -8008,6 +8067,11 @@ ember-rfc176-data@^0.3.12: resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.12.tgz#90d82878e69e2ac9a5438e8ce14d12c6031c5bd2" integrity sha512-g9HeZj/gU5bfIIrGXkP7MhS2b3Vu5DfNUrYr14hy99TgIvtZETO+96QF4WOEUXGjIJdfTRjerVnQlqngPQSv1g== +ember-rfc176-data@^0.3.13: + version "0.3.13" + resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.13.tgz#ed1712a26e65fec703655f35410414aa1982cf3b" + integrity sha512-m9JbwQlT6PjY7x/T8HslnXP7Sz9bx/pz3FrNfNi2NesJnbNISly0Lix6NV1fhfo46572cpq4jrM+/6yYlMefTQ== + ember-router-generator@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-1.2.3.tgz#8ed2ca86ff323363120fc14278191e9e8f1315ee" @@ -8883,6 +8947,16 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= +fake-xml-http-request@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fake-xml-http-request/-/fake-xml-http-request-2.1.1.tgz#279fdac235840d7a4dff77d98ec44bce9fc690a6" + integrity sha512-Kn2WYYS6cDBS5jq/voOfSGCA0TafOYAUPbEp8mUVpD/DVV5bQIDjlq+MLLvNUokkbTpjBVlLDaM5PnX+PwZMlw== + +faker@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" + integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8= + fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -10566,6 +10640,11 @@ infer-owner@^1.0.3, infer-owner@^1.0.4: resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== +inflected@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inflected/-/inflected-2.0.4.tgz#323770961ccbe992a98ea930512e9a82d3d3ef77" + integrity sha512-HQPzFLTTUvwfeUH6RAGjD8cHS069mBqXG5n4qaxX7sJXBhVQrsGgF+0ZJGkSuN6a8pcUWB/GXStta11kKi/WvA== + inflection@1.12.0, inflection@^1.12.0: version "1.12.0" resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" @@ -11890,6 +11969,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash-es@^4.17.11: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" + integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== + lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -11949,11 +12033,21 @@ lodash.assign@^3.2.0: lodash._createassigner "^3.0.0" lodash.keys "^3.0.0" +lodash.assign@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= + lodash.assignin@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + lodash.capitalize@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" @@ -11964,11 +12058,16 @@ lodash.castarray@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115" integrity sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU= -lodash.clonedeep@^4.4.1: +lodash.clonedeep@^4.4.1, lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.compact@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.compact/-/lodash.compact-3.0.1.tgz#540ce3837745975807471e16b4a2ba21e7256ca5" + integrity sha1-VAzjg3dFl1gHRx4WtKK6IeclbKU= + lodash.concat@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.concat/-/lodash.concat-4.5.0.tgz#b053ae02e4a8008582e7256b9d02bda6d0380395" @@ -11996,7 +12095,7 @@ lodash.defaultsdeep@^4.6.0, lodash.defaultsdeep@^4.6.1: resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA== -lodash.find@^4.5.1: +lodash.find@^4.5.1, lodash.find@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= @@ -12009,11 +12108,36 @@ lodash.flatten@^3.0.2: lodash._baseflatten "^3.0.0" lodash._isiterateecall "^3.0.0" +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= +lodash.forin@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.forin/-/lodash.forin-4.4.0.tgz#5d3f20ae564011fbe88381f7d98949c9c9519731" + integrity sha1-XT8grlZAEfvog4H32YlJyclRlzE= + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.has@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" + integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= + +lodash.invokemap@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz#1748cda5d8b0ef8369c4eb3ec54c21feba1f2d62" + integrity sha1-F0jNpdiw74NpxOs+xUwh/rofLWI= + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -12024,6 +12148,31 @@ lodash.isarray@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + lodash.kebabcase@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -12043,11 +12192,21 @@ lodash.last@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.last/-/lodash.last-3.0.0.tgz#242f663112dd4c6e63728c60a3c909d1bdadbd4c" integrity sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw= -lodash.map@^4.5.1: +lodash.lowerfirst@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/lodash.lowerfirst/-/lodash.lowerfirst-4.3.1.tgz#de3c7b12e02c6524a0059c2f6cb7c5c52655a13d" + integrity sha1-3jx7EuAsZSSgBZwvbLfFxSZVoT0= + +lodash.map@^4.5.1, lodash.map@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= +lodash.mapvalues@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" + integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -12063,11 +12222,21 @@ lodash.omit@^4.1.0, lodash.omit@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= +lodash.pick@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= + lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= +lodash.snakecase@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" + integrity sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40= + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -12103,6 +12272,11 @@ lodash.uniqby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= +lodash.values@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= + lodash@4.17.15, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1, lodash@~4.17.12: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -12624,6 +12798,38 @@ minipass@^3.0.0, minipass@^3.1.1: dependencies: yallist "^4.0.0" +miragejs@^0.1.31: + version "0.1.38" + resolved "https://registry.yarnpkg.com/miragejs/-/miragejs-0.1.38.tgz#f2fd3f02d1470119c9aa654c9484785dcdd83e1a" + integrity sha512-6vw0U7QxR9aCHC+u7J+oaUeMxmxwgbUQLxglTINSzqNKfRgWSYk4XtZtgxmkOAheo2fzaNZa+XglbN1iAoRYUg== + dependencies: + "@miragejs/pretender-node-polyfill" "^0.1.0" + inflected "^2.0.4" + lodash.assign "^4.2.0" + lodash.camelcase "^4.3.0" + lodash.clonedeep "^4.5.0" + lodash.compact "^3.0.1" + lodash.find "^4.6.0" + lodash.flatten "^4.4.0" + lodash.forin "^4.4.0" + lodash.get "^4.4.2" + lodash.has "^4.5.2" + lodash.invokemap "^4.6.0" + lodash.isempty "^4.4.0" + lodash.isequal "^4.5.0" + lodash.isfunction "^3.0.9" + lodash.isinteger "^4.0.4" + lodash.isplainobject "^4.0.6" + lodash.lowerfirst "^4.3.1" + lodash.map "^4.6.0" + lodash.mapvalues "^4.6.0" + lodash.pick "^4.4.0" + lodash.snakecase "^4.1.1" + lodash.uniq "^4.5.0" + lodash.uniqby "^4.7.0" + lodash.values "^4.3.0" + pretender "^3.4.0" + mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -13767,6 +13973,14 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +pretender@^3.4.0: + version "3.4.2" + resolved "https://registry.yarnpkg.com/pretender/-/pretender-3.4.2.tgz#26e21f18091b34fea4c7cd0dee2c952cb8193f2c" + integrity sha512-tk0ll+EJP7qyF6uH0W3jWmPToNCStqYXx563SpB+ARM3N7o9973ORLd2E1e3LE0Re62TiWEBQ3ZKGLAX1YKRdw== + dependencies: + fake-xml-http-request "^2.1.1" + route-recognizer "^0.3.3" + prettier@^1.16.4: version "1.19.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" @@ -15218,6 +15432,11 @@ rollup@^1.12.0: "@types/node" "*" acorn "^7.1.0" +route-recognizer@^0.3.3: + version "0.3.4" + resolved "https://registry.yarnpkg.com/route-recognizer/-/route-recognizer-0.3.4.tgz#39ab1ffbce1c59e6d2bdca416f0932611e4f3ca3" + integrity sha512-2+MhsfPhvauN1O8KaXpXAOfR/fwe8dnUXVM+xw7yt40lJRfPVQxV6yryZm0cgRvAj5fMF/mdRZbL2ptwbs5i2g== + rsvp@^3.0.14, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.1.0: version "3.6.2" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" From bf72b02efd9205ce57410a889bdd6183aab93712 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 20 May 2020 11:14:24 -0400 Subject: [PATCH 04/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20update=20namespac?= =?UTF-8?q?e=20to=20`v1`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/core/config/environment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/core/config/environment.js b/ui/core/config/environment.js index 9f1b5f837..1f58b0d53 100644 --- a/ui/core/config/environment.js +++ b/ui/core/config/environment.js @@ -26,7 +26,7 @@ module.exports = function (environment) { }, api: { - namespace: 'api/v1', + namespace: 'v1', }, appName: APP_NAME, From e6bbdcbcf412c543caabdd9c6138ac5569052ff7 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 20 May 2020 11:47:31 -0400 Subject: [PATCH 05/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20support=20f?= =?UTF-8?q?or=20API=5FHOST=20build-time=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/adapters/application.js | 7 +++++++ addons/api/tests/dummy/config/environment.js | 3 ++- ui/core/README.md | 1 + ui/core/config/environment.js | 7 +++++++ ui/core/mirage/config.js | 3 +++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index a64f443bc..3fb95fda9 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -5,6 +5,13 @@ import { get } from '@ember/object'; export default class ApplicationAdapter extends RESTAdapter { // =attributes + /** + * Sets host to the `api.host` string in the application's config, + * if set. + * @type {string} + */ + host = get(config, 'api.host'); + /** * Sets namespace to the `api.namespace` string in the application's config, * if set. diff --git a/addons/api/tests/dummy/config/environment.js b/addons/api/tests/dummy/config/environment.js index b073014e3..914dad3be 100644 --- a/addons/api/tests/dummy/config/environment.js +++ b/addons/api/tests/dummy/config/environment.js @@ -23,7 +23,8 @@ module.exports = function (environment) { }, api: { - namespace: '/api/addon/v1', + host: '', + namespace: 'v1', }, }; diff --git a/ui/core/README.md b/ui/core/README.md index 84d20145c..5bfdb86b4 100644 --- a/ui/core/README.md +++ b/ui/core/README.md @@ -72,6 +72,7 @@ These environment variables may be used to customized the build. | Variable | Default Value | Description | | -------- | ------------- | ----------- | | `APP_NAME` | Application Name | The user-facing name of the application, appearing in titles, etc. | +| `API_HOST` | | The host of the API, if different than UI (e.g. https://example.net:1234). | ### Running Tests diff --git a/ui/core/config/environment.js b/ui/core/config/environment.js index 1f58b0d53..865c26a02 100644 --- a/ui/core/config/environment.js +++ b/ui/core/config/environment.js @@ -1,6 +1,7 @@ 'use strict'; const APP_NAME = process.env.APP_NAME || 'Application Name'; +const API_HOST = process.env.API_HOST || 'http://localhost:9200'; module.exports = function (environment) { let ENV = { @@ -26,6 +27,7 @@ module.exports = function (environment) { }, api: { + host: API_HOST, namespace: 'v1', }, @@ -71,6 +73,11 @@ module.exports = function (environment) { }; enableUnsafeCSP(); + // TODO: should provide an env var to explicitly add a host to CSP + // at build time for any environment (not just development), + // rather than automatically include API_HOST. Changes to CSP should + // be explicit. + if (API_HOST) ENV.contentSecurityPolicy['connect-src'].push(API_HOST); } if (environment === 'test') { diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 7fae775bc..323eca350 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -21,5 +21,8 @@ export default function() { https://www.ember-cli-mirage.com/docs/route-handlers/shorthands */ + if (config.api.host) this.passthrough(`${config.api.host}/**`); + this.passthrough(); + this.get('/orgs/:org_id/projects'); } From 6c0988f05cb4dd881acd476787db808d25378ca6 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 20 May 2020 11:53:48 -0400 Subject: [PATCH 06/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20hard-cod?= =?UTF-8?q?ed=20localhost=20API=5FHOST?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/core/config/environment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/core/config/environment.js b/ui/core/config/environment.js index 865c26a02..fef9febde 100644 --- a/ui/core/config/environment.js +++ b/ui/core/config/environment.js @@ -1,7 +1,7 @@ 'use strict'; const APP_NAME = process.env.APP_NAME || 'Application Name'; -const API_HOST = process.env.API_HOST || 'http://localhost:9200'; +const API_HOST = process.env.API_HOST || ''; module.exports = function (environment) { let ENV = { From b0921724ba9364927d983cad410535998244ad5d Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 20 May 2020 15:39:47 -0400 Subject: [PATCH 07/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20normalizer?= =?UTF-8?q?=20for=20singluar=20unrooted=20responses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/serializers/application.js | 34 ++++++++++++++++++- .../unit/serializers/application-test.js | 25 ++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index 660c5992c..92237e8f8 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -33,7 +33,7 @@ export default class ApplicationSerializer extends RESTSerializer { /** * In our API, array payloads are always rooted under the same key `items`. * Ember Data normally expects them to be rooted under their model name, - * e.g. `{hostCatalogs: [...]}`. This method makes this transformation to + * e.g. `{hostCatalogs: […]}`. This method makes this transformation to * accommodate Ember Data. * * @override @@ -63,4 +63,36 @@ export default class ApplicationSerializer extends RESTSerializer { requestType ); } + + /** + * In our API, singluar resources are _unrooted_, whereas Ember Data expects + * them to be rooted under their model name, e.g. `{project: {…}}`. + * This method makes this transformation to accommodate Ember Data. + * + * @method normalizeSingleResponse + * @param {Store} store + * @param {Model} primaryModelClass + * @param {Object} payload + * @param {String|Number} id + * @param {String} requestType + * @return {Object} JSON-API Document + */ + normalizeSingleResponse(store, primaryModelClass, payload, id, requestType) { + // Setup a new payload data structure. + const transformedPayload = {}; + // Find the Ember-data-expected root key name. + const payloadKey = this.payloadKeyFromModelName( + primaryModelClass.modelName + ); + // Copy the unrooted payload under the expected root key name. + transformedPayload[payloadKey] = copy(payload); + // Return the result of normalizing the transformed payload. + return super.normalizeSingleResponse( + store, + primaryModelClass, + transformedPayload, + id, + requestType + ); + } } diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index 281799a40..fabb999ed 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -67,4 +67,29 @@ module('Unit | Serializer | application', function (hooks) { ], }); }); + + test('it normalizes single, unrooted records', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('project'); + const projectModelClass = store.createRecord('project').constructor; + const payload = { + id: '1', + name: 'Project 1' + }; + const normalized = serializer.normalizeSingleResponse( + store, + projectModelClass, + payload + ); + assert.deepEqual(normalized, { + included: [], + data: { + id: '1', + type: 'project', + attributes: { name: 'Project 1' }, + relationships: {}, + } + }); + }); }); From ebdb938ca606876882f55905d568a62acfb21205 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 21 May 2020 09:22:56 -0400 Subject: [PATCH 08/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20improve=20adapter?= =?UTF-8?q?s=20and=20serializers=20to=20match=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/adapters/application.js | 19 ++++++++++++++ addons/api/addon/models/base.js | 21 ++++++++++++++- addons/api/addon/serializers/application.js | 19 ++++++++++++++ .../tests/unit/adapters/application-test.js | 11 ++++++++ addons/api/tests/unit/models/base-test.js | 26 +++++++++++++++++++ .../unit/serializers/application-test.js | 4 +-- 6 files changed, 97 insertions(+), 3 deletions(-) diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index 3fb95fda9..71c109cb0 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -18,4 +18,23 @@ export default class ApplicationAdapter extends RESTAdapter { * @type {string} */ namespace = get(config, 'api.namespace'); + + // =methods + + /** + * Overrides default ajax method by rewritting PUT requests as PATCH. + * PATCH is the request method used by our API for updates. + * + * @override + * @method ajax + * @private + * @param {String} url + * @param {String} type The request type GET, POST, PUT, DELETE etc. + * @param {Object} options + * @return {Promise} promise + */ + ajax(url, type, options) { + const method = type === 'PUT' ? 'PATCH' : type; + return super.ajax(url, method, options); + } } diff --git a/addons/api/addon/models/base.js b/addons/api/addon/models/base.js index d8d0d004d..389327972 100644 --- a/addons/api/addon/models/base.js +++ b/addons/api/addon/models/base.js @@ -1,3 +1,22 @@ import Model from '@ember-data/model'; +import { computed } from '@ember/object'; -export default class BaseModel extends Model {} +export default class BaseModel extends Model { + // =attributes + + /** + * @type {boolean} + */ + @computed('hasDirtyAttributes', 'isSaving') + get canSave() { + return this.hasDirtyAttributes && !this.isSaving; + } + + /** + * @type {boolean} + */ + @computed('canSave') + get cannotSave() { + return !this.canSave; + } +} diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index 92237e8f8..3ad83956e 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -20,6 +20,8 @@ export default class ApplicationSerializer extends RESTSerializer { /** * 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 @@ -30,6 +32,23 @@ export default class ApplicationSerializer extends RESTSerializer { return options.readOnly ? null : super.serializeAttribute(...arguments); } + /** + * In our API, request payloads are unrooted. But Ember Data roots request + * payloads for this adapter by default. Instead of assigned a root key + * on the outgoing hash, we copy the serialized attributes into it. + * + * @override + * @method serializeIntoHash + * @param {Object} hash + * @param {Model} typeClass + * @param {Snapshot} snapshot + * @param {Object} options + */ + serializeIntoHash(hash, typeClass, snapshot, options) { + const serialized = this.serialize(snapshot, options); + Object.assign(hash, serialized); + } + /** * In our API, array payloads are always rooted under the same key `items`. * Ember Data normally expects them to be rooted under their model name, diff --git a/addons/api/tests/unit/adapters/application-test.js b/addons/api/tests/unit/adapters/application-test.js index 16d34c9b1..5d07e1ccf 100644 --- a/addons/api/tests/unit/adapters/application-test.js +++ b/addons/api/tests/unit/adapters/application-test.js @@ -1,6 +1,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import config from 'ember-get-config'; +import RESTAdapter from '@ember-data/adapter/rest'; module('Unit | Adapter | application', function (hooks) { setupTest(hooks); @@ -17,4 +18,14 @@ module('Unit | Adapter | application', function (hooks) { const adapter = this.owner.lookup('adapter:application'); assert.equal(adapter.urlPrefix(), config.api.namespace); }); + + test('it rewrites PUT to PATCH, but leaves others unchanged', function (assert) { + assert.expect(1); + const adapter = this.owner.lookup('adapter:application'); + // TODO this is icky, should be changed to a spy or stub + RESTAdapter.prototype.ajax = (url, type) => { + assert.equal(type, 'PATCH'); + }; + adapter.ajax('/', 'PUT'); + }); }); diff --git a/addons/api/tests/unit/models/base-test.js b/addons/api/tests/unit/models/base-test.js index d2cb2f2b4..0fe0de40f 100644 --- a/addons/api/tests/unit/models/base-test.js +++ b/addons/api/tests/unit/models/base-test.js @@ -10,4 +10,30 @@ module('Unit | Model | base', function (hooks) { let model = store.createRecord('base', {}); assert.ok(model); }); + + test('it has canSave and cannotSave attributes', function (assert) { + assert.expect(8); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '1', + type: 'project', + attributes: {}, + }, + }); + const model = store.peekRecord('project', '1'); + assert.equal(model.canSave, false); + assert.equal(model.cannotSave, true); + // Should be able to save if dirty + model.name = 'Project'; + assert.equal(model.hasDirtyAttributes, true); + assert.equal(model.canSave, true); + assert.equal(model.cannotSave, false); + // Should not be able to save while currently saving + model.transitionTo('updated.inFlight'); + assert.equal(model.isSaving, true); + assert.equal(model.canSave, false); + assert.equal(model.cannotSave, true); + model.transitionTo('loaded'); + }); }); diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index fabb999ed..7949540b5 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -75,7 +75,7 @@ module('Unit | Serializer | application', function (hooks) { const projectModelClass = store.createRecord('project').constructor; const payload = { id: '1', - name: 'Project 1' + name: 'Project 1', }; const normalized = serializer.normalizeSingleResponse( store, @@ -89,7 +89,7 @@ module('Unit | Serializer | application', function (hooks) { type: 'project', attributes: { name: 'Project 1' }, relationships: {}, - } + }, }); }); }); From 7efaf6de003caadc72e86d5d4a10eceb9f11d84f Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 21 May 2020 10:57:51 -0400 Subject: [PATCH 09/14] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fields=20should=20n?= =?UTF-8?q?ot=20be=20wrapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/rose/addon/components/rose/form/input.js | 1 + addons/rose/addon/components/rose/form/textarea.js | 1 + 2 files changed, 2 insertions(+) diff --git a/addons/rose/addon/components/rose/form/input.js b/addons/rose/addon/components/rose/form/input.js index 2fd69d8c2..0b7915a17 100644 --- a/addons/rose/addon/components/rose/form/input.js +++ b/addons/rose/addon/components/rose/form/input.js @@ -18,4 +18,5 @@ import layout from '../../../templates/components/rose/form/input'; */ export default Component.extend({ layout, + tagName: '' }); diff --git a/addons/rose/addon/components/rose/form/textarea.js b/addons/rose/addon/components/rose/form/textarea.js index 835764c94..f8f4a6a71 100644 --- a/addons/rose/addon/components/rose/form/textarea.js +++ b/addons/rose/addon/components/rose/form/textarea.js @@ -18,4 +18,5 @@ import layout from '../../../templates/components/rose/form/textarea'; */ export default Component.extend({ layout, + tagName: '' }); From 391c933819db35b90beefa829fad78064e1002b0 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 21 May 2020 10:59:42 -0400 Subject: [PATCH 10/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20preliminary?= =?UTF-8?q?=20project=20crud?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/core/app/components/form/project/index.hbs | 44 +++++++++++ ui/core/app/router.js | 6 +- ui/core/app/routes/orgs/org.js | 15 +++- ui/core/app/routes/orgs/org/projects/index.js | 3 + ui/core/app/routes/orgs/org/projects/new.js | 46 ++++++++++++ .../app/routes/orgs/org/projects/project.js | 73 +++++++++++++++++++ ui/core/app/templates/orgs/org/projects.hbs | 8 -- .../app/templates/orgs/org/projects/index.hbs | 13 ++++ .../app/templates/orgs/org/projects/new.hbs | 11 +++ .../templates/orgs/org/projects/project.hbs | 19 +++++ ui/core/mirage/config.js | 6 +- ui/core/mirage/serializers/application.js | 16 ++++ ui/core/mirage/serializers/project.js | 5 ++ ui/core/package.json | 1 + .../components/form/project-test.js | 24 ++++++ .../routes/orgs/org/projects/index-test.js | 11 +++ .../unit/routes/orgs/org/projects/new-test.js | 11 +++ .../routes/orgs/org/projects/project-test.js | 11 +++ ui/core/translations/en-us.yaml | 10 +++ yarn.lock | 23 ++++++ 20 files changed, 343 insertions(+), 13 deletions(-) create mode 100644 ui/core/app/components/form/project/index.hbs create mode 100644 ui/core/app/routes/orgs/org/projects/index.js create mode 100644 ui/core/app/routes/orgs/org/projects/new.js create mode 100644 ui/core/app/routes/orgs/org/projects/project.js create mode 100644 ui/core/app/templates/orgs/org/projects/index.hbs create mode 100644 ui/core/app/templates/orgs/org/projects/new.hbs create mode 100644 ui/core/app/templates/orgs/org/projects/project.hbs create mode 100644 ui/core/mirage/serializers/project.js create mode 100644 ui/core/tests/integration/components/form/project-test.js create mode 100644 ui/core/tests/unit/routes/orgs/org/projects/index-test.js create mode 100644 ui/core/tests/unit/routes/orgs/org/projects/new-test.js create mode 100644 ui/core/tests/unit/routes/orgs/org/projects/project-test.js diff --git a/ui/core/app/components/form/project/index.hbs b/ui/core/app/components/form/project/index.hbs new file mode 100644 index 000000000..50c4e9249 --- /dev/null +++ b/ui/core/app/components/form/project/index.hbs @@ -0,0 +1,44 @@ +
+ + + + + + + {{t 'actions.save'}} + + + + {{t 'actions.cancel'}} + + + diff --git a/ui/core/app/router.js b/ui/core/app/router.js index a20befcc5..68162d24d 100644 --- a/ui/core/app/router.js +++ b/ui/core/app/router.js @@ -9,7 +9,11 @@ export default class Router extends EmberRouter { Router.map(function () { this.route('orgs', function () { this.route('org', { path: ':org_id' }, function () { - this.route('projects'); + this.route('projects', function () { + this.route('project', { path: ':project_id' }); + this.route('new'); + }); }); }); + this.route('projects'); }); diff --git a/ui/core/app/routes/orgs/org.js b/ui/core/app/routes/orgs/org.js index d1fcd307a..fcedca1e1 100644 --- a/ui/core/app/routes/orgs/org.js +++ b/ui/core/app/routes/orgs/org.js @@ -9,12 +9,21 @@ export default class OrgsOrgRoute extends Route { // =methods /** - * Sets the scope to the specified organization and returns the org. + * Returns a mock organization object containing the specified ID. + * TODO: load a real org instance from the API. * @param {object} params * @return {object} */ model({ org_id: id }) { - this.scope.org = { id }; - return this.scope.org; + return { id }; + } + + /** + * Adds the org to the scope. + * @param {object} org + */ + afterModel(org) { + this.scope.org = org; + return super.afterModel(...arguments); } } diff --git a/ui/core/app/routes/orgs/org/projects/index.js b/ui/core/app/routes/orgs/org/projects/index.js new file mode 100644 index 000000000..be2068f6d --- /dev/null +++ b/ui/core/app/routes/orgs/org/projects/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class OrgsOrgProjectsIndexRoute extends Route {} diff --git a/ui/core/app/routes/orgs/org/projects/new.js b/ui/core/app/routes/orgs/org/projects/new.js new file mode 100644 index 000000000..23261b7ee --- /dev/null +++ b/ui/core/app/routes/orgs/org/projects/new.js @@ -0,0 +1,46 @@ +import Route from '@ember/routing/route'; +import { action } from '@ember/object'; + +export default class OrgsOrgProjectsNewRoute extends Route { + // =methods + + /** + * Returns a new unsaved project. + * @return {Promise{ProjectModel}} + */ + model() { + return this.store.createRecord('project'); + } + + // =actions + + /** + * Rollback changes on project (which destroys unsaved instances) + * and redirects to projects index. + * @param {ProjectModel} project + */ + @action + cancel(project) { + project.rollbackAttributes(); + this.transitionTo('orgs.org.projects'); + } + + /** + * Handle save project. + * @param {ProjectModel} project + * @param {Event} e + */ + @action + async save(project, e) { + // Prevent default behavior, since this was trigger from a form submission. + e.preventDefault(); + try { + await project.save(); + this.transitionTo('orgs.org.projects.project', project); + } catch (e) { + // TODO: error handling + throw e; + } + } + +} diff --git a/ui/core/app/routes/orgs/org/projects/project.js b/ui/core/app/routes/orgs/org/projects/project.js new file mode 100644 index 000000000..e9cdf887a --- /dev/null +++ b/ui/core/app/routes/orgs/org/projects/project.js @@ -0,0 +1,73 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class OrgsOrgProjectsProjectRoute extends Route { + // =services + + @service scope; + + // =methods + + /** + * Returns a project by id. + * @param {object} params + * @return {Promise{ProjectModel}} + */ + model({ project_id: id }) { + return this.store.findRecord('project', id); + } + + /** + * Adds the project to the scope. + * @param {ProjectModel} project + */ + afterModel(project) { + this.scope.project = project; + return super.afterModel(...arguments); + } + + // =actions + + /** + * Rollback changes on project. + * @param {ProjectModel} project + */ + @action + cancel(project) { + project.rollbackAttributes(); + } + + /** + * Handle save project. + * @param {ProjectModel} project + * @param {Event} e + */ + @action + async save(project, e) { + // Prevent default behavior, since this was trigger from a form submission. + e.preventDefault(); + try { + await project.save(); + } catch (e) { + // TODO: error handling + throw e; + } + } + + /** + * Deletes the project and redirects to index. + * @param {ProjectModel} project + */ + @action + async delete(project) { + try { + await project.destroyRecord(); + this.transitionTo('orgs.org.projects'); + } catch (e) { + // TODO: error handling + throw e; + } + } + +} diff --git a/ui/core/app/templates/orgs/org/projects.hbs b/ui/core/app/templates/orgs/org/projects.hbs index 777cc4e17..c24cd6895 100644 --- a/ui/core/app/templates/orgs/org/projects.hbs +++ b/ui/core/app/templates/orgs/org/projects.hbs @@ -1,9 +1 @@ -

{{t 'resources.projects'}}

- -
    - {{#each @model as |project|}} -
  • {{project.name}}
  • - {{/each}} -
- {{outlet}} diff --git a/ui/core/app/templates/orgs/org/projects/index.hbs b/ui/core/app/templates/orgs/org/projects/index.hbs new file mode 100644 index 000000000..67fb74a45 --- /dev/null +++ b/ui/core/app/templates/orgs/org/projects/index.hbs @@ -0,0 +1,13 @@ +

{{t "resources.projects"}}

+ +{{t "titles.new"}} + +
    + {{#each @model as |project|}} +
  • + + {{project.name}} + +
  • + {{/each}} +
diff --git a/ui/core/app/templates/orgs/org/projects/new.hbs b/ui/core/app/templates/orgs/org/projects/new.hbs new file mode 100644 index 000000000..d848be793 --- /dev/null +++ b/ui/core/app/templates/orgs/org/projects/new.hbs @@ -0,0 +1,11 @@ +{{t "resources.projects"}} +> +{{t "titles.new"}} + +

{{t "titles.new"}}

+ + diff --git a/ui/core/app/templates/orgs/org/projects/project.hbs b/ui/core/app/templates/orgs/org/projects/project.hbs new file mode 100644 index 000000000..368276ddd --- /dev/null +++ b/ui/core/app/templates/orgs/org/projects/project.hbs @@ -0,0 +1,19 @@ +{{t "resources.projects"}} +> +{{@model.name}} + +

{{@model.name}}

+ + + + + {{t 'actions.delete'}} + diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 323eca350..ce0e06960 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -7,7 +7,7 @@ export default function() { // make this `/api`, for example, if your API is namespaced this.namespace = config.api.namespace; // delay for each request, automatically set to 0 during testing - this.timing = 50; + this.timing = 350; /* Shorthand cheatsheet: @@ -25,4 +25,8 @@ export default function() { this.passthrough(); this.get('/orgs/:org_id/projects'); + this.post('/orgs/:org_id/projects'); + this.get('/orgs/:org_id/projects/:id'); + this.patch('/orgs/:org_id/projects/:id'); + this.del('/orgs/:org_id/projects/:id'); } diff --git a/ui/core/mirage/serializers/application.js b/ui/core/mirage/serializers/application.js index a0b104e4a..bed25389e 100644 --- a/ui/core/mirage/serializers/application.js +++ b/ui/core/mirage/serializers/application.js @@ -40,6 +40,22 @@ export default RestSerializer.extend({ // If array, root it under a standard `items` key if (json.length) json = {items: json}; return json; + }, + + /** + * Adds a root to the payload, since `root: false` doesn't appear to affect + * the normalize method. + * @param {object} json + * @return {object} + */ + normalize(json) { + let newJSON = json; + if (this.modelName) { + newJSON = {}; + newJSON[this.modelName] = json; + } + const value = RestSerializer.prototype.normalize.apply(this, [newJSON]); + return value; } }); diff --git a/ui/core/mirage/serializers/project.js b/ui/core/mirage/serializers/project.js new file mode 100644 index 000000000..370fb5cf9 --- /dev/null +++ b/ui/core/mirage/serializers/project.js @@ -0,0 +1,5 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + modelName: 'project' +}); diff --git a/ui/core/package.json b/ui/core/package.json index b8ff1d161..15cf43ef8 100644 --- a/ui/core/package.json +++ b/ui/core/package.json @@ -63,6 +63,7 @@ "ember-page-title": "^5.2.1", "ember-qunit": "^4.6.0", "ember-resolver": "^7.0.0", + "ember-route-action-helper": "^2.0.8", "ember-source": "~3.16.0", "ember-welcome-page": "^4.0.0", "emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774", diff --git a/ui/core/tests/integration/components/form/project-test.js b/ui/core/tests/integration/components/form/project-test.js new file mode 100644 index 000000000..9c4073d47 --- /dev/null +++ b/ui/core/tests/integration/components/form/project-test.js @@ -0,0 +1,24 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, find } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | form/project', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + this.submit = () => {}; + this.cancel = () => {}; + + await render(hbs` + `); + + assert.ok(find('form')); + }); + +}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/index-test.js b/ui/core/tests/unit/routes/orgs/org/projects/index-test.js new file mode 100644 index 000000000..2bc8af956 --- /dev/null +++ b/ui/core/tests/unit/routes/orgs/org/projects/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | orgs/org/projects/index', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:orgs/org/projects/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/new-test.js b/ui/core/tests/unit/routes/orgs/org/projects/new-test.js new file mode 100644 index 000000000..fe57f0d9e --- /dev/null +++ b/ui/core/tests/unit/routes/orgs/org/projects/new-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | orgs/org/projects/new', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:orgs/org/projects/new'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/project-test.js b/ui/core/tests/unit/routes/orgs/org/projects/project-test.js new file mode 100644 index 000000000..a1e01801f --- /dev/null +++ b/ui/core/tests/unit/routes/orgs/org/projects/project-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | orgs/org/projects/project', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:orgs/org/projects/project'); + assert.ok(route); + }); +}); diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index e7823c763..0e2b062ad 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -1,2 +1,12 @@ +actions: + cancel: Cancel + delete: Delete + save: Save resources: projects: Projects +titles: + new: New +form: + label: + name: Name + description: Description diff --git a/yarn.lock b/yarn.lock index d6e455ee3..c60ae7ac4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7921,6 +7921,13 @@ ember-export-application-global@^2.0.1: resolved "https://registry.yarnpkg.com/ember-export-application-global/-/ember-export-application-global-2.0.1.tgz#b120a70e322ab208defc9e2daebe8d0dfc2dcd46" integrity sha512-B7wiurPgsxsSGzJuPFkpBWnaeuCu2PGpG2BjyrfA1VcL7//o+5RSnZqiCEY326y7qmxb2GoCgo0ft03KBU0rRw== +ember-factory-for-polyfill@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/ember-factory-for-polyfill/-/ember-factory-for-polyfill-1.3.1.tgz#b446ed64916d293c847a4955240eb2c993b86eae" + integrity sha512-y3iG2iCzH96lZMTWQw6LWNLAfOmDC4pXKbZP6FxG8lt7GGaNFkZjwsf+Z5GAe7kxfD7UG4lVkF7x37K82rySGA== + dependencies: + ember-cli-version-checker "^2.1.0" + ember-fetch@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/ember-fetch/-/ember-fetch-7.1.0.tgz#17549e0df0f8c402b68612aa87f77957fa956ebf" @@ -7948,6 +7955,14 @@ ember-get-config@0.2.4, ember-get-config@^0.2.2, ember-get-config@^0.2.4: broccoli-file-creator "^1.1.1" ember-cli-babel "^6.3.0" +ember-getowner-polyfill@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ember-getowner-polyfill/-/ember-getowner-polyfill-2.2.0.tgz#38e7dccbcac69d5ec694000329ec0b2be651d2b2" + integrity sha512-rwGMJgbGzxIAiWYjdpAh04Abvt0s3HuS/VjHzUFhVyVg2pzAuz45B9AzOxYXzkp88vFC7FPaiA4kE8NxNk4A4Q== + dependencies: + ember-cli-version-checker "^2.1.0" + ember-factory-for-polyfill "^1.3.1" + "ember-inflector@^2.0.0 || ^3.0.0", ember-inflector@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ember-inflector/-/ember-inflector-3.0.1.tgz#04be6df4d7e4000f6d6bd70787cdc995f77be4ab" @@ -8072,6 +8087,14 @@ ember-rfc176-data@^0.3.13: resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.13.tgz#ed1712a26e65fec703655f35410414aa1982cf3b" integrity sha512-m9JbwQlT6PjY7x/T8HslnXP7Sz9bx/pz3FrNfNi2NesJnbNISly0Lix6NV1fhfo46572cpq4jrM+/6yYlMefTQ== +ember-route-action-helper@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/ember-route-action-helper/-/ember-route-action-helper-2.0.8.tgz#f227fcccb73e839b65e9b814e241b322fe8c02fc" + integrity sha512-V+4uKwqaYveriVt2rl4e+9mzHJiQOr1B8dCPQQ2TS3iAcmi5RD2giRDFGtCK9d2XY9Arb/f9hJh0obP20iyt3A== + dependencies: + ember-cli-babel "^6.8.1" + ember-getowner-polyfill "^2.0.0" + ember-router-generator@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-1.2.3.tgz#8ed2ca86ff323363120fc14278191e9e8f1315ee" From 3da21e4a8e7c95e2961db78b4ee71899218677b2 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 21 May 2020 11:55:45 -0400 Subject: [PATCH 11/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20temporarily=20sil?= =?UTF-8?q?ence=20projects=20index=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/core/app/routes/orgs/org/projects.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/core/app/routes/orgs/org/projects.js b/ui/core/app/routes/orgs/org/projects.js index ee50e8f8a..11beb0531 100644 --- a/ui/core/app/routes/orgs/org/projects.js +++ b/ui/core/app/routes/orgs/org/projects.js @@ -7,7 +7,11 @@ export default class OrgsOrgProjectsRoute extends Route { * Returns all projects from the store. * @return {Promise[ProjectModel]} */ - model() { - return this.store.findAll('project'); + async model() { + try { + return await this.store.findAll('project'); + } catch (e) { + return []; + } } } From 28f28d96f403c66b8a1f44c93727e0326b86478f Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 21 May 2020 12:21:52 -0400 Subject: [PATCH 12/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20do=20not=20serial?= =?UTF-8?q?ize=20fields=20with=20name=20`disabled`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/serializers/application.js | 8 +++++++- addons/api/tests/unit/serializers/application-test.js | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index 3ad83956e..d5b1e333e 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -29,7 +29,13 @@ export default class ApplicationSerializer extends RESTSerializer { * @param {Object} attribute */ serializeAttribute(snapshot, json, key, { options }) { - return options.readOnly ? null : super.serializeAttribute(...arguments); + let value = super.serializeAttribute(...arguments); + // Do not serialize read-only attributes. + if (options.readOnly) delete json[key]; + // Do not serialize `disabled` fields. + // TODO: disabled is temporarily disabled + if (key === 'disabled') delete json[key]; + return value; } /** diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index 7949540b5..a9f999501 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -22,7 +22,7 @@ module('Unit | Serializer | application', function (hooks) { assert.deepEqual(serializedRecord, { name: 'Project', description: 'Description', - disabled: false, + //disabled: false, // TODO: disabled is temporarily disabled }); }); From ea1897c6fa8a934c2644e68fdead22f6cb3fde18 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Thu, 21 May 2020 12:34:44 -0400 Subject: [PATCH 13/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20Add=20styles=20to?= =?UTF-8?q?=20ui/core=20for=20projects=20overview=20&=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/core/app/components/form/project/index.hbs | 32 ++++---- ui/core/app/routes/orgs/org/projects/new.js | 1 - .../app/routes/orgs/org/projects/project.js | 1 - ui/core/app/styles/app.scss | 73 +++++++++++++++++++ ui/core/app/templates/application.hbs | 4 + .../app/templates/orgs/org/projects/index.hbs | 24 +++--- .../app/templates/orgs/org/projects/new.hbs | 23 +++--- .../components/form/project-test.js | 1 - .../unit/routes/orgs/org/projects/new-test.js | 4 +- 9 files changed, 123 insertions(+), 40 deletions(-) diff --git a/ui/core/app/components/form/project/index.hbs b/ui/core/app/components/form/project/index.hbs index 50c4e9249..e3279fd7d 100644 --- a/ui/core/app/components/form/project/index.hbs +++ b/ui/core/app/components/form/project/index.hbs @@ -1,4 +1,5 @@
@@ -25,20 +26,21 @@ readonly={{false}} /> - - {{t 'actions.save'}} - - - - {{t 'actions.cancel'}} - +
+ + {{t 'actions.save'}} + + + {{t 'actions.cancel'}} + +
diff --git a/ui/core/app/routes/orgs/org/projects/new.js b/ui/core/app/routes/orgs/org/projects/new.js index 23261b7ee..d8ffa28ae 100644 --- a/ui/core/app/routes/orgs/org/projects/new.js +++ b/ui/core/app/routes/orgs/org/projects/new.js @@ -42,5 +42,4 @@ export default class OrgsOrgProjectsNewRoute extends Route { throw e; } } - } diff --git a/ui/core/app/routes/orgs/org/projects/project.js b/ui/core/app/routes/orgs/org/projects/project.js index e9cdf887a..5441e1aaa 100644 --- a/ui/core/app/routes/orgs/org/projects/project.js +++ b/ui/core/app/routes/orgs/org/projects/project.js @@ -69,5 +69,4 @@ export default class OrgsOrgProjectsProjectRoute extends Route { throw e; } } - } diff --git a/ui/core/app/styles/app.scss b/ui/core/app/styles/app.scss index 72d055984..b99d6ced7 100644 --- a/ui/core/app/styles/app.scss +++ b/ui/core/app/styles/app.scss @@ -1 +1,74 @@ @import 'rose'; + +.projects-overview { + margin: 0 10px; + + .projects { + display: flex; + padding: 20px 0; + flex-direction: row; + flex-wrap: wrap; + + .project { + display: flex; + flex-direction: row; + flex: 0 0 33.33%; + margin-bottom: 10px; + + a { + text-decoration: none; + text-transform: capitalize; + color: #000; + cursor: pointer; + padding: 20px 30px 20px 20px; + border: 1px solid #bac1cc; + margin-right: 10px; + } + } + } +} + +.project-new-link { + cursor: pointer; + text-decoration: none; + padding: 10px; + color: #ffffff; + text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25); + background: #1563ff; + border: 1px solid #082766; + box-sizing: border-box; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + border-radius: 2px; + position: absolute; + right: 10px; + top: 70px; + display: inline-block; +} + +.project-new { + margin: 0 10px; + .breadcrumbs, + .breadcrumbs a { + color: #6f7682; + text-decoration: none; + } + + .project-new-form { + .form-actions { + display: flex; + flex-direction: row; + position: relative; + float: right; + + button { + margin-right: 10px; + } + } + + input, + textarea { + box-sizing: border-box; + width: 100%; + } + } +} diff --git a/ui/core/app/templates/application.hbs b/ui/core/app/templates/application.hbs index 71796d43c..221940351 100644 --- a/ui/core/app/templates/application.hbs +++ b/ui/core/app/templates/application.hbs @@ -1,5 +1,9 @@ + + + + {{title (app-name)}} {{outlet}} diff --git a/ui/core/app/templates/orgs/org/projects/index.hbs b/ui/core/app/templates/orgs/org/projects/index.hbs index 67fb74a45..dd364442c 100644 --- a/ui/core/app/templates/orgs/org/projects/index.hbs +++ b/ui/core/app/templates/orgs/org/projects/index.hbs @@ -1,13 +1,15 @@ -

{{t "resources.projects"}}

+
+

{{t "resources.projects"}}

-{{t "titles.new"}} + {{t "titles.new"}} -
    - {{#each @model as |project|}} -
  • - - {{project.name}} - -
  • - {{/each}} -
+
    + {{#each @model as |project|}} +
  • + + {{project.name}} + +
  • + {{/each}} +
+
diff --git a/ui/core/app/templates/orgs/org/projects/new.hbs b/ui/core/app/templates/orgs/org/projects/new.hbs index d848be793..8a101b818 100644 --- a/ui/core/app/templates/orgs/org/projects/new.hbs +++ b/ui/core/app/templates/orgs/org/projects/new.hbs @@ -1,11 +1,16 @@ -{{t "resources.projects"}} -> -{{t "titles.new"}} +
+ -

{{t "titles.new"}}

+

{{t "titles.new"}}

- + +
diff --git a/ui/core/tests/integration/components/form/project-test.js b/ui/core/tests/integration/components/form/project-test.js index 9c4073d47..d4cb276ed 100644 --- a/ui/core/tests/integration/components/form/project-test.js +++ b/ui/core/tests/integration/components/form/project-test.js @@ -20,5 +20,4 @@ module('Integration | Component | form/project', function (hooks) { assert.ok(find('form')); }); - }); diff --git a/ui/core/tests/unit/routes/orgs/org/projects/new-test.js b/ui/core/tests/unit/routes/orgs/org/projects/new-test.js index fe57f0d9e..f084da7bf 100644 --- a/ui/core/tests/unit/routes/orgs/org/projects/new-test.js +++ b/ui/core/tests/unit/routes/orgs/org/projects/new-test.js @@ -1,10 +1,10 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Route | orgs/org/projects/new', function(hooks) { +module('Unit | Route | orgs/org/projects/new', function (hooks) { setupTest(hooks); - test('it exists', function(assert) { + test('it exists', function (assert) { let route = this.owner.lookup('route:orgs/org/projects/new'); assert.ok(route); }); From 27814d1936627860f3c1189c564aa83b4a7b234a Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Thu, 21 May 2020 12:58:17 -0400 Subject: [PATCH 14/14] =?UTF-8?q?chore:=20=F0=9F=A4=96=20Add=20styling=20f?= =?UTF-8?q?or=20project=20overview?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/core/app/styles/app.scss | 2 +- .../templates/orgs/org/projects/project.hbs | 37 +++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/ui/core/app/styles/app.scss b/ui/core/app/styles/app.scss index b99d6ced7..5271833cf 100644 --- a/ui/core/app/styles/app.scss +++ b/ui/core/app/styles/app.scss @@ -12,7 +12,6 @@ .project { display: flex; flex-direction: row; - flex: 0 0 33.33%; margin-bottom: 10px; a { @@ -45,6 +44,7 @@ display: inline-block; } +.project-overview, .project-new { margin: 0 10px; .breadcrumbs, diff --git a/ui/core/app/templates/orgs/org/projects/project.hbs b/ui/core/app/templates/orgs/org/projects/project.hbs index 368276ddd..625c499a6 100644 --- a/ui/core/app/templates/orgs/org/projects/project.hbs +++ b/ui/core/app/templates/orgs/org/projects/project.hbs @@ -1,19 +1,24 @@ -{{t "resources.projects"}} -> -{{@model.name}} +
+ -

{{@model.name}}

+

{{@model.name}}

- + - - {{t 'actions.delete'}} - + + {{t 'actions.delete'}} + +