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

Fixes #21312 - components for formatting dates #4909

Merged
merged 1 commit into from Dec 12, 2017
Merged

Fixes #21312 - components for formatting dates #4909

merged 1 commit into from Dec 12, 2017

Conversation

tstrachota
Copy link
Member

@tstrachota tstrachota commented Oct 12, 2017

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:

  • tests for the components

@theforeman-bot
Copy link
Member

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 });

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 },

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 {

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'

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,

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'

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';

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);

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

@@ -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'

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

@@ -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'

Choose a reason for hiding this comment

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

Missing semicolon semi

Copy link

@waldenraines waldenraines left a 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} />

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

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.

@danseethaler
Copy link
Member

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.

@tstrachota
Copy link
Member Author

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:

filter example for en_US usage notes
shortDate 9/3/10 used for errata issued/updated dates and subscription start/end dates, used 12x
short 9/3/10 12:05 PM various other places, used 23x
medium Sep 3, 2010 12:05:08 PM various other places, used 10x
longDate September 3, 2010 used only at one place (content view last published)

Foreman uses:

format example for en_US usage notes
long February 12, 2021 17:13 didn't find a place where it would be used
short Aug 31, 12:52

My assumptions are:

  • we need a component for showing just a date (without time)
  • in most of the cases it's fine to show the date and time without year
  • we still need a component for showing date, time and year for actions that are less frequent
  • longDate is used only once and can be replaced by date
  • it's not crutial to display seconds in the dates

Based on that I propose to add following components/formats:

component example for en_US
LongDateTime October 13, 2017, 1:54 PM
ShortDateTime Oct 13, 1:54 PM
Date 9/3/2010

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>

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

Copy link
Member Author

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.

@waldenraines
Copy link

What do you think?

@tstrachota that seems like a good analysis and makes sense to me 👍

@Rohoover
Copy link

@tstrachota

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.

@ares
Copy link
Member

ares commented Oct 13, 2017

@tstrachota all above makes sense to me, just one thing

it's not crutial to display seconds in the dates

I think there are some use cases, perhaps at job invocation, seconds matter

@waldenraines
Copy link

@tstrachota updates on this? I think we have reached a decision on date formatting so we should be good to proceed.

@tstrachota
Copy link
Member Author

Updated:

  • added more components according to the discussion earlier in this PR
  • components reflect user's timezone
  • rails helpers now use the components
  • small refactoring of component registry to provide a single entry point for HOC wrappers

@mmoll
Copy link
Contributor

mmoll commented Oct 30, 2017

this needs a rebase :)

Copy link
Member

@ohadlevy ohadlevy left a 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)
Copy link
Member

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?

Copy link
Member Author

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 ?

Copy link
Member

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.

end

def mount_date_component(component, time, seconds)
date_id = generate_date_id
Copy link
Member

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';
Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

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

left over?

Copy link
Member Author

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.

Copy link
Member

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;
Copy link
Member

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

@ohadlevy
Copy link
Member

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;
Copy link
Contributor

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
});

Copy link
Contributor

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;

Copy link
Contributor

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) {
Copy link
Contributor

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 {
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 that the WrapperFactory together with the functions above should be separate into a different file

@tstrachota
Copy link
Member Author

Updated according to comments and tests added.

@ohadlevy
Copy link
Member

ohadlevy commented Nov 7, 2017

@tstrachota can you rebase please? also note the test failures.

@ohadlevy
Copy link
Member

ohadlevy commented Nov 7, 2017

@ares any feedback on this PR?

@ares
Copy link
Member

ares commented Nov 7, 2017

👍 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?

@tstrachota
Copy link
Member Author

Rebeased. I need to investigate what went wrong with the tests.

@ares sure, I can update the docs.

@@ -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,

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 ,

@ohadlevy
Copy link
Member

ohadlevy commented Nov 8, 2017 via email

@tstrachota
Copy link
Member Author

Ok, tests are green now.
The previous issue was a typo while resolving rebase conflicts, @ohadlevy . I'm sorry.

Copy link

@waldenraines waldenraines left a 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.

@ares
Copy link
Member

ares commented Nov 22, 2017

This needs a rebase

describe('WrapperFactory', () => {
it('builds a wrapper', () => {
wrapperRegistry.register('name_wrapper', (name) => {
return (component) => {

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) => {

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';

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) {

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) {

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';

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'

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'

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; };

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) => {

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

@tstrachota
Copy link
Member Author

Rebased

@waldenraines
Copy link

@ares @ohadlevy any other comments here?

@ares
Copy link
Member

ares commented Dec 7, 2017

I'm trying to test but I can't install dependencies, I always get this, anyone knows?

npm WARN react-addons-test-utils@15.6.2 requires a peer of react-dom@^15.4.2 but none is installed. You must install peer dependencies yourself.

npm ERR! Cannot read property '0' of undefined

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/ares/.npm/_logs/2017-12-07T21_18_53_635Z-debug.log

EDIT: nvm, looks unrelated, can reproduce now even after resetting back to develop, suggestions welcome though :-)

@waldenraines
Copy link

EDIT: nvm, looks unrelated, can reproduce now even after resetting back to develop, suggestions welcome though :-)

There's always the big hammer (rm -fr node_modules; npm i). Sometimes npm gets confused it seems.

@ohadlevy
Copy link
Member

@ares were you able to review this pr?

@ares
Copy link
Member

ares commented Dec 12, 2017

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.

@ohadlevy ohadlevy merged commit f2121ff into theforeman:develop Dec 12, 2017
@ares
Copy link
Member

ares commented Dec 13, 2017

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
10 participants