Skip to content

Commit

Permalink
Improve documentation how to handle date times in the graylog fronten…
Browse files Browse the repository at this point in the history
…d. (#17007)

* Extend documentation for date time handling.

* Use h2 instead of h3 for ux patterns headlines.

* Move documentation into UserDateTimeProvider.

* Add documentation for Timestamp component.

* Extend documentation for date tiem utils and RelativeTime component.

* Improve documentation for toUTCFromTz date time util.

* Cleanup

* Fix toUTCFromTz test.
  • Loading branch information
linuspahl authored Oct 20, 2023
1 parent 5885cd2 commit 392ed65
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 14 deletions.
11 changes: 7 additions & 4 deletions graylog2-web-interface/docs/StyleGuideWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import GraylogThemeProvider from '../src/theme/GraylogThemeProvider';
import CurrentUserContext from '../src/contexts/CurrentUserContext';
import User from '../src/logic/users/User';
import UserDateTimeProvider from '../src/contexts/UserDateTimeProvider';
/* eslint-enable import/no-relative-packages */

export const adminUser = User.builder()
Expand Down Expand Up @@ -58,10 +59,12 @@ const StyleGuideWrapper = ({ children }: Props) => {
path: '/:url?',
element: (
<CurrentUserContext.Provider value={adminUser}>
<GraylogThemeProvider initialThemeModeOverride="light">
<StyleGuideStyles />
{children}
</GraylogThemeProvider>
<UserDateTimeProvider>
<GraylogThemeProvider initialThemeModeOverride="light">
<StyleGuideStyles />
{children}
</GraylogThemeProvider>
</UserDateTimeProvider>
</CurrentUserContext.Provider>
),
}]);
Expand Down
17 changes: 16 additions & 1 deletion graylog2-web-interface/docs/common-functionality.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
### Setting up keyboard shortcuts
## Setting up keyboard shortcuts

If you want to add a keyboard shortcut, you need to:
- extend the `hotkeysCollections` in the `hotkeysProvider`.
- call `useHotkey` hook in the place which provides the functionality you want to execute on keypress.

## Handling date times

When receiving or sending dates to the backend they are always in UTC and expressed according to ISO 8601, for example `2010-07-30T16:03:25.000Z`.
This is also the preferred format to store date times in the state of, for example, UI components.

When displaying date times in the UI they are always displayed in the user timezone.
The [UserDateTimeProvider](https://github.com/Graylog2/graylog2-server/blob/master/graylog2-web-interface/src/contexts/UserDateTimeProvider.tsx) contains the related functionality
and provides further information. It can be access using the [useUserDateTime](https://github.com/Graylog2/graylog2-server/blob/master/graylog2-web-interface/src/hooks/useUserDateTime.ts) hook.

If you just want to display a date time, you can render the [Timestamp](#timestamp) component, which implements methods provided by the `useUserDateTime` hook.
If you want to display the relative time in a human-readable format you can render the [RelativeTime](#relativetime) component.

For all other cases where you need to transform a date time you can use the [DateTime](https://github.com/Graylog2/graylog2-server/blob/master/graylog2-web-interface/src/util/DateTime.ts) utils.
Instead of using `moment` directly, use or (if necessary) extend the `DateTime` utils. It makes it easier to replace moment with an alternative.
4 changes: 2 additions & 2 deletions graylog2-web-interface/docs/ux-patterns.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
### Form & Modal Submit Buttons
## Form & Modal Submit Buttons

- Rely on the shared components `FormSubmit` and `ModalSubmit` to implement the submit and cancel button.
The `FormSubmit` can be used for all forms on pages. The `ModalSubmit` can be used for modals and similar element like
Expand All @@ -11,7 +11,7 @@
- Make sure to write only the first letter uppercase and all other letter lowercase.
- Always use `Cancel` for the cancel button name.

### `EmptyEntity` & `NoEntitiesExist` & `NoSearchResults` components
## `EmptyEntity` & `NoEntitiesExist` & `NoSearchResults` components

- These three components are closely related and maybe confusing to decide which one to use for which situation.
- `EmptyEntity` should be used to display a message for an entity that does not have any entries in the database yet. This components supports displaying a message explaining what the entity is and can also support including a button link to create one via the children props
Expand Down
5 changes: 5 additions & 0 deletions graylog2-web-interface/src/components/common/RelativeTime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Relative time since `2010-07-30T16:03:25.000Z`.

```tsx
<RelativeTime dateTime="2010-07-30T16:03:25.000Z" />
```
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ type Props = {
};

/**
* This component receives any date time and displays the relative time until now in a human readable format.
* This component receives any date time and displays the relative time until now in a human-readable format.
*/

const RelativeTime = ({ dateTime: dateTimeProp }: Props) => {
const dateTime = dateTimeProp ?? new Date();
const relativeTime = relativeDifference(dateTime);
Expand Down
38 changes: 38 additions & 0 deletions graylog2-web-interface/src/components/common/Timestamp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
For the following examples we are using the date time `2010-07-30T16:03:25.000Z`.
By default, the output will be based on the user time zone defined for the style guide.

#### Default

The component displays the date time in the default format for date times in the graylog UI, when no format is specified.

```tsx
<Timestamp dateTime="2010-07-30T16:03:25.000Z" />
```

#### Specific timezone

In this example we are displaying the provided time as UTC.

```tsx
<Timestamp dateTime="2010-07-30T16:03:25.000Z" tz="UTC" />
```

#### Different formats

```tsx
import { DATE_TIME_FORMATS } from 'util/DateTime';


<table cellPadding="10">
<tr>
<th style={{ width: '150px' }}>Format</th>
<th>Output</th>
</tr>
{Object.keys(DATE_TIME_FORMATS).map((format) => (
<tr>
<td>{format}</td>
<td><Timestamp dateTime="2010-07-30T16:03:25.000Z" format={format}/></td>
</tr>
))}
</table>
```
2 changes: 1 addition & 1 deletion graylog2-web-interface/src/components/common/Timestamp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type Props = {
/**
* Component that renders a given date time based on the user time zone in a `time` HTML element.
* It is capable of render date times in different formats, accepting ISO 8601
* strings, JS native Date objects, and Moment.js Date objects.
* strings, JS native Date objects, and Moment.js Date objects. On hover the component displays the time in UTC.
*
* While the component is using the user time zone by default, it is also possible
* to change the time zone for the given date, something that helps, for instance, to display a local time
Expand Down
5 changes: 5 additions & 0 deletions graylog2-web-interface/src/contexts/UserDateTimeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ const getUserTimezone = (userTimezone: string, tzOverride?: string) => {
/**
* Provides methods to convert times based on the user time zone.
* Should be used when displaying times and the related components are not a suitable option.
*
* userTimezone - time zone of the current user.
* formatTime - method which takes a date and optionally a format and returns it as a string in the current user timezone.
* For example, it transforms `2010-07-30T16:03:25.000Z` to `2010-07-30 17:03:25` for a user with the timezone `Europe/Berlin`.
* toUserTimezone - method which takes a date and transforms it a moment date object, based on the user timezone.
*/

const StaticTimezoneProvider = ({ children, tz }: Required<Props>) => {
Expand Down
4 changes: 2 additions & 2 deletions graylog2-web-interface/src/util/DateTime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,11 @@ describe('DateTime utils', () => {

describe('toUTCFromTz', () => {
it('should transform time to UTC based on defined tz', () => {
expect(adjustFormat(toUTCFromTz('2020-01-01T10:00:00.000', moscowTZ), 'internal')).toBe('2020-01-01T07:00:00.000+00:00');
expect(toUTCFromTz('2020-01-01T10:00:00.000', moscowTZ).toISOString()).toEqual('2020-01-01T07:00:00.000Z');
});

it('should prioritize time zone of date time over provided time zone when calculating UTC time', () => {
expect(adjustFormat(toUTCFromTz('2020-01-01T12:00:00.000+02:00', 'Europe/Berlin'), 'internal')).toBe('2020-01-01T10:00:00.000+00:00');
expect(toUTCFromTz('2020-01-01T12:00:00.000+05:00', 'Europe/Berlin').toISOString()).toBe('2020-01-01T07:00:00.000Z');
});
});
});
30 changes: 28 additions & 2 deletions graylog2-web-interface/src/util/DateTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ const getFormatStringsForDateTimeFormats = (dateTimeFormats: Array<DateTimeForma
return format;
});

/**
* Takes a date and returns it as a moment object. Optionally you can define a time zone, which will be considered when displaying the date.
* You can also define `acceptedFormats` in case you want to throw an error if the provided date does not match an expected format.
*/
export const toDateObject = (dateTime: DateTime, acceptedFormats?: Array<DateTimeFormats>, tz = DEFAULT_OUTPUT_TZ) => {
const acceptedFormatStrings = getFormatStringsForDateTimeFormats(acceptedFormats);
const dateObject = moment(dateTime, acceptedFormatStrings, true).tz(tz);
Expand All @@ -70,24 +74,46 @@ export const toDateObject = (dateTime: DateTime, acceptedFormats?: Array<DateTim
return validateDateTime(dateObject, dateTime, validationInfo);
};

/**
* Transforms an ISO 8601 date time to a moment date object. It throws an error if the provided date time is not expressed according to ISO 8601.
*/
export const parseFromIsoString = (dateTimeString: string, tz = DEFAULT_OUTPUT_TZ) => toDateObject(dateTimeString, ['internal'], tz);

/**
* Returns the estimated browser time zone.
*/
export const getBrowserTimezone = () => moment.tz.guess();

/**
* Returns the provided date time as a string, based on the targeted format and timezone.
*/
export const adjustFormat = (dateTime: DateTime, format: DateTimeFormats = 'default', tz = DEFAULT_OUTPUT_TZ) => toDateObject(dateTime, undefined, tz).format(DATE_TIME_FORMATS[format]);

/**
* Returns the provided date time as a string, based on the targeted format and the browser timezone.
*/
export const formatAsBrowserTime = (time: DateTime, format: DateTimeFormats = 'default') => adjustFormat(time, format, getBrowserTimezone());

/**
* Returns the time in a human-readable format, relative to the provided date time.
* If you just want to display the output, you can use the `RelativeTime` component.
*/
export const relativeDifference = (dateTime: DateTime) => {
const dateObject = toDateObject(dateTime);

return validateDateTime(dateObject, dateTime).fromNow();
};

/**
* Validate if the provided time has a supported format.
*/
export const isValidDate = (dateTime: DateTime) => moment(dateTime, Object.values(DATE_TIME_FORMATS), true).isValid();

// This function allows transforming a date time, which does not contain a time zone like `2010-01-01 10:00:00`, to UTC.
// For this calculation it is necessary to define the time zone of the provided date time.
/**
* This function transforms the provided date time to UTC, based on the defined time zone.
* This is useful for date times like `2010-01-01 10:00:00`, which do not include the timezone information.
* For this calculation it is necessary to define the timezone which the date time is currently based on.
*/
export const toUTCFromTz = (dateTime: string, sourceTimezone: string) => {
if (!sourceTimezone) {
throw new Error('It is required to define the time zone of the date time provided for internalUTCTime.');
Expand Down
1 change: 1 addition & 0 deletions graylog2-web-interface/styleguide.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const defaultComponentIgnore = [
];

module.exports = {
skipComponentsWithoutExample: true,
require: [
'core-js/stable',
'regenerator-runtime/runtime',
Expand Down

0 comments on commit 392ed65

Please sign in to comment.