Compare commits

...
This repository has been archived on 2025-03-30. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.

16 commits

Author SHA1 Message Date
b3a5d1caa2 MAINT: Read-only user 2020-01-27 14:40:20 -07:00
Matthew Dillon
14283f0c77 Strain form referencing wrong notes variable 2015-12-02 11:52:36 -07:00
Matthew Dillon
3313b6e5f9 Don't unload user models on delete 2015-12-02 11:43:58 -07:00
Matthew Dillon
8d4affd572 Merge pull request #70 from thermokarst/refactormeas
Refactor measurements
2015-12-02 11:31:13 -07:00
Matthew Dillon
e283f3f046 Stop creating unnecessary characteristic 2015-12-02 11:18:18 -07:00
Matthew Dillon
0956ef3a25 Fix characteristic naming to measurement 2015-12-02 10:47:07 -07:00
Matthew Dillon
658304f728 Clean up no measurements on record 2015-12-02 10:42:59 -07:00
Matthew Dillon
50cc073d2f Prevent null-related error in new strain, linting 2015-12-02 10:25:04 -07:00
Matthew Dillon
66c185e441 Chained error handling in strains controller 2015-12-02 10:13:44 -07:00
Matthew Dillon
df5a50b1e2 Use ajax error service in password reset 2015-12-02 10:13:22 -07:00
Matthew Dillon
689ea55bed Ajax Error Service 2015-12-02 10:13:06 -07:00
Matthew Dillon
9496de21d9 Clean up characteristics dropdown 2015-12-01 19:25:29 -07:00
Matthew Dillon
2811143066 Queue new measurements
Still some bound attributes / coupling, but getting better
2015-12-01 13:13:24 -07:00
Matthew Dillon
14698d0394 Fix up serializer for ember-data 2 2015-12-01 13:11:42 -07:00
Matthew Dillon
233a2d09a1 Refactoring delete measurement 2015-12-01 07:03:43 -07:00
Matthew Dillon
a4dbb4a94d Add measurement table cancel 2015-11-30 17:31:12 -07:00
19 changed files with 13289 additions and 98 deletions

1
.gitignore vendored
View file

@ -15,3 +15,4 @@
/libpeerconnection.log /libpeerconnection.log
npm-debug.log npm-debug.log
testem.log testem.log
.firebase

View file

@ -8,7 +8,10 @@ export default Mixin.create({
actions: { actions: {
delete: function() { delete: function() {
this.get('model').destroyRecord().then(() => { this.get('model').destroyRecord().then(() => {
this.get('store').unloadAll(); // Instead of unloading the entire store, we keep the loaded user models
['species', 'strain', 'characteristic', 'measurement'].map((model) => {
this.get('store').unloadAll(model);
});
this.transitionToRoute(this.get('transitionRoute')); this.transitionToRoute(this.get('transitionRoute'));
}); });
}, },

View file

@ -10,3 +10,7 @@
<div> <div>
{{link-to 'Forget your password?' 'users.requestlockouthelp'}} {{link-to 'Forget your password?' 'users.requestlockouthelp'}}
</div> </div>
<br>
<div>
Just checking things out? Log in with email <code>read-only</code> and password <code>bacteria</code>!
</div>

View file

@ -1,36 +1,61 @@
import Ember from 'ember'; import Ember from 'ember';
import SaveModel from '../../../../mixins/save-model';
import ajaxError from '../../../../utils/ajax-error';
const { Controller } = Ember; const { Controller, RSVP, inject: { service } } = Ember;
export default Controller.extend({
ajaxError: service('ajax-error'),
export default Controller.extend(SaveModel, {
// Required for SaveModel mixin
fallbackRouteSave: 'protected.strains.show', fallbackRouteSave: 'protected.strains.show',
fallbackRouteCancel: 'protected.strains.show', fallbackRouteCancel: 'protected.strains.show',
actions: { actions: {
addCharacteristic: function() { save: function(properties, deleteQueue, updateQueue) {
return this.store.createRecord('measurement', { let promises = [];
characteristic: this.store.createRecord('characteristic', { sortOrder: -999 }), properties.measurements.forEach((measurement) => {
if (measurement.get('isNew')) {
promises.push(measurement.save());
}
});
updateQueue.forEach((measurement) => {
promises.push(measurement.save());
});
deleteQueue.forEach((measurement) => {
promises.push(measurement.destroyRecord());
});
const model = this.get('model');
const fallbackRoute = this.get('fallbackRouteSave');
RSVP.all(promises).then(() => {
// Can't call _super inside promise, have to reproduce save-model
// mixin here :-(
model.setProperties(properties);
model.save().then((model) => {
this.get('flashMessages').clearMessages();
this.transitionToRoute(fallbackRoute, model);
});
}, (errors) => {
this.get('ajaxError').alert(errors);
}); });
}, },
saveMeasurement: function(measurement, properties) { cancel: function() {
measurement.setProperties(properties); const model = this.get('model');
measurement.save().then(() => {
this.get('flashMessages').clearMessages();
}, () => {
ajaxError(measurement.get('errors'), this.get('flashMessages'));
});
},
deleteMeasurement: function(measurement) { model.get('errors').clear();
const characteristic = measurement.get('characteristic'); model.rollbackAttributes();
if (characteristic.get('isNew')) {
characteristic.destroyRecord(); if (model.get('isNew')) {
this.transitionToRoute(this.get('fallbackRouteCancel'));
} else {
this.transitionToRoute(this.get('fallbackRouteCancel'), model);
} }
measurement.destroyRecord(); },
addMeasurement: function() {
return this.store.createRecord('measurement');
}, },
}, },

View file

@ -2,10 +2,8 @@
protected/strains/strain-form protected/strains/strain-form
strain=model strain=model
speciesList=speciesList speciesList=speciesList
add-characteristic=(action "addCharacteristic") add-measurement=(action "addMeasurement")
allCharacteristics=allCharacteristics allCharacteristics=allCharacteristics
save-measurement=(action "saveMeasurement")
delete-measurement=(action "deleteMeasurement")
on-save=(action "save") on-save=(action "save")
on-cancel=(action "cancel") on-cancel=(action "cancel")
}} }}

View file

@ -10,6 +10,8 @@ export default Component.extend({
allCharacteristics: null, allCharacteristics: null,
measurement: null, measurement: null,
isDirty: null, isDirty: null,
isNew: false,
isQueued: false,
// Actions // Actions
"save-measurement": null, "save-measurement": null,
@ -22,11 +24,23 @@ export default Component.extend({
notes: null, notes: null,
resetOnInit: Ember.on('init', function() { resetOnInit: Ember.on('init', function() {
this._resetProperties();
}),
_resetProperties: function() {
this.get('propertiesList').forEach((field) => { this.get('propertiesList').forEach((field) => {
const valueInMeasurement = this.get('measurement').get(field); const valueInMeasurement = this.get('measurement').get(field);
this.set(field, valueInMeasurement); this.set(field, valueInMeasurement);
}); });
}), // Read-only attributes
this.set('isNew', this.get('measurement.isNew'));
if (this.get('isNew') && !this.get('isQueued')) {
this.set('isEditing', true);
} else {
this.set('isEditing', false);
}
this.set('isDirty', false);
},
updateField: function(property, value) { updateField: function(property, value) {
this.set(property, value); this.set(property, value);
@ -40,12 +54,22 @@ export default Component.extend({
actions: { actions: {
edit: function() { edit: function() {
this.toggleProperty('isEditing'); this.set('isEditing', true);
}, },
save: function() { save: function() {
this.attrs['save-measurement'](this.get('measurement'), this.getProperties(this.get('propertiesList'))); this.attrs['save-measurement'](this.get('measurement'), this.getProperties(this.get('propertiesList')));
this.toggleProperty('isEditing'); this.set('isQueued', true);
this._resetProperties();
},
cancel: function() {
if (this.get('isNew')) {
this.attrs['delete-measurement'](this.get('measurement'));
} else {
this._resetProperties();
this.set('isEditing', false);
}
}, },
delete: function() { delete: function() {

View file

@ -0,0 +1 @@
{{loading-panel}}

View file

@ -1,5 +1,7 @@
{{#if isEditing}} {{#if isEditing}}
<td></td> <td>
{{{characteristic.characteristicTypeName}}}
</td>
<td> <td>
<select onchange={{action "characteristicDidChange" value="target.value"}}> <select onchange={{action "characteristicDidChange" value="target.value"}}>
{{#each allCharacteristics as |characteristicChoice|}} {{#each allCharacteristics as |characteristicChoice|}}
@ -15,14 +17,13 @@
</td> </td>
{{#if canEdit}} {{#if canEdit}}
<td> <td>
{{#if isDirty}} <button class="button-gray smaller" {{action 'cancel'}}>
<button class="button-green smaller" {{action 'save'}}>
Save
</button>
{{else}}
<button class="button-gray smaller" {{action 'save'}}>
Cancel Cancel
</button> </button>
{{#if isDirty}}
<button class="button-green smaller" {{action 'save'}}>
Save
</button>
{{/if}} {{/if}}
</td> </td>
{{/if}} {{/if}}

View file

@ -5,13 +5,13 @@ const { sort } = computed;
export default Component.extend({ export default Component.extend({
// Passed in // Passed in
strain: null, measurements: null,
allCharacteristics: null, allCharacteristics: null,
canEdit: false, canEdit: false,
canAdd: false, canAdd: false,
// Actions // Actions
"add-characteristic": null, "add-measurement": null,
"save-measurement": null, "save-measurement": null,
"delete-measurement": null, "delete-measurement": null,
@ -19,15 +19,11 @@ export default Component.extend({
sortParams: ['characteristic.characteristicTypeName', 'characteristic.sortOrder', 'characteristic.characteristicName'], sortParams: ['characteristic.characteristicTypeName', 'characteristic.sortOrder', 'characteristic.characteristicName'],
sortAsc: true, sortAsc: true,
paramsChanged: false, paramsChanged: false,
sortedMeasurements: sort('strain.measurements', 'sortParams'), sortedMeasurements: sort('measurements', 'sortParams'),
measurementsPresent: computed('strain.measurements', function() {
return this.get('strain.measurements.length') > 0;
}),
actions: { actions: {
addCharacteristic: function() { addMeasurement: function() {
const newChar = this.attrs['add-characteristic'](); return this.attrs['add-measurement']();
this.get('strain.measurements').addObject(newChar);
}, },
changeSortParam: function(col) { changeSortParam: function(col) {

View file

@ -1,17 +1,16 @@
{{#if canAdd}} {{#if canAdd}}
<br> <br>
<button class="button-green smaller" {{action "addCharacteristic"}}> <button class="button-green smaller" {{action "addMeasurement"}}>
Add characteristic Add measurement
</button> </button>
<br><br> <br><br>
{{/if}} {{/if}}
{{#if measurementsPresent}} {{#if paramsChanged}}
{{#if paramsChanged}} <button class="button-gray smaller" {{action 'resetSortParam'}}>
<button class="button-gray smaller" {{action 'resetSortParam'}}> Reset sort
Reset sort </button>
</button> {{/if}}
{{/if}}
<table class="flakes-table"> <table class="flakes-table">
<colgroup> <colgroup>
{{#if canEdit}} {{#if canEdit}}
@ -48,9 +47,10 @@
allCharacteristics=allCharacteristics allCharacteristics=allCharacteristics
canEdit=canEdit canEdit=canEdit
}} }}
{{else}}
<tr>
<td colspan="5">No Measurements on Record</td>
</tr>
{{/each}} {{/each}}
</tbody> </tbody>
</table> </table>
{{else}}
No measurements on record.
{{/if}}

View file

@ -71,11 +71,11 @@
{{! ROW 5 }} {{! ROW 5 }}
<div class="grid-1 gutter-20"> <div class="grid-1 gutter-20">
<dl class="span-1"> <dl class="span-1">
<dt>Characteristics</dt> <dt>Characteristic Measurements</dt>
<dd> <dd>
{{ {{
protected/strains/measurements-table protected/strains/measurements-table
strain=strain measurements=strain.measurements
canEdit=false canEdit=false
canAdd=false canAdd=false
}} }}

View file

@ -9,22 +9,22 @@ export default Component.extend(SetupMetaData, {
isNew: null, isNew: null,
isDirty: false, isDirty: false,
speciesList: null, speciesList: null,
allCharacteristics: null, allCharacteristics: [],
updateQueue: [],
deleteQueue: [],
// Actions // Actions
"on-save": null, "on-save": null,
"on-cancel": null, "on-cancel": null,
"on-update": null, "on-update": null,
"add-characteristic": null, "add-measurements": null,
"save-measurement": null,
"delete-measurement": null,
// CPs // CPs
sortParams: ['sortOrder'], sortParams: ['sortOrder'],
sortedSpeciesList: sort('speciesList', 'sortParams'), sortedSpeciesList: sort('speciesList', 'sortParams'),
// Property mapping // Property mapping
propertiesList: ['strainName', 'typeStrain', 'species', 'isolatedFrom', 'accessionNumbers', 'genbank', 'wholeGenomeSequence', 'notes'], propertiesList: ['strainName', 'typeStrain', 'species', 'isolatedFrom', 'accessionNumbers', 'genbank', 'wholeGenomeSequence', 'notes', 'measurements'],
strainName: null, strainName: null,
typeStrain: null, typeStrain: null,
species: null, species: null,
@ -33,15 +33,55 @@ export default Component.extend(SetupMetaData, {
genbank: null, genbank: null,
wholeGenomeSequence: null, wholeGenomeSequence: null,
notes: null, notes: null,
measurements: [],
// Dropdown menu
characteristics: [],
charSortParams: ['characteristicTypeName', 'sortOrder', 'characteristicName'],
sortedCharacteristics: sort('characteristics', 'charSortParams'),
setupCharacteristics: Ember.on('init', function() {
const tempArray = this._resetArray(this.get('allCharacteristics'));
this.set('characteristics', tempArray);
}),
resetOnInit: Ember.on('init', function() { resetOnInit: Ember.on('init', function() {
this._resetProperties();
}),
_resetArray: function(arr) {
let tempArray = [];
arr.forEach((val) => {
if (!val.get('isNew')) {
tempArray.push(val);
}
});
return tempArray;
},
_resetProperties: function() {
// Still some coupling going on here because of adding strain to measurement
this.get('measurements').forEach((val) => {
if (val.get('hasDirtyAttributes')) {
val.rollbackAttributes();
}
if (val.get('isNew')) {
this.get('strain.measurements').removeObject(val);
}
});
this.get('propertiesList').forEach((field) => { this.get('propertiesList').forEach((field) => {
const valueInStrain = this.get('strain').get(field); const valueInStrain = this.get('strain').get(field);
this.set(field, valueInStrain); if (field === 'measurements') {
const tempArray = this._resetArray(valueInStrain);
this.set(field, tempArray);
} else {
this.set(field, valueInStrain);
}
}); });
this.set('updateQueue', []);
this.set('deleteQueue', []);
// Read-only attributes // Read-only attributes
this.set('isNew', this.get('strain.isNew')); this.set('isNew', this.get('strain.isNew'));
}), },
updateField: function(property, value) { updateField: function(property, value) {
this.set(property, value); this.set(property, value);
@ -55,23 +95,32 @@ export default Component.extend(SetupMetaData, {
actions: { actions: {
save: function() { save: function() {
return this.attrs['on-save'](this.getProperties(this.get('propertiesList'))); return this.attrs['on-save'](this.getProperties(this.get('propertiesList')), this.get('deleteQueue'), this.get('updateQueue'));
}, },
cancel: function() { cancel: function() {
this._resetProperties();
return this.attrs['on-cancel'](); return this.attrs['on-cancel']();
}, },
addCharacteristic: function() { addMeasurement: function() {
return this.attrs['add-characteristic'](); const measurement = this.attrs['add-measurement']();
this.get('measurements').pushObject(measurement);
}, },
saveMeasurement: function(measurement, properties) { saveMeasurement: function(measurement, properties) {
return this.attrs['save-measurement'](measurement, properties); measurement.setProperties(properties);
measurement.set('strain', this.get('strain'));
if (!measurement.get('isNew')) {
this.get('updateQueue').pushObject(measurement);
}
this.set('isDirty', true);
}, },
deleteMeasurement: function(measurement) { deleteMeasurement: function(measurement) {
return this.attrs['delete-measurement'](measurement); this.get('deleteQueue').pushObject(measurement);
this.get('measurements').removeObject(measurement);
this.set('isDirty', true);
}, },
strainNameDidChange: function(value) { strainNameDidChange: function(value) {
@ -104,7 +153,7 @@ export default Component.extend(SetupMetaData, {
}, },
notesDidChange: function(value) { notesDidChange: function(value) {
this.updateField('strain.notes', value); this.updateField('notes', value);
}, },
}, },
}); });

View file

@ -61,9 +61,9 @@
<div> <div>
{{ {{
protected/strains/measurements-table protected/strains/measurements-table
strain=strain measurements=measurements
add-characteristic=(action "addCharacteristic") add-measurement=(action "addMeasurement")
allCharacteristics=allCharacteristics allCharacteristics=sortedCharacteristics
save-measurement=(action "saveMeasurement") save-measurement=(action "saveMeasurement")
delete-measurement=(action "deleteMeasurement") delete-measurement=(action "deleteMeasurement")
canEdit=strain.canEdit canEdit=strain.canEdit

View file

@ -1,11 +1,11 @@
import Ember from 'ember'; import Ember from 'ember';
import ajaxErrorNew from '../../../../utils/ajax-error-new';
const { Controller, inject: { service } } = Ember; const { Controller, inject: { service } } = Ember;
export default Controller.extend({ export default Controller.extend({
session: service(), session: service(),
ajax: service(), ajax: service(),
ajaxError: service('ajax-error'),
currentUser: service('session-account'), currentUser: service('session-account'),
actions: { actions: {
@ -15,12 +15,13 @@ export default Controller.extend({
this.get('ajax').post('/users/password', { data: data }).then(() => { this.get('ajax').post('/users/password', { data: data }).then(() => {
this.transitionToRoute('protected.users.show', id); this.transitionToRoute('protected.users.show', id);
this.get('flashMessages').information('Your password has been changed.'); this.get('flashMessages').information('Your password has been changed.');
}, (error) => { }, (errors) => {
ajaxErrorNew(error, this.get('flashMessages')); this.get('ajaxError').alert(errors);
}); });
}, },
cancel: function() { cancel: function() {
this.get('flashMessages').clearMessages();
this.transitionToRoute('protected.users.show', this.get('currentUser.account.id')); this.transitionToRoute('protected.users.show', this.get('currentUser.account.id'));
}, },
}, },

View file

@ -5,24 +5,41 @@ const { RESTSerializer } = DS;
const { isNone } = Ember; const { isNone } = Ember;
export default RESTSerializer.extend({ export default RESTSerializer.extend({
isNewSerializerAPI: true,
serializeBelongsTo: function(snapshot, json, relationship) { serializeBelongsTo: function(snapshot, json, relationship) {
let key = relationship.key; const key = relationship.key;
const belongsTo = snapshot.belongsTo(key); if (this._canSerialize(key)) {
key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo", "serialize") : key; const belongsToId = snapshot.belongsTo(key, { id: true });
json[key] = isNone(belongsTo) ? belongsTo : +belongsTo.record.id; let payloadKey = this._getMappedKey(key, snapshot.type);
if (payloadKey === key && this.keyForRelationship) {
payloadKey = this.keyForRelationship(key, "belongsTo", "serialize");
}
if (isNone(belongsToId)) {
json[payloadKey] = null;
} else {
json[payloadKey] = +belongsToId;
}
if (relationship.options.polymorphic) {
this.serializePolymorphicType(snapshot, json, relationship);
}
}
}, },
serializeHasMany: function(snapshot, json, relationship) { serializeHasMany: function(snapshot, json, relationship) {
let key = relationship.key; const key = relationship.key;
const hasMany = snapshot.hasMany(key); if (this._shouldSerializeHasMany(snapshot, key, relationship)) {
key = this.keyForRelationship ? this.keyForRelationship(key, "hasMany", "serialize") : key; const hasMany = snapshot.hasMany(key, { ids: true });
if (hasMany !== undefined) {
json[key] = []; let payloadKey = this._getMappedKey(key, snapshot.type);
hasMany.forEach((item) => { if (payloadKey === key && this.keyForRelationship) {
json[key].push(+item.id); payloadKey = this.keyForRelationship(key, "hasMany", "serialize");
}); }
json[payloadKey] = [];
hasMany.forEach((item) => {
json[payloadKey].push(+item);
});
}
}
}, },
}); });

View file

@ -0,0 +1,19 @@
import Ember from 'ember';
const { Service, inject: { service } } = Ember;
export default Service.extend({
flashMessages: service(),
alert: function(error) {
const flash = this.get('flashMessages');
flash.clearMessages();
window.scrollTo(0,0);
error.errors.forEach((error) => {
console.error(error);
const source = error.source.pointer.split('/');
flash.error(`${source[source.length-1].replace(/([A-Z])/g, ' $1').capitalize()} - ${error.detail}`);
});
}
});

View file

@ -1,7 +0,0 @@
export default function ajaxErrorNew(error, flash) {
flash.clearMessages();
error.errors.forEach((error) => {
const source = error.source.pointer.split('/');
flash.error(`${source[source.length-1].replace(/([A-Z])/g, ' $1').capitalize()} - ${error.detail}`);
});
}

13051
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,11 @@
"scripts": { "scripts": {
"build": "ember build", "build": "ember build",
"start": "ember server", "start": "ember server",
"test": "ember test" "test": "ember test",
"bower": "bower",
"ember": "ember",
"firebase": "firebase",
"deployProd": "firebase deploy -e prod"
}, },
"repository": "", "repository": "",
"engines": { "engines": {
@ -40,5 +44,9 @@
"ember-export-application-global": "^1.0.4", "ember-export-application-global": "^1.0.4",
"ember-one-way-input": "0.1.3", "ember-one-way-input": "0.1.3",
"ember-simple-auth": "1.0.1" "ember-simple-auth": "1.0.1"
},
"dependencies": {
"bower": "^1.8.8",
"firebase-tools": "^7.12.1"
} }
} }