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

[BD-03] [BB-2542] ]Add discussions configuration UI wireframe in MFE #2

Conversation

rusrushal13
Copy link
Contributor

@rusrushal13 rusrushal13 commented Jun 26, 2020

Wireframe for the discussion configurations, currently work in progress

JIRA tickets:

Discussions:

Dependencies:

Mockups: https://edx.invisionapp.com/share/XDXMC3XU9MR#/screens/420742420

Screenshots:
image

Sandbox URL: TBD - sandbox is being provisioned.

Merge deadline:

Testing instructions:

Author notes and concerns:

  • Work is in progress

Reviewers

@openedx-webhooks
Copy link

openedx-webhooks commented Jun 26, 2020

Thanks for the pull request, @rusrushal13! I've created BLENDED-371 to keep track of it in Jira.

When this pull request is ready, tag your edX technical lead.

@openedx-webhooks openedx-webhooks added needs triage open-source-contribution PR author is not from Axim or 2U labels Jun 26, 2020
@rusrushal13 rusrushal13 force-pushed the rushal/BB-2542-discussion-configuration-UI branch from 75dbee4 to 0b69e69 Compare June 26, 2020 13:44
@rusrushal13 rusrushal13 marked this pull request as ready for review June 26, 2020 13:46
@marcotuts
Copy link

Looking great! I will flag this to the UX team who can provide final visual details on this next. Thanks!

@stvstnfrd stvstnfrd requested review from stvstnfrd and a team June 29, 2020 16:52
<main>
<div className="container-fluid">
<div className="container-fluid-container d-flex justify-content-between align-items-center border-bottom">
<h1 className="container-fluid-heading mt-3">Pages & Resources</h1>
Copy link
Contributor

Choose a reason for hiding this comment

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

All text like this should be translated not directly entered here.
The process for that is roughly as follows:

In this case here is how I would do it:

messages.js

import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
  heading: {
    id: 'course-authoring.pages-resources.heading`,
    defaultMessage: 'Pages & Resourses'
  }
...
}

Copy link
Contributor

Choose a reason for hiding this comment

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

The "View Live" button, the "Course Pages" subheading etc all should be in the messages file.

<h1 className="container-fluid-heading mt-3">Pages & Resources</h1>
<Button className="container-fluid-button font-weight-bold rounded my-3">
View Live
</Button>
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 a link masquerading as a button, so you can just use a link button styling in bootstrap.

Copy link

Choose a reason for hiding this comment

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

Using the following, I get a rounded button instead of the rectangular button in the wireframe. What should I do?

          <a class="btn btn-primary" href="#" role="button">                                                             
            {intl.formatMessage(messages['course-authoring.pages-resources.viewLive.button'])}                           
          </a>                                                                                                           

Copy link
Contributor

Choose a reason for hiding this comment

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

@taniwha Feel free to just leave it as is for right now.
We'll have the UI folks run over the details later to confirm expectations.

enabledButton
coursePageStatus={false}
description="Encourage Participation and engagement in your course with discussion forums"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

This should not be hard-coded, it will be constructed based on data from the API.

Copy link

Choose a reason for hiding this comment

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

I assume you mean cousePageStatus.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note: As I mentioned above, these tiles should not be hard-coded, they will be returned by an API. So you did not need to move all this to the translation file. i.e. Remove all <CoursePageConfigTile objects from here and have this list be provided by the parent element for CoursePageResources

@@ -1,4 +1,4 @@
describe('example', () => {
describe('coursepageresources', () => {
it('will pass because it is an example', () => {

});
Copy link
Contributor

Choose a reason for hiding this comment

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

Add some snapshot tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note - we'd like to avoid overuse of snapshot tests, as while they're easy to write, they're brittle and difficult to maintain. Favoring specific assertions about the DOM is preferable, as it's much less likely to break when trivial changes are made to the markup/CSS classnames.

Snapshots are good for small components, but not great for larger components. i.e., the cost of ensuring that the snapshot is always 100% correct is so high for large components that it's very easy for the snapshot to become invalid, in which case the test starts asserting that things are still wrong, but no one realizes because it passes. :)

A large, invalid snapshot is very difficult to go back and prove wrong because it requires a developer to go understand every little thing that's in the snapshot, and to go against what the machine is telling them - that the test passed. Visual inconsistencies are better caught with our eyes when viewing the UI, rather than trying to parse a not-very-human-readable HTML document.

Some good rationale in here: https://engineering.ezcater.com/the-case-against-react-snapshot-testing

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for this explanation! Definitely useful to keep in mind for future PRs as well.

Copy link

Choose a reason for hiding this comment

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

What are snapshot tests? Or maybe better, can you point me to some examples? Will I find some in frontend-app-learning?

Copy link

Choose a reason for hiding this comment

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

Just read David Joy's comment (my page was out of date) and I'm inclined to agree that meaning is more important than appearance (to paraphrase).

Copy link
Contributor

Choose a reason for hiding this comment

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

Snapshot tests record a copy of the DOM generated from react when they're first run, and then on subsequent runs compare the results to that original snapshot. It's like asserting that the entire DOM is what you expect it to be, down to every single tag and attribute. As mentioned above, this means that any tiny change no matter how trivial invalidates a snapshot test and can make noisy diffs - often this can result in masking real issues if the test is "retrained" under faulty assumptions about what's correct.

You can see some in this commit in frontend-app-learning: openedx/frontend-app-learning@ada0750

Note that I've asked that those be modified (in the same spirit as above), so that code may not make it into master. :)

Copy link

Choose a reason for hiding this comment

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

Yeah, I read the rational and couldn't agree more. I had done similar by hand years ago doing compiler testing and hated it, thus avoided doing automated tests until I figured out how to do meaningful tests. More work to write the tests, but they are very much more reliable.

@@ -0,0 +1,48 @@
.container-fluid {
background-color: #EAF3FB;
color: #2E8ED3;
Copy link
Contributor

Choose a reason for hiding this comment

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

Colours shouldn't be hard-coded. Get them from the theme, or at least set them globally in a theme file.

Comment on lines 23 to 35
.border-enabled {
border-color: #4293C4 !important;
border-width: 2px;
border-style: solid;
border-radius: 5px;
}

.border-disabled {
border-color: #ffffff !important;
border-width: 2px;
border-style: solid;
border-radius: 5px;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Both of these share most of their code. It would be better to have it as:

.course-page-tile {
  border: 2px solid $white;
  border-radius: 5px; //Maybe this can be standardised?
      .disabled {
          border-coloor: #4293C4;
      }
  }
}

Comment on lines 6 to 22
.container-fluid-container {
border-color: #B2D5EE;
}

.container-fluid-enabled-button {
border-color: #0075B4;
color: #2E8ED3;
}

.container-fluid-enabled-button:hover {
color: inherit;
}

.sub-container-text {
color: #39AAE3;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

These names are very generic and tied to bootstraps naming scheme instead of being specific to this app.

Comment on lines 37 to 44
.container-fluid-button {
background-color: #0075B4;
color: #ffffff;
font-size: 12px;
&:hover {
color: #ffffff;
}
}
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 a very generic name for this button, and again uses the bootstrap names instead of something like course-pages-button or something.
Other points:

  • Colours should be in the theme, not here.
  • Font size should be in rem not pixels.

import { faCog } from '@fortawesome/free-solid-svg-icons';
import { Button } from '@edx/paragon';

function CoursePage({
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this component name is a bit misleading. A better name I feel would be: CoursePageConfigTile.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe these are generally called "Cards" by both Bootstrap and the edX UX team: https://getbootstrap.com/docs/4.0/components/card/

Copy link

Choose a reason for hiding this comment

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

Should I rename the file as well? What about the directory? The former seems very reasonable, but I am unsure about the latter.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's a virtue to keep naming consistent... so if we think these are 'cards' instead of 'pages', then I'd consistently change the term, yeah. Assuming that the course-page module is all about code related to these cards.

Copy link

Choose a reason for hiding this comment

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

It seems to be about the cards, and I'll go with that naming convention. Thank you.

Copy link
Contributor

Choose a reason for hiding this comment

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

@taniwha As mentioned above in the comment, the convention here is to call these UI elements Cards, not Tiles. So could you rename this accordingly?

let classes = 'bg-white shadow p-3 m-3';
classes += coursePageStatus ? ' border-enabled' : ' border-disabled';
return classes;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of this, use the classNames utility. You can read more about that here, but to give a brief intro with that you can replace the above with:

classNames('course-page-tile bg-white shadow p-3 m-d', {'disabled': !coursePageStatus});

@@ -0,0 +1,48 @@
.container-fluid {
Copy link
Contributor

Choose a reason for hiding this comment

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

To agree with @xitij2000 's points below, we try to minimize semantic class names in our code to the bare minimum. Instead, we can use Bootstrap's utility classes to accomplish most of these things.

Paragon is built on top of Bootstrap, and provides a wealth of utility class names: https://edx.github.io/paragon/foundations/css-utilities

Also, as noted below, we definitely don't want to attach additional styling to existing Bootstrap class names; this will create very difficult to debug situations where unrelated code receive styling they're not expecting.

Copy link

Choose a reason for hiding this comment

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

Thank you. I will look into that.

import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
'course-authoring.pages-resources.heading': {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think the idea is to give is a more friendly name here.

I think the 'id' needs to be globally unique, but this key in the dictionary does not need to be.
So I would recommend keeping them simple, so, 'heading' instead of 'course-authoring.pages-resources.heading'.

Copy link

Choose a reason for hiding this comment

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

OK, I'll remove "course-authoring.pages-resources." from the dictionary keys but leave the id fields alone.

.disabled {
border-color: theme-color("gray", 100);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I am wondering if this can all be standardised, and then this could just include the standard styling if it needed to be customised.

Comment on lines 17 to 20
return classNames('bg-white shadow p-3 m-3', {
'border-disabled': !coursePageStatus,
'border-enabled': coursePageStatus,
'course-page-title.disabled': !coursePageStatus,
'course-page-title': coursePageStatus,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be simplified to:

    return classNames('bg-white shadow p-3 m-3 course-page-title', {disabled: !coursePageStatus});

Copy link

Choose a reason for hiding this comment

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

That didn't work as intended because course-page-title.disabled is required, so I had to use a variation.

Copy link
Contributor

Choose a reason for hiding this comment

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

@taniwha that might be because you are defining the SCSS styling incorrectly.
Note:

.class1 {
 color: red;
 .class2 {
   color: green;
 }
}

In the above the inner class2 will not match a div with both class1 and class2, It will match a child element of class2 inside an element with class1.

If you want to match an element that has both class1 and class2 (in this case both course-page-title and disabled), the correct scss code is:

.class1 {
 color: red;
 &.class2 {
   color: green;
 }
}

@@ -35,7 +35,7 @@ function CoursePageConfigTile({
<p>{coursePageStatus ? 'Enabled' : 'Disabled'}</p>
<div>{description}</div>
<div className={getEnabledButtonClasses()}>
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see why we need to call an intermediary function here. Just add the entire classNames function call here.
If that makes it harder to understand, you can just put it in a variable. So something like:

const containerClasses = classNames('course-page-tile bg-white shadow p-3 m-d', {'disabled': !coursePageStatus});
... // Later
 <div className={containerClasses} ...

enabledButton
coursePageStatus={false}
description="Encourage Participation and engagement in your course with discussion forums"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

Note: As I mentioned above, these tiles should not be hard-coded, they will be returned by an API. So you did not need to move all this to the translation file. i.e. Remove all <CoursePageConfigTile objects from here and have this list be provided by the parent element for CoursePageResources

import { faCog } from '@fortawesome/free-solid-svg-icons';
import { Button } from '@edx/paragon';

function CoursePage({
Copy link
Contributor

Choose a reason for hiding this comment

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

@taniwha As mentioned above in the comment, the convention here is to call these UI elements Cards, not Tiles. So could you rename this accordingly?

@taniwha
Copy link

taniwha commented Jul 6, 2020

@xitij2000 I had passed on the renaming for the moment because the user-view shows pages and wanted to query that, but I suppose it doesn't really matter if it's "page" externally and card internally (especially since it's a repleacable string).

@taniwha
Copy link

taniwha commented Jul 6, 2020

@xitij2000 I believe I have addressed all of your points other than the testing (not sure what to do there at this stage) and the API issue (thus some instances of page remain: they will be deleted when the API is sorted out). For that...
I take it that there would be no hard-coded cards and all card configs would be passed into CourseCardResources (hope I got the renaming intent right) in an array, and the cards put into the container in a for-loop. At this stage, I don:t know how to do this as I am actually not familiar with React or JavaScript (I've been getting along because things are similar enough to C that I can guess what's needed). What examples I can reference?
The same goes for the tests, actually: it is only unfamiliarity with appropriate testing methods that prevents me from implementing tests.

@xitij2000
Copy link
Contributor

I had passed on the renaming for the moment because the user-view shows pages and wanted to query that, but I suppose it doesn't really matter if it's "page" externally and card internally (especially since it's a repleacable string).

I think you misunderstood the intent behind the renaming. Just to clarify, we are building the UI for the "Course Pages" part of the app. So, it makes sense to call the internal component there: CoursePages.

The original name for the each individual component inside that Page (i.e. Discussion, Teams, Progress etc) was: CoursePage, which makes sense, but is confusing since a page can also be misunderstood to correspond to an actual "Course Page". Hence, I suggested that it be renamed to "CoursePageConfigTile" to clarify that it is a Tile that is for the CoursePage configuration. What David suggested is that instead of calling it a tile, we call it a Card, since that is the convention used by bootstrap, which is the UI styling toolkit we're using. i.e. the name of the component rendering each individual block with Discussions, Teams etc should be called "CoursePageCard" instead of the current "CourseCardConfigTile".

I take it that there would be no hard-coded cards and all card configs would be passed into CourseCardResources (hope I got the renaming intent right) in an array, and the cards put into the container in a for-loop. At this stage, I don:t know how to do this as I am actually not familiar with React or JavaScript (I've been getting along because things are similar enough to C that I can guess what's needed). What examples I can reference?

Sure, I can reach out about this on the internal Mattermost.

@taniwha
Copy link

taniwha commented Jul 8, 2020

I think you misunderstood the intent behind the renaming. Just to clarify, we are building the UI for the "Course Pages" part of the app. So, it makes sense to call the internal component there: CoursePages.

The original name for the each individual component inside that Page (i.e. Discussion, Teams, Progress etc) was: CoursePage, which makes sense, but is confusing since a page can also be misunderstood to correspond to an actual "Course Page". Hence, I suggested that it be renamed to "CoursePageConfigTile" to clarify that it is a Tile that is for the CoursePage configuration. What David suggested is that instead of calling it a tile, we call it a Card, since that is the convention used by bootstrap, which is the UI styling toolkit we're using. i.e. the name of the component rendering each individual block with Discussions, Teams etc should be called "CoursePageCard" instead of the current "CourseCardConfigTile".

I had indeed misunderstood. Thank you for the clarification. I will sort that out ASAP.

Copy link
Contributor

@xitij2000 xitij2000 left a comment

Choose a reason for hiding this comment

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

In my testing the style isn't matching the original mockup. Could you please make sure that it looks as close as possible without needing to add excessive overrides.
Screenshot_2020-07-10 Application Template edX

<div className="card-footer">
<div className={buttonClasses}>
<Button className="course-page-button bg-white border font-weight-bold rounded">
Enable
Copy link
Contributor

Choose a reason for hiding this comment

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

Move to messages file.

</div>
</div>
<div className="card-body">
<p>{isEnabled ? 'Enabled' : 'Disabled'}</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

Move to messages file for translation.

}].isRequired,
};

export default injectIntl(CoursePageResources);
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename this directory to course-page-resources to stick with the approach everywhere else.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also add an index.js file that re exports this component. i.e an index.js file that has:

export {default as CoursePageResources} from './CoursePageResources'; 

This inport this in the main index.jsx file as:

import {CoursePageResources} from './course-page-resources'

isEnabled: null,
};

export default injectIntl(CoursePageConfigCard);
Copy link
Contributor

Choose a reason for hiding this comment

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

This component is only going to be used by CoursePageResources, so move it to a subdirectory.

@taniwha
Copy link

taniwha commented Jul 13, 2020

@xitij2000 I've addressed your points and got the page looking much more like the wireframe. However, I noticed one issue that is probably a mistake on my part as I failed to notice it before I made my simplifications, but the wireframe has "Disabled" on some cards that do not have an "Enable" button: this currently does not make sense to me. Is this a mistake in the wireframe, or do I need to make the logic for the two items separate again?

[edit] question sorted in mattermost.

const messages = defineMessages({
heading: {
id: 'course-authoring.pages-resources.heading',
defaultMessage: 'Pages & Resourses',
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: fix spelling

},
'resources.custom.description': {
id: 'course-authoring.pages-resources.resources.custom.description',
defaultMessage: 'Create and edit custom pages to provide students with additional course content and resources. Pages are publicaly visible. If users know the URL of a page, they can view the page even if they are not registered for or logged in to your course.',
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: spelling fixes

Comment on lines 1 to 3
.course-page-title {
border: 2px solid $white;
border-color: theme-color("info", 300);
border-radius: 5px; //Maybe this can be standardised?
&.disabled {
border-color: theme-color("gray", 100);
}
}

.course-page-button {
outline: 2px solid theme-color("info", 500);
outline-radius: 5px; //Maybe this can be standardised?
background-color: theme-color("gray", 100);
color: theme-color("info", 500);
font-size: 0.8;
&:hover {
color: inherit;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think these are hurting more than helping, just remove them. Let's get the basic structure in.

Copy link

Choose a reason for hiding this comment

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

For now, I have left them in as they are what provide the specified shading.

@@ -1,6 +1,6 @@
@import '~@edx/paragon/scss/edx/theme.scss';

@import './example/index.scss';
@import './course-page-resources/index.scss';
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this as well.

Copy link

Choose a reason for hiding this comment

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

See above.

Comment on lines 19 to 21
const cardSettings = 'bg-white shadow p-3 m-0 my-3 course-page-title';
const cardClasses = classNames('card', cardSettings, { disabled: !pageStatus });
const cardSubSettings = 'bg-white px-3 py-0';
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these called settings? I think extracting them only makes sense if the styles are locked, i.e. if you change one you'll change all. That isn't the case here.

Please don't use the 'card' classes. While they might match the bootstrap concept we have a pretty different structure and styling requiremtn so we shouldn't force them in here.

i.e. remove the card style here, the card-header, card-footer, and card-body styles as well.

Copy link

Choose a reason for hiding this comment

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

They probably should be styles rather than settings. I was stuck for naming at the time.

Also, I am no longer using the card class, but still referring to the objects as cards. However, I am still in the process of cleaning up that area of the code.

Copy link

Choose a reason for hiding this comment

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

Actually, this has been cleaned up entirely.

Comment on lines 23 to 24
const buttonSettings = 'd-flex mt-0 justify-content-center align-items-center';
const buttonClasses = classNames(buttonSettings, { visible: !pageStatus, invisible: pageStatus });
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const buttonSettings = 'd-flex mt-0 justify-content-center align-items-center';
const buttonClasses = classNames(buttonSettings, { visible: !pageStatus, invisible: pageStatus });
const buttonClasses = classNames('d-flex mt-0 justify-content-center align-items-center', { visible: !pageStatus, invisible: pageStatus });

Don't make it set visibility, just don't add it to the dom if it shouldn't show up unless there is a good reason to have it invisible (for screenreaders for instance).
Also buttonClasses is a bit odd since they don't apply to a button.

Copy link

Choose a reason for hiding this comment

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

This has been cleaned up entirely.

Comment on lines 28 to 30
<div className={cardClasses} key={id}>
<div className={classNames('card-header', cardSubSettings)}>
<div className="row">
Copy link
Contributor

Choose a reason for hiding this comment

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

Too many divs for such a simple layout. Condense this.

</div>
</div>
<div className={classNames('card-body', cardSubSettings)}>
{showSettings && <p>{intl.formatMessage(messages[pageStatusMsgId])}</p>}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
{showSettings && <p>{intl.formatMessage(messages[pageStatusMsgId])}</p>}
<span>{intl.formatMessage(messages[pageStatusMsgId])}</span>

These are not related, and this is not a para of text.

Copy link

Choose a reason for hiding this comment

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

They seem to be related: the page enabled/disabled status is shown only when the settings gear is shown. However, span seems to work nicely after some tweaking of the surroundings.

Copy link
Contributor

Choose a reason for hiding this comment

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

@taniwha The mockup doesn't describe the functionality, it's just a mockup with a few sample cases.

</div>
<div className={classNames('card-footer', cardSubSettings)}>
<div className={buttonClasses}>
{!showSettings && (
Copy link
Contributor

Choose a reason for hiding this comment

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

Showing the enabled button has nothing to do with settings? Why is it linked here?

Copy link

Choose a reason for hiding this comment

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

It is not "enabled" (status) but "enable" (action), and it is shown only when the settings icon is not shown.

Copy link
Contributor

Choose a reason for hiding this comment

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

As I mentioned in the previous comment, the mockup doesn't dictate this functonality.

id: PropTypes.string,
title: PropTypes.string,
description: PropTypes.string,
pageStatus: PropTypes.bool,
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 very ambiguous. What status is this? It seems to signify that the page/app is enabled.

Copy link

Choose a reason for hiding this comment

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

From examining the various combinations shown in the wire-frame, that is my understanding of what is going on: the page can be added (possibly in a disabled state) and then later enabled and disabled at will. This is why I have lumped the behavior of several seemingly unrelated items into "showSettings".

I thought I had asked for specs on actual behavior (not just appearance), but I can't find the request. Knowing how things are meant to behave would help immensely by doing away with a lot of the guesswork.

Copy link
Contributor

Choose a reason for hiding this comment

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

@taniwha I think did mention that you should make it as flexible as possible i.e. support all combinations.

@xitij2000
Copy link
Contributor

@taniwha Please rebase this as well.

@xitij2000
Copy link
Contributor

@taniwha To save some rounds I've applied some of my suggestions and made further changes to reduce usage of custom styling in favour of bootstrap classes.
In general if you need to apply a style look into a bootstrap or paragon class that applies that style instead.

@taniwha
Copy link

taniwha commented Jul 16, 2020

@xitij2000 Your changes have resulted in a rather broken layout. The merging has broken the display entirely.
broken-layout
broken-display

Also, why did you undo the passing of the coursePage object through to CoursePageConfigCard? It kept CoursePageResources simple because CoursePageResources then has no need to know anything about what is in a cousePage object other than the id (needed for the key). It makes no architectural sense for the outer container (CoursePageResources) to care what is in the inner container: that is the job of CoursePageConfigCard. I was even trying to sort out how to keep the course page shape out of CoursePageResources.jsx (since it duplicates the shape in CoursePageConfigCard.jsx)

However, the merging breaking the display is probably something I would have to take care of no matter what as it would have happened eventually. The layout I'll probably be able to fix.

@xitij2000
Copy link
Contributor

xitij2000 commented Jul 16, 2020

@taniwha
In your first screenshot that is because of the resolution you're on, the sizes are not final so fixing that is a simple matter of changing alignments and flex basis. Here is what I see for instance:
Screenshot_2020-07-16 Application Template edX(1)

For the second one, that's because the previous PR that merged added routing, so you need to have a path at which the component is mounted. You should be able to see this at: http://localhost:2001/course-pages/demo now.

Also, why did you undo the passing of the coursePage object through to CoursePageConfigCard?

I think having them as explicit properties is cleaner.

It kept CoursePageResources simple because CoursePageResources then has no need to know anything about what is in a cousePage object other than the id (needed for the key).

Currently, we don't even know if it will have an id.

It makes no architectural sense for the outer container (CoursePageResources) to care what is in the inner container: that is the job of CoursePageConfigCard. I was even trying to sort out how to keep the course page shape out of CoursePageResources.jsx (since it duplicates the shape in CoursePageConfigCard.jsx)

I think you're trying to over architect things here. This is not a generic container like a list or queue or something. It is a UI element that's designed to do one thing. It will not be reused, and making it generic is of no value, so it is better instead to have everything be explicit and clear.

Even if you want to go down the route of making things generic, designing an item based on the container it will be in also seems wrong doesn't it? Why should the CoursePageConfigCard get a single parameter because that makes it easier to pass in rather than what makes sense there?

Note there is another way out here. Instead of the way it's coded right now, you can do:

<CoursePageConfigCard key={coursePage.id} {...coursePage} />

This will work instead of sending each value explicitly like I have.

If we really wanted to make things generic, then you would need to do the following:

  • Create something like a CardTileLayoutContainer that lays out CardItems in a tile.
  • CoursePageResources can then use the CardTileLayoutContainer component and put each CoursePageConfigCard in a CardItem component.

Unfortunately, this is of little value here because you can get a similar layout using just a few css classes.

@taniwha
Copy link

taniwha commented Jul 17, 2020

Thank you for the that url.

As for the layout, it is due to the use of m-3 on the course-page-config-card div. The margin attribute effectively grows the div (by adding space around the outside of the div's box), so columns with a width of 1/3 of the available space cannot fit. This is why I had an outer div with p-3: same overall effect, but 3 columns fit side-by-side even when their width is 1/3 of the available space.

Explicit properties to CoursePageConfigCard do not make it clearer that CoursePageConfigCard is for displaying a course page's configuration. Instead, the properties make it seem like CoursePageConfigCard is for displaying a title, description, and some buttons and a status. Such "clarity" is neither necessary nor desirable.

Also, having the explicit parameters means one more place that needs to be edited when the contents of a course page changes, meaning more testing and the opening for more errors.

I added the id field only to take care of the warning about list items needing a key (and not simply use the map index). I am not particularly attached to it.

I have fixed the layout to work for any horizontal resolution (tested from as narrow as firefox will go to as wide as 1920), and I have gone back to passing the object, this time with the shape defined reuseably.

@xitij2000
Copy link
Contributor

As for the layout, it is due to the use of m-3 on the course-page-config-card div. The margin attribute effectively grows the div (by adding space around the outside of the div's box), so columns with a width of 1/3 of the available space cannot fit. This is why I had an outer div with p-3: same overall effect, but 3 columns fit side-by-side even when their width is 1/3 of the available space.

I understand your point, however the layout you are using will squish the tiles to accommodate the desired number in each row as the UI is resized, whereas the one I used would simply flow them over to the next line. Neither of these approaches are in the mockup so fair enough.

Explicit properties to CoursePageConfigCard do not make it clearer that CoursePageConfigCard is for displaying a course page's configuration. Instead, the properties make it seem like CoursePageConfigCard is for displaying a title, description, and some buttons and a status. Such "clarity" is neither necessary nor desirable.

You are right, explicit properties do not make it clear that it is for display a course page's configuration. What makes that clear is the name of the component CoursePageConfigCard, hence my initial push for an appropriate name.

Also, having the explicit parameters means one more place that needs to be edited when the contents of a course page changes, meaning more testing and the opening for more errors.

I think we need to think in broader terms here. Right now this is just about getting the mockup, so it is at its simplest state, there will be a data layer for this app eventually, and the UI logic will only get more complicated from here.

The data that the CoursePageResources object will get will come from an API. It is safe to assume that the format of this data will not be one that matches the current shape of coursePage, i.e. the edx-platform API will not return values like "showSettings", "showEnable". At some place there will need to be some data mapping that converts the values the API returns into something that CoursePageConfigCard.

At that point either CoursePageConfigCard will need to be tied to the API response, tying it to the shape of the API data, or the data layer will need to convert the array of objects it gets to a format that CoursePageConfigCard will understand. If the data layer does this, then the data layer is now tied to the UI implementation, which is bad practice.

The middle-ground here is that CoursePageConfigCard can focus on showing just the data it is designed to show, and CoursePageResources is the area where we define this mapping for how data from the API should correspond to properties of CoursePageConfigCard. The idea is to the keep this logic as close to where it is used as possible. If we pass an object instead of individual props, then it will still have to build a new object that CoursePageConfigCard understands, at which point, might as well pass them as attributes right?

Currently, the settings icon, and enable button are decorative. Eventually, they will perform actions, these actions will be performed by functions that will be passed to the CoursePageConfigCard, such as by adding an onClickSettings and onEnable attribute. Those too will change the data shape and how it's mapped to each object.


This task is a first step in starting to build up other parts of the UI, and a lot of it will be updated, and changed anyway, so I don't think it makes sense to spend more time on this.

@taniwha
Copy link

taniwha commented Jul 17, 2020

You are right, explicit properties do not make it clear that it is for display a course page's configuration. What makes that clear is the name of the component CoursePageConfigCard, hence my initial push for an appropriate name.

I very much agreed with your push. It's just that at the time, I got a little confused about the intended name (and the classes to use in the implementation).

Really, I don't think I can thank you enough. I have learned a lot from this.

Copy link
Contributor

@xitij2000 xitij2000 left a comment

Choose a reason for hiding this comment

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

👍 While I disagree with the approach of passing all the arguments as an object, I don't want to block this work on that point. It's good to go from my side after addressing the point in my comments.

  • I tested this: Tested on master devstack locally via npm start
  • I read through the code

showEnable: PropTypes.bool.isRequired,
});

export default CoursePageShape;
Copy link
Contributor

Choose a reason for hiding this comment

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

This does not need to be a .jsx file. In fact, it does not need to be a separate file, you can keep this with the same component and just export it from there.

@taniwha
Copy link

taniwha commented Jul 20, 2020

@stvstnfrd This PR is ready.

Copy link
Contributor

@stvstnfrd stvstnfrd left a comment

Choose a reason for hiding this comment

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

This is looking good! Thanks for all of the iteration!

And thanks for bearing with me; we just finally launched the new Courseware/learning MFE, so BD-03 has much more of my attention now :)

I've got a few questions, pretty minor, and some of which are general styling questions (on which I probably tagged @davidjoy lol) that aren't necessarily an issue now, but just wanted to flag in case they fall under any potential "best practices" that I just wanted to get out of the way now, before later iteration.

Regarding next steps:
Is the screenshot(s) up-to-date?
I know things changed a lot, so just want to make sure we can run the most recent by @marcotuts and UX. Also, if there are any unresolved questions, we can run it by them too (I saw there was discussion about whether or not to collapse columns as screen width decreases).

And then regarding merging, would merging this in its current state (or thereabouts) cause any issues? I know it wouldn't be functional (actions not hooked up, API not wired up, etc.), but if it will "sit there quietly" until we start linking to it, we can merge this sooner. Just to mention, in case you haven't seen, other edx developers have begun contributing to other parts of this repo (and I believe deploying it live, CC: @MichaelRoytman ), so it's no longer just us "messing around in an empty repo" 😁 and we'll need to take appropriate caution.

@@ -44,6 +44,7 @@
"@fortawesome/free-solid-svg-icons": "5.11.2",
"@fortawesome/react-fontawesome": "0.1.9",
"babel-polyfill": "6.26.0",
"classnames": "^2.2.6",
Copy link
Contributor

Choose a reason for hiding this comment

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

edit: I see @xitij2000 originally suggested this approach, though the relevant lines were much shorter then, but happy to discuss.

Tagging @davidjoy since you're often a firewall against new dependencies (which is a good thing!).

Do we use this library in other frontend-apps?

It's not a big library, but it still feels like a heavy integration for what is effectively "append one class to the list based on a boolean".

From below:

return (
  <div className={classNames('course-page-config-card d-flex flex-column align-content-stretch bg-white p-3 border shadow', { 'border-info-300': coursePage.isEnabled, 'border-gray-100': !coursePage.isEnabled })}>
  // ... 

could be simplified (without dependency) as:

let classNames = 'course-page-config-card d-flex flex-column align-content-stretch bg-white p-3 border shadow';
if (coursePage.isEnabled) {
  classNames += ' border-info-300';
} else {
  classNames += ' border-gray-100;
}
return (
  <div className={classNames}>
  // ...

or maybe even:

let extraClassNames;
if (coursePage.isEnabled) {
  extraClassNames = 'border-info-300';
} else {
  extraClassNames = 'border-gray-100;
}
return (
  <div className={`${extraClassNames} course-page-config-card d-flex flex-column align-content-stretch bg-white p-3 border shadow`}>
  // ...

Comparing the example and counter-examples, beyond the dependency issue itself, my other concern is with just how long the line is, and in particular, that the important parts (the conditionals) are tucked in at the end.

Important code that happens off-screen tends to be more likely to introduce issues/regressions, as it is easy for both author/reviewer/debugger to overlook mistakes; it's literally harder to see.

I don't mean to push for 80 character widths or anything (I mean, I do want to, but I wont; and I think 120 is fairly standard in the edx ecosystem), but this clocks in at 210 (!?) characters, with the critical bits happening off-screen.

Python's ternary operator should also be avoided, at least in verbose situations, for the same reasoning:

my_variable = the_result_of_some_computed_value('with', 'arguments' and 'stuff').trim().lower()) if CONDITION_THAT_RUNS_SO_FAR_OFF_THE_SCREEN_AND_CANT_BE_EASILY_SEEN else IT_IS_MORE_LIKELY_TO_CAUSE_BUGS

Yes, that code is contrived, but yes, this issue does bite us/me in real life, including the first time I read this PR and missed that line 😉

So unless @davidjoy is strongly in favor of this library (and we can still refactor to shrink lines), I'd prefer to remove this.

Copy link
Contributor

Choose a reason for hiding this comment

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

and if we were to keep this, is there a package-lock.json that should be committed too?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we use this library in other frontend-apps?

Yes, I think it is used in almost all other frontend app, and in edx-platform itself. While I had prior experience with it, I did look into it before suggesting it here.

Even if we'd like to avoid another dependency (npm dependencies can quickly get unwieldy), I think a helper for this is a pretty good thing to include in the frontend-platform.

Copy link
Contributor

Choose a reason for hiding this comment

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

Unless @davidjoy protests, I'm fine with this library then, thanks for checking.

If we could though, still refactor its usage to break up the long line(s) so the conditional logic isn't hidden off-screen?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we use classnames in many apps and have found it really useful. 👍 It shorthands a lot of complicated logic very nicely.

src/course-page-resources/CoursePageResources.jsx Outdated Show resolved Hide resolved
src/course-page-resources/CoursePageResources.jsx Outdated Show resolved Hide resolved

import messages from './messages';

function CoursePageResources({ intl, coursePages }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@davidjoy Is there any pattern to where/how we unpack the props (in the function signature versus in the function body)? I know I've seen it (eg: frontend-app-learning) done both ways, but just curious if there are (stylistic) benefits either way or if we're looking to consolidate around either approach.

src/index.jsx Outdated Show resolved Hide resolved
@@ -12,6 +12,9 @@ temp/babel-plugin-react-intl
### pyenv ###
.python-version

### vim ###
Copy link
Contributor

Choose a reason for hiding this comment

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

Respect :)

src/course-page-resources/index.scss Show resolved Hide resolved
@openedx-webhooks openedx-webhooks removed the open-source-contribution PR author is not from Axim or 2U label Jul 23, 2020
@xitij2000
Copy link
Contributor

@stvstnfrd Note this is ready for review as well I had applied the suggested fixes.

.editorconfig Outdated
@@ -0,0 +1,83 @@
# ***************************
Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW, we haven't put any .editorconfig files in our frontend repos... I'm not sure it's necessary. I'm fairly sure our eslint config accounts for all this.

Copy link
Contributor

@davidjoy davidjoy left a comment

Choose a reason for hiding this comment

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

Think this is fine to merge in - not sure I think we need the .editorconfig.

Copy link
Contributor

@stvstnfrd stvstnfrd left a comment

Choose a reason for hiding this comment

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

@rusrushal13 @xitij2000 Maybe pull out the .editorconfig, but otherwise this looks good.
Let me know if it needs rebased/squashed or anything, otherwise I'll merge it.
Thanks!

@openedx-webhooks openedx-webhooks added waiting on author PR author needs to resolve review requests, answer questions, fix tests, etc. and removed awaiting prioritization labels Oct 7, 2020
@stvstnfrd
Copy link
Contributor

CC: @MichaelRoytman That we'd like to merge this, though it won't be used anywhere, nor does it do anything dynamic yet, but we want to start iterating on the UX. Please let me know if you have any concerns.

Adds views (with test data) for the course apps configuration pages.
@xitij2000 xitij2000 force-pushed the rushal/BB-2542-discussion-configuration-UI branch from 7a9169c to 09a21c9 Compare October 7, 2020 21:44
@xitij2000
Copy link
Contributor

@stvstnfrd I've removed the .editorconfig file and squashed the commits.

@stvstnfrd stvstnfrd merged commit 9989cb7 into openedx:master Oct 14, 2020
@openedx-webhooks openedx-webhooks added merged and removed needs triage waiting on author PR author needs to resolve review requests, answer questions, fix tests, etc. labels Oct 14, 2020
xitij2000 referenced this pull request in open-craft/frontend-app-authoring Jul 2, 2021
# This is the 1st commit message:

WIP

# The commit message #2 will be skipped:

# WIP
@xitij2000 xitij2000 deleted the rushal/BB-2542-discussion-configuration-UI branch August 12, 2021 04:01
bradenmacdonald pushed a commit that referenced this pull request Aug 17, 2024
feat: setup repo as NPM package with edX standards.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blended PR is managed through 2U's blended developmnt program merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants