-
Notifications
You must be signed in to change notification settings - Fork 78
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
Conversations Feature #1597
Conversations Feature #1597
Conversation
I pushed a big commit, because I'm afraid there was a bit of a snowball effect of things that needed to work to make the PR self-contained. Will add comments to the code where applicable. |
* Returns true if the user is the owner of the project. | ||
* @type {Boolean} | ||
*/ | ||
canAdminister: or('{userIsAdmin,userIsOwner}'), |
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.
Until this PR, all of the bigger actions on a project were accessible by owners only. The only thing admins could do was approve pending members.
import { alias } from '@ember/object/computed'; | ||
|
||
export default Controller.extend({ | ||
conversation: alias('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.
ember inspector expects a model
property on the controller, so I started using this alias instead of setupController
we usually do. If we don't want this, I'm fine with reverting to setupController
|
||
export default Controller.extend({ | ||
message: alias('model'), | ||
user: alias('model.conversations.firstObject.user') |
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.
This, like many other places in the code, assumes a single user per message, meaning a single conversation as well.
As we add UI support for multiple users, we will need to update this.
app/router.js
Outdated
@@ -65,9 +65,13 @@ Router.map(function() { | |||
|
|||
this.route('project', { path: '/:slugged_route_slug/:project_slug' }, function() { | |||
this.route('checkout'); // Where you enter your credit card details | |||
this.route('contributors'); |
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.
contributors
was under settings
, meaning, since settings
is owner only, contributors
was also owner only. To make it admin
or owner
, I had to move it.
If we move it back, each settings subroute will have to implement it's own CanMixin
, while the base settings
route should not have it.
If we want it here, but keep the old /:orgslug/slug/settings/contributors
url, we can just set the path here.
Personally, I prefer the new placement and the new :orgslug/:slug/contributors
url. I would even move donations
in the same way
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 agree with this move.
app/routes/project/contributors.js
Outdated
// 1. Sideload project user records | ||
// 2. Have the server compute an ability table for the current user | ||
return get(project, 'projectUsers').then(() => { | ||
if (this.cannot('administer project', project)) { |
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.
used to be cannot('manage project', project)
app/routes/project/messages.js
Outdated
// peekAll returns a live array, so it will alse render any newly created | ||
// messages, causing the list to update properly on project.messages.new | ||
await store.query('conversation', { project_id: projectId }); | ||
return store.peekAll('conversation'); |
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.
We have a list-detail
UI here
- a list of conversations in the sidebar on
/messages/
- clicking one of the items navigates to
/messages/:id
, and renders the sidebar in the outlet of the main area, while still displaying the list - navigating to
/messages/new
does the same
This model hook allows the store to detect a new conversation record and show it in the list (styled differently).
It's hacky, but it's the only way I could find to do this.
An alternative would be to clone the query result of the project.messages
model into an array, then render that array in controller, instead of the query result directly.
That would allow us to push the new conversation into that array during the messages.new
afterModel
hook.
Pushing into the query result directly, which is what I initially tried, does not seem to be possible without resorting to using private functions and properties of DS.model
app/routes/project/messages/new.js
Outdated
}); | ||
|
||
let conversation = store.createRecord('conversation', { user }); | ||
get(message, 'conversations').pushObject(conversation); |
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.
This causes the conversation to appear in the list of conversations in the sidebar.
app/routes/project/messages/new.js
Outdated
// Since it's created without an ID, the store will not detect the | ||
// association was saved and will keep it around. | ||
// We need to store a reference to it, so we can unload it on save | ||
let unsavedConversation = get(message, 'conversations.firstObject'); |
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.
Comment explains it. Since we store save the two records (message and conversation) together as one, ember does not detect the conversation child has changed, so we need to unload it manually so it gets replaced with the saved record.
app/routes/project/messages/new.js
Outdated
|
||
if (get(conversation, 'isNew')) { | ||
conversation.destroyRecord(); | ||
} |
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.
This will, unfortunately, mess with our proposed mixin.
// support saving child conversations alongside record | ||
if (!json.id) { | ||
json.included = snapshot.hasMany('conversations').map((c) => c.serialize()); | ||
} |
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.
This is how we include the conversation alongside the message in our payload when creating a message.
Apparently, ember data does not do this automatically and provides no configuration in order to do it, so I had to provide a manual implementation. It's according to spec, as far as I know, but the sad reality is, this whole process involves custom code both API and client side.
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.
This looks about right!
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.
Wow. This is phenomenal work!
app/models/conversation-part.js
Outdated
body: attr(), | ||
readAt: attr(), | ||
|
||
author: belongsTo('user', { async: 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.
I think async: true is default...but I also like the explicitness...
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.
It wasn't always, and I was about to ask if it is now finally the default.
app/routes/project/messages.js
Outdated
}, | ||
|
||
async model() { | ||
console.log('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.
console
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.
Some updates on the changes I made today:
- removed skills from the user list
- redesigned the users list some
- other general design changes
- fixed the test failures
- notes below
app/router.js
Outdated
this.route('new'); | ||
}); | ||
this.route('donate'); // Where you choose your donation amount | ||
this.route('people'); |
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've reworked the conventions to be:
/contributors
=>/people
/messages
=>/conversations
The latter is more appropriate because /messages
would actually be a different route entirely. These would be the automated/manual messages the project has sent/is actively sending to users. These would be more like /messages/auto
and /messages/manual
, anyway.
/converations
serves more as a unified inbox for the project's conversations with users and replies to messages.
<div class="container"> | ||
<div class="conversations"> | ||
<section> | ||
<aside> |
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 have yet to add the styling for .conversations
but it will differ from the .panel
styling. The left sidebar will be a scrolling list of messages and the right side will contain two panes: left for the message thread, right for the information about the target user or project. In total, then, the UI will appear to have three panes, but the right two panes are basically the outlet
for the individual conversation.
Also note that creating a message in the UI prompts you to confirm save of the record before redirecting, even though we're redirecting after saving the record. |
We should have a follow-up issue to change the |
I don't want to spend a bunch of time writing acceptance tests for logic that's going to change (in terms of new message creation), so I think we should instead:
|
let { conversations } = this.modelFor('project.conversations'); | ||
if (get(conversations, 'length') > 0) { | ||
this.transitionTo('project.conversations.conversation', get(conversations, 'firstObject')); | ||
} |
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 have a better way to handle this. Could use thoughts @begedin. The downside here is not getting the .active
class on the project menu at all, which we want. We could force it some other way, but it's not ideal.
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.
Fixed by using conversations.index
redirect
.
I think it's possible we're missing a |
In response to my above comment, I think we might be able to infer whether the conversation was read based on whether the |
A lot of changes above:
The only things half-implemented in the project messages UI are:
To sum up what's missing outside the scope of this specific PR, but which will need to be added before anything here is merged into
|
app/services/conversation-channel.js
Outdated
_onNewMessage(conversation) { | ||
let store = get(this, 'store'); | ||
let id = get(conversation, 'id'); | ||
store.findRecord('conversation', id); |
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.
Could we not send the json payload for the conversation part with the event and then simply do store.pushPayload
?
This would save us two queries at least.
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.
We cannot do that because the user who sent the message would have the payload pushed twice, and ember-data would rightly reject it.
There also may be data that has changed on the backend since the payload was sent, so best to simply update the parent model, IMO.
assert.expect(1); | ||
|
||
set(this, 'onSend', (body) => { | ||
assert.equal(body, 'foo', 'Correct value was send with action.'); |
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.
sent
9529832
to
c24b18c
Compare
b3bb2e2
to
608b0f0
Compare
Wow, 3,333 lines of code exactly. 👏 |
The API changes required for this to work are in PR code-corps/code-corps-api#1301
Closes #1592
Closes #1593
Closes #1594
Closes #1596