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
Fixes #21312 - components for formatting dates #4909
Conversation
Issues: #21312 |
@@ -52,7 +52,7 @@ describe('Component registry', () => { | |||
const name = 'MarkupComponent'; | |||
|
|||
componentRegistry.register({ name, type: FakeComponent, store: false }); | |||
const markup = componentRegistry.markup(name, { fakeData: true }, {}); | |||
const markup = componentRegistry.markup(name, { data: { fakeData: true }, store: {}, wrapWithIntl: false }); |
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.
Line 55 exceeds the maximum line length of 100 max-len
{ name: 'StorageContainer', type: StorageContainer } | ||
{ name: 'StorageContainer', type: StorageContainer }, | ||
{ name: 'RelativeDate', type: RelativeDate, data: false, store: false }, | ||
{ name: 'AbsoluteDate', type: AbsoluteDate, data: false, store: false }, |
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.
Unexpected trailing comma comma-dangle
getType(wrapWithIntl = true) { | ||
if (wrapWithIntl) { | ||
return wrapWithIntlProvider(this.type); | ||
} else { |
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.
Unnecessary 'else' after 'return' no-else-return
import StorageContainer from './hosts/storage/vmware/'; | ||
import forEach from 'lodash/forEach'; | ||
import map from 'lodash/map'; | ||
import { wrapWithIntlProvider } from '../common/i18n' |
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.
Missing semicolon semi
|
||
RelativeDate.propTypes = { | ||
date: PropTypes.any.isRequired, | ||
default: PropTypes.string, |
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.
Unexpected trailing comma comma-dangle
return ( | ||
<span title={title}> | ||
<FormattedDate value={date} | ||
day='2-digit' |
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.
Unexpected usage of singlequote jsx-quotes
import React from 'react'; | ||
import {injectIntl, IntlProvider, addLocaleData} from 'react-intl'; | ||
|
||
export var locale = document.getElementsByTagName('html')[0].lang || 'en'; |
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.
Unexpected var, use let or const instead no-var
@@ -7,7 +7,7 @@ export function mount(component, selector, data) { | |||
|
|||
if (reactNode) { | |||
ReactDOM.unmountComponentAtNode(reactNode); | |||
ReactDOM.render(componentRegistry.markup(component, data, store), reactNode); | |||
ReactDOM.render(componentRegistry.markup(component, { data: data, store: store, wrapWithIntl: false }), reactNode); |
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.
Line 10 exceeds the maximum line length of 100 max-len
webpack/assets/javascripts/bundle.js
Outdated
@@ -10,6 +10,9 @@ require('./bundle_multiselect'); | |||
require('./bundle_select2'); | |||
require('./bundle_datatables'); | |||
import compute from './foreman_compute_resource'; | |||
import componentRegistry from './react_app/components/componentRegistry'; | |||
import { locale } from './react_app/common/i18n' | |||
|
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.
More than 1 blank line not allowed no-multiple-empty-lines
webpack/assets/javascripts/bundle.js
Outdated
@@ -10,6 +10,9 @@ require('./bundle_multiselect'); | |||
require('./bundle_select2'); | |||
require('./bundle_datatables'); | |||
import compute from './foreman_compute_resource'; | |||
import componentRegistry from './react_app/components/componentRegistry'; | |||
import { locale } from './react_app/common/i18n' |
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.
Missing semicolon semi
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 wondering about the date formatting. For instance I see the format Oct 16, 13:27
in several places. I think that we should include the year because in some places (sync plans, for instance) the date may be in the past and it may not be clear which year it is referring to.
See also my other comments about displaying locale specific dates as well.
month='short' | ||
hour='2-digit' | ||
minute='2-digit' | ||
hour12={false} /> |
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 know that most of the world uses 24hr time but to set this to false is saying that we are okay with actively showing US users a time that they may not readily understand.
Shouldn't this be updated by whatever intl library we are using based on the user's locale?
month: 'short', | ||
hour: '2-digit', | ||
minute: '2-digit', | ||
hour12: false |
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.
This comment also applies to the AbsoluteDate
class above but I would think that all of these should be set based on the user's locale and not hardcoded.
Aside from the couple of configuration things mentioned I think this is great. I really like the approach of utilizing the new register components to make the helper components available. |
Thanks @waldenraines your notes make sense. I'll remove the hardcoded 24 hour format. My initial idea was to get as close as possible to the date format that we currently sue in the foreman. But your point about the year is valid, we need more components. I looked at the date and time formats that we use in both projects. Katello uses:
Foreman uses:
My assumptions are:
Based on that I propose to add following components/formats:
What do you think? The rails helpers in foreman can probably just mount the new components to avoid nuances between the dates rendered form js and at the server side. @Rohoover I'd also like to know your opinion on that. |
month="short" | ||
hour="2-digit" | ||
minute="2-digit" /> | ||
</span> |
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.
Parsing error: Adjacent JSX elements must be wrapped in an enclosing tag
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.
Fixed, I pushed wrong commit by mistake.
@tstrachota that seems like a good analysis and makes sense to me 👍 |
Looks good. Just as a note, a lot of users I've talked to would like to be able to set a preference. Not something for this PR, but worth mentioning. |
@tstrachota all above makes sense to me, just one thing
I think there are some use cases, perhaps at job invocation, seconds matter |
@tstrachota updates on this? I think we have reached a decision on date formatting so we should be good to proceed. |
Updated:
|
this needs a rebase :) |
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.
thanks, a few minor comments inline from a quick review.
How do we extract the strings from react-int? in case there are new strings/languages that we support?
@@ -51,23 +51,17 @@ def multiple_with_filter? | |||
# it supports two formats :short and :long | |||
# example of long is February 12, 2021 17:13 | |||
# example of short is Aug 31, 12:52 | |||
def date_time_absolute(time, format = :short) |
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.
can you please extract all of the date/time methods to dedicated helper?
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 can do that but I'm not sure if some plugins aren't using it already. How about foreman-openscap. Is it used there @ares ?
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's already used, which should not prevent the extraction. I don't understand why it should be in separate helper though? This is application wide helper method. If it was extracted to separate module, I'd still like to include it everywhere.
app/helpers/application_helper.rb
Outdated
end | ||
|
||
def mount_date_component(component, time, seconds) | ||
date_id = generate_date_id |
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.
can't we use something like a timestamp instead of class vars?
import React from 'react'; | ||
import {injectIntl, IntlProvider, addLocaleData} from 'react-intl'; | ||
|
||
let langAttr = document.getElementsByTagName('html')[0].getAttribute('lang') || 'en'; |
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 be const?
import { FormattedDate } from 'react-intl'; | ||
import { timezone } from '../../../common/i18n'; | ||
|
||
// 9/3/2010 |
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.
left over?
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 really. I planned to keep the date format with each component just to give people better clue about what is the output. But I agree it deserves some more explanation.
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.
once you add jest snapshots, we could see the output ;)
let date, title; | ||
|
||
if (this.props.data.date) { | ||
date = this.props.data.date; |
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.
please use const date instead of the above let
also, rebase and tests for new components are missings :) |
if (this.props.data.date) { | ||
date = this.props.data.date; | ||
title = this.props.intl.formatRelative(date); | ||
seconds = this.props.data.seconds ? '2-digit' : undefined; |
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.
These variables should be defined inside the if
block as consts
year: 'numeric', | ||
timeZone: timezone | ||
}); | ||
|
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.
These variables should be defined inside the if
block as consts
date = this.props.data.date; | ||
title = this.props.intl.formatRelative(date); | ||
seconds = this.props.data.seconds ? '2-digit' : undefined; | ||
|
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.
These variables should be defined inside the if
block as consts
import { withIntlProvider } from '../common/i18n'; | ||
import { Provider } from 'react-redux'; | ||
|
||
function getDisplayName(Component) { |
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.
This function is also used in common/i18n.js
can we reuse it?
}; | ||
} | ||
|
||
class WrapperFactory { |
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 believe that the WrapperFactory together with the functions above should be separate into a different file
Updated according to comments and tests added. |
@tstrachota can you rebase please? also note the test failures. |
@ares any feedback on this PR? |
👍 from me, I'm happy the method usage didn't change much, @tstrachota would you mind adding the third param to how to write a plugin wiki after this is merged? |
Rebeased. I need to investigate what went wrong with the tests. @ares sure, I can update the docs. |
webpack/assets/javascripts/bundle.js
Outdated
@@ -25,5 +27,10 @@ window.tfm = Object.assign(window.tfm || {}, { | |||
numFields: require('./jquery.ui.custom_spinners'), | |||
reactMounter: require('./react_app/common/MountingService'), | |||
editor: require('./foreman_editor'), | |||
nav: require('./foreman_navigation') | |||
nav: require('./foreman_navigation'). | |||
componentRegistry: componentRegistry, |
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.
Parsing error: Unexpected token, expected ,
What about i18n?
On Nov 8, 2017 5:48 PM, "Hound" <notifications@github.com> wrote:
*@houndci-bot* commented on this pull request.
------------------------------
In webpack/assets/javascripts/bundle.js
<#4909 (comment)>:
@@ -25,5 +27,10 @@ window.tfm = Object.assign(window.tfm || {}, {
numFields: require('./jquery.ui.custom_spinners'),
reactMounter: require('./react_app/common/MountingService'),
editor: require('./foreman_editor'),
- nav: require('./foreman_navigation')
+ nav: require('./foreman_navigation').
+ componentRegistry: componentRegistry,
Parsing error: Unexpected token, expected ,
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#4909 (review)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AABOx4So1sA09Q-AMFoPm12pJtkyi0Aeks5s0c1bgaJpZM4P27Ua>
.
|
Ok, tests are green now. |
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.
This LGTM and worked where tested (also tested with katello) but will allow someone else more familiar with foreman the final approval.
This needs a rebase |
describe('WrapperFactory', () => { | ||
it('builds a wrapper', () => { | ||
wrapperRegistry.register('name_wrapper', (name) => { | ||
return (component) => { |
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.
Unexpected block statement surrounding arrow body arrow-body-style
|
||
describe('WrapperFactory', () => { | ||
it('builds a wrapper', () => { | ||
wrapperRegistry.register('name_wrapper', (name) => { |
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.
Unexpected block statement surrounding arrow body arrow-body-style
@@ -0,0 +1,36 @@ | |||
jest.unmock('./wrapperFactory'); | |||
import { WrapperFactory, wrapperRegistry } from './wrapperFactory'; |
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.
Import in body of module; reorder to top import/first
with(name, ...params) { | ||
const previousWrapper = this.wrapper; | ||
|
||
this.wrapper = function (component) { |
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.
Unexpected unnamed function func-names
}; | ||
|
||
export class WrapperFactory { | ||
wrapper(component) { |
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.
Expected 'this' to be used by class method 'wrapper' class-methods-use-this
@@ -0,0 +1,89 @@ | |||
import React from 'react'; | |||
import { intlProviderWrapper } from '../common/i18n'; | |||
import { Provider } from 'react-redux'; |
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.
Absolute imports should come before relative imports import/first
expect(componentRegistry.defaultWrapper).toBeCalledWith( | ||
componentRegistry.getComponent(name), | ||
'DATA', | ||
'STORE' |
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.
Missing trailing comma comma-dangle
|
||
componentRegistry.markup(name, { | ||
data: 'DATA', | ||
store: 'STORE' |
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.
Missing trailing comma comma-dangle
|
||
componentRegistry.register({ name, type: FakeComponent, store: true, data: true }); | ||
componentRegistry.defaultWrapper = jest.fn((component, data, store) => { | ||
return function (cmp) { return cmp; }; |
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.
Unexpected unnamed function func-names
const name = 'WrappedMarkupComponent'; | ||
|
||
componentRegistry.register({ name, type: FakeComponent, store: true, data: true }); | ||
componentRegistry.defaultWrapper = jest.fn((component, data, store) => { |
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.
Unexpected block statement surrounding arrow body arrow-body-style
Rebased |
I'm trying to test but I can't install dependencies, I always get this, anyone knows?
EDIT: nvm, looks unrelated, can reproduce now even after resetting back to develop, suggestions welcome though :-) |
There's always the big hammer ( |
@ares were you able to review this pr? |
Today, npm was in a good mood so I was able to test. All works well, including timezone and locale. If there are no more comments, I'm happy to merge. |
Turns out this adds 15MB of strings because of intl dependency, @tstrachota will take a look whether we can keep intl out of this, if not, we'll revert this commit before 1.17 branching. We should have decision by the end of the day. @xprazak2 FYI |
This patch reuses components from react-intl and extends the component registry to provide an intl wrapper (react-intl requires the component to be wrapped in IntlProvider). That could be used also if we decide that react-intl is the way to for translating our whole react stack.
TODO: