chore: 🤖 add preliminary project crud

This commit is contained in:
Randall Morey committed 2020-05-21 10:59:42 -04:00
1 parent 7efaf6de00
commit 391c933819
20 files changed
+343 -13

No files matched your search

@@ -0,0 +1,44 @@
<form
novalidate
method="post"
{{on "submit" @submit}}>
<Rose::Form::Input
@id="name"
name="name"
@type="name"
@value={{@model.name}}
@label={{t "form.label.name"}}
@error={{false}}
disabled={{@model.isSaving}}
readonly={{false}}
/>
<Rose::Form::Textarea
@id="description"
name="description"
@type="description"
@value={{@model.description}}
@label={{t "form.label.description"}}
@error={{false}}
disabled={{@model.isSaving}}
readonly={{false}}
/>
<Rose::Button
@disabled={{@model.cannotSave}}
@submit={{true}}
@style="primary"
>
{{t 'actions.save'}}
</Rose::Button>
<Rose::Button
@disabled={{@model.cannotSave}}
@style="secondary"
{{on "click" @cancel}}
>
{{t 'actions.cancel'}}
</Rose::Button>
</form>
+5 -1
View File
@@ -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');
});
+12 -3
View File
@@ -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);
}
}
@@ -0,0 +1,3 @@
import Route from '@ember/routing/route';
export default class OrgsOrgProjectsIndexRoute extends Route {}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -1,9 +1 @@
<h1>{{t 'resources.projects'}}</h1>
<ul>
{{#each @model as |project|}}
<li>{{project.name}}</li>
{{/each}}
</ul>
{{outlet}}
@@ -0,0 +1,13 @@
<h2>{{t "resources.projects"}}</h2>
<LinkTo @route="orgs.org.projects.new">{{t "titles.new"}}</LinkTo>
<ul>
{{#each @model as |project|}}
<li>
<LinkTo @route="orgs.org.projects.project" @model={{project}}>
{{project.name}}
</LinkTo>
</li>
{{/each}}
</ul>
@@ -0,0 +1,11 @@
<LinkTo @route='orgs.org.projects'>{{t "resources.projects"}}</LinkTo>
>
<LinkTo @route='orgs.org.projects.new'>{{t "titles.new"}}</LinkTo>
<h2>{{t "titles.new"}}</h2>
<Form::Project
@model={{@model}}
@submit={{route-action "save" @model}}
@cancel={{route-action "cancel" @model}}
/>
@@ -0,0 +1,19 @@
<LinkTo @route='orgs.org.projects'>{{t "resources.projects"}}</LinkTo>
>
<LinkTo @route='orgs.org.projects.project'>{{@model.name}}</LinkTo>
<h2>{{@model.name}}</h2>
<Form::Project
@model={{@model}}
@submit={{route-action "save" @model}}
@cancel={{route-action "cancel" @model}}
/>
<Rose::Button
@style="warning"
@disabled={{@model.canSave}}
{{on "click" (route-action "delete" @model)}}
>
{{t 'actions.delete'}}
</Rose::Button>
+5 -1
View File
@@ -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');
}
+16
View File
@@ -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;
}
});
+5
View File
@@ -0,0 +1,5 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'project'
});
+1
View File
@@ -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",
@@ -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`
<Form::Project
@submit={{this.submit}}
@cancel={{this.cancel}}
/>`);
assert.ok(find('form'));
});
});
@@ -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);
});
});
@@ -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);
});
});
@@ -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);
});
});
+10
View File
@@ -1,2 +1,12 @@
actions:
cancel: Cancel
delete: Delete
save: Save
resources:
projects: Projects
titles:
new: New
form:
label:
name: Name
description: Description
+23
View File
@@ -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"