Commit 6141fc14 authored by Lukas 'Eipi' Eipert's avatar Lukas 'Eipi' Eipert Committed by Ezekiel Kigbo

Enable user setting for absolute dates

parent cd7057b8
import $ from 'jquery'; import $ from 'jquery';
import * as timeago from 'timeago.js'; import * as timeago from 'timeago.js';
import { languageCode, s__ } from '../../../locale'; import { languageCode, s__, createDateTimeFormat } from '../../../locale';
import { formatDate } from './date_format_utility'; import { formatDate } from './date_format_utility';
window.timeago = timeago;
/** /**
* Timeago uses underscores instead of dashes to separate language from country code. * Timeago uses underscores instead of dashes to separate language from country code.
* *
...@@ -76,7 +74,26 @@ const memoizedLocale = () => { ...@@ -76,7 +74,26 @@ const memoizedLocale = () => {
timeago.register(timeagoLanguageCode, memoizedLocale()); timeago.register(timeagoLanguageCode, memoizedLocale());
timeago.register(`${timeagoLanguageCode}-remaining`, memoizedLocaleRemaining()); timeago.register(`${timeagoLanguageCode}-remaining`, memoizedLocaleRemaining());
export const getTimeago = () => timeago; let memoizedFormatter = null;
function setupAbsoluteFormatter() {
if (memoizedFormatter === null) {
const formatter = createDateTimeFormat({
dateStyle: 'medium',
timeStyle: 'short',
});
memoizedFormatter = {
format(date) {
return formatter.format(date instanceof Date ? date : new Date(date));
},
};
}
return memoizedFormatter;
}
export const getTimeago = () =>
window.gon?.time_display_relative === false ? setupAbsoluteFormatter() : timeago;
/** /**
* For the given elements, sets a tooltip with a formatted date. * For the given elements, sets a tooltip with a formatted date.
...@@ -84,8 +101,9 @@ export const getTimeago = () => timeago; ...@@ -84,8 +101,9 @@ export const getTimeago = () => timeago;
* @param {Boolean} setTimeago * @param {Boolean} setTimeago
*/ */
export const localTimeAgo = ($timeagoEls, setTimeago = true) => { export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
const { format } = getTimeago();
$timeagoEls.each((i, el) => { $timeagoEls.each((i, el) => {
$(el).text(timeago.format($(el).attr('datetime'), timeagoLanguageCode)); $(el).text(format($(el).attr('datetime'), timeagoLanguageCode));
}); });
if (!setTimeago) { if (!setTimeago) {
...@@ -117,6 +135,7 @@ export const timeFor = (time, expiredLabel) => { ...@@ -117,6 +135,7 @@ export const timeFor = (time, expiredLabel) => {
return timeago.format(time, `${timeagoLanguageCode}-remaining`).trim(); return timeago.format(time, `${timeagoLanguageCode}-remaining`).trim();
}; };
window.timeago = getTimeago();
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.utils = { window.gl.utils = {
...(window.gl.utils || {}), ...(window.gl.utils || {}),
......
...@@ -128,29 +128,27 @@ ...@@ -128,29 +128,27 @@
= f.label :first_day_of_week, class: 'label-bold' do = f.label :first_day_of_week, class: 'label-bold' do
= _('First day of the week') = _('First day of the week')
= f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'select2' = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'select2'
- if Feature.enabled?(:user_time_settings)
.col-sm-12 .col-sm-12
%hr %hr
.col-lg-4.profile-settings-sidebar .row.js-preferences-form.js-search-settings-section
%h4.gl-mt-0= s_('Preferences|Time preferences') .col-lg-4.profile-settings-sidebar#time-preferences
%p= s_('Preferences|These settings will update how dates and times are displayed for you.') %h4.gl-mt-0
= s_('Preferences|Time preferences')
%p
= s_('Preferences|Configure how dates and times display for you.')
= succeed '.' do
= link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'time-preferences'), target: '_blank'
.col-lg-8 .col-lg-8
.form-group .form-group.form-check
%h5= s_('Preferences|Time format') = f.check_box :time_display_relative, class: 'form-check-input'
.checkbox-icon-inline-wrapper = f.label :time_display_relative, class: 'form-check-label' do
- time_format_label = capture do
= s_('Preferences|Display time in 24-hour format')
= f.check_box :time_format_in_24h
= f.label :time_format_in_24h do
= time_format_label
%h5= s_('Preferences|Time display')
.checkbox-icon-inline-wrapper
- time_display_label = capture do
= s_('Preferences|Use relative times') = s_('Preferences|Use relative times')
= f.check_box :time_display_relative
= f.label :time_display_relative do
= time_display_label
.form-text.text-muted .form-text.text-muted
= s_('Preferences|For example: 30 mins ago.') = s_('Preferences|For example: 30 minutes ago.')
- if Feature.enabled?(:user_time_settings)
.form-group.form-check
= f.check_box :time_format_in_24h, class: 'form-check-input'
= f.label :time_format_in_24h, class: 'form-check-label' do
= s_('Preferences|Display time in 24-hour format')
#js-profile-preferences-app{ data: data_attributes } #js-profile-preferences-app{ data: data_attributes }
...@@ -165,6 +165,30 @@ You can choose one of the following options as the first day of the week: ...@@ -165,6 +165,30 @@ You can choose one of the following options as the first day of the week:
If you select **System Default**, the [instance default](../admin_area/settings/index.md#default-first-day-of-the-week) setting is used. If you select **System Default**, the [instance default](../admin_area/settings/index.md#default-first-day-of-the-week) setting is used.
## Time preferences
### Use relative times
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65570) in GitLab 14.1.
You can select your preferred time format for the GitLab user interface:
- Relative times, for example, `30 minutes ago`.
- Absolute times, for example, `May 18, 2021, 3:57 PM`.
The times are formatted depending on your chosen language.
To set your time preference:
1. On the **Preferences** page, go to **Time preferences**.
1. Select the **Use relative times** checkbox to use relative times,
or clear the checkbox to use absolute times.
1. Select **Save changes**.
NOTE:
This feature is experimental, and choosing absolute times might break certain layouts.
Please open an issue if you notice that using absolute times breaks a layout.
## Integrations ## Integrations
Configure your preferences with third-party services which provide enhancements to your GitLab experience. Configure your preferences with third-party services which provide enhancements to your GitLab experience.
......
...@@ -33,6 +33,7 @@ module Gitlab ...@@ -33,6 +33,7 @@ module Gitlab
gon.disable_animations = Gitlab.config.gitlab['disable_animations'] gon.disable_animations = Gitlab.config.gitlab['disable_animations']
gon.suggested_label_colors = LabelsHelper.suggested_colors gon.suggested_label_colors = LabelsHelper.suggested_colors
gon.first_day_of_week = current_user&.first_day_of_week || Gitlab::CurrentSettings.first_day_of_week gon.first_day_of_week = current_user&.first_day_of_week || Gitlab::CurrentSettings.first_day_of_week
gon.time_display_relative = true
gon.ee = Gitlab.ee? gon.ee = Gitlab.ee?
gon.dot_com = Gitlab.com? gon.dot_com = Gitlab.com?
...@@ -41,6 +42,7 @@ module Gitlab ...@@ -41,6 +42,7 @@ module Gitlab
gon.current_username = current_user.username gon.current_username = current_user.username
gon.current_user_fullname = current_user.name gon.current_user_fullname = current_user.name
gon.current_user_avatar_url = current_user.avatar_url gon.current_user_avatar_url = current_user.avatar_url
gon.time_display_relative = current_user.time_display_relative
end end
# Initialize gon.features with any flags that should be # Initialize gon.features with any flags that should be
......
...@@ -24655,6 +24655,9 @@ msgstr "" ...@@ -24655,6 +24655,9 @@ msgstr ""
msgid "Preferences|Choose what content you want to see on your homepage." msgid "Preferences|Choose what content you want to see on your homepage."
msgstr "" msgstr ""
msgid "Preferences|Configure how dates and times display for you."
msgstr ""
msgid "Preferences|Customize integrations with third party services." msgid "Preferences|Customize integrations with third party services."
msgstr "" msgstr ""
...@@ -24673,7 +24676,7 @@ msgstr "" ...@@ -24673,7 +24676,7 @@ msgstr ""
msgid "Preferences|Failed to save preferences." msgid "Preferences|Failed to save preferences."
msgstr "" msgstr ""
msgid "Preferences|For example: 30 mins ago." msgid "Preferences|For example: 30 minutes ago."
msgstr "" msgstr ""
msgid "Preferences|Gitpod" msgid "Preferences|Gitpod"
...@@ -24721,9 +24724,6 @@ msgstr "" ...@@ -24721,9 +24724,6 @@ msgstr ""
msgid "Preferences|Tab width" msgid "Preferences|Tab width"
msgstr "" msgstr ""
msgid "Preferences|These settings will update how dates and times are displayed for you."
msgstr ""
msgid "Preferences|This feature is experimental and translations are not complete yet" msgid "Preferences|This feature is experimental and translations are not complete yet"
msgstr "" msgstr ""
...@@ -24733,12 +24733,6 @@ msgstr "" ...@@ -24733,12 +24733,6 @@ msgstr ""
msgid "Preferences|This setting allows you to customize the behavior of the system layout and default views." msgid "Preferences|This setting allows you to customize the behavior of the system layout and default views."
msgstr "" msgstr ""
msgid "Preferences|Time display"
msgstr ""
msgid "Preferences|Time format"
msgstr ""
msgid "Preferences|Time preferences" msgid "Preferences|Time preferences"
msgstr "" msgstr ""
......
import $ from 'jquery';
import { getTimeago, localTimeAgo, timeFor } from '~/lib/utils/datetime/timeago_utility';
import { s__ } from '~/locale';
import '~/commons/bootstrap';
describe('TimeAgo utils', () => {
let oldGon;
afterEach(() => {
window.gon = oldGon;
});
beforeEach(() => {
oldGon = window.gon;
});
describe('getTimeago', () => {
describe('with User Setting timeDisplayRelative: true', () => {
beforeEach(() => {
window.gon = { time_display_relative: true };
});
it.each([
[new Date().toISOString(), 'just now'],
[new Date().getTime(), 'just now'],
[new Date(), 'just now'],
[null, 'just now'],
])('formats date `%p` as `%p`', (date, result) => {
expect(getTimeago().format(date)).toEqual(result);
});
});
describe('with User Setting timeDisplayRelative: false', () => {
beforeEach(() => {
window.gon = { time_display_relative: false };
});
it.each([
[new Date().toISOString(), 'Jul 6, 2020, 12:00 AM'],
[new Date(), 'Jul 6, 2020, 12:00 AM'],
[new Date().getTime(), 'Jul 6, 2020, 12:00 AM'],
// Slightly different behaviour when `null` is passed :see_no_evil`
[null, 'Jan 1, 1970, 12:00 AM'],
])('formats date `%p` as `%p`', (date, result) => {
expect(getTimeago().format(date)).toEqual(result);
});
});
});
describe('timeFor', () => {
it('returns localize `past due` when in past', () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 1);
expect(timeFor(date)).toBe(s__('Timeago|Past due'));
});
it('returns localized remaining time when in the future', () => {
const date = new Date();
date.setFullYear(date.getFullYear() + 1);
// Add a day to prevent a transient error. If date is even 1 second
// short of a full year, timeFor will return '11 months remaining'
date.setDate(date.getDate() + 1);
expect(timeFor(date)).toBe(s__('Timeago|1 year remaining'));
});
});
describe('localTimeAgo', () => {
beforeEach(() => {
document.body.innerHTML =
'<time title="some time" datetime="2020-02-18T22:22:32Z">1 hour ago</time>';
});
describe.each`
timeDisplayRelative | text
${true} | ${'4 months ago'}
${false} | ${'Feb 18, 2020, 10:22 PM'}
`(
`With User Setting timeDisplayRelative: $timeDisplayRelative`,
({ timeDisplayRelative, text }) => {
it.each`
timeagoArg | title
${false} | ${'some time'}
${true} | ${'Feb 18, 2020 10:22pm UTC'}
`(
`has content: '${text}' and tooltip: '$title' with timeagoArg = $timeagoArg`,
({ timeagoArg, title }) => {
window.gon = { time_display_relative: timeDisplayRelative };
const element = document.querySelector('time');
localTimeAgo($(element), timeagoArg);
jest.runAllTimers();
expect(element.getAttribute('title')).toBe(title);
expect(element.innerText).toBe(text);
},
);
},
);
});
});
import $ from 'jquery';
import timezoneMock from 'timezone-mock'; import timezoneMock from 'timezone-mock';
import * as datetimeUtility from '~/lib/utils/datetime_utility'; import * as datetimeUtility from '~/lib/utils/datetime_utility';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import '~/commons/bootstrap'; import '~/commons/bootstrap';
describe('Date time utils', () => { describe('Date time utils', () => {
describe('timeFor', () => {
it('returns localize `past due` when in past', () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 1);
expect(datetimeUtility.timeFor(date)).toBe(s__('Timeago|Past due'));
});
it('returns localized remaining time when in the future', () => {
const date = new Date();
date.setFullYear(date.getFullYear() + 1);
// Add a day to prevent a transient error. If date is even 1 second
// short of a full year, timeFor will return '11 months remaining'
date.setDate(date.getDate() + 1);
expect(datetimeUtility.timeFor(date)).toBe(s__('Timeago|1 year remaining'));
});
});
describe('get localized day name', () => { describe('get localized day name', () => {
it('should return Sunday', () => { it('should return Sunday', () => {
const day = datetimeUtility.getDayName(new Date('07/17/2016')); const day = datetimeUtility.getDayName(new Date('07/17/2016'));
...@@ -870,25 +849,6 @@ describe('approximateDuration', () => { ...@@ -870,25 +849,6 @@ describe('approximateDuration', () => {
}); });
}); });
describe('localTimeAgo', () => {
beforeEach(() => {
document.body.innerHTML = `<time title="some time" datetime="2020-02-18T22:22:32Z">1 hour ago</time>`;
});
it.each`
timeagoArg | title
${false} | ${'some time'}
${true} | ${'Feb 18, 2020 10:22pm UTC'}
`('converts $seconds seconds to $approximation', ({ timeagoArg, title }) => {
const element = document.querySelector('time');
datetimeUtility.localTimeAgo($(element), timeagoArg);
jest.runAllTimers();
expect(element.getAttribute('title')).toBe(title);
});
});
describe('differenceInSeconds', () => { describe('differenceInSeconds', () => {
const startDateTime = new Date('2019-07-17T00:00:00.000Z'); const startDateTime = new Date('2019-07-17T00:00:00.000Z');
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment