-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow users to create deliveries #21
Conversation
Adds deliveries/new/select-project route deliveries/new/select-recipient route deliveries/new/enter-user-message route Passses IDs as queryParams between routes TODO: replace underscore query params with camel case filter projects to those we can actually deliver filter recipient to not include current user cleanup select recipient screen - still confusing
Adds checkboxes for selecting projects and users. Filters users to exclude current user.
also fixes failing new tests for controllers
application now contains logic for currentDukeDsUser so this is no longer needed.
adds tests for new routes
Th computed field shareUsers returns a javascript array of promises so we can't just call `then` on the returned array.
Switch to match existing pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks good, only had a couple in-line observations. I wanted to see how this worked, so most of the feedback below is about the select-recipients page:
- A label/heading for the primary recipient should be more prominent (maybe an h4?), and indicate that this is required.
- "Select additional users to share project." - The meaning of this text is not clear. This text should describe that this area is for selecting additional users that will get access to the data but not ownership, and that this is optional.
- While the table of users is sortable, it doesn't come up in any discernable order. Perhaps sort by last name initially? (That could be done in the JS)
- There are some empty rows in my listing - We should probably filter these out in the UI. I can't imagine any good things can come from delivering to these users :)
- The name and email filters are case-sensitive, which I didn't expect or discover right away. At first I thought it was broken. I used a filterFunction for doing case-insensitive filtering on project statuses, and that may help here.
- It's possible to select the primary user as one of the additional "share with" users, which doesn't make sense.
- The more I think about this, the more I think the "picking additional users" part should be a separate step. It makes it easy to filter out the primary user and lets the user focus or skip that step more easily.
import Ember from 'ember'; | ||
|
||
export default Ember.Controller.extend({ | ||
queryParams: ['project_id', 'to_user_id', 'share_user_ids'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for using snake_case
for some variables and parameters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to match the underlying API but it does look weird. Will fix.
}), | ||
shareUsers: Ember.computed('share_user_ids', function () { | ||
const store = this.get('store'); | ||
const shareUserIds = this.get('share_user_ids'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice that the shareUserIds are passed around as a comma-separated string. Do arrays not work as queryParam variables?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my experience it automatically comma separates (%2C) array elements, but this seems rather unexpected and not deserialized on the other end.
It sounds like query parameters serialization is an ongoing issue in ember: emberjs/ember.js#14174
}), | ||
isResendSelected: Ember.computed('selectedRouteName', function () { | ||
return this.get('selectedRouteName') === 'deliveries.show.resend'; | ||
}) | ||
}), | ||
isNewDeliverySelected: Ember.computed('selectedRouteName', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use Ember.computed.equal here
@@ -6,9 +6,13 @@ export default Ember.Component.extend({ | |||
transfer: null, /* specific transfer to be passed to child breadcrumbs */ | |||
selectedRouteName: null, /* route we should select ('deliveries.index', 'deliveries.show', etc) */ | |||
isDeliverySelected: Ember.computed('selectedRouteName', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know that a regex is easier to read than indexOf == 0, but FYI there's Ember.computed.match
Instead of filtering by deliverable filters by is_deleted to work around ember DS caching issues.
Adds some code to show errors.
This test only fails on travis.
I simplified this PR by removing the optional step of picking share users. I will create an issue to implement this feature in a future PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice improvements. Some suggestions for Ember practices that I've found to make things easier and didn't catch on the first review.
@@ -24,7 +26,7 @@ export default Ember.Controller.extend({ | |||
} | |||
}), | |||
currentUserCanDeliver: Ember.computed('currentUserProjectAuthRole', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this be Ember.computed.equal()
?
@@ -6,6 +6,7 @@ | |||
showGlobalFilter=false | |||
showPageSize=false | |||
pageSize=12 | |||
filteringIgnoreCase=true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wish I had known about this before!
import ApplicationAdapter from './application'; | ||
|
||
export default ApplicationAdapter.extend({ | ||
urlForQuery(query) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I follow this, but I wanted to clarify.
In the discussion of Duke-GCB/D4S2#170, I argued that it didn't make sense for the API to have a required query parameter to fetch project permissions. I recognize that, without a proper hasMany
relationship to get the related records, Ember Data offers store.query()
to do fetch a filtered list for a resource, and this code overrides the default query implementation to work with the project detail route in Duke-GCB/D4S2#170. I think it's probably worth a comment about why this is here.
I'll follow up with an issue, but I wanted to have a discussion on whether or not it makes sense to just make ajax calls for the project permissions instead of shoehorning them into ember data models.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is the hasMany
relationship requires a top level endpoint to get the related keys (ie. /duke-ds-project-permission/<id>
). I'm all for just making an ajax call, but not sure how easy it would be to re-use the emberjs auth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think we're on the same page - I was mostly trying to think out loud as to why this is done this way. Through the course of that I think I came to the conclusion that this doesn't fit 100% in a vanilla Ember Data store, but there are ways to make an authenticated AJAX call here and customize how the response is handled. We do it in bespin-ui.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #22
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a comment to the adapter.
} | ||
return this.get('store').query('duke-ds-project-permission', { | ||
'project': projectId, | ||
'user': currentUserId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user parameter is not used, and we don't need quotes around the key names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user parameter is added to the end of the url and filters the permissions to just those for the current user. Otherwise the firstObject
might be another user's permissions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, so my understanding of the urlForQuery method wasn’t complete. This query parameter still gets passed through
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. That's why I had to delete query.project;
in urlForQuery or it ended up being used twice.
project: null, | ||
application: Ember.inject.controller(), | ||
currentDukeDsUser: Ember.computed.alias('application.currentDukeDsUser'), | ||
currentUserProjectPermissions: Ember.computed('project', 'currentDukeDsUser.id', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see several instances where computed properties are returning promises (e.g. the result of a store.query()
), which can make you want to tear your hair out. I know it's given me a lot of grief during development and testing, and I'm guessing it's why these functions have so many early bail outs.
I'd encourage you to have a look at that article to see if any of those patterns apply, and I'll mention that I've had good results with setting the property after the promise resolves:
- For controllers, you can add an observer on init (or some other property). This is how the application controller sets the current user:
datadelivery-ui/app/controllers/application.js
Lines 7 to 16 in 86edec2
authenticatedDidChange: Ember.on('init', Ember.observer('session.isAuthenticated', function () { | |
if (this.get('session.isAuthenticated')) { | |
this.get('dukeDsUser').currentDukeDsUser().then(currentDukeDsUser => { | |
this.set('currentDukeDsUser', currentDukeDsUser); | |
}); | |
} else { | |
this.set('currentDukeDsUser', null); | |
} | |
}) | |
) |
- For components, kick off the promises in
didInsertElement()
, since they don't have the same object lifecycle as other objects (via https://emberigniter.com/render-promise-before-it-resolves/):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That guide says that there is a danger with this approach (#1
in the guide):
waiting for promises and this.setting – on a potentially destroyed component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to use the #3 Special promise-backed objects
option.
export default DS.Model.extend({ | ||
project: DS.belongsTo('DukeDsProject'), | ||
user: DS.belongsTo('DukeDsUser'), | ||
auth_role: DS.attr('string') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be camelCase
currentDukeDsUser: Ember.computed.alias('application.currentDukeDsUser'), | ||
validUsersList: Ember.computed('model.[]', function () { | ||
// remove users with invalid fullName values | ||
return this.get('model') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see users in my listing with empty email addresses too. Won't this be problematic too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added code to filter out users without emails.
actions: { | ||
toUserSelectionChanged(actionData) { | ||
var selectedItem = null; | ||
if (actionData.selectedItems) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One benefit of Ember is that arrays (and other enumerables have additional, observable properties like firstObject
and lastObject
that don't raise errors for empty arrays.
So, rather than having to check the length of the array first and then index into it, you could reduce the entire body to
this.set('toUser', selectedItems.get('firstObject'));
}); | ||
}), | ||
currentUserProjectAuthRole: Ember.computed('currentUserProjectPermissions.[]', function () { | ||
const authRoles = this.get('currentUserProjectPermissions').mapBy('auth_role'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can also use .get('firstObject')
here
}), | ||
actions: { | ||
projectSelectionChanged(actionData) { | ||
var selectedItem = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See other comments about firstObject
Adds changes to allow a user to select a project, recipient and enter a message to be included in the email.
High Level Changes
New Delivery
button to the Outgoing deliveriesdeliveries/new/select-project
route where user selects a project to delivernew/select-recipient?project_id=<id>
route where user selects recipientand share usersdeliveries/new/enter-user-message?project_id=<id>&to_user_id=<id>...
route to enter a message and send the deliveryFuture changes
Needs Duke-GCB/D4S2#169
Fixes #6