-
Notifications
You must be signed in to change notification settings - Fork 74
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
THREESCALE-10245: access tokens expiration UI #3943
Open
jlledom
wants to merge
20
commits into
master
Choose a base branch
from
THREESCALE-10245-access-tokens-UI
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
dc05a73
New React component: ExpirationDatePicker
jlledom 4f4690d
Add the new component to Access Token forms
jlledom 995530a
Access token UI: Update and add tests
jlledom 4036f22
Move access token strings to root level
jlledom d2f7411
Accept timestamps with milliseconds
jlledom 6d88683
Send timestamps with milliseconds
jlledom 14b083d
Rename `ExpirationItem.name` to `label`
jlledom fda4ff2
Use more meaningful ids
jlledom 667e146
Better spacing
jlledom 0921951
Fix tests after changes
jlledom 588b314
Show the hint as a helper text
jlledom 587a618
Hint visible when picking a date from calendar
jlledom 39b8c24
Update tests after changes
jlledom 88badaf
Add expiration time to index table
jlledom bfd0398
Update cukes after changes
jlledom 62d048d
Better hint date format
jlledom 7f45144
Add a time zone warning
jlledom 05bc601
Use OutlinedQuestionCircleIcon instead of HelpIcon
jlledom 6dbcdfe
Add tests for time zone mismatch warning
jlledom 63f4496
Fix linter error
jlledom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
89 changes: 52 additions & 37 deletions
89
app/controllers/provider/admin/user/access_tokens_controller.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,59 @@ | ||
class Provider::Admin::User::AccessTokensController < Provider::Admin::User::BaseController | ||
inherit_resources | ||
defaults route_prefix: 'provider_admin_user', resource_class: AccessToken | ||
actions :index, :new, :create, :edit, :update, :destroy | ||
|
||
authorize_resource | ||
activate_menu :account, :personal, :tokens | ||
before_action :authorize_access_tokens | ||
before_action :disable_client_cache | ||
|
||
def create | ||
create! do |success, _failure| | ||
success.html do | ||
flash[:token] = @access_token.id | ||
flash[:notice] = 'Access Token was successfully created.' | ||
redirect_to(collection_url) | ||
end | ||
end | ||
end | ||
# frozen_string_literal: true | ||
|
||
def index | ||
index! | ||
@last_access_key = flash[:token] | ||
end | ||
module Provider | ||
module Admin | ||
module User | ||
class AccessTokensController < BaseController | ||
inherit_resources | ||
defaults route_prefix: 'provider_admin_user', resource_class: AccessToken | ||
actions :index, :new, :create, :edit, :update, :destroy | ||
|
||
def update | ||
update! do |success, _failure| | ||
success.html do | ||
flash[:notice] = 'Access Token was successfully updated.' | ||
redirect_to(collection_url) | ||
end | ||
end | ||
end | ||
authorize_resource | ||
activate_menu :account, :personal, :tokens | ||
before_action :authorize_access_tokens | ||
before_action :disable_client_cache | ||
before_action :load_presenter, only: %i[new create] | ||
|
||
private | ||
def new; end | ||
|
||
def authorize_access_tokens | ||
authorize! :manage, :access_tokens, current_user | ||
end | ||
def create | ||
create! do |success, _failure| | ||
success.html do | ||
flash[:token] = @access_token.id | ||
flash[:notice] = 'Access Token was successfully created.' | ||
redirect_to(collection_url) | ||
end | ||
end | ||
end | ||
|
||
def begin_of_association_chain | ||
current_user | ||
def index | ||
index! | ||
@last_access_key = flash[:token] | ||
end | ||
|
||
def update | ||
update! do |success, _failure| | ||
success.html do | ||
flash[:notice] = 'Access Token was successfully updated.' | ||
redirect_to(collection_url) | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def load_presenter | ||
@presenter = AccessTokensNewPresenter.new(current_account) | ||
end | ||
|
||
def authorize_access_tokens | ||
authorize! :manage, :access_tokens, current_user | ||
end | ||
|
||
def begin_of_association_chain | ||
current_user | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { ExpirationDatePickerWrapper } from 'AccessTokens/components/ExpirationDatePicker' | ||
|
||
import type { Props } from 'AccessTokens/components/ExpirationDatePicker' | ||
|
||
import { safeFromJsonString } from '../src/utilities/json-utils' | ||
|
||
document.addEventListener('DOMContentLoaded', () => { | ||
const containerId = 'expiration-date-picker-container' | ||
const container = document.getElementById(containerId) | ||
|
||
if (!container) { | ||
throw new Error(`Missing container with id "${containerId}"`) | ||
} | ||
|
||
const props = safeFromJsonString<Props>(container.dataset.props) | ||
|
||
if (!props) { | ||
throw new Error('Missing props') | ||
} | ||
|
||
ExpirationDatePickerWrapper(props, containerId) | ||
}) |
14 changes: 14 additions & 0 deletions
14
app/javascript/src/AccessTokens/components/ExpirationDatePicker.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
@import '~@patternfly/patternfly/patternfly-addons.css'; | ||
|
||
.pf-c-calendar-month, .pf-c-form-control-expiration { | ||
width: 50%; | ||
} | ||
|
||
.pf-c-form-control-expiration { | ||
margin-right: 1em; | ||
} | ||
|
||
button.pf-c-form__group-label-help { | ||
min-width: auto; | ||
width: auto; | ||
} |
167 changes: 167 additions & 0 deletions
167
app/javascript/src/AccessTokens/components/ExpirationDatePicker.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { useState, useMemo } from 'react' | ||
import { Alert, CalendarMonth, FormGroup, FormSelect, FormSelectOption, Popover } from '@patternfly/react-core' | ||
import OutlinedQuestionCircleIcon from '@patternfly/react-icons/dist/js/icons/outlined-question-circle-icon' | ||
|
||
import { createReactWrapper } from 'utilities/createReactWrapper' | ||
|
||
import type { FunctionComponent, FormEvent } from 'react' | ||
|
||
import './ExpirationDatePicker.scss' | ||
|
||
interface ExpirationItem { | ||
id: string; | ||
label: string; | ||
period: number; // In seconds | ||
} | ||
|
||
const collection: ExpirationItem[] = [ | ||
{ id: '7', label: '7 days', period: 7 }, | ||
{ id: '30', label: '30 days', period: 30 }, | ||
{ id: '60', label: '60 days', period: 60 }, | ||
{ id: '90', label: '90 days', period: 90 }, | ||
{ id: 'custom', label: 'Custom...', period: 0 }, | ||
{ id: 'no-exp', label: 'No expiration', period: 0 } | ||
] | ||
|
||
const dayMs = 60 * 60 * 24 * 1000 | ||
|
||
interface Props { | ||
id: string; | ||
label: string | null; | ||
tzOffset?: number; | ||
} | ||
|
||
const ExpirationDatePicker: FunctionComponent<Props> = ({ id, label, tzOffset }) => { | ||
const [selectedItem, setSelectedItem] = useState(collection[0]) | ||
const [pickedDate, setPickedDate] = useState(new Date()) | ||
const fieldName = `human_${id}` | ||
const fieldLabel = label ?? 'Expires in' | ||
|
||
const fieldDate = useMemo(() => { | ||
if (selectedItem.period === 0) return null | ||
|
||
return new Date(new Date().getTime() + selectedItem.period * dayMs) | ||
}, [selectedItem]) | ||
|
||
const dateValue = useMemo(() => { | ||
let value = '' | ||
|
||
if (fieldDate) { | ||
value = fieldDate.toISOString() | ||
} else if (selectedItem.id === 'custom' ) { | ||
value = pickedDate.toISOString() | ||
} | ||
|
||
return value | ||
}, [fieldDate, selectedItem, pickedDate]) | ||
|
||
const formattedDateValue = useMemo(() => { | ||
if (!dateValue) return | ||
|
||
const formatter = Intl.DateTimeFormat('en-US', { | ||
month: 'long', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: false | ||
}) | ||
const date = new Date(dateValue) | ||
|
||
return formatter.format(date) | ||
}, [dateValue]) | ||
|
||
const fieldHint = useMemo(() => { | ||
if (!formattedDateValue) return | ||
|
||
return `The token will expire on ${formattedDateValue}` | ||
}, [formattedDateValue]) | ||
|
||
const tzMismatch = useMemo(() => { | ||
if (tzOffset === undefined) return | ||
|
||
// Timezone offset in the same format as ActiveSupport | ||
const jsTzOffset = new Date().getTimezoneOffset() * -60 | ||
|
||
return jsTzOffset !== tzOffset | ||
}, [tzOffset]) | ||
|
||
const labelIcon = useMemo(() => { | ||
if (!tzMismatch) return | ||
|
||
return ( | ||
<Popover | ||
bodyContent={( | ||
<p> | ||
Your local time zone differs from the provider default. | ||
The token will expire at the time you selected in your local time zone. | ||
</p> | ||
)} | ||
headerContent={( | ||
<span>Time zone mismatch</span> | ||
)} | ||
> | ||
<button | ||
aria-describedby="form-group-label-info" | ||
aria-label="Time zone mismatch warning" | ||
className="pf-c-form__group-label-help" | ||
type="button" | ||
> | ||
<OutlinedQuestionCircleIcon noVerticalAlign /> | ||
</button> | ||
</Popover> | ||
) | ||
}, [tzMismatch]) | ||
|
||
const handleOnChange = (_value: string, event: FormEvent<HTMLSelectElement>) => { | ||
const value = (event.target as HTMLSelectElement).value | ||
const selected = collection.find(i => i.id === value) ?? null | ||
|
||
if (selected === null) return | ||
|
||
setSelectedItem(selected) | ||
setPickedDate(new Date()) | ||
} | ||
|
||
return ( | ||
<> | ||
<FormGroup | ||
isRequired | ||
fieldId={fieldName} | ||
helperText={fieldHint} | ||
label={fieldLabel} | ||
labelIcon={labelIcon} | ||
> | ||
<FormSelect | ||
className="pf-c-form-control-expiration" | ||
id={fieldName} | ||
value={selectedItem.id} | ||
onChange={handleOnChange} | ||
> | ||
{collection.map((item: ExpirationItem) => { | ||
return ( | ||
<FormSelectOption | ||
key={item.id} | ||
label={item.label} | ||
value={item.id} | ||
/> | ||
) | ||
})} | ||
</FormSelect> | ||
</FormGroup> | ||
<input id={id} name={id} type="hidden" value={dateValue} /> | ||
{selectedItem.id === 'custom' && ( | ||
<CalendarMonth className="pf-u-mt-md" date={pickedDate} onChange={setPickedDate} /> | ||
)} | ||
{dateValue === '' && ( | ||
<> | ||
<br /> | ||
<Alert title="Expiration is recommended" variant="warning"> | ||
It is strongly recommended that you set an expiration date for your token to help keep your information | ||
secure | ||
</Alert> | ||
</> | ||
)} | ||
</> | ||
) | ||
} | ||
|
||
const ExpirationDatePickerWrapper = (props: Props, containerId: string): void => { createReactWrapper(<ExpirationDatePicker id={props.id} label={props.label} tzOffset={props.tzOffset} />, containerId) } | ||
|
||
export type { ExpirationItem, Props } | ||
export { ExpirationDatePicker, ExpirationDatePickerWrapper } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
app/presenters/provider/admin/user/access_tokens_new_presenter.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# frozen_string_literal: true | ||
|
||
class Provider::Admin::User::AccessTokensNewPresenter | ||
|
||
def initialize(provider) | ||
@provider = provider | ||
@timezone = ActiveSupport::TimeZone.new(provider.timezone) | ||
end | ||
|
||
def provider_timezone_offset | ||
@timezone.utc_offset | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this break here, but I couldn't find an easy way to add some top margin, so that's OK...
I was looking at https://pf4.patternfly.org/utilities/spacing/, and was hoping that adding
className="pf-u-mt-md"
would help, but it didn't 😬 I think that probably the CSS containing this class is not being loaded... not sure, but I didn't investigate further.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.
667e146
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, now I think the parenthesis
()
are not needed, but well, not important.