From 532c6469395aff255b0e6325ca14c02187b84895 Mon Sep 17 00:00:00 2001 From: daniele-mng Date: Thu, 29 Aug 2024 11:36:56 +0200 Subject: [PATCH 1/7] add: user settings time and date format --- src/gmp/commands/users.js | 8 ++ src/gmp/locale/date.js | 47 +++++++-- .../components/date/__tests__/datetime.jsx | 96 ++++++++++++++++--- .../hooks/__tests__/useUserSessionTimeout.jsx | 4 +- src/web/pages/login/loginpage.jsx | 68 +++++++------ src/web/pages/usersettings/dialog.jsx | 16 +++- src/web/pages/usersettings/generalpart.jsx | 30 +++++- .../pages/usersettings/usersettingspage.jsx | 87 +++++++++++++---- src/web/store/usersettings/reducers.js | 1 - src/web/store/usersettings/selectors.js | 1 - src/web/utils/render.jsx | 15 ++- .../utils/userSettingTimeDateFormatters.js | 40 ++++++++ 12 files changed, 329 insertions(+), 84 deletions(-) create mode 100644 src/web/utils/userSettingTimeDateFormatters.js diff --git a/src/gmp/commands/users.js b/src/gmp/commands/users.js index d4549a997d..7aab548cd3 100644 --- a/src/gmp/commands/users.js +++ b/src/gmp/commands/users.js @@ -70,6 +70,11 @@ export const DEFAULT_FILTER_SETTINGS = { vulnerability: '17c9d269-95e7-4bfa-b1b2-bc106a2175c7', }; +const PARAM_KEYS = { + DATE: 'date_format', + TIME: 'time_format', +}; + const saveDefaultFilterSettingId = entityType => `settings_filter:${DEFAULT_FILTER_SETTINGS[entityType]}`; @@ -251,9 +256,12 @@ export class UserCommand extends EntityCommand { saveSettings(data) { log.debug('Saving settings', data); + return this.httpPost({ cmd: 'save_my_settings', text: data.timezone, + [PARAM_KEYS.DATE]: data.dateFormat, + [PARAM_KEYS.TIME]: data.timeFormat, old_password: data.oldPassword, password: data.newPassword, lang: data.userInterfaceLanguage, diff --git a/src/gmp/locale/date.js b/src/gmp/locale/date.js index 253a60b3a3..6d03596357 100644 --- a/src/gmp/locale/date.js +++ b/src/gmp/locale/date.js @@ -34,7 +34,7 @@ export const ensureDate = date => { return date; }; -export const dateFormat = (date, format, tz) => { +export const getFormattedDate = (date, format, tz) => { date = ensureDate(date); if (!isDefined(date)) { return undefined; @@ -43,14 +43,49 @@ export const dateFormat = (date, format, tz) => { if (isDefined(tz)) { date.tz(tz); } + return date.format(format); }; -export const shortDate = (date, tz) => dateFormat(date, 'L', tz); +export const dateTimeFormatOptions = { + time: {12: 'h:mm A', 24: 'H:mm'}, + date: {wmdy: 'ddd, MMM D, YYYY', wdmy: 'ddd, D MMM YYYY'}, +}; + +export const shortDate = (date, tz, userInterfaceDateFormat) => { + const formatString = dateTimeFormatOptions.date[userInterfaceDateFormat] + ? 'DD/MM/YYYY' + : 'L'; + + return getFormattedDate(date, formatString, tz); +}; + +export const longDate = ( + date, + tz, + userInterfaceTimeFormat, + userInterfaceDateFormat, +) => { + const formatString = + dateTimeFormatOptions.date[userInterfaceDateFormat] && + dateTimeFormatOptions.time[userInterfaceTimeFormat] + ? `${dateTimeFormatOptions.date[userInterfaceDateFormat]} ${dateTimeFormatOptions.time[userInterfaceTimeFormat]} z` + : 'llll'; -export const longDate = (date, tz) => dateFormat(date, 'llll', tz); + return getFormattedDate(date, formatString, tz); +}; -export const dateTimeWithTimeZone = (date, tz) => - dateFormat(date, 'llll z', tz); +export const dateTimeWithTimeZone = ( + date, + tz, + userInterfaceTimeFormat, + userInterfaceDateFormat, +) => { + const formatString = + dateTimeFormatOptions.date[userInterfaceDateFormat] && + dateTimeFormatOptions.time[userInterfaceTimeFormat] + ? `${dateTimeFormatOptions.date[userInterfaceDateFormat]} ${dateTimeFormatOptions.time[userInterfaceTimeFormat]} z` + : 'llll z'; -// vim: set ts=2 sw=2 tw=80: + return getFormattedDate(date, formatString, tz); +}; diff --git a/src/web/components/date/__tests__/datetime.jsx b/src/web/components/date/__tests__/datetime.jsx index edcf0bfaeb..55933e789b 100644 --- a/src/web/components/date/__tests__/datetime.jsx +++ b/src/web/components/date/__tests__/datetime.jsx @@ -4,7 +4,14 @@ */ /* eslint-disable no-console */ -import {describe, test, expect, testing} from '@gsa/testing'; +import { + describe, + test, + expect, + testing, + beforeAll, + afterAll, +} from '@gsa/testing'; import Date from 'gmp/models/date'; @@ -13,10 +20,35 @@ import {rendererWith} from 'web/utils/testing'; import {setTimezone} from 'web/store/usersettings/actions'; import DateTime from '../datetime'; +import {loadingActions} from 'web/store/usersettings/defaults/actions'; + +const getSetting = testing.fn().mockResolvedValue({}); + +const gmp = { + user: { + getSetting, + }, +}; describe('DateTime render tests', () => { + let originalSetItem; + + beforeAll(() => { + originalSetItem = localStorage.setItem; + localStorage.setItem = testing.fn(); + }); + + afterAll(() => { + localStorage.setItem = originalSetItem; + }); test('should render nothing if date is undefined', () => { - const {render} = rendererWith({store: true}); + const {render, store} = rendererWith({gmp, store: true}); + store.dispatch( + loadingActions.success({ + userinterfacetimeformat: {value: 12}, + userinterfacedateformat: {value: 'wdmy'}, + }), + ); const {element} = render(); @@ -25,10 +57,16 @@ describe('DateTime render tests', () => { test('should render nothing for invalid date', () => { // deactivate console.warn for test - const consolewarn = console.warn; + const consoleWarn = console.warn; console.warn = () => {}; - const {render} = rendererWith({store: true}); + const {render, store} = rendererWith({gmp, store: true}); + store.dispatch( + loadingActions.success({ + userinterfacetimeformat: {value: 12}, + userinterfacedateformat: {value: 'wdmy'}, + }), + ); const date = Date('foo'); @@ -38,12 +76,13 @@ describe('DateTime render tests', () => { expect(element).toBeNull(); - console.warn = consolewarn; + console.warn = consoleWarn; }); test('should call formatter', () => { const formatter = testing.fn().mockReturnValue('foo'); - const {render, store} = rendererWith({store: true}); + + const {render, store} = rendererWith({gmp, store: true}); const date = Date('2019-01-01T12:00:00Z'); @@ -51,6 +90,9 @@ describe('DateTime render tests', () => { store.dispatch(setTimezone('CET')); + localStorage.setItem('userInterfaceTimeFormat', 12); + localStorage.setItem('userInterfaceDateFormat', 'wdmy'); + const {baseElement} = render( , ); @@ -59,17 +101,49 @@ describe('DateTime render tests', () => { expect(baseElement).toHaveTextContent('foo'); }); - test('should render with default formatter', () => { - const {render, store} = rendererWith({store: true}); + test.each([ + [ + 'should render with default formatter', + { + userinterfacetimeformat: {value: undefined}, + userinterfacedateformat: {value: undefined}, + }, + 'Tue, Jan 1, 2019 1:00 PM CET', + ], + [ + 'should render with 24 h and WeekDay, Month, Day, Year formatter', + { + userinterfacetimeformat: {value: 24}, + userinterfacedateformat: {value: 'wmdy'}, + }, + 'Tue, Jan 1, 2019 13:00 CET', + ], + [ + 'should render with 12 h and WeekDay, Day, Month, Year formatter', + { + userinterfacetimeformat: {value: 12}, + userinterfacedateformat: {value: 'wdmy'}, + }, + 'Tue, 1 Jan 2019 1:00 PM CET', + ], + ])('%s', (_, settings, expectedText) => { + const {render, store} = rendererWith({gmp, store: true}); + + localStorage.setItem( + 'userInterfaceTimeFormat', + settings.userinterfacetimeformat.value, + ); + localStorage.setItem( + 'userInterfaceDateFormat', + settings.userinterfacedateformat.value, + ); const date = Date('2019-01-01T12:00:00Z'); - expect(date.isValid()).toEqual(true); store.dispatch(setTimezone('CET')); const {baseElement} = render(); - - expect(baseElement).toHaveTextContent('Tue, Jan 1, 2019 1:00 PM CET'); + expect(baseElement).toHaveTextContent(expectedText); }); }); diff --git a/src/web/hooks/__tests__/useUserSessionTimeout.jsx b/src/web/hooks/__tests__/useUserSessionTimeout.jsx index 3cd07b265c..eea92334d0 100644 --- a/src/web/hooks/__tests__/useUserSessionTimeout.jsx +++ b/src/web/hooks/__tests__/useUserSessionTimeout.jsx @@ -5,7 +5,7 @@ import {describe, test, expect} from '@gsa/testing'; -import {dateFormat} from 'gmp/locale/date'; +import {getFormattedDate} from 'gmp/locale/date'; import date from 'gmp/models/date'; import {setSessionTimeout as setSessionTimeoutAction} from 'web/store/usersettings/actions'; @@ -21,7 +21,7 @@ const TestUserSessionTimeout = () => { onClick={() => setSessionTimeout(date('2020-03-10'))} onKeyDown={() => {}} > - {dateFormat(sessionTimeout, 'DD-MM-YY')} + {getFormattedDate(sessionTimeout, 'DD-MM-YY')} ); }; diff --git a/src/web/pages/login/loginpage.jsx b/src/web/pages/login/loginpage.jsx index 74d867c631..c261029269 100644 --- a/src/web/pages/login/loginpage.jsx +++ b/src/web/pages/login/loginpage.jsx @@ -94,38 +94,46 @@ class LoginPage extends React.Component { this.login(gmp.settings.guestUsername, gmp.settings.guestPassword); } - login(username, password) { + async login(username, password) { const {gmp} = this.props; - gmp.login(username, password).then( - data => { - const {locale, timezone, sessionTimeout} = data; - - const {location, navigate} = this.props; - - this.props.setTimezone(timezone); - this.props.setLocale(locale); - this.props.setSessionTimeout(sessionTimeout); - this.props.setUsername(username); - // must be set before changing the location - this.props.setIsLoggedIn(true); - - if ( - location && - location.state && - location.state.next && - location.state.next !== location.pathname - ) { - navigate(location.state.next, {replace: true}); - } else { - navigate('/dashboards', {replace: true}); - } - }, - rej => { - log.error(rej); - this.setState({error: rej}); - }, - ); + try { + const data = await gmp.login(username, password); + + const {location, navigate} = this.props; + const {locale, timezone, sessionTimeout} = data; + + this.props.setTimezone(timezone); + this.props.setLocale(locale); + this.props.setSessionTimeout(sessionTimeout); + this.props.setUsername(username); + // must be set before changing the location + this.props.setIsLoggedIn(true); + + if (location?.state?.next && location.state.next !== location.pathname) { + navigate(location.state.next, {replace: true}); + } else { + navigate('/dashboards', {replace: true}); + } + } catch (error) { + log.error(error); + this.setState({error}); + } + + try { + const userSettings = await gmp.user.currentSettings(); + + localStorage.setItem( + 'userInterfaceTimeFormat', + userSettings.data.userinterfacetimeformat.value, + ); + localStorage.setItem( + 'userInterfaceDateFormat', + userSettings.data.userinterfacedateformat.value, + ); + } catch (error) { + log.error(error); + } } componentDidMount() { diff --git a/src/web/pages/usersettings/dialog.jsx b/src/web/pages/usersettings/dialog.jsx index 1673f37f13..a2172e1cd3 100644 --- a/src/web/pages/usersettings/dialog.jsx +++ b/src/web/pages/usersettings/dialog.jsx @@ -39,7 +39,7 @@ const FormGroupSizer = styled(Column)` const fieldsToValidate = ['rowsPerPage']; -let UserSettingsDialog = ({ +const UserSettingsDialogComponent = ({ alerts, credentials, filters, @@ -49,6 +49,8 @@ let UserSettingsDialog = ({ schedules, targets, timezone, + userInterfaceTimeFormat, + userInterfaceDateFormat, userInterfaceLanguage, rowsPerPage, maxRowsPerPage, @@ -102,6 +104,8 @@ let UserSettingsDialog = ({ }) => { const settings = { timezone, + userInterfaceTimeFormat, + userInterfaceDateFormat, oldPassword: '', newPassword: '', confPassword: '', @@ -194,6 +198,8 @@ let UserSettingsDialog = ({ { +const UserSettingsDialog = connect(rootState => { const entities = isDefined(rootState.entities) ? rootState.entities : []; return { entities, }; -})(UserSettingsDialog); +})(UserSettingsDialogComponent); export default UserSettingsDialog; diff --git a/src/web/pages/usersettings/generalpart.jsx b/src/web/pages/usersettings/generalpart.jsx index c8470ae5af..236ceb1f3f 100644 --- a/src/web/pages/usersettings/generalpart.jsx +++ b/src/web/pages/usersettings/generalpart.jsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import styled from 'styled-components'; @@ -68,6 +67,8 @@ Notification.propTypes = { const GeneralPart = ({ timezone, + userInterfaceDateFormat, + userInterfaceTimeFormat, oldPassword, newPassword, confPassword, @@ -82,11 +83,35 @@ const GeneralPart = ({ onChange, }) => { const [_] = useTranslation(); + return ( <> + + + { - this.closeDialog(); - this.props.setLocale( - userInterfaceLanguage === BROWSER_LANGUAGE - ? undefined - : userInterfaceLanguage, + this.handleInteraction(); + await gmp.user.saveSetting( + 'd9857b7c-1159-4193-9bc0-18fae5473a69', + data.userInterfaceDateFormat, + ); + await gmp.user.saveSetting( + '11deb7ff-550b-4950-aacf-06faeb7c61b9', + data.userInterfaceTimeFormat, ); - this.props.setTimezone(timezone); - this.loadSettings(); - }); + await gmp.user.saveSettings(data).then(() => { + this.closeDialog(); + this.props.setLocale( + userInterfaceLanguage === BROWSER_LANGUAGE + ? undefined + : userInterfaceLanguage, + ); + this.props.setTimezone(timezone); + + localStorage.setItem( + 'userInterfaceTimeFormat', + data.userInterfaceTimeFormat, + ); + localStorage.setItem( + 'userInterfaceDateFormat', + data.userInterfaceDateFormat, + ); + + this.loadSettings(); + }); + } catch (error) { + console.error(error); + } } handleValueChange(value, name) { @@ -274,6 +295,8 @@ class UserSettings extends React.Component { targets, isLoading = true, timezone, + userInterfaceDateFormat = {}, + userInterfaceTimeFormat = {}, userInterfaceLanguage = {}, rowsPerPage = {}, maxRowsPerPage = {}, @@ -364,11 +387,10 @@ class UserSettings extends React.Component { nvtFilter = hasValue(nvtFilter) ? nvtFilter : {}; certBundFilter = hasValue(certBundFilter) ? certBundFilter : {}; dfnCertFilter = hasValue(dfnCertFilter) ? dfnCertFilter : {}; - const openVasScanners = scanners.filter(openVasScannersFilter); return ( - + <> ) : ( - + <> {_('Timezone')} {timezone} + + {_('Time Format')} + + {userInterfaceTimeFormat.value}h + + + + {_('Date Format')} + {userInterfaceDateFormat.value} + + {_('Password')} ******** @@ -723,7 +756,7 @@ class UserSettings extends React.Component { )} - + )} {dialogVisible && !isLoading && ( )} - + ); } } @@ -811,6 +846,7 @@ UserSettings.propTypes = { credentials: PropTypes.array, credentialsFilter: PropTypes.object, cveFilter: PropTypes.object, + userInterfaceDateFormat: PropTypes.oneOf(['wdmy', 'wmdy']), defaultAlert: PropTypes.object, defaultEsxiCredential: PropTypes.object, defaultOpenvasScanConfig: PropTypes.object, @@ -870,6 +906,7 @@ UserSettings.propTypes = { tasksFilter: PropTypes.object, ticketsFilter: PropTypes.object, timezone: PropTypes.string, + userInterfaceTimeFormat: PropTypes.oneOf([12, 24]), tlsCertificatesFilter: PropTypes.object, userInterfaceLanguage: PropTypes.object, usersFilter: PropTypes.object, @@ -879,11 +916,21 @@ UserSettings.propTypes = { const mapStateToProps = rootState => { const userDefaultsSelector = getUserSettingsDefaults(rootState); + const userDefaultFilterSelector = getUserSettingsDefaultFilter(rootState); const userInterfaceLanguage = userDefaultsSelector.getByName( 'userinterfacelanguage', ); + + const userInterfaceTimeFormat = userDefaultsSelector.getByName( + 'userinterfacetimeformat', + ); + + const userInterfaceDateFormat = userDefaultsSelector.getByName( + 'userinterfacedateformat', + ); + const rowsPerPage = userDefaultsSelector.getByName('rowsperpage'); const detailsExportFileName = userDefaultsSelector.getByName( 'detailsexportfilename', @@ -1006,6 +1053,8 @@ const mapStateToProps = rootState => { schedules: schedulesSel.getEntities(ALL_FILTER), targets: targetsSel.getEntities(ALL_FILTER), timezone: getTimezone(rootState), + userInterfaceTimeFormat, + userInterfaceDateFormat, userInterfaceLanguage, rowsPerPage, detailsExportFileName, diff --git a/src/web/store/usersettings/reducers.js b/src/web/store/usersettings/reducers.js index 311422ea19..9110a1eebd 100644 --- a/src/web/store/usersettings/reducers.js +++ b/src/web/store/usersettings/reducers.js @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import {combineReducers} from 'web/store/utils'; import defaults from './defaults/reducers'; diff --git a/src/web/store/usersettings/selectors.js b/src/web/store/usersettings/selectors.js index 28e99689f1..215d38ef8c 100644 --- a/src/web/store/usersettings/selectors.js +++ b/src/web/store/usersettings/selectors.js @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - export const getReportComposerDefaults = rootState => { const {userSettings = {}} = rootState; const {reportComposerDefaults} = userSettings; diff --git a/src/web/utils/render.jsx b/src/web/utils/render.jsx index f5a5097c92..6abb0ec7fa 100644 --- a/src/web/utils/render.jsx +++ b/src/web/utils/render.jsx @@ -3,13 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import {format} from 'd3-format'; import React from 'react'; import {_} from 'gmp/locale/lang'; -import {dateFormat} from 'gmp/locale/date'; +import {getFormattedDate} from 'gmp/locale/date'; import {isDefined, isFunction, isObject} from 'gmp/utils/identity'; import {isEmpty, shorten, split} from 'gmp/utils/string'; @@ -521,12 +520,12 @@ export const generateFilename = ({ mTime = currentTime; } - const percentC = dateFormat(cTime, 'YYYYMMDD'); - const percentc = dateFormat(cTime, 'HHMMSS'); - const percentD = dateFormat(currentTime, 'YYYYMMDD'); - const percentt = dateFormat(currentTime, 'HHMMSS'); - const percentM = dateFormat(mTime, 'YYYYMMDD'); - const percentm = dateFormat(mTime, 'HHMMSS'); + const percentC = getFormattedDate(cTime, 'YYYYMMDD'); + const percentc = getFormattedDate(cTime, 'HHMMSS'); + const percentD = getFormattedDate(currentTime, 'YYYYMMDD'); + const percentt = getFormattedDate(currentTime, 'HHMMSS'); + const percentM = getFormattedDate(mTime, 'YYYYMMDD'); + const percentm = getFormattedDate(mTime, 'HHMMSS'); const percentN = isDefined(resourceName) ? resourceName : resourceType; const fileNameMap = { diff --git a/src/web/utils/userSettingTimeDateFormatters.js b/src/web/utils/userSettingTimeDateFormatters.js new file mode 100644 index 0000000000..cfd2d4d457 --- /dev/null +++ b/src/web/utils/userSettingTimeDateFormatters.js @@ -0,0 +1,40 @@ +/* SPDX-FileCopyrightText: 2024 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {longDate, shortDate, dateTimeWithTimeZone} from 'gmp/locale/date'; + +export const formattedUserSettingShortDate = (date, tz) => { + const userInterfaceDateFormat = localStorage.getItem( + 'userInterfaceTimeFormat', + ); + + return shortDate(date, tz, userInterfaceDateFormat); +}; + +export const formattedUserSettingLongDate = (date, tz) => { + const userInterfaceDateFormat = localStorage.getItem( + 'userInterfaceDateFormat', + ); + + const userInterfaceTimeFormat = localStorage.getItem( + 'userInterfaceTimeFormat', + ); + return longDate(date, tz, userInterfaceTimeFormat, userInterfaceDateFormat); +}; + +export const formattedUserSettingDateTimeWithTimeZone = (date, tz) => { + const userInterfaceDateFormat = localStorage.getItem( + 'userInterfaceDateFormat', + ); + const userInterfaceTimeFormat = localStorage.getItem( + 'userInterfaceTimeFormat', + ); + return dateTimeWithTimeZone( + date, + tz, + userInterfaceTimeFormat, + userInterfaceDateFormat, + ); +}; From d974f6801edd850f28e553473e4ebed0cec792fe Mon Sep 17 00:00:00 2001 From: daniele-mng Date: Thu, 29 Aug 2024 11:40:16 +0200 Subject: [PATCH 2/7] change dateTimeWithTimeZone --- src/web/components/chart/schedule.jsx | 10 +- src/web/components/date/datetime.jsx | 10 +- .../components/menu/__tests__/usermenu.jsx | 91 --------- src/web/components/menu/usermenu.jsx | 186 ------------------ .../tasks/dashboard/schedulesdisplay.jsx | 4 +- src/web/pages/tasks/icons/scheduleicon.jsx | 8 +- 6 files changed, 20 insertions(+), 289 deletions(-) delete mode 100644 src/web/components/menu/__tests__/usermenu.jsx delete mode 100644 src/web/components/menu/usermenu.jsx diff --git a/src/web/components/chart/schedule.jsx b/src/web/components/chart/schedule.jsx index 7422279f34..737cd1afae 100644 --- a/src/web/components/chart/schedule.jsx +++ b/src/web/components/chart/schedule.jsx @@ -10,7 +10,7 @@ import {LinearGradient} from '@visx/gradient'; import {scaleBand, scaleUtc} from 'd3-scale'; import _ from 'gmp/locale'; -import {dateTimeWithTimeZone} from 'gmp/locale/date'; +import {formattedUserSettingDateTimeWithTimeZone} from 'web/utils/userSettingTimeDateFormatters'; import date from 'gmp/models/date'; @@ -63,12 +63,14 @@ const cloneSchedule = (d, start) => { duration === 0 ? _('{{name}} Start: {{date}}', { name: d.label, - date: dateTimeWithTimeZone(start), + date: formattedUserSettingDateTimeWithTimeZone(start), }) : _('{{name}} Start: {{startdate}} End: {{enddate}}', { name: d.label, - startdate: dateTimeWithTimeZone(start), - enddate: dateTimeWithTimeZone(start.clone().add(duration, 'seconds')), + startdate: formattedUserSettingDateTimeWithTimeZone(start), + enddate: formattedUserSettingDateTimeWithTimeZone( + start.clone().add(duration, 'seconds'), + ), }); return { ...d, diff --git a/src/web/components/date/datetime.jsx b/src/web/components/date/datetime.jsx index 5f673c643b..5af2bf60c9 100644 --- a/src/web/components/date/datetime.jsx +++ b/src/web/components/date/datetime.jsx @@ -3,14 +3,19 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {dateTimeWithTimeZone, ensureDate} from 'gmp/locale/date'; +import {ensureDate} from 'gmp/locale/date'; +import {formattedUserSettingDateTimeWithTimeZone} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined, hasValue} from 'gmp/utils/identity'; import PropTypes from 'web/utils/proptypes'; import useUserTimezone from 'web/hooks/useUserTimezone'; -const DateTime = ({formatter = dateTimeWithTimeZone, timezone, date}) => { +const DateTime = ({ + formatter = formattedUserSettingDateTimeWithTimeZone, + timezone, + date, +}) => { date = ensureDate(date); const [userTimezone] = useUserTimezone(); @@ -18,6 +23,7 @@ const DateTime = ({formatter = dateTimeWithTimeZone, timezone, date}) => { if (!hasValue(timezone)) { timezone = userTimezone; } + return !isDefined(date) || !date.isValid() ? null : formatter(date, timezone); }; diff --git a/src/web/components/menu/__tests__/usermenu.jsx b/src/web/components/menu/__tests__/usermenu.jsx deleted file mode 100644 index 2b4038fbdf..0000000000 --- a/src/web/components/menu/__tests__/usermenu.jsx +++ /dev/null @@ -1,91 +0,0 @@ -/* SPDX-FileCopyrightText: 2024 Greenbone AG - * - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import {describe, test, expect, testing} from '@gsa/testing'; - -import date from 'gmp/models/date'; -import {longDate} from 'gmp/locale/date'; - -import {setSessionTimeout, setUsername} from 'web/store/usersettings/actions'; - -import {fireEvent, rendererWith, screen, act} from 'web/utils/testing'; - -import UserMenu from '../usermenu'; - -describe('UserMenu component tests', () => { - test('should render UserMenu', () => { - const {render} = rendererWith({gmp: {}, router: true, store: true}); - - const {element} = render(); - - expect(element).toBeInTheDocument(); - }); - - test('should render username and sessionTimeout', () => { - const {render, store} = rendererWith({gmp: {}, router: true, store: true}); - const timeout = date('2018-10-10'); - - store.dispatch(setSessionTimeout(timeout)); - store.dispatch(setUsername('foo')); - - const {element} = render(); - - expect(element).toHaveTextContent(longDate(timeout)); - expect(element).toHaveTextContent('foo'); - }); - - test('should route to usersettings on click', () => { - const {render} = rendererWith({ - gmp: {}, - store: true, - router: true, - }); - - const {getByTestId} = render(); - const userSettingsElement = getByTestId('usermenu-settings'); - - fireEvent.click(userSettingsElement); - - expect(window.location.pathname).toMatch('usersettings'); - }); - - test('should logout user on click', async () => { - const doLogout = testing.fn().mockResolvedValue(); - const gmp = { - doLogout, - }; - const {render} = rendererWith({gmp, store: true, router: true}); - - render(); - const userSettingsElement = screen.getByTestId('usermenu-logout'); - - await act(async () => { - fireEvent.click(userSettingsElement); - }); - - expect(gmp.doLogout).toHaveBeenCalled(); - }); - - test('should renew session timeout on click', async () => { - const renewSession = testing - .fn() - .mockResolvedValue({data: '2019-10-10T12:00:00Z'}); - const gmp = { - user: { - renewSession, - }, - }; - const {render} = rendererWith({gmp, store: true, router: true}); - - const {getAllByTestId} = render(); - const icons = getAllByTestId('svg-icon'); - - await act(async () => { - fireEvent.click(icons[3]); - }); - - expect(gmp.user.renewSession).toHaveBeenCalled(); - }); -}); diff --git a/src/web/components/menu/usermenu.jsx b/src/web/components/menu/usermenu.jsx deleted file mode 100644 index d4b6623c52..0000000000 --- a/src/web/components/menu/usermenu.jsx +++ /dev/null @@ -1,186 +0,0 @@ -/* SPDX-FileCopyrightText: 2024 Greenbone AG - * - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import React from 'react'; - -import {useNavigate} from 'react-router-dom'; - -import styled, {keyframes} from 'styled-components'; - -import _ from 'gmp/locale'; -import {dateTimeWithTimeZone} from 'gmp/locale/date'; - -import LogoutIcon from 'web/components/icon/logouticon'; -import MySettingsIcon from 'web/components/icon/mysettingsicon'; -import RefreshIcon from 'web/components/icon/refreshicon'; -import ScheduleIcon from 'web/components/icon/scheduleicon'; -import UserIcon from 'web/components/icon/usericon'; - -import Divider from 'web/components/layout/divider'; -import Link from 'web/components/link/link'; - -import Theme from 'web/utils/theme'; -import useGmp from 'web/hooks/useGmp'; -import useUserName from 'web/hooks/useUserName'; -import useUserSessionTimeout from 'web/hooks/useUserSessionTimeout'; -import useUserTimezone from 'web/hooks/useUserTimezone'; - -const UserMenu = styled.span` - display: inline-flex; - flex-direction: column; -`; - -const Div = styled.div` - position: relative; - display: none; - ${UserMenu}:hover & { - display: block; - } - animation: ${keyframes({ - '0%': { - transform: 'scale(0.9)', - opacity: 0.2, - }, - '100%': { - transform: 'scale(1.0)', - opacity: 1, - }, - })} - 0.1s ease-in; -`; - -const List = styled.ul` - position: absolute; - margin: 0; - padding: 0; - right: 0; - top: 0; - z-index: ${Theme.Layers.menu}; - list-style: none; - font-size: 10px; - width: 300px; -`; - -const Entry = styled.li` - height: 30px; - width: 300px; - border-left: 1px solid ${Theme.mediumGray}; - border-right: 1px solid ${Theme.mediumGray}; - display: flex; - align-items: center; - background-color: ${Theme.white}; - padding-left: 12px; - - &:hover { - background: ${Theme.green}; - color: ${Theme.white}; - cursor: pointer; - } - &:first-child { - border-top: 1px solid ${Theme.mediumGray}; - cursor: default; - background-color: ${Theme.dialogGray} - &:hover { - color: ${Theme.black}; - } - } - &:nth-child(2) { - cursor: default; - background-color: ${Theme.dialogGray} - &:hover { - color: ${Theme.black}; - } - } - &:last-child { - border-top: 1px solid ${Theme.mediumGray}; - border-bottom: 1px solid ${Theme.mediumGray}; - } -`; - -const StyledUserIcon = styled(UserIcon)` - margin-right: 10px; -`; - -const StyledLink = styled(Link)` - width: 100%; - height: 100%; - &:link, - &:hover, - &:active, - &:visited, - &:hover { - color: inherit; - text-decoration: none; - } -`; - -const UserMenuContainer = () => { - const [sessionTimeout, setSessionTimeout] = useUserSessionTimeout(); - const [userTimezone] = useUserTimezone(); - const [userName] = useUserName(); - const gmp = useGmp(); - const navigate = useNavigate(); - - const handleLogout = event => { - event.preventDefault(); - - gmp.doLogout().then(() => { - navigate('/login?type=logout'); - }); - }; - - const handleRenewSessionTimeout = () => { - gmp.user.renewSession().then(response => setSessionTimeout(response.data)); - }; - - return ( - - -
- - - - - {userName} - - - - - - - {_('Session timeout: {{date}}', { - date: dateTimeWithTimeZone(sessionTimeout, userTimezone), - })} - - - - - - - - - {_('My Settings')} - - - - - - - {_('Log Out')} - - - -
-
- ); -}; - -export default UserMenuContainer; - -// vim: set ts=2 sw=2 tw=80: diff --git a/src/web/pages/tasks/dashboard/schedulesdisplay.jsx b/src/web/pages/tasks/dashboard/schedulesdisplay.jsx index 68219581e0..31d4a51bc9 100644 --- a/src/web/pages/tasks/dashboard/schedulesdisplay.jsx +++ b/src/web/pages/tasks/dashboard/schedulesdisplay.jsx @@ -4,7 +4,7 @@ */ import {_, _l} from 'gmp/locale/lang'; -import {dateTimeWithTimeZone} from 'gmp/locale/date'; +import {formattedUserSettingDateTimeWithTimeZone} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined} from 'gmp/utils/identity'; @@ -43,7 +43,7 @@ const transformScheduleData = (data = [], {endDate}) => { return { label: name, duration, - nextStart: dateTimeWithTimeZone(event.nextDate), + nextStart: formattedUserSettingDateTimeWithTimeZone(event.nextDate), starts: event.getNextDates(endDate), timezone, isInfinite: isDefined(recurrence.isFinite) && !recurrence.isFinite(), diff --git a/src/web/pages/tasks/icons/scheduleicon.jsx b/src/web/pages/tasks/icons/scheduleicon.jsx index fd9b12fc3e..e0cab189eb 100644 --- a/src/web/pages/tasks/icons/scheduleicon.jsx +++ b/src/web/pages/tasks/icons/scheduleicon.jsx @@ -17,7 +17,7 @@ import ScheduleIcon from 'web/components/icon/scheduleicon'; import DetailsLink from 'web/components/link/detailslink'; -import {dateTimeWithTimeZone} from 'gmp/locale/date'; +import {formattedUserSettingDateTimeWithTimeZone} from 'web/utils/userSettingTimeDateFormatters'; import {getTimezone} from 'web/store/usersettings/selectors'; const TaskScheduleIcon = ({size, links = true, schedule, timezone}) => { @@ -47,7 +47,7 @@ const TaskScheduleIcon = ({size, links = true, schedule, timezone}) => { } else if (count === 1) { title = _('View Details of Schedule {{name}} (Next due: {{time}} Once)', { name, - time: dateTimeWithTimeZone(nextDate, timezone), + time: formattedUserSettingDateTimeWithTimeZone(nextDate, timezone), }); } else if (count > 1) { title = _( @@ -55,14 +55,14 @@ const TaskScheduleIcon = ({size, links = true, schedule, timezone}) => { '{{time}}, {{periods}} more times )', { name, - time: dateTimeWithTimeZone(nextDate, timezone), + time: formattedUserSettingDateTimeWithTimeZone(nextDate, timezone), periods: count, }, ); } else { title = _('View Details of Schedule {{name}} (Next due: {{time}})', { name, - time: dateTimeWithTimeZone(nextDate, timezone), + time: formattedUserSettingDateTimeWithTimeZone(nextDate, timezone), }); } From 44d3778ee510a2caacd7ff5a6929404e92e42e15 Mon Sep 17 00:00:00 2001 From: daniele-mng Date: Thu, 29 Aug 2024 11:41:26 +0200 Subject: [PATCH 3/7] change shortDate --- .../display/created/createdtransform.jsx | 4 +-- src/web/pages/audits/detailspage.jsx | 34 +++++++++++-------- .../pages/hosts/dashboard/modifieddisplay.jsx | 4 +-- .../hosts/dashboard/modifiedhighdisplay.jsx | 4 +-- src/web/pages/reportformats/details.jsx | 5 ++- src/web/pages/reportformats/row.jsx | 7 ++-- .../reports/details/tlscertificatestable.jsx | 13 ++++--- src/web/pages/tasks/detailspage.jsx | 10 ++++-- .../tickets/dashboard/createddisplay.jsx | 4 +-- .../dashboard/modifieddisplay.jsx | 4 +-- 10 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/web/components/dashboard/display/created/createdtransform.jsx b/src/web/components/dashboard/display/created/createdtransform.jsx index 53d52cdf9b..8ddcbf8aec 100644 --- a/src/web/components/dashboard/display/created/createdtransform.jsx +++ b/src/web/components/dashboard/display/created/createdtransform.jsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseInt, parseDate} from 'gmp/parser'; @@ -14,7 +14,7 @@ const transformCreated = (data = {}) => { const createdDate = parseDate(value); return { x: createdDate, - label: shortDate(createdDate), + label: formattedUserSettingShortDate(createdDate), y: parseInt(count), y2: parseInt(c_count), }; diff --git a/src/web/pages/audits/detailspage.jsx b/src/web/pages/audits/detailspage.jsx index d76a7c685b..bf4577fcef 100644 --- a/src/web/pages/audits/detailspage.jsx +++ b/src/web/pages/audits/detailspage.jsx @@ -3,11 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import _ from 'gmp/locale'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined} from 'gmp/utils/identity'; @@ -171,25 +170,30 @@ export const ToolBarIcons = ({ id={entity.current_report.id} title={_('Current Report for Audit {{- name}} from {{- date}}', { name: entity.name, - date: shortDate(entity.current_report.scan_start), + date: formattedUserSettingShortDate( + entity.current_report.scan_start, + ), })} > )} - {!isDefined(entity.current_report) && isDefined(entity.last_report) && ( - - - - )} + {!isDefined(entity.current_report) && + isDefined(entity.last_report) && ( + + + + )} { const modified = parseDate(value); return { x: modified, - label: shortDate(modified), + label: formattedUserSettingShortDate(modified), y: parseInt(count), y2: parseInt(c_count), }; diff --git a/src/web/pages/hosts/dashboard/modifiedhighdisplay.jsx b/src/web/pages/hosts/dashboard/modifiedhighdisplay.jsx index 402b47f962..6ff1d3787e 100644 --- a/src/web/pages/hosts/dashboard/modifiedhighdisplay.jsx +++ b/src/web/pages/hosts/dashboard/modifiedhighdisplay.jsx @@ -6,7 +6,7 @@ import React from 'react'; import {_, _l} from 'gmp/locale/lang'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseInt, parseDate} from 'gmp/parser'; @@ -38,7 +38,7 @@ const transformModified = (data = {}) => { const modified = parseDate(value); return { x: modified, - label: shortDate(modified), + label: formattedUserSettingShortDate(modified), y: parseInt(count), y2: parseInt(c_count), }; diff --git a/src/web/pages/reportformats/details.jsx b/src/web/pages/reportformats/details.jsx index d2d4664270..859840594b 100644 --- a/src/web/pages/reportformats/details.jsx +++ b/src/web/pages/reportformats/details.jsx @@ -3,11 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import _ from 'gmp/locale'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined} from 'gmp/utils/identity'; @@ -72,7 +71,7 @@ const ReportFormatDetails = ({entity, links = true}) => { {renderYesNo(trust.value)} {isDefined(trust.time) && ( - ({shortDate(trust.time)}) + ({formattedUserSettingShortDate(trust.time)}) )} diff --git a/src/web/pages/reportformats/row.jsx b/src/web/pages/reportformats/row.jsx index fdf57d2083..3fd51dcb05 100644 --- a/src/web/pages/reportformats/row.jsx +++ b/src/web/pages/reportformats/row.jsx @@ -3,11 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import _ from 'gmp/locale'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import EntityNameTableData from 'web/entities/entitynametabledata'; @@ -82,7 +81,9 @@ const Row = ({ {entity.content_type} {renderYesNo(entity.trust.value)} - {entity.trust.time && ({shortDate(entity.trust.time)})} + {entity.trust.time && ( + ({formattedUserSettingShortDate(entity.trust.time)}) + )} {renderYesNo(entity.isActive())} diff --git a/src/web/pages/reports/details/tlscertificatestable.jsx b/src/web/pages/reports/details/tlscertificatestable.jsx index 87d2b2d10b..62b2c9f0e4 100644 --- a/src/web/pages/reports/details/tlscertificatestable.jsx +++ b/src/web/pages/reports/details/tlscertificatestable.jsx @@ -3,13 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import styled from 'styled-components'; import {_, _l} from 'gmp/locale/lang'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import PropTypes from 'web/utils/proptypes'; @@ -123,10 +122,16 @@ const Row = ({ {serial} - + - + @@ -195,7 +197,9 @@ export const ToolBarIcons = ({ id={entity.last_report.id} title={_('Last Report for Task {{- name}} from {{- date}}', { name: entity.name, - date: shortDate(entity.last_report.scan_start), + date: formattedUserSettingShortDate( + entity.last_report.scan_start, + ), })} > diff --git a/src/web/pages/tickets/dashboard/createddisplay.jsx b/src/web/pages/tickets/dashboard/createddisplay.jsx index c79f5266ad..16bc6c96dd 100644 --- a/src/web/pages/tickets/dashboard/createddisplay.jsx +++ b/src/web/pages/tickets/dashboard/createddisplay.jsx @@ -4,7 +4,7 @@ */ import {_, _l} from 'gmp/locale/lang'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import date from 'gmp/models/date'; import {TICKETS_FILTER_FILTER} from 'gmp/models/filter'; @@ -63,7 +63,7 @@ export const TicketsCreatedDisplay = createDisplay({ }); export const TicketsCreatedTableDisplay = createDisplay({ - dataRow: row => [row.y, row.y2, shortDate(row.x)], + dataRow: row => [row.y, row.y2, formattedUserSettingShortDate(row.x)], dataTitles: [_l('Created Tickets'), _l('Total Tickets'), _l('Time')], dataTransform: transfromCreated, displayComponent: DataTableDisplay, diff --git a/src/web/pages/tlscertificates/dashboard/modifieddisplay.jsx b/src/web/pages/tlscertificates/dashboard/modifieddisplay.jsx index ac11cecfa5..40c631cd36 100644 --- a/src/web/pages/tlscertificates/dashboard/modifieddisplay.jsx +++ b/src/web/pages/tlscertificates/dashboard/modifieddisplay.jsx @@ -6,7 +6,7 @@ import React from 'react'; import {_, _l} from 'gmp/locale/lang'; -import {shortDate} from 'gmp/locale/date'; +import {formattedUserSettingShortDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseInt, parseDate} from 'gmp/parser'; @@ -37,7 +37,7 @@ const transformModified = (data = {}) => { const modified = parseDate(value); return { x: modified, - label: shortDate(modified), + label: formattedUserSettingShortDate(modified), y: parseInt(count), y2: parseInt(c_count), }; From 721f75fac115aff156ac1a28fa62a625e2fedc24 Mon Sep 17 00:00:00 2001 From: daniele-mng Date: Thu, 29 Aug 2024 11:41:48 +0200 Subject: [PATCH 4/7] change longDate --- .../pages/hosts/dashboard/vulnscoredisplay.jsx | 4 ++-- src/web/pages/notes/detailspage.jsx | 8 +++++--- .../dashboard/vulnscoredisplay.jsx | 6 +++--- src/web/pages/overrides/detailspage.jsx | 16 ++++++++++------ .../reports/dashboard/highresultsdisplay.jsx | 4 ++-- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/web/pages/hosts/dashboard/vulnscoredisplay.jsx b/src/web/pages/hosts/dashboard/vulnscoredisplay.jsx index ebe54913b9..f539b3c351 100644 --- a/src/web/pages/hosts/dashboard/vulnscoredisplay.jsx +++ b/src/web/pages/hosts/dashboard/vulnscoredisplay.jsx @@ -10,7 +10,7 @@ import {withRouter} from 'web/utils/withRouter'; import styled from 'styled-components'; import {_, _l} from 'gmp/locale/lang'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseFloat, parseSeverity} from 'gmp/parser'; @@ -51,7 +51,7 @@ const transformVulnScoreData = (data = {}, {severityClass}) => { const {severity} = stats; const averageSeverity = parseSeverity(severity.mean); const riskFactor = resultSeverityRiskFactor(averageSeverity); - const modifiedDate = longDate(modified); + const modifiedDate = formattedUserSettingLongDate(modified); const toolTip = ( {name}: diff --git a/src/web/pages/notes/detailspage.jsx b/src/web/pages/notes/detailspage.jsx index 18c559a7d7..beb0f2b022 100644 --- a/src/web/pages/notes/detailspage.jsx +++ b/src/web/pages/notes/detailspage.jsx @@ -3,13 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import {connect} from 'react-redux'; import _ from 'gmp/locale'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined} from 'gmp/utils/identity'; @@ -146,7 +145,10 @@ const Details = connect(rootState => ({ isDefined(entity.endTime) && ' ' + _('until {{- enddate}}', { - enddate: longDate(entity.endTime, timezone), + enddate: formattedUserSettingLongDate( + entity.endTime, + timezone, + ), })} diff --git a/src/web/pages/operatingsystems/dashboard/vulnscoredisplay.jsx b/src/web/pages/operatingsystems/dashboard/vulnscoredisplay.jsx index 96c4409643..920246e407 100644 --- a/src/web/pages/operatingsystems/dashboard/vulnscoredisplay.jsx +++ b/src/web/pages/operatingsystems/dashboard/vulnscoredisplay.jsx @@ -10,7 +10,7 @@ import {withRouter} from 'web/utils/withRouter'; import styled from 'styled-components'; import {_, _l} from 'gmp/locale/lang'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseFloat, parseSeverity} from 'gmp/parser'; @@ -37,7 +37,7 @@ const ToolTip = styled.div` line-height: 1.2em; `; -const transformVulnScoreData = (data = {}, {severityClass}) => { +const transformVulnScoreData = (data = {}) => { const {groups = []} = data; const tdata = groups .filter(group => { @@ -51,7 +51,7 @@ const transformVulnScoreData = (data = {}, {severityClass}) => { const {average_severity, average_severity_score} = stats; const averageSeverity = parseSeverity(average_severity.mean); const riskFactor = resultSeverityRiskFactor(averageSeverity); - const modifiedDate = longDate(modified); + const modifiedDate = formattedUserSettingLongDate(modified); const toolTip = ( {name}: diff --git a/src/web/pages/overrides/detailspage.jsx b/src/web/pages/overrides/detailspage.jsx index a081378351..5193f91237 100644 --- a/src/web/pages/overrides/detailspage.jsx +++ b/src/web/pages/overrides/detailspage.jsx @@ -3,13 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ - import React from 'react'; import {connect} from 'react-redux'; import _ from 'gmp/locale'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {isDefined} from 'gmp/utils/identity'; @@ -109,9 +108,11 @@ ToolBarIcons.propTypes = { onOverrideEditClick: PropTypes.func.isRequired, }; -const Details = connect(rootState => ({ - timezone: getTimezone(rootState), -}))(({entity, timezone, ...props}) => { +const Details = connect(rootState => { + return { + timezone: getTimezone(rootState), + }; +})(({entity, timezone, ...props}) => { const {nvt} = entity; return ( @@ -149,7 +150,10 @@ const Details = connect(rootState => ({ isDefined(entity.endTime) && ' ' + _('until {{- enddate}}', { - enddate: longDate(entity.endTime, timezone), + enddate: formattedUserSettingLongDate( + entity.endTime, + timezone, + ), })} diff --git a/src/web/pages/reports/dashboard/highresultsdisplay.jsx b/src/web/pages/reports/dashboard/highresultsdisplay.jsx index 79beaafcf3..4f73fb79aa 100644 --- a/src/web/pages/reports/dashboard/highresultsdisplay.jsx +++ b/src/web/pages/reports/dashboard/highresultsdisplay.jsx @@ -6,7 +6,7 @@ import React from 'react'; import {_, _l} from 'gmp/locale/lang'; -import {longDate} from 'gmp/locale/date'; +import {formattedUserSettingLongDate} from 'web/utils/userSettingTimeDateFormatters'; import {parseInt, parseFloat, parseDate} from 'gmp/parser'; @@ -33,7 +33,7 @@ const transformHighResults = (data = {}) => { return groups.map(group => { const reportDate = parseDate(group.value); return { - label: longDate(reportDate), + label: formattedUserSettingLongDate(reportDate), x: reportDate, y: parseInt(group.stats.high.max), y2: parseFloat(group.stats.high_per_host.max), From fea5b365e96eda6458497d542195ec4407aa46b5 Mon Sep 17 00:00:00 2001 From: daniele-mng Date: Fri, 30 Aug 2024 13:34:34 +0200 Subject: [PATCH 5/7] add translation and sys default --- public/locales/gsa-de.json | 5 + src/gmp/locale/__tests__/date.js | 152 ++++++++++++++++-- src/gmp/locale/date.js | 128 ++++++++++++--- src/web/pages/usersettings/generalpart.jsx | 19 ++- .../pages/usersettings/usersettingspage.jsx | 10 +- .../utils/userSettingTimeDateFormatters.js | 2 +- 6 files changed, 269 insertions(+), 47 deletions(-) diff --git a/public/locales/gsa-de.json b/public/locales/gsa-de.json index 5daaf66f28..ae5fa919fc 100644 --- a/public/locales/gsa-de.json +++ b/public/locales/gsa-de.json @@ -439,6 +439,7 @@ "Dashboards": "Dashboards", "Dashboards limit reached": "Dashboardgrenze erreicht", "Date": "Datum", + "Date Format": "Datumsformat", "Day": "Tag", "Debug": "Debug", "Decrease the minimum QoD in the filter settings to 30 percent to see those results.": "Die Minimum-QdE im Filter auf 30 Prozent verringern, um diese Ergebnisse zu sehen.", @@ -1548,6 +1549,7 @@ "Super (Has super access)": "Super (hat Super-Zugriff)", "Support for LDAP is not available.": "Unterstützung für LDAP ist nicht verfügbar.", "Support for RADIUS is not available.": "Unterstützung für RADIUS ist nicht verfügbar.", + "System Default": "Systemstandard", "System Logger": "System-Logger", "System Reports": "Systemberichte", "Synchronization issue: {{error}}": "Synchronisationsproblem: {{error}}", @@ -1739,6 +1741,7 @@ "Tickets by Creation Time (Total: {{count}})": "Tickets nach Erstellungszeit (Gesamt: {{count}})", "Tickets by Status (Total: {{count}})": "Tickets nach Status (Gesamt: {{count}})", "Time": "Zeit", + "Time Format": "Zeitformat", "Timeout": "Timeout", "Timezone": "Zeitzone", "TippingPoint SMS": "TippingPoint SMS", @@ -1869,6 +1872,8 @@ "Wednesday": "Mittwoch", "Week": "Woche", "Weekly": "Wöchentlich", + "Weekday, Month, Day, Year": "Wochentag, Monat, Tag, Jahr", + "Weekday, Day, Month, Year": "Wochentag, Tag, Monat, Jahr", "When changing status to \"closed\", a \"Note for Closed\" is required.": "Wenn der Status zu \"Geschlossen\" geändert wird, ist eine \"Notiz für Geschlossen\" erforderlich.", "When changing status to \"fixed\", a \"Note for Fixed\" is required.": "Wenn der Status zu \"Behoben\" geändert wird, ist eine \"Notiz für Behoben\" erforderlich.", "When changing status to \"open\", a \"Note for Open\" is required.": "Wenn der Status zu \"Offen\" geändert wird, ist eine \"Notiz für Offen\" erforderlich.", diff --git a/src/gmp/locale/__tests__/date.js b/src/gmp/locale/__tests__/date.js index c7ab574391..a55762f249 100644 --- a/src/gmp/locale/__tests__/date.js +++ b/src/gmp/locale/__tests__/date.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {describe, test, expect} from '@gsa/testing'; +import {describe, test, expect, beforeEach} from '@gsa/testing'; import date, {setLocale as locale} from '../../models/date'; @@ -55,15 +55,30 @@ describe('shortDate tests', () => { expect(shortDate(d)).toEqual('01/01/2018'); }); - test('should format string', () => { - setLocale('en'); - expect(shortDate('2018-01-01')).toEqual('01/01/2018'); + test('should format date locale', () => { + setLocale('de'); + expect( + shortDate(date('2018-11-24T15:30:00Z'), 'UTC', 'system_default'), + ).toEqual('24.11.2018'); }); - test('should format JS date', () => { - const d = new Date('2018-01-01'); - setLocale('en'); - expect(shortDate(d)).toEqual('01/01/2018'); + describe('shortDate tests', () => { + beforeEach(() => { + setLocale('en'); + }); + + test.each([ + ['2018-01-01', undefined, undefined, '01/01/2018'], + [new Date('2018-01-01'), undefined, undefined, '01/01/2018'], + [new Date('2018-11-24'), undefined, 'wmdy', '11/24/2018'], + [new Date('2018-11-24'), undefined, 'wdmy', '24/11/2018'], + [new Date('2018-11-24'), undefined, 'system_default', '11/24/2018'], + ])( + 'should format date %p with tz %p and userInterfaceDateFormat %p to %p', + (input, tz, userInterfaceDateFormat, expected) => { + expect(shortDate(input, tz, userInterfaceDateFormat)).toEqual(expected); + }, + ); }); }); @@ -83,15 +98,55 @@ describe('longDate tests', () => { expect(longDate(d)).toEqual('Mon, Jan 1, 2018 12:00 AM'); }); - test('should format string', () => { - setLocale('en'); - expect(longDate('2018-01-01')).toEqual('Mon, Jan 1, 2018 12:00 AM'); + test('should format date locale', () => { + setLocale('de'); + expect( + longDate( + date('2018-11-24T15:30:00Z'), + 'UTC', + 'system_default', + 'system_default', + ), + ).toEqual('Sa., 24. Nov. 2018 15:30'); }); - test('should format JS date', () => { - const d = new Date('2018-01-01T00:00:00'); - setLocale('en'); - expect(longDate(d)).toEqual('Mon, Jan 1, 2018 12:00 AM'); + describe('longDate tests', () => { + beforeEach(() => { + setLocale('en'); + }); + + test.each([ + ['2018-11-24', 'UTC', undefined, undefined, 'Fri, Nov 23, 2018 11:00 PM'], + [ + new Date('2018-11-23T00:00:00'), + 'UTC', + undefined, + undefined, + 'Thu, Nov 22, 2018 11:00 PM', + ], + ['2018-11-24T15:30:00Z', 'UTC', 12, 'wdmy', 'Sat, 24 Nov 2018 3:30 PM'], + ['2018-11-24T15:30:00Z', 'UTC', 24, 'wmdy', 'Sat, Nov 24, 2018 15:30'], + [ + '2018-11-24T15:30:00Z', + 'UTC', + 'system_default', + 'system_default', + 'Sat, Nov 24, 2018 3:30 PM', + ], + ])( + 'should format date %p with tz %p, userInterfaceTimeFormat %p, and userInterfaceDateFormat %p to %p', + ( + input, + tz, + userInterfaceTimeFormat, + userInterfaceDateFormat, + expected, + ) => { + expect( + longDate(input, tz, userInterfaceTimeFormat, userInterfaceDateFormat), + ).toEqual(expected); + }, + ); }); }); @@ -110,6 +165,73 @@ describe('dateTimeWithTimeZone tests', () => { const d = date('2018-01-01T00:00:00+01:00').tz('CET'); expect(dateTimeWithTimeZone(d)).toEqual('Mon, Jan 1, 2018 12:00 AM CET'); }); + + test('should format date locale', () => { + setLocale('de'); + expect( + dateTimeWithTimeZone( + date('2018-11-24T15:30:00Z'), + 'UTC', + 'system_default', + 'system_default', + ), + ).toEqual('Sa., 24. Nov. 2018 15:30 UTC'); + }); + + describe('dateTimeWithTimeZone tests', () => { + beforeEach(() => { + setLocale('en'); + }); + + test.each([ + [ + new Date('2018-11-23T00:00:00'), + 'UTC', + undefined, + undefined, + 'Thu, Nov 22, 2018 11:00 PM UTC', + ], + [ + '2018-11-24T15:30:00Z', + 'UTC', + 12, + 'wdmy', + 'Sat, 24 Nov 2018 3:30 PM UTC', + ], + [ + '2018-11-24T15:30:00Z', + 'UTC', + 24, + 'wmdy', + 'Sat, Nov 24, 2018 15:30 UTC', + ], + [ + '2018-11-24T15:30:00Z', + 'UTC', + 'system_default', + 'system_default', + 'Sat, Nov 24, 2018 3:30 PM UTC', + ], + ])( + 'should format date %p with tz %p, userInterfaceTimeFormat %p, and userInterfaceDateFormat %p to %p', + ( + input, + tz, + userInterfaceTimeFormat, + userInterfaceDateFormat, + expected, + ) => { + expect( + dateTimeWithTimeZone( + input, + tz, + userInterfaceTimeFormat, + userInterfaceDateFormat, + ), + ).toEqual(expected); + }, + ); + }); }); // vim: set ts=2 sw=2 tw=80: diff --git a/src/gmp/locale/date.js b/src/gmp/locale/date.js index 6d03596357..1f0f6a562f 100644 --- a/src/gmp/locale/date.js +++ b/src/gmp/locale/date.js @@ -13,6 +13,38 @@ import {setLocale as setMomentLocale, isDate} from 'gmp/models/date'; const log = logger.getLogger('gmp.locale.date'); +const SYSTEM_DEFAULT = 'system_default'; +const LONG_DATE = 'longDate'; +const SHORT_DATE = 'shortDate'; +const TIME = 'time'; + +export const dateTimeFormatOptions = { + [TIME]: { + options: { + 12: {format: 'h:mm A', label: '12h'}, + 24: {format: 'H:mm', label: '24h'}, + [SYSTEM_DEFAULT]: { + format: 'LT', + label: 'System Default', + }, + }, + }, + [SHORT_DATE]: { + options: { + wmdy: {format: 'MM/DD/YYYY'}, + wdmy: {format: 'DD/MM/YYYY'}, + [SYSTEM_DEFAULT]: {format: 'L'}, + }, + }, + [LONG_DATE]: { + options: { + wmdy: {format: 'ddd, MMM D, YYYY', label: 'Weekday, Month, Day, Year'}, + wdmy: {format: 'ddd, D MMM YYYY', label: 'Weekday, Day, Month, Year'}, + [SYSTEM_DEFAULT]: {format: 'llll', label: 'System Default'}, + }, + }, +}; + export const setLocale = lang => { log.debug('Setting date locale to', lang); setMomentLocale(lang); @@ -47,45 +79,99 @@ export const getFormattedDate = (date, format, tz) => { return date.format(format); }; -export const dateTimeFormatOptions = { - time: {12: 'h:mm A', 24: 'H:mm'}, - date: {wmdy: 'ddd, MMM D, YYYY', wdmy: 'ddd, D MMM YYYY'}, +/** + * Retrieves the format string based on the category and key. + * + * @param {string} category - The category of the format. + * @param {string} key - The key for the specific format. + * @returns {string|undefined} - The format string if found, otherwise undefined. + */ + +export const getFormatString = (category, key) => { + return dateTimeFormatOptions[category].options[key]?.format; }; -export const shortDate = (date, tz, userInterfaceDateFormat) => { - const formatString = dateTimeFormatOptions.date[userInterfaceDateFormat] - ? 'DD/MM/YYYY' - : 'L'; +/** + * Formats a date with a given time zone and user setting date format. + * + * @param {Date} date - The date to format. + * @param {string} tz - The time zone. + * @param {string} [userInterfaceDateFormat=SYSTEM_DEFAULT] - The user setting date format. + * @returns {string} - The formatted date string. + */ +export const shortDate = ( + date, + tz, + userInterfaceDateFormat = SYSTEM_DEFAULT, +) => { + const dateFormatString = getFormatString(SHORT_DATE, userInterfaceDateFormat); + + const formatString = + isDefined(dateFormatString) && userInterfaceDateFormat !== SYSTEM_DEFAULT + ? dateFormatString + : 'L'; return getFormattedDate(date, formatString, tz); }; +/** + * Formats a date with a given time zone and user setting formats. + * + * @param {Date} date - The date to format. + * @param {string} tz - The time zone. + * @param {string} [userInterfaceTimeFormat=SYSTEM_DEFAULT] - The user setting time format. + * @param {string} [userInterfaceDateFormat=SYSTEM_DEFAULT] - The user setting date format. + * @returns {string} - The formatted date string. + */ + export const longDate = ( date, tz, - userInterfaceTimeFormat, - userInterfaceDateFormat, + userInterfaceTimeFormat = SYSTEM_DEFAULT, + userInterfaceDateFormat = SYSTEM_DEFAULT, ) => { - const formatString = - dateTimeFormatOptions.date[userInterfaceDateFormat] && - dateTimeFormatOptions.time[userInterfaceTimeFormat] - ? `${dateTimeFormatOptions.date[userInterfaceDateFormat]} ${dateTimeFormatOptions.time[userInterfaceTimeFormat]} z` - : 'llll'; + const dateFormatString = getFormatString(LONG_DATE, userInterfaceDateFormat); + const timeFormatString = getFormatString(TIME, userInterfaceTimeFormat); + + const useDefaultFormat = + userInterfaceTimeFormat === SYSTEM_DEFAULT || + userInterfaceDateFormat === SYSTEM_DEFAULT || + (!isDefined(dateFormatString) && !isDefined(timeFormatString)); + + const formatString = useDefaultFormat + ? 'llll' + : `${dateFormatString} ${timeFormatString}`; return getFormattedDate(date, formatString, tz); }; +/** + * Formats a date with a given time zone and user setting formats. + * + * @param {Date} date - The date to format. + * @param {string} tz - The time zone. + * @param {string} [userInterfaceTimeFormat=SYSTEM_DEFAULT] - The user setting time format. + * @param {string} [userInterfaceDateFormat=SYSTEM_DEFAULT] - The user setting date format. + * @returns {string} - The formatted date string with time zone. + */ + export const dateTimeWithTimeZone = ( date, tz, - userInterfaceTimeFormat, - userInterfaceDateFormat, + userInterfaceTimeFormat = SYSTEM_DEFAULT, + userInterfaceDateFormat = SYSTEM_DEFAULT, ) => { - const formatString = - dateTimeFormatOptions.date[userInterfaceDateFormat] && - dateTimeFormatOptions.time[userInterfaceTimeFormat] - ? `${dateTimeFormatOptions.date[userInterfaceDateFormat]} ${dateTimeFormatOptions.time[userInterfaceTimeFormat]} z` - : 'llll z'; + const dateFormatString = getFormatString(LONG_DATE, userInterfaceDateFormat); + const timeFormatString = getFormatString(TIME, userInterfaceTimeFormat); + + const useDefaultFormat = + userInterfaceTimeFormat === SYSTEM_DEFAULT || + userInterfaceDateFormat === SYSTEM_DEFAULT || + (!isDefined(dateFormatString) && !isDefined(timeFormatString)); + + const formatString = useDefaultFormat + ? 'llll z' + : `${dateFormatString} ${timeFormatString} z`; return getFormattedDate(date, formatString, tz); }; diff --git a/src/web/pages/usersettings/generalpart.jsx b/src/web/pages/usersettings/generalpart.jsx index 236ceb1f3f..2491b3af72 100644 --- a/src/web/pages/usersettings/generalpart.jsx +++ b/src/web/pages/usersettings/generalpart.jsx @@ -8,6 +8,7 @@ import styled from 'styled-components'; import {parseYesNo, YES_VALUE, NO_VALUE} from 'gmp/parser'; import {isDefined} from 'gmp/utils/identity'; +import {dateTimeFormatOptions} from 'gmp/locale/date'; import Checkbox from 'web/components/form/checkbox'; import FormGroup from 'web/components/form/formgroup'; @@ -84,6 +85,14 @@ const GeneralPart = ({ }) => { const [_] = useTranslation(); + const getSelectItems = category => { + return Object.entries(dateTimeFormatOptions[category].options).map( + ([value, {label}]) => ({ + value: isNaN(value) ? value : Number(value), + label: _(label), + }), + ); + }; return ( <> @@ -94,10 +103,7 @@ const GeneralPart = ({ label={_('Time Format')} name="userInterfaceTimeFormat" value={userInterfaceTimeFormat} - items={[ - {value: 12, label: _('12h')}, - {value: 24, label: _('24h')}, - ]} + items={getSelectItems('time')} onChange={onChange} /> @@ -105,10 +111,7 @@ const GeneralPart = ({ label={_('Date Format')} name="userInterfaceDateFormat" value={userInterfaceDateFormat} - items={[ - {value: 'wdmy', label: _('Weekday, Day, Month, Year')}, - {value: 'wmdy', label: _('Weekday, Month, Day, Year')}, - ]} + items={getSelectItems('longDate')} onChange={onChange} /> diff --git a/src/web/pages/usersettings/usersettingspage.jsx b/src/web/pages/usersettings/usersettingspage.jsx index f538808b49..f6681a38a0 100644 --- a/src/web/pages/usersettings/usersettingspage.jsx +++ b/src/web/pages/usersettings/usersettingspage.jsx @@ -433,12 +433,18 @@ class UserSettings extends React.Component { {_('Time Format')} - {userInterfaceTimeFormat.value}h + {userInterfaceTimeFormat.value === 'system_default' + ? _('System Default') + : `${Number(userInterfaceTimeFormat.value)}h`} {_('Date Format')} - {userInterfaceDateFormat.value} + + {userInterfaceDateFormat.value === 'system_default' + ? _('System Default') + : userInterfaceDateFormat.value} + diff --git a/src/web/utils/userSettingTimeDateFormatters.js b/src/web/utils/userSettingTimeDateFormatters.js index cfd2d4d457..733728824f 100644 --- a/src/web/utils/userSettingTimeDateFormatters.js +++ b/src/web/utils/userSettingTimeDateFormatters.js @@ -7,7 +7,7 @@ import {longDate, shortDate, dateTimeWithTimeZone} from 'gmp/locale/date'; export const formattedUserSettingShortDate = (date, tz) => { const userInterfaceDateFormat = localStorage.getItem( - 'userInterfaceTimeFormat', + 'userInterfaceDateFormat', ); return shortDate(date, tz, userInterfaceDateFormat); From 109bba562c4d6f49334828c52c9b472b6fdebe9e Mon Sep 17 00:00:00 2001 From: daniele-mng Date: Fri, 30 Aug 2024 16:28:36 +0200 Subject: [PATCH 6/7] update dialog for sys default --- public/locales/gsa-de.json | 1 + src/gmp/locale/__tests__/date.js | 16 +++-- src/gmp/locale/date.js | 8 +-- src/web/pages/usersettings/dialog.jsx | 14 ++++- src/web/pages/usersettings/generalpart.jsx | 60 +++++++++++++++++-- .../pages/usersettings/usersettingspage.jsx | 22 +++++-- 6 files changed, 95 insertions(+), 26 deletions(-) diff --git a/public/locales/gsa-de.json b/public/locales/gsa-de.json index ae5fa919fc..fad030d98c 100644 --- a/public/locales/gsa-de.json +++ b/public/locales/gsa-de.json @@ -1810,6 +1810,7 @@ "Uploading": "Hochladen", "Urgency": "Dringlichkeit", "Use LDAPS only": "Ausschließlich LDAPS verwenden", + "Use System Default for Time and Date Format": "Systemstandard für Zeit- und Datumsformat verwenden", "Use workaround for default certificate": "Problemumgehung für Standard-Zertifikat verwenden", "User": "Benutzer", "User ID": "Benutzer-ID", diff --git a/src/gmp/locale/__tests__/date.js b/src/gmp/locale/__tests__/date.js index a55762f249..6b81640622 100644 --- a/src/gmp/locale/__tests__/date.js +++ b/src/gmp/locale/__tests__/date.js @@ -116,13 +116,19 @@ describe('longDate tests', () => { }); test.each([ - ['2018-11-24', 'UTC', undefined, undefined, 'Fri, Nov 23, 2018 11:00 PM'], + [ + '2018-11-24', + undefined, + undefined, + undefined, + 'Sat, Nov 24, 2018 12:00 AM', + ], [ new Date('2018-11-23T00:00:00'), - 'UTC', undefined, undefined, - 'Thu, Nov 22, 2018 11:00 PM', + undefined, + 'Fri, Nov 23, 2018 12:00 AM', ], ['2018-11-24T15:30:00Z', 'UTC', 12, 'wdmy', 'Sat, 24 Nov 2018 3:30 PM'], ['2018-11-24T15:30:00Z', 'UTC', 24, 'wmdy', 'Sat, Nov 24, 2018 15:30'], @@ -186,10 +192,10 @@ describe('dateTimeWithTimeZone tests', () => { test.each([ [ new Date('2018-11-23T00:00:00'), - 'UTC', undefined, undefined, - 'Thu, Nov 22, 2018 11:00 PM UTC', + undefined, + 'Fri, Nov 23, 2018 12:00 AM ', ], [ '2018-11-24T15:30:00Z', diff --git a/src/gmp/locale/date.js b/src/gmp/locale/date.js index 1f0f6a562f..bb6700e162 100644 --- a/src/gmp/locale/date.js +++ b/src/gmp/locale/date.js @@ -13,7 +13,7 @@ import {setLocale as setMomentLocale, isDate} from 'gmp/models/date'; const log = logger.getLogger('gmp.locale.date'); -const SYSTEM_DEFAULT = 'system_default'; +export const SYSTEM_DEFAULT = 'system_default'; const LONG_DATE = 'longDate'; const SHORT_DATE = 'shortDate'; const TIME = 'time'; @@ -23,24 +23,18 @@ export const dateTimeFormatOptions = { options: { 12: {format: 'h:mm A', label: '12h'}, 24: {format: 'H:mm', label: '24h'}, - [SYSTEM_DEFAULT]: { - format: 'LT', - label: 'System Default', - }, }, }, [SHORT_DATE]: { options: { wmdy: {format: 'MM/DD/YYYY'}, wdmy: {format: 'DD/MM/YYYY'}, - [SYSTEM_DEFAULT]: {format: 'L'}, }, }, [LONG_DATE]: { options: { wmdy: {format: 'ddd, MMM D, YYYY', label: 'Weekday, Month, Day, Year'}, wdmy: {format: 'ddd, D MMM YYYY', label: 'Weekday, Day, Month, Year'}, - [SYSTEM_DEFAULT]: {format: 'llll', label: 'System Default'}, }, }, }; diff --git a/src/web/pages/usersettings/dialog.jsx b/src/web/pages/usersettings/dialog.jsx index a2172e1cd3..0026e1cfb4 100644 --- a/src/web/pages/usersettings/dialog.jsx +++ b/src/web/pages/usersettings/dialog.jsx @@ -11,7 +11,9 @@ import {connect} from 'react-redux'; import {isDefined} from 'gmp/utils/identity'; -import {parseFloat, parseYesNo} from 'gmp/parser'; +import {parseFloat, parseYesNo, YES_VALUE, NO_VALUE} from 'gmp/parser'; + +import {SYSTEM_DEFAULT} from 'gmp/locale/date'; import SaveDialog from 'web/components/dialog/savedialog'; @@ -51,6 +53,7 @@ const UserSettingsDialogComponent = ({ timezone, userInterfaceTimeFormat, userInterfaceDateFormat, + isUserInterfaceTimeDateDefault, userInterfaceLanguage, rowsPerPage, maxRowsPerPage, @@ -106,6 +109,7 @@ const UserSettingsDialogComponent = ({ timezone, userInterfaceTimeFormat, userInterfaceDateFormat, + isUserInterfaceTimeDateDefault, oldPassword: '', newPassword: '', confPassword: '', @@ -200,6 +204,9 @@ const UserSettingsDialogComponent = ({ timezone={values.timezone} userInterfaceTimeFormat={values.userInterfaceTimeFormat} userInterfaceDateFormat={values.userInterfaceDateFormat} + isUserInterfaceTimeDateDefault={ + values.isUserInterfaceTimeDateDefault + } oldPassword={values.oldPassword} newPassword={values.newPassword} confPassword={values.confPassword} @@ -308,7 +315,7 @@ UserSettingsDialogComponent.propTypes = { credentials: PropTypes.array, credentialsFilter: PropTypes.string, cveFilter: PropTypes.string, - userInterfaceDateFormat: PropTypes.oneOf(['wmdy', 'wdmy']), + userInterfaceDateFormat: PropTypes.oneOf(['wmdy', 'wdmy', SYSTEM_DEFAULT]), defaultAlert: PropTypes.string, defaultEsxiCredential: PropTypes.string, defaultOpenvasScanConfig: PropTypes.string, @@ -327,6 +334,7 @@ UserSettingsDialogComponent.propTypes = { filtersFilter: PropTypes.string, groupsFilter: PropTypes.string, hostsFilter: PropTypes.string, + isUserInterfaceTimeDateDefault: PropTypes.oneOfType([YES_VALUE, NO_VALUE]), listExportFileName: PropTypes.string, maxRowsPerPage: PropTypes.number, notesFilter: PropTypes.string, @@ -353,7 +361,7 @@ UserSettingsDialogComponent.propTypes = { tasksFilter: PropTypes.string, ticketsFilter: PropTypes.string, timezone: PropTypes.string, - userInterfaceTimeFormat: PropTypes.oneOf([12, 24]), + userInterfaceTimeFormat: PropTypes.oneOf([12, 24, SYSTEM_DEFAULT]), tlsCertificatesFilter: PropTypes.string, userInterfaceLanguage: PropTypes.string, usersFilter: PropTypes.string, diff --git a/src/web/pages/usersettings/generalpart.jsx b/src/web/pages/usersettings/generalpart.jsx index 2491b3af72..40e8dcfccd 100644 --- a/src/web/pages/usersettings/generalpart.jsx +++ b/src/web/pages/usersettings/generalpart.jsx @@ -3,12 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import React from 'react'; +import {useState} from 'react'; import styled from 'styled-components'; import {parseYesNo, YES_VALUE, NO_VALUE} from 'gmp/parser'; import {isDefined} from 'gmp/utils/identity'; -import {dateTimeFormatOptions} from 'gmp/locale/date'; +import {dateTimeFormatOptions, SYSTEM_DEFAULT} from 'gmp/locale/date'; import Checkbox from 'web/components/form/checkbox'; import FormGroup from 'web/components/form/formgroup'; @@ -70,6 +70,7 @@ const GeneralPart = ({ timezone, userInterfaceDateFormat, userInterfaceTimeFormat, + isUserInterfaceTimeDateDefault, oldPassword, newPassword, confPassword, @@ -93,28 +94,74 @@ const GeneralPart = ({ }), ); }; + + const [prevUserInterfaceTimeFormat, setPrevUserInterfaceTimeFormat] = + useState(undefined); + const [prevUserInterfaceDateFormat, setPrevUserInterfaceDateFormat] = + useState(undefined); + + const handleSysDefaultChange = event => { + const isSystemDefault = parseYesNo(event); + + const defaultTimeFormat = 24; + const defaultDateFormat = 'wdmy'; + + const currentUserInterfaceTimeFormat = + userInterfaceTimeFormat || defaultTimeFormat; + const currentUserInterfaceDateFormat = + userInterfaceDateFormat || defaultDateFormat; + + if (!isSystemDefault) { + onChange( + prevUserInterfaceTimeFormat || defaultTimeFormat, + 'userInterfaceTimeFormat', + ); + onChange( + prevUserInterfaceDateFormat || defaultDateFormat, + 'userInterfaceDateFormat', + ); + } else { + setPrevUserInterfaceTimeFormat(currentUserInterfaceTimeFormat); + setPrevUserInterfaceDateFormat(currentUserInterfaceDateFormat); + + onChange(SYSTEM_DEFAULT, 'userInterfaceTimeFormat'); + onChange(SYSTEM_DEFAULT, 'userInterfaceDateFormat'); + } + + onChange(isSystemDefault, 'isUserInterfaceTimeDateDefault'); + }; + return ( <> + + - {_('Time Format')} - {userInterfaceTimeFormat.value === 'system_default' + {userInterfaceTimeFormat.value === SYSTEM_DEFAULT ? _('System Default') : `${Number(userInterfaceTimeFormat.value)}h`} @@ -441,7 +449,7 @@ class UserSettings extends React.Component { {_('Date Format')} - {userInterfaceDateFormat.value === 'system_default' + {userInterfaceDateFormat.value === SYSTEM_DEFAULT ? _('System Default') : userInterfaceDateFormat.value} @@ -777,6 +785,9 @@ class UserSettings extends React.Component { timezone={timezone} userInterfaceTimeFormat={userInterfaceTimeFormat.value} userInterfaceDateFormat={userInterfaceDateFormat.value} + isUserInterfaceTimeDateDefault={ + isUserInterfaceTimeDateDefault.value + } userInterfaceLanguage={userInterfaceLanguage.value} rowsPerPage={rowsPerPage.value} maxRowsPerPage={maxRowsPerPage.value} @@ -852,7 +863,7 @@ UserSettings.propTypes = { credentials: PropTypes.array, credentialsFilter: PropTypes.object, cveFilter: PropTypes.object, - userInterfaceDateFormat: PropTypes.oneOf(['wdmy', 'wmdy']), + userInterfaceDateFormat: PropTypes.oneOf(['wdmy', 'wmdy', SYSTEM_DEFAULT]), defaultAlert: PropTypes.object, defaultEsxiCredential: PropTypes.object, defaultOpenvasScanConfig: PropTypes.object, @@ -870,6 +881,7 @@ UserSettings.propTypes = { dynamicSeverity: PropTypes.object, filters: PropTypes.array, filtersFilter: PropTypes.object, + isUserInterfaceTimeDateDefault: PropTypes.oneOfType([YES_VALUE, NO_VALUE]), gmp: PropTypes.gmp.isRequired, groupsFilter: PropTypes.object, hostsFilter: PropTypes.object, @@ -912,7 +924,7 @@ UserSettings.propTypes = { tasksFilter: PropTypes.object, ticketsFilter: PropTypes.object, timezone: PropTypes.string, - userInterfaceTimeFormat: PropTypes.oneOf([12, 24]), + userInterfaceTimeFormat: PropTypes.oneOf([12, 24, SYSTEM_DEFAULT]), tlsCertificatesFilter: PropTypes.object, userInterfaceLanguage: PropTypes.object, usersFilter: PropTypes.object, From c7b3f4bd789fca3418e475924fd4fc6ada0aaea5 Mon Sep 17 00:00:00 2001 From: daniele-mng Date: Mon, 2 Sep 2024 18:08:51 +0200 Subject: [PATCH 7/7] address comments --- src/gmp/commands/users.js | 4 ++-- src/web/pages/overrides/detailspage.jsx | 8 +++----- src/web/pages/usersettings/generalpart.jsx | 9 ++++----- src/web/pages/usersettings/usersettingspage.jsx | 10 ---------- 4 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/gmp/commands/users.js b/src/gmp/commands/users.js index 7aab548cd3..4487e04370 100644 --- a/src/gmp/commands/users.js +++ b/src/gmp/commands/users.js @@ -260,8 +260,8 @@ export class UserCommand extends EntityCommand { return this.httpPost({ cmd: 'save_my_settings', text: data.timezone, - [PARAM_KEYS.DATE]: data.dateFormat, - [PARAM_KEYS.TIME]: data.timeFormat, + [PARAM_KEYS.DATE]: data.userInterfaceDateFormat, + [PARAM_KEYS.TIME]: data.userInterfaceTimeFormat, old_password: data.oldPassword, password: data.newPassword, lang: data.userInterfaceLanguage, diff --git a/src/web/pages/overrides/detailspage.jsx b/src/web/pages/overrides/detailspage.jsx index 5193f91237..aa47d15b18 100644 --- a/src/web/pages/overrides/detailspage.jsx +++ b/src/web/pages/overrides/detailspage.jsx @@ -108,11 +108,9 @@ ToolBarIcons.propTypes = { onOverrideEditClick: PropTypes.func.isRequired, }; -const Details = connect(rootState => { - return { - timezone: getTimezone(rootState), - }; -})(({entity, timezone, ...props}) => { +const Details = connect(rootState => ({ + timezone: getTimezone(rootState), +}))(({entity, timezone, ...props}) => { const {nvt} = entity; return ( diff --git a/src/web/pages/usersettings/generalpart.jsx b/src/web/pages/usersettings/generalpart.jsx index 40e8dcfccd..04e7c8f44f 100644 --- a/src/web/pages/usersettings/generalpart.jsx +++ b/src/web/pages/usersettings/generalpart.jsx @@ -85,6 +85,10 @@ const GeneralPart = ({ onChange, }) => { const [_] = useTranslation(); + const [prevUserInterfaceTimeFormat, setPrevUserInterfaceTimeFormat] = + useState(undefined); + const [prevUserInterfaceDateFormat, setPrevUserInterfaceDateFormat] = + useState(undefined); const getSelectItems = category => { return Object.entries(dateTimeFormatOptions[category].options).map( @@ -95,11 +99,6 @@ const GeneralPart = ({ ); }; - const [prevUserInterfaceTimeFormat, setPrevUserInterfaceTimeFormat] = - useState(undefined); - const [prevUserInterfaceDateFormat, setPrevUserInterfaceDateFormat] = - useState(undefined); - const handleSysDefaultChange = event => { const isSystemDefault = parseYesNo(event); diff --git a/src/web/pages/usersettings/usersettingspage.jsx b/src/web/pages/usersettings/usersettingspage.jsx index 82738aa5d5..df3752eee8 100644 --- a/src/web/pages/usersettings/usersettingspage.jsx +++ b/src/web/pages/usersettings/usersettingspage.jsx @@ -243,16 +243,6 @@ class UserSettings extends React.Component { const {userInterfaceLanguage = BROWSER_LANGUAGE, timezone} = data; - this.handleInteraction(); - await gmp.user.saveSetting( - 'd9857b7c-1159-4193-9bc0-18fae5473a69', - data.userInterfaceDateFormat, - ); - await gmp.user.saveSetting( - '11deb7ff-550b-4950-aacf-06faeb7c61b9', - data.userInterfaceTimeFormat, - ); - await gmp.user.saveSettings(data).then(() => { this.closeDialog(); this.props.setLocale(