Update the API Tokens UI for setting token expiration dates.
Review Request #12597 — Created Sept. 15, 2022 and submitted
This change allows people to set token expiration dates from the API tokens
config page through a date picker. Consequently, expiration dates can also be
updated through the API. We create aRB.DateInlineEditorView
to handle the
UI for the date editing.This also updates the page to show the invalidated/expired text in
in red so that they stand out against valid tokens.While running the JS unit tests it was discovered that the
RB.InlineEditorView
would not disconnect from a window resize event that
it was handling, which caused problems with other JS tests when running the
full suite. This change adds aRB.InlineEditorView.remove()
method to
handle this and disconnect from any events.
- Created new unit tests for setting the expiration date via the API
and ran all tests inreviewboard.webapi.tests.test_api_token.py
. - Manually tested the API tokens page, setting various expiration dates and
making sure they were displayed and saved to the database properly. - Created
DateInlineEditorView
JS tests and ran all JS tests. - Manually tested resizing my browser that had the new review request page
open on it and saw that any inline editors that I was using on the page
would resize accordingly.
Summary | ID |
---|---|
c0df10e2110a52e84e275b56efc4f739ee29647d |
Description | From | Last Updated |
---|---|---|
Missing semicolon. Column: 82 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 49 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 23 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 44 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 56 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 80 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 66 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 41 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 44 Error code: W033 |
reviewbot | |
Question: Where should I document the options that I added (descriptorText, minDate, maxDate)? I'll remove the comments that are next … |
maubin | |
Missing semicolon. Column: 44 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 53 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 60 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 34 Error code: W033 |
reviewbot | |
I'll cover some of the stuff in the HTML and the CSS in this comment. Our modern CSS component conventions … |
chipx86 | |
Since it's just 0, no need for em. |
chipx86 | |
This line is too long. |
chipx86 | |
For JavaScript, multi-line comments should look like: /* * ... * ... */ |
chipx86 | |
Date objects in JavaScript are the absolute worst, and doing operations on them scares me. So many things can, and … |
chipx86 | |
No need fro the parens here. We should include a default value inside of <time> though. |
chipx86 | |
Two notes: We should keep this alphabetical. The last item should have a trailing comma (safe for ES6 files). |
chipx86 | |
As a ES6 file, you can do: $(window).on(`resize.${this.cid}`, ...) Also, this can be one line. |
chipx86 | |
As this will be (eventually) ReStructuredText, code literals need double backticks. |
chipx86 | |
5.0. |
chipx86 | |
This won't ultimately parse right. Instead, let's use our standard doc syntax we'd use for any attribute. /** * Summary … |
chipx86 | |
I suspect this can fit in one line. |
chipx86 | |
Let's be explicit about local time vs. UTC in these. |
chipx86 | |
<input> tags don't have an end tag, so this should be <input type="date"/>. How important is it that we have … |
chipx86 | |
.attr takes a dictionary of keys/values, which is good for multiple attributes. |
chipx86 | |
When there's no need to continue chaining, you can keep this as one line. |
chipx86 | |
If this never changes, it should be const. |
chipx86 | |
These cam use <URL> if decorating with @webapi_test_template. |
chipx86 | |
Let's add/update tests to do some explicit checking around timezones and conversion. |
chipx86 | |
Missing semicolon. Column: 74 Error code: W033 |
reviewbot | |
Missing semicolon. Column: 15 Error code: W033 |
reviewbot | |
I think it makes more sense and follows our CSS docs standards more if I document these modifiers above the … |
maubin | |
Should be </p> |
chipx86 | |
I don't see usage in here. Even if we don't style it, we should have a documented block and an … |
chipx86 | |
The * should all be aligned. |
chipx86 | |
We should pick one, and use styling to ensure consistent presentation (being explicit about any margins/padding that would otherwise be … |
chipx86 | |
</...> for end tags, <.../> for self-closing tags (like <input> or <img>). |
chipx86 | |
These can be one rule: &.-is-expired, &.-is-invalid { color: red; } |
chipx86 | |
The caller may pass null (as per the event handler further down). We should document what happens if passed. |
chipx86 | |
saveExpires is indented 1 space too far. |
chipx86 | |
expiresTimeHTML needs to be passed in when instantiating the template. I don't see that happening in this change. That would … |
chipx86 | |
datetime would be more technically correct. |
chipx86 | |
Maybe it'd be better to just store _$tokenState, and then we can determine things as-needed based on the modifiers? |
chipx86 | |
No need for a blank line here. These are related statements. |
chipx86 | |
I'm not sure if async is needed here, since I don't think we're dealing with any functions that also need … |
chipx86 | |
Let's put parens around this so order of operations is very clear. |
chipx86 | |
check_post_result does a good job of making sure the response and the resulting object match expectations, but we still need … |
chipx86 | |
Let's hard-code the dates we want to set and compare, to ensure there's no variability based on time wonkiness. "now" … |
chipx86 | |
Same comments about "now" timestamps. Let's hard-code known ISO 8601 timestamps and check results explicitly. |
chipx86 | |
One last thing :) We want to keep the model as the owner of this value. Models keep state/state-related logic, … |
chipx86 | |
dedent is no longer needed. You can just use backticks. Right before this statement is where we should pull out … |
chipx86 | |
Rather than depend back on expires, we want to check the exact generated timestamp string we want to see. The … |
chipx86 | |
We want to avoid a multi-line string here (this will capture all whitespace inside the of the backticks), so let's … |
chipx86 | |
We just need to make sure the user's profile's timezone is actually taken into account. So what I suggest is … |
chipx86 |
- Description:
-
Add UI for setting token expiration dates.
~ This also updates the API token UI to show in red text when tokens
~ became invalidated/expired so that they stand out against valid tokens. ~ This also updates the API token UI to show the text when tokens
~ became invalidated/expired in red so that they stand out against valid tokens. + + Note: I'm going to fill out this description more and add screenshots
+ showing off the UI tomorrow. - Testing Done:
-
+ - Created new unit tests for setting the expiration date via the API
and ran all tests inreviewboard.webapi.tests.test_api_token.py
.
+ - Manually tested the API tokens page, setting various expiration dates and
making sure they were displayed and saved to the database properly.
+ - QUESTION: I'm unsure of what to do for frontend tests for
DateInlineEditorView
and theapiTokensView
page. TheinlineEditorView
doesn't have any tests but the other UI views in that directory have some
so I might base some tests off that.
+ + Note: Will run all js tests and unit tests tomorrow.
- Created new unit tests for setting the expiration date via the API
- Commits:
-
Summary ID 113cc0c3d70cbff350a93a6bac8340258d1f1efa 0b01b729032cfd24ff9c40388dbce3eeb9a604a3 - Depends On:
-
- Diff:
Revision 2 (+448 -32)
- Change Summary:
-
Fixed some spacing issues around the expiration dates UI and added photos to the review request.
- Summary:
-
[WIP] Add UI for setting API token expiration dates.[WIP] Update the API Tokens UI for setting token expiration dates.
- Description:
-
~ Add UI for setting token expiration dates.
~ This change allows people to set token expiration dates from the API tokens
+ config page through a date picker. Consequently, expiration dates can also be + updated through the API. We create a RB.DateInlineEditorView
to handle the+ UI for the date editing. ~ This also updates the API token UI to show the text when tokens
~ became invalidated/expired in red so that they stand out against valid tokens. ~ This also updates the page to show the invalidated/expired text in
~ in red so that they stand out against valid tokens. - - Note: I'm going to fill out this description more and add screenshots
- showing off the UI tomorrow. - Commits:
-
Summary ID 0b01b729032cfd24ff9c40388dbce3eeb9a604a3 c5889832619c08676070f9d594fca6d85fbf687e - Diff:
-
Revision 3 (+464 -32)
- Added Files:
Checks run (2 succeeded)
- Change Summary:
-
- Added JS tests for
DateInlineEditorView
- Instead of repeating the
InlineEditorView
's event handler code we call it's event handler
method and then add our own event handling.
- Added JS tests for
- Summary:
-
[WIP] Update the API Tokens UI for setting token expiration dates.Update the API Tokens UI for setting token expiration dates.
- Testing Done:
-
- Created new unit tests for setting the expiration date via the API
and ran all tests inreviewboard.webapi.tests.test_api_token.py
.
- Manually tested the API tokens page, setting various expiration dates and
making sure they were displayed and saved to the database properly.
~ - QUESTION: I'm unsure of what to do for frontend tests for
DateInlineEditorView
and theapiTokensView
page. TheinlineEditorView
doesn't have any tests but the other UI views in that directory have some
so I might base some tests off that.
~ - Created
DateInlineEditorView
JS tests and ran all JS tests.
- - Note: Will run all js tests and unit tests tomorrow.
- Created new unit tests for setting the expiration date via the API
- Commits:
-
Summary ID c5889832619c08676070f9d594fca6d85fbf687e 80b22d1eb1365113d442b48c7765abe7429bd823 - Diff:
-
Revision 4 (+742 -32)
- Change Summary:
-
Added some missing semicolons and got rid of console logging from testing.
- Commits:
-
Summary ID 80b22d1eb1365113d442b48c7765abe7429bd823 eaba2cb6052f8933f2a66f831dcad79608f8cc73 - Diff:
-
Revision 5 (+740 -32)
Checks run (2 succeeded)
- Change Summary:
-
Removes the test container and view after each test to avoid any effects on other tests.
- Commits:
-
Summary ID eaba2cb6052f8933f2a66f831dcad79608f8cc73 00000156ae30faaf67c9297a1e54daec8b1f145b - Diff:
-
Revision 6 (+750 -32)
Checks run (2 succeeded)
- Change Summary:
-
Added the
RB.InlineEditorView.remove()
method. - Description:
-
This change allows people to set token expiration dates from the API tokens
config page through a date picker. Consequently, expiration dates can also be updated through the API. We create a RB.DateInlineEditorView
to handle theUI for the date editing. This also updates the page to show the invalidated/expired text in
in red so that they stand out against valid tokens. + + While running the JS unit tests it was discovered that the
+ RB.InlineEditorView
would not disconnect from a window resize event that+ it was handling, which caused problems with other JS tests when running the + full suite. This change adds a RB.InlineEditorView.remove()
method to+ handle this and disconnect from any events. - Testing Done:
-
- Created new unit tests for setting the expiration date via the API
and ran all tests inreviewboard.webapi.tests.test_api_token.py
.
- Manually tested the API tokens page, setting various expiration dates and
making sure they were displayed and saved to the database properly.
- Created
DateInlineEditorView
JS tests and ran all JS tests.
+ - Manually tested resizing my browser that had the new review request page
open on it and saw that any inline editors that I was using on the page
would resize accordingly.
- Created new unit tests for setting the expiration date via the API
- Commits:
-
Summary ID 00000156ae30faaf67c9297a1e54daec8b1f145b 8dda79c7e7e36d546a0550451f95d031fa35f640 - Diff:
-
Revision 7 (+784 -34)
Checks run (2 succeeded)
- Change Summary:
-
- Hides the inline editor after certain tests.
- Commits:
-
Summary ID 8dda79c7e7e36d546a0550451f95d031fa35f640 ffc92bb5ca55b3adc37ee23d2816cf85a51d0e7b - Diff:
-
Revision 8 (+782 -34)
Checks run (2 succeeded)
-
-
I'll cover some of the stuff in the HTML and the CSS in this comment.
Our modern CSS component conventions basically define two ways of naming a CSS class for something within a component:
component__child
, of which there should really only ever be one level of nesting-modifier
, which is tacked onto a component/child CSS name to modify something about it.
A big part of the approach with this is to think hard about the structure of the component, giving everything a purpose. So rather than "these things go in
<p>
tags", we want to think "these things go in these named child objects."So let's go through and see how we can take advantage of that here.
The HTML in this change is introducing:
rb-c-config-api-tokens__info-is-invalid
rb-c-config-api-tokens__info__expires
This is close, but not quite to spec.
Looking at this, it seems this specific section has essentially four states:
- Token is expired ("Expired ...")
- Token is valid and will expire ("Expires ...")
- Token is valid and will not expire ("Never expires")
- Token is invalid ("Invalidated ...")
We could go a couple ways here. We could combine these under one concept ("lifecycle" or "state" or something?) or break them into multiple types of children.
Let's go with the former. We'll call this
rb-c-config-api-tokens__token-state
, and control the state through modifiers:-is-expired
-is-invalid
We'd only ever use one at once. Think of it like enum values. If neither are set, it's in a default situation.
(We should also have
rb-c-config-api-tokens__usage
, to cover the "Last used"/"Never used". Even if we don't style it differently. We're defining the component structure.)Now I think ideally, we'd actually give each token item its own full CSS component, moving things like the state modifiers (
-is-expired
, ...) to the main component class. That's a bigger change, though, and I'd rather think about that separately. -
-
-
-
Date
objects in JavaScript are the absolute worst, and doing operations on them scares me. So many things can, and will, go wrong.I'd recommend we instead use the
moment.js
library. This library lets us do operations on dates without worrying about the problems that tend to come up, and keeping code very self-explanatory. We already bundle this with Review Board.We also have to be very careful with timezones. Some notes on that:
new Date()
always gives us local time in the browser.- Timestamps we get from the server contain timezone information.
- Timestamps we're sending currently do not, but they need to.
- If they don't, the server will interpret them based off the timezone in the Profile, which may not match
- We want to be sure we always show local time to the user.
Okay, so let's update the code to be more readable, safer, and timezone-aware:
This first block can work like:
// Here, we're operating in local time, and can perform some safe date math. const tomorrow = moment.local().add(1, 'days'); /* * Since we're dealing with a server-side value (as per #2 above), we know * we have the timezone, which moment() will handle. We also want to convert * to local time. */ const expires = moment(this.model.get('expires')).local(); ... // Now we can format that how we wish. { ... minDate: tomorrow.format('YYYY-MM-DD'), rawValue: expires.format('YYYY-MM-DD'), } // To build a timezone-aware midnight ISO 8601 timestamp off a local timestamp to send to the server: moment.local(value).startOf('day').format() // *If* we had wanted to explicitly convert to UTC, but with midnight local time: moment.local(value).startOf('day').utc().format() etc.
It's quite a nice library, and if we're careful and explicit, we avoid a lot of timezone conversion issues.
-
-
Two notes:
- We should keep this alphabetical.
- The last item should have a trailing comma (safe for ES6 files).
-
-
-
-
This won't ultimately parse right. Instead, let's use our standard doc syntax we'd use for any attribute.
/** * Summary here. * * Optional description. * * Type: * ... */
-
-
-
<input>
tags don't have an end tag, so this should be<input type="date"/>
.How important is it that we have the
<span>
? -
-
-
-
-
- Commits:
-
Summary ID ffc92bb5ca55b3adc37ee23d2816cf85a51d0e7b fc727909011d591baf6be378682d119d2b381839 - Diff:
-
Revision 9 (+924 -74)
- Change Summary:
-
Adds tests for explicit checking around timezones and conversion
- Commits:
-
Summary ID fc727909011d591baf6be378682d119d2b381839 b3f3a7d518ddd58f15c615a5f3e5ccc16264c23a - Diff:
-
Revision 10 (+1108 -80)
Checks run (2 succeeded)
- Change Summary:
-
Fixed up the CSS documentation
- Commits:
-
Summary ID b3f3a7d518ddd58f15c615a5f3e5ccc16264c23a 355ecc956a136c5ceceb644d4282656a6fcffad3 - Diff:
-
Revision 11 (+1140 -102)
Checks run (2 succeeded)
-
-
-
I don't see
usage
in here.Even if we don't style it, we should have a documented block and an empty rule for it, so the structure is clearly-defined.
-
-
We should pick one, and use styling to ensure consistent presentation (being explicit about any margins/padding that would otherwise be up to the browser for something like a
<p>
).Especially since
<span>
is inline and<p>
is block-level. That can make it hard to maintain. Can we stick with<p>
for all? -
-
-
The caller may pass
null
(as per the event handler further down). We should document what happens if passed. -
-
Maybe it'd be better to just store
_$tokenState
, and then we can determine things as-needed based on the modifiers? -
-
I'm not sure if
async
is needed here, since I don't think we're dealing with any functions that also need to operate async. I might be wrong. If so, can we document this? -
-
Let's hard-code the dates we want to set and compare, to ensure there's no variability based on time wonkiness. "now" dates in unit tests can become difficult to debug when things go wrong.
-
Same comments about "now" timestamps. Let's hard-code known ISO 8601 timestamps and check results explicitly.
- Commits:
-
Summary ID 355ecc956a136c5ceceb644d4282656a6fcffad3 0ddd826bfa4192038f90da01ba0b33eb4a03297e - Diff:
-
Revision 12 (+1164 -106)
Checks run (2 succeeded)
-
-
expiresTimeHTML
needs to be passed in when instantiating the template. I don't see that happening in this change. That would need to be done by returning it ingetRenderContext()
.By default, a template for a
ListItemView
gets possible<%= ... %>
variables from the model attributes or fromgetRenderContext()
results.It would only make sense to do this if you are trying to also set
datetime=
here, and if we're taking this same approach for all other<time>
elements in this template.Otherwise, it doesn't really make sense to feed in
<%= expiresTimeHTML %>
at all, just for an empty-by-default<time>
. If the goal is to just save a few characters but to populate this tag dynamically after, just hard-code<time></time>
here and let that code take care of it. -
-
check_post_result
does a good job of making sure the response and the resulting object match expectations, but we still need to explicitly compare the resulting timestamp in these tests and make sure they contain the value we expect them to.
- Commits:
-
Summary ID 0ddd826bfa4192038f90da01ba0b33eb4a03297e d30f9eb4a0b2a260b1dc94d540a78ceb8e88b399 - Diff:
-
Revision 13 (+1234 -106)
Checks run (2 succeeded)
-
-
One last thing :)
We want to keep the model as the owner of this value. Models keep state/state-related logic, and views represent values. By duplicated storage of state, we risk things getting out of sync.
The ideal setup is to have the UI always update based on the model. To do this, a lot of views will listen to a
change:<attr>
for a givenattr
name that the UI depends on. Some UIs will then selectively update appropriate parts of the DOM, and some will just callrender()
again.This view doesn't do that, which is fine (that very well may change down the road), but the ability to do that depends on avoiding duplication of state from the model. If we copy it here, and use this copy, we get out of sync.
So let's remove this here, and instead only pull out at the times we need it.
-
dedent
is no longer needed. You can just use backticks.Right before this statement is where we should pull out
expires
. Just to keep as much logic out of the string as possible, referencing only variables. -
Rather than depend back on
expires
, we want to check the exact generated timestamp string we want to see.The goal is to be very explicit about what we expect to come back, so we can protect ourselves from any issues introduced by logic changes down the road to either the executed code or the test itself. It also gives us a reference if debugging things down the road, so we know precisely what format of string we think we're working with.
- Commits:
-
Summary ID d30f9eb4a0b2a260b1dc94d540a78ceb8e88b399 af3a42e20c80dc82581aedb2b67e45b81fd3ddd2 - Diff:
-
Revision 14 (+1246 -106)
Checks run (2 succeeded)
-
-
We want to avoid a multi-line string here (this will capture all whitespace inside the of the backticks), so let's put the backtick as close to the tags as possible. It's okay to start this on the second line. So:
return { expiresTimeHTML: `<time class="timesince" datetime="${expires}"></time>`, };
-
We just need to make sure the user's profile's timezone is actually taken into account. So what I suggest is something like:
profile = self.user.get_profile() profile.timezone = 'US/Eastern' profile.save(update_fields=('timezone',)) # .... # Make sure resulting timezone is offset from Eastern.
Same idea for the PUT.
- Commits:
-
Summary ID af3a42e20c80dc82581aedb2b67e45b81fd3ddd2 c0df10e2110a52e84e275b56efc4f739ee29647d - Diff:
-
Revision 15 (+1260 -106)