chore: 🤖 add preliminary project crud
This commit is contained in:
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>
|
||||
@@ -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');
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'project'
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -1,2 +1,12 @@
|
||||
actions:
|
||||
cancel: Cancel
|
||||
delete: Delete
|
||||
save: Save
|
||||
resources:
|
||||
projects: Projects
|
||||
titles:
|
||||
new: New
|
||||
form:
|
||||
label:
|
||||
name: Name
|
||||
description: Description
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user