Skip to content
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

Add conversation-part-closed component #1638

Merged
merged 18 commits into from
Jan 16, 2018

Conversation

daveconnis
Copy link
Contributor

@daveconnis daveconnis commented Dec 20, 2017

What's in this PR?

Adding a 'conversation-part-closed' component.

References #1636

Progress on: #1597

@daveconnis
Copy link
Contributor Author

Clocking out for the day. Working through this is hard, but I think i have a semi-direction? It could be/probably is wrong, but my plan when I start tomorrow is to write a computed object that gets the author (either part.author.id or currentUser.user.id) and then passes it to the hbs which is written-ish (I know I'm missing stuff.)

isSelf: computed('author', 'user', function() {
return get(this, 'author.id') === get(this, 'user.id');

closedUser: computed ('isSelf', 'currentUser', function() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the component I'm talking about. Hoping that this will compute what author will be passed into the HBS.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your reasoning here is good, but remember, the component will spell out You closed this or {{username}} closed this, so you don't really care about the closedUser. Your isSelf is correct, so what you need now is to modify your template and replace {{#if currentUser}} with {{#if isSelf}}.

@joshsmith joshsmith changed the title added conversation-part-closed component Add conversation-part-closed component Dec 20, 2017
@@ -0,0 +1,9 @@
<div class="conversation-part-closed">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This div is not necessary since the component itself creates a div with this class (using the classNames you defined).

isSelf: computed('author', 'user', function() {
return get(this, 'author.id') === get(this, 'user.id');

closedUser: computed ('isSelf', 'currentUser', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your reasoning here is good, but remember, the component will spell out You closed this or {{username}} closed this, so you don't really care about the closedUser. Your isSelf is correct, so what you need now is to modify your template and replace {{#if currentUser}} with {{#if isSelf}}.

You closed this at...
{{else}}
<div class="conversation-part-closed__message-wrapper">
{{author.username}} closed this at...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above on what to do here. Also, your syntax is incorrect. You use the if helper like this:

{{#if something}}
  Foo
{{else}}
  Bar
{{/if}}

Note the indentations, the `#` and the `{{/if}}`

return get(this, 'author.id') === get(this, 'user.id');


})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blank lines need to go

@daveconnis daveconnis force-pushed the conversation-part-for-open-and-closed-status-changed branch from 79b55de to 7da31f3 Compare December 21, 2017 19:35
author=author
closedAt=closedAt
}}
`);

moduleForComponent('conversations/conversation-part-closed', 'Integration | Component | conversations/conversation part closed', {
integration: true
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love feedback on test statements on 21 + 25.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future reference, you should comment below those lines so we can see them in this view. Also, would be helpful to have some direction in terms of what you want feedback on specifically?

} from 'ember-cli-page-object';

export default {
title: text('h1')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would also love feed back on page object. Think I have everything I need though, at least that I've found I need so far.

@joshsmith
Copy link
Contributor

@daveconnis remember to set the label, too.

Copy link
Contributor

@begedin begedin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented on the component file. Also, not to forget, you need to rename the other conversation-part files - the .hbs, the integration/.../...-test.js and the pages/.../conversation-part.js

test('it renders', function(assert) {

// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to remove this test.


test('if user closes message, "You closed this at" is rendered', function(assert){

})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These blank tests seems like they would test correct behavior, so the thinking is probably sound. Just need to implement them.


isSelf: computed('author', 'user', function() {
return get(this, 'author.id') === get(this, 'user.id');
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two problems here

It's not clear what author is

You specify the value of author through the .hbs, as exampled in the test module you wrote:

{{conversations/conversation-part-closed author=author closedAt=closedAt}}

When we do that, we also add that property into the .js and give it a default value, usually null, unless some other value makes sense. You already did this with closedAt: null, so you should do the same with author: null.

The code will work regardless, but we do this so that, when someone is reading the .js, they do not need to navigate back and forth between the .js and the .hbs and the component is relatively clear from the .hbs alone.

user is not defined at all

The author is whichever user is responsible for this specific conversation part being created.

The user is supposed to be whichever user is currently signed in to the app. You get it from the currentUser service as currentUser.user, so to have it available to you as just user, you need to define an alias. You can look at the basic conversation-part-comment component to see how that's done.

@daveconnis
Copy link
Contributor Author

daveconnis commented Jan 2, 2018

Clocking out for the day. Started working on tests for conversation-part-closed and realized I had a lot of tests failing. Went through the tests and got most passing by changing names from conversation-part to conversation-part-comment (had messed the switch up on the first go). The failing tests dipped into two or three different tests, all are passing except code-corps-ember/tests/acceptance/project-conversations-test.js.

test('System is notified of new conversation part', function(assert) {
  assert.expect(3);

  let { project, user } = server.create('project-user', { role: 'admin' });
  authenticateSession(this.application, { user_id: user.id });

  let [conversation] = createConversations(1, project, user);

  page.visit({
    organization: project.organization.slug,
    project: project.slug
  });

  andThen(() => {
    page.conversations(0).click();
  });

  andThen(() => {
    assert.equal(page.conversationThread.conversationParts().count, 1, 'Just the message head is rendered.');
    server.create('conversation-part', { conversation });
  });

  andThen(() => {
    assert.equal(page.conversationThread.conversationParts().count, 1, 'No notification yet, so new part was not rendered.');
    let conversationChannelService = this.application.__container__.lookup('service:conversation-channel');
    let socket = get(conversationChannelService, 'socket.socket');
    let [channel] = socket.channels;
    channel.trigger('new:conversation-part', {});
  });

  andThen(() => {
    assert.equal(page.conversationThread.conversationParts().count, 2, 'Notification was sent. New part is rendered.');
  });
});

This one is giving me the trouble, specifically

andThen(() => {
    assert.equal(page.conversationThread.conversationParts().count, 2, 'Notification was sent. New part is rendered.');
  });

I'm getting an expected result of 2, but only getting 1 on my test. Considering how were trying to create a second conversation part, I'm assuming that 'channel.trigger('new:conversation-part', {});' is somehow missing the mark. When I change channel.trigger('new:conversation-part', {}); to channel.trigger('new:conversation-part-comment', {}); the test error tells me it's not a model, which makes sense considering in our model app/models/conversation.js, conversationParts: hasMany('conversation-part', { async: true }),. Before I needed to wrap up, I changed this last part to conversationParts: hasMany('conversation-part-comment', { async: true }), and suddenly nothing was rendering and all my asserts for code-corps-ember/tests/acceptance/project-conversations-test.js were failing.

I'm planning on starting here in the morning, but if someone could give me some thoughts on if I'm heading in the right direction, that'd be swell.

Copy link
Contributor

@begedin begedin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good progress, but a few things left to do

author=conversationPartComment.author
body=conversationPartComment.body
readAt=conversationPartComment.readAt
sentAt=conversationPartComment.insertedAt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what you probably wanna do here is keep the {{#each conversation.conversationParts as |conversation_part|}} and then do a couple of if-else chains inside the {{#if conversationPart.isLoaded}}

Think of this way. A conversation has a bunch of conversation parts and we want to render all of them in the order we got them in.

Then, for each of them, if it's a comment-type conversation part, render it as a comment type. If it's a closed-type conversation part, render is as a closed type, etc.

{{#each conversation.conversationParts as |conversationPart|}}
  {{#if conversationPart.isLoaded}} 
    {{#if (eq conversationPart.partType 'closed')}}
      {{conversations/conversation-part-closed ..}}
    {{else}}
      {{conversations/conversation-part-comment ...}}
    {{/if}}
  {{/if}}
{{/each}}

Note that in the example, the comment type is sort of considered the default - the thing we render if it's not any other type. It's not perfect and really, if we do not support all the types our API supports, we have a bug, but at least this will render something, which is preferable to and easier to detect as an issue than nothing.

There may be more elegant ways to do this, but for now, this is good enough. Then, when we add another conversation part type, such as conversation-part-open, we would add an else if here, between the if and the else.

renderPage();

assert.ok(page.closedAt.text, 'You closed this two days ago', 'The closed at timestamp is rendered');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is good, but I would call it 'if the current user closes the message..., just to emphasize the current

The other test is 'if someone other than the current user closes the message...'

@daveconnis
Copy link
Contributor Author

daveconnis commented Jan 3, 2018

Okay, I'm stuck. It seems like I'm getting conflicting thoughts here. Right now, I'm working on failing tests for code-corps-ember/tests/acceptance/project-conversations-test.js. I'm getting a bunch of

Error: Element not found.

PageObject: 'page.conversationParts(0).body.text'

Which leads me to believe that my path to that part is wrong. This is probably the case considering I've renamed things to conversation-part-comment.

Considering Nikola's earlier comments about app/templates/components/conversations/conversation-thread.hbs

what you probably wanna do here is keep the {{#each conversation.conversationParts as |conversation_part|}} and then do a couple of if-else chains inside the {{#if conversationPart.isLoaded}}

I went back and fixed this, taking the comment out that I'd added yesterday when switching conversation-part to conversation-part-comment. However, app/templates/components/conversations/conversation-thread.hbs
still calls conversations/conversation-part-comment because this is where author, body, readAt, sentAt are all defined.
IE:

{{conversations/conversation-part-comment
        author=conversation.message.author
        body=conversation.message.body
        readAt=conversation.message.readAt
        sentAt=conversation.message.insertedAt

In [https://github.com//issues/1636], you guys stated that you wanted conversation-part to conversation-part-comment. So, I feel like, right now, in order to fix my path, I need to change the name of conversation-part-comment back to conversation-part in order to stay with Nikola's comments, however I'm sure I'm missing something. I'm getting confused that there's a model for Conversation part AND conversation as well as a component for conversation-part-comment and conversation-part-closed I think I'm missing a change in some of the ways the touch together somehow.


set(this, 'author', { user1 });
set(this, 'closedAt', moment().from());
stubService(this, 'current-user', { user });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also can't get the "author" nor the "time" to pass in here, either. Been trying for too long so moving onto something else.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a misunderstanding here with the author.

{ user } is an object shorthand. Basically, it's a shorter way of writing { user: user }

With that in mind

set(this, 'author', { user1 });

means you're actually doing

set(this, 'author', { user1: user1 });

which clearly wont work.

In your setup, there's no author.username, author.id, etc., there's 'author.user1.username, author.user1.id`, etc.

Thus, what you need to do instead is,

set(this, 'author', user1);

Meaning, the value of author in our component is the same as user1

What might be confusing you is the

stubService(this, 'current-user', { user });

but we actually do a different thing there.

If you take a look at how the actual current-user service is implemented, under app/services/current-user.js, you will see that it's an Ember.Service, with a user property in it, so what we're doing is exactly what we want to do - replace the actual service with a fake one, where the value of user is our user.

That means, we will have currentUser.user, currentUser.user.id, currentUser.user.username, etc., exactly as we want.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the time stamp, look for an example in admin-github-event-test.js

The general idea is to get some timestamp, either using Date.now() or moment(). Both will return the exact current time. The example above uses moment().

Then, store that time stamp into a variable. This is critical. You can't just go and do the following, for example:

let timestamp = moment();
assert.equal(timestamp, moment());

The simple reason you can't do this is that both Date.now() and moment() return a different value every time you call them - the current timestamp, meaning the exact time at the time the function was called.

So if we want to test our code here, we need to understand what our component is supposed to do - The intent of the test is to ensure that the component does what it supposed to do.

Our component is supposed to tell us who closed our conversation and when.

The who will be either the username of the author, or just "You" if the author is the current user, so we need two tests for that, which I think you'll know how to cover.

The "when" will be the time of the closing event, meaning the time the conversation part of "partType" "closed" was created (or, in terms of the code, the value of closedAt). We also want the when to nicely formatted in some manner, which is something I described in my comment above.

To give a few examples for the username part

  • If the current user is begedin, and the person who closed the conversation is daveconnis, we want the text to contain "daveconnis closed this"
  • If the current user is daveconnis and daveconnis closed the conversation, we want the text to say "You closed this"

As for the timestamp, it gets a bit more complicated because you sort of need to know how the friendly formatting works, but what you would do is something like

  • Create a couple of timestamps at varying moments in the past
  • For each of them
    • set the value of closedAt to that
    • check if the text contains the friendly version of that timestamp
    • due to some weird ember internals, you need to use the run loop to do that, otherwise it won't update

As a concrete example

// moment() returns the current time. `subtract(amount, type)` then modifies that
let twoMinutesAgo = moment().subtract(2, 'minutes');
// from() is the friendly formatted version of the above, same as what the {{moment-from-now}} helper does
let twoMinutesAgoFriendly = twoMinutesAgo.from();
// this is where we use the run loop to set the new value
run(() => { set(this, 'closedAt', twoMinutesAgo) }
// indexOf will return -1 if the text does not contain twoMinutesAgoFriendly
// If it does, it will contain the position in the text where it starts, so 0 or higher
// checking if it's higher than -1 means we check if the text contains it
assert.ok(page.closedAt.text.indexOf(twoMinutesAgoFriendly) > -1) 

Then you do the same thing with, for example, two seconds ago, 2 days ago, etc. Actually, just those 3 examples ought to be enough in total.

set(this, 'author', user);
set(this, 'closedAt', moment().subtract(2, 'days'));
set(this, 'closedAt', moment().fromNow());
stubService(this, 'current-user', { user: { id: 1 } });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the life of me, I can't figure out how to get the timestamp to pass in and have been working on it for an very inefficient length of time, by that I mean all day. This thing has kicked my ass to and fro and I'm sick of looking at it and am going to move to something else so the day isn't a complete wash. Nikola has stepped in a bunch with some guidance, but it didn't seem to help my stupidity.

set(this, 'author', user);
set(this, 'closedAt', moment().subtract(2, 'days'));
set(this, 'closedAt', moment().fromNow());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found `fromNow()); from the moment docs here .

You closed this at {{moment-format conversationPart.updatedAt 'MM/DD/YYYY hh:mm:ss'}}
{{else}}
{{author.username}} closed this at {{moment-format conversationPart.updatedAt 'MM/DD/YYYY hh:mm:ss'}}
{{/if}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation error here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still have this indentation error, It starts at line 5, if it's not clear. The whole if-else chunk needs to be indented 3 levels deeper.

{{#if conversationPart.isLoaded}}
{{conversations/conversation-part
{{#if (eq conversationPart.partType 'Closed')}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reasonably sure the type will be lowercased at all times, so this should probably be

{{#if (eq conversationPart.partType 'closed')}}

{ isLoaded: true, body: 'foo 3' }
{ isLoaded:true, body: 'foo 1' },
{ isLoaded:true, body: 'foo 2' },
{ isLoaded:true, body: 'foo 3' }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You removed the spaces between isLoaded: and true here. You should put them back. That is the preferred formatting.


set(this, 'author', { user1 });
set(this, 'closedAt', moment().from());
stubService(this, 'current-user', { user });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a misunderstanding here with the author.

{ user } is an object shorthand. Basically, it's a shorter way of writing { user: user }

With that in mind

set(this, 'author', { user1 });

means you're actually doing

set(this, 'author', { user1: user1 });

which clearly wont work.

In your setup, there's no author.username, author.id, etc., there's 'author.user1.username, author.user1.id`, etc.

Thus, what you need to do instead is,

set(this, 'author', user1);

Meaning, the value of author in our component is the same as user1

What might be confusing you is the

stubService(this, 'current-user', { user });

but we actually do a different thing there.

If you take a look at how the actual current-user service is implemented, under app/services/current-user.js, you will see that it's an Ember.Service, with a user property in it, so what we're doing is exactly what we want to do - replace the actual service with a fake one, where the value of user is our user.

That means, we will have currentUser.user, currentUser.user.id, currentUser.user.username, etc., exactly as we want.

{{#if isSelf}}
You closed this on {{moment-format conversationPart.closedAt 'MM/DD/YYYY hh:mm:ss'}}
{{else}}
{{author.username}} closed this on {{moment-format conversationPart.closedAt 'MM/DD/YYYY hh:mm:ss'}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, even if you write the test correctly, it wont pass because your component has a bug here:

It's supposed to be

{{#if isSelf}}
  You closed this on {{moment-format closedAt 'MM/DD/YYYY hh:mm:ss'}}
{{else}}
  {{author.username}} closed this on {{moment-format closedAt 'MM/DD/YYYY hh:mm:ss'}}
{{/if}}

Secondly, I apologize for this, but the task was incorrectly written. We don't want a formatted timestamp, we want a "friendly" timestamp, which we get ,using moment-from instead of moment-format, so what you actually want here is

{{#if isSelf}}
  You closed this {{moment-from-now closedAt interval=60000}}
{{else}}
  {{author.username}} closed this {{moment-from-now closedAt interval=60000}}
{{/if}}

Similar to how the conversation-part-comment does it with its own timestamp.

This will cause the text to say something like

"You closed this a few seconds ago"
"Joe closed this a minute ago"
"James closed this a couple of hours ago"
"etc."


set(this, 'author', { user1 });
set(this, 'closedAt', moment().from());
stubService(this, 'current-user', { user });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the time stamp, look for an example in admin-github-event-test.js

The general idea is to get some timestamp, either using Date.now() or moment(). Both will return the exact current time. The example above uses moment().

Then, store that time stamp into a variable. This is critical. You can't just go and do the following, for example:

let timestamp = moment();
assert.equal(timestamp, moment());

The simple reason you can't do this is that both Date.now() and moment() return a different value every time you call them - the current timestamp, meaning the exact time at the time the function was called.

So if we want to test our code here, we need to understand what our component is supposed to do - The intent of the test is to ensure that the component does what it supposed to do.

Our component is supposed to tell us who closed our conversation and when.

The who will be either the username of the author, or just "You" if the author is the current user, so we need two tests for that, which I think you'll know how to cover.

The "when" will be the time of the closing event, meaning the time the conversation part of "partType" "closed" was created (or, in terms of the code, the value of closedAt). We also want the when to nicely formatted in some manner, which is something I described in my comment above.

To give a few examples for the username part

  • If the current user is begedin, and the person who closed the conversation is daveconnis, we want the text to contain "daveconnis closed this"
  • If the current user is daveconnis and daveconnis closed the conversation, we want the text to say "You closed this"

As for the timestamp, it gets a bit more complicated because you sort of need to know how the friendly formatting works, but what you would do is something like

  • Create a couple of timestamps at varying moments in the past
  • For each of them
    • set the value of closedAt to that
    • check if the text contains the friendly version of that timestamp
    • due to some weird ember internals, you need to use the run loop to do that, otherwise it won't update

As a concrete example

// moment() returns the current time. `subtract(amount, type)` then modifies that
let twoMinutesAgo = moment().subtract(2, 'minutes');
// from() is the friendly formatted version of the above, same as what the {{moment-from-now}} helper does
let twoMinutesAgoFriendly = twoMinutesAgo.from();
// this is where we use the run loop to set the new value
run(() => { set(this, 'closedAt', twoMinutesAgo) }
// indexOf will return -1 if the text does not contain twoMinutesAgoFriendly
// If it does, it will contain the position in the text where it starts, so 0 or higher
// checking if it's higher than -1 means we check if the text contains it
assert.ok(page.closedAt.text.indexOf(twoMinutesAgoFriendly) > -1) 

Then you do the same thing with, for example, two seconds ago, 2 days ago, etc. Actually, just those 3 examples ought to be enough in total.

Copy link
Contributor

@begedin begedin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there. Only minor details left :)


let text = `You closed this ${twoMinutesAgoFriendly}`;
assert.equal(page.closedAt.text, text, 'The closed at timestamp is rendered');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thinking with this. Easier to read than indexOf.

A further suggestion would be to name it expectedText, so you'd have

let expectedText = //...
assert.equal(page.closedAt.text, expectedText, //...

You closed this at {{moment-format conversationPart.updatedAt 'MM/DD/YYYY hh:mm:ss'}}
{{else}}
{{author.username}} closed this at {{moment-format conversationPart.updatedAt 'MM/DD/YYYY hh:mm:ss'}}
{{/if}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still have this indentation error, It starts at line 5, if it's not clear. The whole if-else chunk needs to be indented 3 levels deeper.

@daveconnis
Copy link
Contributor Author

daveconnis commented Jan 10, 2018

Made the changes that Nikola laid out, got to the end where he said tests would break (they did) but I can't figure out what to do now.

We've changed a lot of things, mostly exactly what Nik asked to change, but what I'm not sure about his HOW tests are breaking due to renaming closedByCurrentUser and sentByCurrentUser into isByCurrentUser.

isComment: hasClass('conversation-part--comment'),
 isClosed: hasClass('conversation-part--closed'),
 isByCurrentUser: hasClass('conversation-part--is-self')

Does adding isComment and isClosed mean we have to add these attribute somewhere in the corresponding hbs files? Where? I can't find where closedByCurrentUserand sentByCurrentUser is defined. I found a few sentBy... and changed them to isByCurrentUser in the conversation part comment test, but I'm just missing how closedByCurrentUserand sentByCurrentUser were interacting with the code, and I feel stupid because Nik laid it all out super clearly and I can't even do that.

Copy link
Contributor

@joshsmith joshsmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be much easier to help if you provided output of where the tests are failing and what you've done to diagnose.

@@ -4,7 +4,7 @@ import { alias } from '@ember/object/computed';
import { inject as service } from '@ember/service';

export default Component.extend({
classNames: ['conversation-part'],
classNames: ['conversation-part, conversation-part-comment'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure precisely where your tests are breaking, but this is certainly a syntactical problem.


author: null,

classNames: ['conversation-part-closed'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing a conversation-part class name.

author: null,

classNames: ['conversation-part-closed'],
classNameBindings: ['isSelf:conversation-part-closed--is-self'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? I'm not sure what would differ stylistically here so it looks like it's just extra work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean classNameBindings: ['isSelf:conversation-part-closed--is-self'],

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I'm not sure why it's needed here, since there's nothing special about you vs someone else closing (vs the chat bubble where it's gray vs blue).

@@ -7,6 +7,7 @@ export default Model.extend({
insertedAt: attr(),
readAt: attr(),
updatedAt: attr(),
closedAt: attr(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs alpha-ordered.

You closed this {{moment-from-now closedAt interval=60000}}
{{else}}
{{author.username}} closed this {{moment-from-now closedAt interval=60000}}
{{/if}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have indentation issues here.

{{conversation/conversation-part-closed
author=conversationPart.author
closedAt=conversationPart.insertedAt
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation here is off.

author=conversationPart.author
body=conversationPart.body
readAt=conversationPart.readAt
sentAt=conversationPart.insertedAt
}}
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation here is also off.

sentByCurrentUser: hasClass('conversation-part--is-self')
isComment: hasClass('conversation-part--comment'),
isClosed: hasClass('conversation-part--closed'),
isByCurrentUser: hasClass('conversation-part--is-self')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alpha-ordering here is off.

author: null,

classNames: ['conversation-part', 'conversation-part--closed'],
classNameBindings: ['isSelf:conversation-part-closed--is-self'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joshsmith Your comment got marked as outdated. I still think it's needed for the "displaying it left vs right" part of the style.

@daveconnis This needs to be

classNameBindings: ['isSelf:conversation-part--is-self'],

In case it's not clear, this tells Ember to add a conversation-part--is-self css class to the component's HTML element, whenever the isSelf property on the component is true (or truthy).

We then use that class to style component differently when it's displayed to the user. Like in any other chat interface, if it's your message, or your action, it's written on your side (left vs right), compared to when it's the other user's message, where it's written on their side.

readAt=conversationPart.readAt
sentAt=conversationPart.insertedAt
}}
{{/if}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole {{else}} block needs to be indented one level deeper.

@@ -4,6 +4,7 @@ import { belongsTo } from 'ember-data/relationships';

export default Model.extend({
body: attr(),
closedAt: attr(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no closedAt on the model

The component's closedAt property can be set to the model's insertedAt or updatedAt as we discussed. You already do this on line 16 of app/templates/components/conversations/conversation-thread.hbs.


isComment: hasClass('conversation-part--comment'),
isClosed: hasClass('conversation-part--closed'),
isByCurrentUser: hasClass('conversation-part--is-self'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw a question that got marked as outdated, which caused me to think page objects might not be completely clear to you. They are only ever used in tests and they are defined in a way that provides us with information we need to answer questions in tests.

If a conversation thread lists multiple conversations of different partType values, we want to ask

  • is this part displayed as a comment
  • is this other part displayed as "user closed at"

We know that a comment has a conversation-part--coment class, and we know that the other one has a conversation-part--closed class, so we could ask

assert.ok($('conversation-part').classNames().indexOf('conversation-part--comment') > -1);
assert.ok($('conversation-part').classNames().indexOf('conversation-part--closed') > -1);

and that works, except it's not always completely clear what it is we're actually asking. Also, in multiple tests, we tend to ask the same or similar questions, so we have to repeat all of that. Then, if, for example, the class name changes, we have to change it in all of those places.

What page objects allow us to do is to specify

isClosed: hasClass('conversation-part--closed'),

in just one place and use that.

assert.ok(conversationPart.isClosed);
assert.ok(conversationPart.isComment);

That makes the question much clearer. Also, if the class changes, we change it in just that one place - in the page object.

closedAt=conversationPart.insertedAt
}}
{{else}}
{{conversations/conversation-part-comment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your indentation here is wrong.

Copy link
Contributor

@begedin begedin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to reorder properties in app/components/conversations/conversation-part-closed.js and then this is good to go.

@daveconnis daveconnis requested a review from begedin January 15, 2018 18:46
@daveconnis
Copy link
Contributor Author

daveconnis commented Jan 15, 2018

Just realized I can't really finish the "client filtering of parts based on authorization" without making a feature branch off of https://github.com/code-corps/code-corps-ember/pull/1689 which is already a branch on top of https://github.com/code-corps/code-corps-ember/pull/1638. I don't want to have multiple branches contingent on the branch below it, so we need to merge #1638 , then change 1689 base to develop and merge, THEN I can write the filtering component. I'll make a issue for client filtering of parts based on authorization and label it blocked by 1638 and 1689.

@joshsmith
Copy link
Contributor

Realistically we don’t want to be merging all these branches into develop when there’s authorization stuff that is unfinished. What this means is that #1689 will have to serve as the base branch and all PRs will be done to it and merged there.

We could also – and this might be easier – branch off develop with some conversation-part- branch (not sure naming) and then use that as the base for all these PRs. That way develop doesn't have stuff in it that's not ready to go live, and the existing PRs can just point at the new branch as their base, which itself serves as the "feature branch" containing the full feature.

Does that make sense?

@daveconnis
Copy link
Contributor Author

I think that makes sense. So I'd create a Conversation-parts branch on develop
What would my base code be, here? Would I just set all my conversation part PRs using this as a base? Would that mean I'd have each PR to work with? My brain is saying I wouldn't because 'conversation-parts' wouldn't actually include the code for my other PRs. So I'd create conversation-parts then merge my existing PRs into conversation-parts, then finish authorization stuff on `conversation-parts'?

@begedin begedin merged commit 7bfd335 into develop Jan 16, 2018
@begedin
Copy link
Contributor

begedin commented Jan 16, 2018

👍 merged. Good work.

jderr-mx pushed a commit to jderr-mx/code-corps-ember that referenced this pull request Mar 13, 2018
Add conversation-part-closed component
Rewrite existing conversation-part as conversation-part-comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants