From 391c933819db35b90beefa829fad78064e1002b0 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 21 May 2020 10:59:42 -0400 Subject: [PATCH] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20preliminary=20pro?= =?UTF-8?q?ject=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"