-
Notifications
You must be signed in to change notification settings - Fork 34
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
Integrate StatusAlert into Delete Workflow #40
Conversation
src/data/constants/assets.js
Outdated
@@ -0,0 +1,3 @@ | |||
/* ASSETS RESPONSE MESSAGES */ | |||
export const ERROR_FILE_DELETE = 'File could not be deleted.'; | |||
export const SUCCESS_FILE_DELETE = 'File was successfully deleted.'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want the messages for responses to be more descriptive? Do we also want to store other static messages (i.e modal body text / confirmation button text / status alert text in this constants file as well, or do we prefer to have that inline? Is any of it reusable on other pages that we may do in the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also this file was created because of the use of checking the status message to determine if the request being returned by the action creator was a success or failure, moving the constant string to a shared location reduces the number of places that will need to get changed in the future if we decide to change it. This checking of the message currently determines where focus should move from the StatusAlert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're going to need to consider i18n with these strings as well - the "inline vs centralized" string storage decision will need to be made and standardized across the repo soon.
@@ -23,6 +23,13 @@ const list = (state = [], action) => { | |||
|
|||
const status = (state = {}, action) => { | |||
switch (action.type) { | |||
case assetActions.CLEAR_ASSETS_STATUS: | |||
return {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this because the status message is what is being used to determine the styling of the StatusAlert -- there is a short amount of time where the alert will show up before the delete request has completed and returned a response, this could cause the styling to show incorrectly briefly (green first to red failure or vice versa). By clearing the status after the close of the alert, the next time the alert is triggered it will show the warning/"Deleting asset" text before showing the correct status (yellow to red/green).
src/data/actions/assets.js
Outdated
@@ -42,9 +46,12 @@ export const deleteAsset = (assetsParameters, assetId) => | |||
clientApi.requestDeleteAsset(assetsParameters.courseId, assetId) | |||
.then((response) => { | |||
if (response.ok) { | |||
dispatch(deleteAssetSuccess(assetId)); | |||
dispatch(deleteAssetSuccess(assetId, response, AssetsConstants.SUCCESS_FILE_DELETE)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense to me that we would send the response details on each response completion (success or failure). Even if we don't do anything with the response body, I think returning back to redux the updated status of success or failure on call is good practice. Also if we ever do want to track our calls for success/failure in the future we are already set up for collecting that information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should pose the question -- do we want to do this in this manner where the SUCCESS_FILE_DELETE
action type is used in two different reducers, or do we want two dispatch separate dispatch
calls and create a new assetXHRSuccess
/ ASSET_XHR_SUCCESS
type? I feel like the way I did it isn't recommended even if it does technically work. It might also be more clear if we do two separate dispatch calls? @arizzitano specific react/redux framework question
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so there's a lot going on here.
- I'm fine passing the whole response payload back to the reducer. Fat vs skinny action creators/reducers is a matter of debate. Either way, we should make sure the reducer only stores the fields we care about, not the entire payload -- this will eat up browser memory.
- I'm team use-the-same-action-type-in-multiple-reducers. IMO this is one of the most powerful features of Redux, and Abramov himself pushes it.
- Seems like we're getting to the point of feeling XHR pain and maybe needing to start thinking about a layer of abstraction. I don't think you need to do it in this PR (we're not there QUITE yet) but we need to start thinking about it. i started typing up a proposed implementation but realized it would be better as a PR so adding that to my backlog.
src/AssetsPage/AssetsTable/index.jsx
Outdated
return focusRowButton; | ||
} | ||
|
||
addSupplementalTableElements() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed this fuction as it now has more use cases than just adding the delete button -- will add lock button and now adds an ID for the row # that the data is on. That row # is used for determining the next focusable element once the status alert is closed.
5bc6ad5
to
fbdc725
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice tests! I left a few questions for you. Thanks!
src/AssetsPage/AssetsTable/index.jsx
Outdated
focusRowButton = this.trashcanRefs[focusAsset.id]; | ||
} | ||
|
||
return focusRowButton; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I wonder if focusRowButton
can be removed entirely and for the return to just be:
return this.trashcanRefs[focusAsset.id];
Just to be clear, I have no qualms with how this is written as it's quite understandable, and the suggestion is just to make it a bit tighter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely -- good catch on some clean up.
src/AssetsPage/AssetsTable/index.jsx
Outdated
import classNames from 'classnames'; | ||
import { connect } from 'react-redux'; | ||
|
||
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css'; | ||
import { deleteAsset } from '../../data/actions/assets'; | ||
|
||
import { deleteAsset, clearAssetsStatus } from '../../data/actions/assets'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: do we care for these to be in alphabetical order? It's definitely a preference in the platform. Personally, I don't have strong opinions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was not aware of the preference, but I'm all for keeping some sort of convention across repos, I'll swap that to be alphabetical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok! Either way works for me, but I just wanted to check on that just for my own sake mostly. :)
@@ -41,3 +48,97 @@ describe('<AssetsTable />', () => { | |||
expect(wrapper.find('tr')).toHaveLength(defaultProps.assetsList.length + 1); | |||
}); | |||
}); | |||
|
|||
describe('it displays alert properly', () => { | |||
it('renders info alert with blank status', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that this is testing the blank status. Should it be checking that the text is empty? Maybe the test is misnamed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! It does not check the status message, it is misnamed.
|
||
const setAssetToDelete = (asset, wrapper) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this pulled out to a function for clarity? Just wondering!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured we may want to test this in other tests moving forward so I moved it out to reduce the duplication that I was already doing - it could easily go back into each test, I wasn't sure which was preferable for testing. I could see it making sense to have it in each since they're supposed to be individual tests, but this seemed cleaner for the duplication.
}; | ||
|
||
let wrapper; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I always get sort of nervous about globals. It seems like each use of wrapper
could be declared with let
to scope it within the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In all fairness this was predefined when I got into this file - I just had to move it's location below the setAssetToDelete
from above. I don't mind changing it from the global to individual declarations, I just wasn't the one to define it this way initially so I'm not sure if it was done for a reason?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see! I guess I'd personally not do it globally and would probably make that change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super opinionated about this personally but Dennis makes a good point.
@@ -48,19 +53,40 @@ export class AssetsTable extends React.Component { | |||
}); | |||
} | |||
|
|||
addDeleteButton() { | |||
const newAssetsList = this.props.assetsList.map((asset) => { | |||
getNextFocusElementOnDelete(assetToDelete) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe getNextFocusElementOnDeleteClose
? That's so long, but I think clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! And agreed, it is clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, after reading more of your comments -- it's determining where to focus after closing the StatusAlert that appears on delete. I'm not sure if the OnDeleteClose
is clear about that, but if we're going to be using the status alert for multiple alert types (delete/copy URL/...) I'm not sure if OnAlertClose
will be clear either because that will have different rules, though I guess we could maybe extend that function to encompass all the rules related to the alert and focus?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see! Maybe the current name makes the most sense actually. I'm reading it again and finding it to be clear!
src/AssetsPage/AssetsTable/index.jsx
Outdated
let focusAsset = assetToDelete; | ||
let focusRowButton = this.trashcanRefs[assetToDelete.id]; | ||
|
||
if (this.props.assetsStatus.text === AssetsConstants.SUCCESS_FILE_DELETE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this function (in theory) ever called if this isn't the case? Also, the comparison of text is a bit worrisome especially in the case that the text is translated. Is there some sort of unique state/status/prop/code/you-get-the-gist that we could use instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it will still be called because regardless of the response status we need to know where to send focus when the status alert is closed out of. The text comparison is using the response body text, not text that will ever been shown to the user / internationalized. (The ones that now live in the constants/assets.js file). But I understand the concern, I'll test out if I can use response code or something else here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see! I think that assetsStatus
has a text
field is what confused me. That's your status code, right? I don't want to bloat your PR, but I think that if text
were renamed to statusCode
or type
(or something else), it would be clearer. To me, text
indicates that it's displayed to the user.
@@ -104,7 +138,7 @@ export class AssetsTable extends React.Component { | |||
); | |||
} | |||
|
|||
renderBody() { | |||
renderModalBody() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think this is clearer. 🍨
src/AssetsPage/AssetsTable/index.jsx
Outdated
let alertType = 'info'; | ||
|
||
if (assetsStatus.text === AssetsConstants.ERROR_FILE_DELETE) { | ||
alertDialog = `Unable to delete ${deleteAssetName}.`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not something to worry about now, but we'll need for this to be internationalized too (e.g. string filled in via template) or some way to do this. CC: @efischer19
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Agreed. We'll also need it for the Modal body text & buttons.
Codecov Report
@@ Coverage Diff @@
## master #40 +/- ##
==========================================
+ Coverage 64.81% 69.23% +4.41%
==========================================
Files 4 5 +1
Lines 54 91 +37
Branches 3 6 +3
==========================================
+ Hits 35 63 +28
- Misses 18 27 +9
Partials 1 1
Continue to review full report at Codecov.
|
src/data/reducers/assets.js
Outdated
case assetActions.DELETE_ASSET_SUCCESS: | ||
return { | ||
response: action.response, | ||
text: action.text, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recap of our in-person conversation:
- we'll use the existing
type
instead of text to compare against - the
type
constants will be moved into a separate file
Thanks for the discussion!
7aaf728
to
e5a813c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thank you for talking through the PR with me; I'm learning! 🎖
@@ -0,0 +1,9 @@ | |||
/* ASSETS ACTION TYPES */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, sorry this took so long -- 🚢, and could you give it a minor version bump also?
}; | ||
|
||
let wrapper; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super opinionated about this personally but Dennis makes a good point.
src/data/actions/assets.js
Outdated
@@ -42,9 +46,12 @@ export const deleteAsset = (assetsParameters, assetId) => | |||
clientApi.requestDeleteAsset(assetsParameters.courseId, assetId) | |||
.then((response) => { | |||
if (response.ok) { | |||
dispatch(deleteAssetSuccess(assetId)); | |||
dispatch(deleteAssetSuccess(assetId, response, AssetsConstants.SUCCESS_FILE_DELETE)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so there's a lot going on here.
- I'm fine passing the whole response payload back to the reducer. Fat vs skinny action creators/reducers is a matter of debate. Either way, we should make sure the reducer only stores the fields we care about, not the entire payload -- this will eat up browser memory.
- I'm team use-the-same-action-type-in-multiple-reducers. IMO this is one of the most powerful features of Redux, and Abramov himself pushes it.
- Seems like we're getting to the point of feeling XHR pain and maybe needing to start thinking about a layer of abstraction. I don't think you need to do it in this PR (we're not there QUITE yet) but we need to start thinking about it. i started typing up a proposed implementation but realized it would be better as a PR so adding that to my backlog.
return this.trashcanRefs[focusAsset.id]; | ||
} | ||
|
||
addSupplementalTableElements() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better name. This is going to be a big source of conflicts with @MichaelRoytman's PR though so once it merges the three of us should circle back at some point and talk about his changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New tests LGTM
43e02ff
to
7a2a98f
Compare
🙌 🍰 💯 |
Integrate the proper Paragon StatusAlert to appear on success or failure of an asset being deleted.