From 563ee357c275facb4ea3a29046aac543da707135 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Thu, 4 May 2023 10:50:19 +0200 Subject: [PATCH] [NO JIRA] Rename ISO date helpers The "NotSoISO" part of the old names was incorrect; the return value is definitely ISO 8601. --- .../js/api/mocks/ComputeEngineServiceMock.ts | 14 +++-- .../main/js/app/components/StartupModal.tsx | 4 +- .../__tests__/StartupModal-test.tsx | 31 +++++------ .../__tests__/BackgroundTasks-it.tsx | 53 +++++++++++++++---- .../components/BackgroundTasksApp.tsx | 6 +-- .../main/js/apps/background-tasks/utils.ts | 6 +-- .../apps/overview/branches/BranchOverview.tsx | 4 +- .../ProjectActivityAnalysesList.tsx | 4 +- .../components/BranchAnalysisList.tsx | 4 +- .../components/BranchAnalysisListRenderer.tsx | 4 +- .../components/BranchBaselineSettingModal.tsx | 4 +- .../BulkApplyTemplateModal.tsx | 6 +-- .../apps/projectsManagement/DeleteModal.tsx | 6 +-- .../ProjectManagementApp.tsx | 6 +-- .../changelog/ChangelogContainer.tsx | 6 +-- .../quality-profiles/home/EvolutionRules.tsx | 4 +- .../js/apps/system/components/PageHeader.tsx | 4 +- .../src/main/js/apps/users/UsersApp.tsx | 10 ++-- .../main/js/helpers/__tests__/dates-test.ts | 8 +-- server/sonar-web/src/main/js/helpers/dates.ts | 11 ++-- .../src/main/js/helpers/l10nBundle.ts | 4 +- server/sonar-web/src/main/js/helpers/query.ts | 9 ++-- .../sonar-web/src/main/js/helpers/tokens.ts | 4 +- 23 files changed, 125 insertions(+), 87 deletions(-) diff --git a/server/sonar-web/src/main/js/api/mocks/ComputeEngineServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/ComputeEngineServiceMock.ts index d892eda5901..93d2e349372 100644 --- a/server/sonar-web/src/main/js/api/mocks/ComputeEngineServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/ComputeEngineServiceMock.ts @@ -22,6 +22,7 @@ import { cloneDeep, groupBy, sortBy } from 'lodash'; import { PAGE_SIZE } from '../../apps/background-tasks/constants'; import { parseDate } from '../../helpers/dates'; import { mockTask } from '../../helpers/mocks/tasks'; +import { isDefined } from '../../helpers/types'; import { ActivityRequestParameters, Task, TaskStatuses, TaskTypes } from '../../types/tasks'; import { cancelAllTasks, @@ -63,7 +64,7 @@ export default class ComputeEngineServiceMock { constructor() { (cancelAllTasks as jest.Mock).mockImplementation(this.handleCancelAllTasks); (cancelTask as jest.Mock).mockImplementation(this.handleCancelTask); - (getActivity as jest.Mock).mockImplementation(this.handleGetActivity); + jest.mocked(getActivity).mockImplementation(this.handleGetActivity); (getStatus as jest.Mock).mockImplementation(this.handleGetStatus); (getTypes as jest.Mock).mockImplementation(this.handleGetTypes); (getTask as jest.Mock).mockImplementation(this.handleGetTask); @@ -96,7 +97,6 @@ export default class ComputeEngineServiceMock { handleGetActivity = (data: ActivityRequestParameters) => { let results = cloneDeep(this.tasks); - results = results.filter((task) => { return !( (data.component && task.componentKey !== data.component) || @@ -115,12 +115,10 @@ export default class ComputeEngineServiceMock { }); if (data.onlyCurrents) { - /* - * This is more complex in real life, but it's a good enough approximation to suit tests - */ - results = Object.values(groupBy(results, (t) => t.componentKey)).map( - (tasks) => sortBy(tasks, (t) => t.executedAt).pop()! - ); + // This is more complex in real life, but it's a good enough approximation to suit tests. + results = Object.values(groupBy(results, (t) => t.componentKey)) + .map((tasks) => sortBy(tasks, (t) => t.executedAt).pop()) + .filter(isDefined); } const page = data.p ?? 1; diff --git a/server/sonar-web/src/main/js/app/components/StartupModal.tsx b/server/sonar-web/src/main/js/app/components/StartupModal.tsx index 127b7f6cead..50b97a2df13 100644 --- a/server/sonar-web/src/main/js/app/components/StartupModal.tsx +++ b/server/sonar-web/src/main/js/app/components/StartupModal.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import { showLicense } from '../../api/editions'; import LicensePromptModal from '../../apps/marketplace/components/LicensePromptModal'; import { Location, Router, withRouter } from '../../components/hoc/withRouter'; -import { parseDate, toShortNotSoISOString } from '../../helpers/dates'; +import { parseDate, toShortISO8601String } from '../../helpers/dates'; import { hasMessage } from '../../helpers/l10n'; import { get, save } from '../../helpers/storage'; import { AppState } from '../../types/appstate'; @@ -71,7 +71,7 @@ export class StartupModal extends React.PureComponent showLicense() .then((license) => { if (!license || !license.isValidEdition) { - save(LICENSE_PROMPT, toShortNotSoISOString(new Date()), currentUser.login); + save(LICENSE_PROMPT, toShortISO8601String(new Date()), currentUser.login); this.setState({ open: true }); } }) diff --git a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx index 5c36e772144..5908e9d4460 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx @@ -21,8 +21,9 @@ import { differenceInDays } from 'date-fns'; import { shallow, ShallowWrapper } from 'enzyme'; import * as React from 'react'; import { showLicense } from '../../../api/editions'; -import { toShortNotSoISOString } from '../../../helpers/dates'; +import { toShortISO8601String } from '../../../helpers/dates'; import { hasMessage } from '../../../helpers/l10n'; +import { mockLicense } from '../../../helpers/mocks/editions'; import { get, save } from '../../../helpers/storage'; import { mockAppState, mockLocation, mockRouter } from '../../../helpers/testMocks'; import { waitAndUpdate } from '../../../helpers/testUtils'; @@ -45,7 +46,7 @@ jest.mock('../../../helpers/l10n', () => ({ jest.mock('../../../helpers/dates', () => ({ parseDate: jest.fn().mockReturnValue('parsed-date'), - toShortNotSoISOString: jest.fn().mockReturnValue('short-not-iso-date'), + toShortISO8601String: jest.fn().mockReturnValue('short-not-iso-date'), })); jest.mock('date-fns', () => ({ differenceInDays: jest.fn().mockReturnValue(1) })); @@ -60,12 +61,12 @@ const LOGGED_IN_USER: LoggedInUser = { }; beforeEach(() => { - (differenceInDays as jest.Mock).mockClear(); - (hasMessage as jest.Mock).mockClear(); - (get as jest.Mock).mockClear(); - (save as jest.Mock).mockClear(); - (showLicense as jest.Mock).mockClear(); - (toShortNotSoISOString as jest.Mock).mockClear(); + jest.mocked(differenceInDays).mockClear(); + jest.mocked(hasMessage).mockClear(); + jest.mocked(get).mockClear(); + jest.mocked(save).mockClear(); + jest.mocked(showLicense).mockClear(); + jest.mocked(toShortISO8601String).mockClear(); }); it('should render only the children', async () => { @@ -76,14 +77,14 @@ it('should render only the children', async () => { await shouldNotHaveModals(getWrapper({ appState: mockAppState({ canAdmin: false }) })); - (hasMessage as jest.Mock).mockReturnValueOnce(false); + jest.mocked(hasMessage).mockReturnValueOnce(false); await shouldNotHaveModals(getWrapper()); - (showLicense as jest.Mock).mockResolvedValueOnce({ isValidEdition: true }); + jest.mocked(showLicense).mockResolvedValueOnce(mockLicense({ isValidEdition: true })); await shouldNotHaveModals(getWrapper()); - (get as jest.Mock).mockReturnValueOnce('date'); - (differenceInDays as jest.Mock).mockReturnValueOnce(0); + jest.mocked(get).mockReturnValueOnce('date'); + jest.mocked(differenceInDays).mockReturnValueOnce(0); await shouldNotHaveModals(getWrapper()); await shouldNotHaveModals( @@ -99,11 +100,11 @@ it('should render license prompt', async () => { await shouldDisplayLicense(getWrapper()); expect(save).toHaveBeenCalledWith('sonarqube.license.prompt', 'short-not-iso-date', 'luke'); - (get as jest.Mock).mockReturnValueOnce('date'); - (differenceInDays as jest.Mock).mockReturnValueOnce(1); + jest.mocked(get).mockReturnValueOnce('date'); + jest.mocked(differenceInDays).mockReturnValueOnce(1); await shouldDisplayLicense(getWrapper()); - (showLicense as jest.Mock).mockResolvedValueOnce({ isValidEdition: false }); + jest.mocked(showLicense).mockResolvedValueOnce(mockLicense({ isValidEdition: false })); await shouldDisplayLicense(getWrapper()); }); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx index 2db1eb701ce..e49c2ef8e12 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx @@ -21,8 +21,13 @@ import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { byLabelText, byPlaceholderText, byRole, byText } from 'testing-library-selector'; import ComputeEngineServiceMock from '../../../api/mocks/ComputeEngineServiceMock'; +import { parseDate } from '../../../helpers/dates'; import { mockAppState } from '../../../helpers/testMocks'; -import { RenderContext, renderAppWithAdminContext } from '../../../helpers/testReactTestingUtils'; +import { + RenderContext, + dateInputEvent, + renderAppWithAdminContext, +} from '../../../helpers/testReactTestingUtils'; import { EditionKey } from '../../../types/editions'; import { TaskStatuses, TaskTypes } from '../../../types/tasks'; import routes from '../routes'; @@ -87,6 +92,8 @@ describe('The Global background task page', () => { }); computeEngineServiceMock.addTask({ status: TaskStatuses.Pending, type: TaskTypes.IssueSync }); computeEngineServiceMock.addTask({ + executedAt: '2022-02-04T11:45:36+0200', + submittedAt: '2022-02-04T11:45:35+0200', componentKey: 'otherComponent', status: TaskStatuses.Success, type: TaskTypes.AppRefresh, @@ -99,25 +106,36 @@ describe('The Global background task page', () => { expect(ui.getAllRows()).toHaveLength(4); + // Status filter. await ui.changeTaskFilter('status', 'background_task.status.IN_PROGRESS'); expect(ui.getAllRows()).toHaveLength(1); await ui.changeTaskFilter('status', 'background_task.status.ALL'); expect(ui.getAllRows()).toHaveLength(5); + // Type filter. await ui.changeTaskFilter('type', `background_task.type.${TaskTypes.AppRefresh}`); expect(ui.getAllRows()).toHaveLength(3); + // Latest analysis. await user.click(ui.onlyLatestAnalysis.get()); expect(ui.getAllRows()).toHaveLength(2); - await user.click(ui.onlyLatestAnalysis.get()); - /* - * Must test date range filters, but it requires refactoring the DateRange component - */ + // Reset filters. + await user.click(ui.resetFilters.get()); + expect(ui.getAllRows()).toHaveLength(4); + + // Date filters. + await ui.setDateRange('2022-02-01', '2022-02-04'); + expect(ui.getAllRows()).toHaveLength(1); + + // Reset filters. + await user.click(ui.resetFilters.get()); + expect(ui.getAllRows()).toHaveLength(4); + + // Search. await user.click(ui.search.get()); await user.keyboard('other'); - expect(ui.getAllRows()).toHaveLength(1); // Reset filters @@ -298,6 +316,8 @@ function getPageObject() { numberOfWorkers: byText('background_tasks.number_of_workers'), onlyLatestAnalysis: byRole('checkbox', { name: 'yes' }), search: byPlaceholderText('background_tasks.search_by_task_or_component'), + fromDateInput: byRole('textbox', { name: 'start_date' }), + toDateInput: byRole('textbox', { name: 'end_date' }), resetFilters: byRole('button', { name: 'reset_verb' }), showMoreButton: byRole('button', { name: 'show_more' }), reloadButton: byRole('button', { name: 'reload' }), @@ -309,20 +329,33 @@ function getPageObject() { const ui = { ...selectors, - appLoaded: async () => { + async appLoaded() { await waitFor(() => { expect(selectors.loading.query()).not.toBeInTheDocument(); }); }, - getAllRows: () => screen.getAllByRole('row').slice(1), // Excludes header + getAllRows() { + return screen.getAllByRole('row').slice(1); // Excludes header + }, - changeTaskFilter: async (fieldLabel: string, value: string) => { + async changeTaskFilter(fieldLabel: string, value: string) { await user.click(screen.getByLabelText(fieldLabel, { selector: 'input' })); await user.click(screen.getByText(value)); }, - clickOnTaskAction: async (rowIndex: number, label: string) => { + async setDateRange(from?: string, to?: string) { + const dateInput = dateInputEvent(user); + if (from) { + await dateInput.pickDate(ui.fromDateInput.get(), parseDate(from)); + } + + if (to) { + await dateInput.pickDate(ui.toDateInput.get(), parseDate(to)); + } + }, + + async clickOnTaskAction(rowIndex: number, label: string) { const row = ui.getAllRows()[rowIndex]; expect(row).toBeVisible(); await user.click(within(row).getByRole('button', { name: 'background_tasks.show_actions' })); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx index 7d8d47d5f7c..bc27414d2e9 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx @@ -32,7 +32,7 @@ import ListFooter from '../../../components/controls/ListFooter'; import Suggestions from '../../../components/embed-docs-modal/Suggestions'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { toShortNotSoISOString } from '../../../helpers/dates'; +import { toShortISO8601String } from '../../../helpers/dates'; import { translate } from '../../../helpers/l10n'; import { parseAsDate } from '../../../helpers/query'; import { Task, TaskStatuses } from '../../../types/tasks'; @@ -162,11 +162,11 @@ export class BackgroundTasksApp extends React.PureComponent { }); if (nextQuery.minSubmittedAt) { - nextQuery.minSubmittedAt = toShortNotSoISOString(nextQuery.minSubmittedAt); + nextQuery.minSubmittedAt = toShortISO8601String(nextQuery.minSubmittedAt); } if (nextQuery.maxExecutedAt) { - nextQuery.maxExecutedAt = toShortNotSoISOString(nextQuery.maxExecutedAt); + nextQuery.maxExecutedAt = toShortISO8601String(nextQuery.maxExecutedAt); } this.props.router.push({ diff --git a/server/sonar-web/src/main/js/apps/background-tasks/utils.ts b/server/sonar-web/src/main/js/apps/background-tasks/utils.ts index 314b8cbf8ef..9007eafbb57 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/utils.ts +++ b/server/sonar-web/src/main/js/apps/background-tasks/utils.ts @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { toShortNotSoISOString } from '../../helpers/dates'; +import { toShortISO8601String } from '../../helpers/dates'; import { ActivityRequestParameters, Task, TaskStatuses } from '../../types/tasks'; import { ALL_TYPES, CURRENTS, STATUSES } from './constants'; @@ -65,11 +65,11 @@ export function mapFiltersToParameters(filters: Partial = {}) { } if (filters.minSubmittedAt) { - parameters.minSubmittedAt = toShortNotSoISOString(filters.minSubmittedAt); + parameters.minSubmittedAt = toShortISO8601String(filters.minSubmittedAt); } if (filters.maxExecutedAt) { - parameters.maxExecutedAt = toShortNotSoISOString(filters.maxExecutedAt); + parameters.maxExecutedAt = toShortISO8601String(filters.maxExecutedAt); } if (filters.query) { diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx index db2be496150..020ae38d128 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx @@ -34,7 +34,7 @@ import { getBranchLikeQuery, isMainBranch, } from '../../../helpers/branch-like'; -import { parseDate, toNotSoISOString } from '../../../helpers/dates'; +import { parseDate, toISO8601WithOffsetString } from '../../../helpers/dates'; import { enhanceConditionWithMeasure, enhanceMeasuresWithMetrics } from '../../../helpers/measures'; import { extractStatusConditionsFromApplicationStatusChildProject, @@ -78,7 +78,7 @@ export const BRANCH_OVERVIEW_ACTIVITY_GRAPH = 'sonar_branch_overview.graph'; export const NO_CI_DETECTED = 'undetected'; // Get all history data over the past year. -const FROM_DATE = toNotSoISOString(new Date().setFullYear(new Date().getFullYear() - 1)); +const FROM_DATE = toISO8601WithOffsetString(new Date().setFullYear(new Date().getFullYear() - 1)); export default class BranchOverview extends React.PureComponent { mounted = false; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.tsx index 130b24c6cfb..3e67f481a9a 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.tsx @@ -22,7 +22,7 @@ import { isEqual } from 'date-fns'; import * as React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; import DateFormatter from '../../../components/intl/DateFormatter'; -import { toShortNotSoISOString } from '../../../helpers/dates'; +import { toShortISO8601String } from '../../../helpers/dates'; import { translate } from '../../../helpers/l10n'; import { ComponentQualifier } from '../../../types/component'; import { ParsedAnalysis } from '../../../types/project-activity'; @@ -143,7 +143,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent (
  • diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx index 5ed0f7692b7..11012335e56 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx @@ -21,7 +21,7 @@ import { subDays } from 'date-fns'; import { throttle } from 'lodash'; import * as React from 'react'; import { getProjectActivity } from '../../../api/projectActivity'; -import { parseDate, toShortNotSoISOString } from '../../../helpers/dates'; +import { parseDate, toShortISO8601String } from '../../../helpers/dates'; import { Analysis, ParsedAnalysis } from '../../../types/project-activity'; import { Dict } from '../../../types/types'; import BranchAnalysisListRenderer from './BranchAnalysisListRenderer'; @@ -81,7 +81,7 @@ export default class BranchAnalysisList extends React.PureComponent { // If the selected analysis wasn't found in the default 30 days range, redo the search if (initial && analysis && !result.analyses.find((a) => a.key === analysis)) { diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisListRenderer.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisListRenderer.tsx index 8bb4bf17c76..94aa4bcf902 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisListRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisListRenderer.tsx @@ -26,7 +26,7 @@ import Tooltip from '../../../components/controls/Tooltip'; import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter'; import TimeFormatter from '../../../components/intl/TimeFormatter'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { parseDate, toShortNotSoISOString } from '../../../helpers/dates'; +import { parseDate, toShortISO8601String } from '../../../helpers/dates'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { ParsedAnalysis } from '../../../types/project-activity'; import Events from '../../projectActivity/components/Events'; @@ -173,7 +173,7 @@ function BranchAnalysisListRenderer( {days.map((day) => (
  • diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx index 6b94e209a34..1c1d8822ce6 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx @@ -22,7 +22,7 @@ import { setNewCodePeriod } from '../../../api/newCodePeriod'; import Modal from '../../../components/controls/Modal'; import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { toNotSoISOString } from '../../../helpers/dates'; +import { toISO8601WithOffsetString } from '../../../helpers/dates'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Branch, BranchWithNewCodePeriod } from '../../../types/branch-like'; import { ParsedAnalysis } from '../../../types/project-activity'; @@ -113,7 +113,7 @@ export default class BranchBaselineSettingModal extends React.PureComponent { diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx index 337f9dedef3..c8b0a57ac34 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx @@ -19,13 +19,13 @@ */ import * as React from 'react'; import { bulkApplyTemplate, getPermissionTemplates } from '../../api/permissions'; -import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons'; import Modal from '../../components/controls/Modal'; import Select from '../../components/controls/Select'; +import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons'; import { Alert } from '../../components/ui/Alert'; import MandatoryFieldMarker from '../../components/ui/MandatoryFieldMarker'; import MandatoryFieldsExplanation from '../../components/ui/MandatoryFieldsExplanation'; -import { toNotSoISOString } from '../../helpers/dates'; +import { toISO8601WithOffsetString } from '../../helpers/dates'; import { addGlobalErrorMessageFromAPI } from '../../helpers/globalMessages'; import { translate, translateWithParameters } from '../../helpers/l10n'; import { PermissionTemplate } from '../../types/types'; @@ -94,7 +94,7 @@ export default class BulkApplyTemplateModal extends React.PureComponent { projects: this.props.selection.join(), } : { - analyzedBefore: analyzedBefore && toNotSoISOString(analyzedBefore), + analyzedBefore: analyzedBefore && toISO8601WithOffsetString(analyzedBefore), onProvisionedOnly: this.props.provisioned || undefined, qualifiers: this.props.qualifier, q: this.props.query || undefined, diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx index 2f4a02b75b5..730d7539134 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx @@ -20,13 +20,13 @@ import { debounce, uniq, without } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; -import { getComponents, Project } from '../../api/components'; +import { Project, getComponents } from '../../api/components'; import { changeProjectDefaultVisibility } from '../../api/permissions'; import { getValue } from '../../api/settings'; import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext'; import ListFooter from '../../components/controls/ListFooter'; import Suggestions from '../../components/embed-docs-modal/Suggestions'; -import { toShortNotSoISOString } from '../../helpers/dates'; +import { toShortISO8601String } from '../../helpers/dates'; import { throwGlobalError } from '../../helpers/error'; import { translate } from '../../helpers/l10n'; import { hasGlobalPermission } from '../../helpers/users'; @@ -109,7 +109,7 @@ export class ProjectManagementApp extends React.PureComponent { requestProjects = () => { const { analyzedBefore } = this.state; const parameters = { - analyzedBefore: analyzedBefore && toShortNotSoISOString(analyzedBefore), + analyzedBefore: analyzedBefore && toShortISO8601String(analyzedBefore), onProvisionedOnly: this.state.provisioned || undefined, p: this.state.page !== 1 ? this.state.page : undefined, ps: PAGE_SIZE, diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx index 63863e966c9..2d99c5cb61a 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { getProfileChangelog } from '../../../api/quality-profiles'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { parseDate, toShortNotSoISOString } from '../../../helpers/dates'; +import { parseDate, toShortISO8601String } from '../../../helpers/dates'; import { translate } from '../../../helpers/l10n'; import { withQualityProfilesContext } from '../qualityProfilesContext'; import { Profile, ProfileChangelogEvent } from '../types'; @@ -117,8 +117,8 @@ export class ChangelogContainer extends React.PureComponent { handleDateRangeChange = ({ from, to }: { from?: Date; to?: Date }) => { const path = getProfileChangelogPath(this.props.profile.name, this.props.profile.language, { - since: from && toShortNotSoISOString(from), - to: to && toShortNotSoISOString(to), + since: from && toShortISO8601String(from), + to: to && toShortISO8601String(to), }); this.props.router.push(path); }; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx index 369672147cc..a087612cbe6 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx @@ -21,7 +21,7 @@ import { sortBy } from 'lodash'; import * as React from 'react'; import { searchRules } from '../../../api/rules'; import Link from '../../../components/common/Link'; -import { toShortNotSoISOString } from '../../../helpers/dates'; +import { toShortISO8601String } from '../../../helpers/dates'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; import { getRulesUrl } from '../../../helpers/urls'; @@ -54,7 +54,7 @@ export default class EvolutionRules extends React.PureComponent<{}, State> { this.state = {}; const startDate = new Date(); startDate.setFullYear(startDate.getFullYear() - 1); - this.periodStartDate = toShortNotSoISOString(startDate); + this.periodStartDate = toShortISO8601String(startDate); } componentDidMount() { diff --git a/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx index 19d64ecdc80..3b09b583951 100644 --- a/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; import { ClipboardButton } from '../../../components/controls/clipboard'; import { Alert } from '../../../components/ui/Alert'; -import { toShortNotSoISOString } from '../../../helpers/dates'; +import { toShortISO8601String } from '../../../helpers/dates'; import { translate } from '../../../helpers/l10n'; import { AppState } from '../../../types/appstate'; import PageActions from './PageActions'; @@ -86,7 +86,7 @@ function PageHeader(props: Props) { copyValue={`SonarQube ID information Server ID: ${serverId} Version: ${version} -Date: ${toShortNotSoISOString(Date.now())} +Date: ${toShortISO8601String(Date.now())} `} > {translate('system.copy_id_info')} diff --git a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx index 053cfe7003f..c17382ae7fd 100644 --- a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx +++ b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx @@ -29,7 +29,7 @@ import Select, { LabelValueSelectOption } from '../../components/controls/Select import Suggestions from '../../components/embed-docs-modal/Suggestions'; import { useManageProvider } from '../../components/hooks/useManageProvider'; import DeferredSpinner from '../../components/ui/DeferredSpinner'; -import { now, toNotSoISOString } from '../../helpers/dates'; +import { now, toISO8601WithOffsetString } from '../../helpers/dates'; import { translate } from '../../helpers/l10n'; import { IdentityProvider, Paging } from '../../types/types'; import { User } from '../../types/users'; @@ -59,16 +59,16 @@ export default function UsersApp() { switch (usersActivity) { case UserActivity.ActiveSonarLintUser: return { - slLastConnectedAfter: toNotSoISOString(nowDateMinus30Days), + slLastConnectedAfter: toISO8601WithOffsetString(nowDateMinus30Days), }; case UserActivity.ActiveSonarQubeUser: return { - lastConnectedAfter: toNotSoISOString(nowDateMinus30Days), - slLastConnectedBefore: toNotSoISOString(nowDateMinus30DaysAnd1Second), + lastConnectedAfter: toISO8601WithOffsetString(nowDateMinus30Days), + slLastConnectedBefore: toISO8601WithOffsetString(nowDateMinus30DaysAnd1Second), }; case UserActivity.InactiveUser: return { - lastConnectedBefore: toNotSoISOString(nowDateMinus30DaysAnd1Second), + lastConnectedBefore: toISO8601WithOffsetString(nowDateMinus30DaysAnd1Second), }; default: return {}; diff --git a/server/sonar-web/src/main/js/helpers/__tests__/dates-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/dates-test.ts index 0652223990f..fa8bec73b9c 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/dates-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/dates-test.ts @@ -22,12 +22,12 @@ import * as dates from '../dates'; const { parseDate } = dates; const recentDate = parseDate('2017-08-16T12:00:00.000Z'); -it('toShortNotSoISOString', () => { - expect(dates.toShortNotSoISOString(recentDate)).toBe('2017-08-16'); +it('toShortISO8601String', () => { + expect(dates.toShortISO8601String(recentDate)).toBe('2017-08-16'); }); -it('toNotSoISOString', () => { - expect(dates.toNotSoISOString(recentDate)).toBe('2017-08-16T12:00:00+0000'); +it('toISO8601WithOffsetString', () => { + expect(dates.toISO8601WithOffsetString(recentDate)).toBe('2017-08-16T12:00:00+0000'); }); it('isValidDate', () => { diff --git a/server/sonar-web/src/main/js/helpers/dates.ts b/server/sonar-web/src/main/js/helpers/dates.ts index 9b1ea096353..01785173308 100644 --- a/server/sonar-web/src/main/js/helpers/dates.ts +++ b/server/sonar-web/src/main/js/helpers/dates.ts @@ -30,15 +30,18 @@ export function parseDate(rawDate: ParsableDate): Date { return new Date(rawDate); } -export function toShortNotSoISOString(rawDate: ParsableDate): string { +export function toShortISO8601String(rawDate: ParsableDate): string { const date = parseDate(rawDate); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; } -export function toNotSoISOString(rawDate: ParsableDate): string { +export function toISO8601WithOffsetString(rawDate: ParsableDate): string { const date = parseDate(rawDate); - const dateWithoutZone = date.toISOString().split('.')[0]; - return dateWithoutZone + '+0000'; + // JS ISO Date implementation returns a datetime in UTC time (suffixed by "Z"). But the backend + // expects a datetime with a timeoffset (e.g., +0200). UTC time is actually "+0000", so we convert + // the string to this other format for the backend. The backend also doesn't expect milliseconds, so + // we truncate that part, too. + return date.toISOString().split('.')[0] + '+0000'; } export function isValidDate(date: Date): boolean { diff --git a/server/sonar-web/src/main/js/helpers/l10nBundle.ts b/server/sonar-web/src/main/js/helpers/l10nBundle.ts index 91a999f9268..13a42a9e2fa 100644 --- a/server/sonar-web/src/main/js/helpers/l10nBundle.ts +++ b/server/sonar-web/src/main/js/helpers/l10nBundle.ts @@ -20,7 +20,7 @@ import { fetchL10nBundle } from '../api/l10n'; import { L10nBundle, L10nBundleRequestParams } from '../types/l10nBundle'; import { Dict } from '../types/types'; -import { toNotSoISOString } from './dates'; +import { toISO8601WithOffsetString } from './dates'; const DEFAULT_LOCALE = 'en'; const DEFAULT_MESSAGES: Dict = { @@ -70,7 +70,7 @@ export async function loadL10nBundle() { }); const bundle = { - timestamp: toNotSoISOString(new Date()), + timestamp: toISO8601WithOffsetString(new Date()), locale: effectiveLocale, messages, }; diff --git a/server/sonar-web/src/main/js/helpers/query.ts b/server/sonar-web/src/main/js/helpers/query.ts index f2a00dc17ef..f332c3117a7 100644 --- a/server/sonar-web/src/main/js/helpers/query.ts +++ b/server/sonar-web/src/main/js/helpers/query.ts @@ -19,7 +19,7 @@ */ import { isEqual, isNil, omitBy } from 'lodash'; import { RawQuery } from '../types/types'; -import { isValidDate, parseDate, toNotSoISOString, toShortNotSoISOString } from './dates'; +import { isValidDate, parseDate, toISO8601WithOffsetString, toShortISO8601String } from './dates'; export function queriesEqual(a: RawQuery, b: RawQuery): boolean { const keysA = Object.keys(a); @@ -91,7 +91,10 @@ export function parseAsOptionalArray( return value ? parseAsArray(value, itemParser) : undefined; } -export function serializeDate(value?: Date, serializer = toNotSoISOString): string | undefined { +export function serializeDate( + value?: Date, + serializer = toISO8601WithOffsetString +): string | undefined { if (value != null) { return serializer(value); } @@ -99,7 +102,7 @@ export function serializeDate(value?: Date, serializer = toNotSoISOString): stri } export function serializeDateShort(value: Date | undefined): string | undefined { - return serializeDate(value, toShortNotSoISOString); + return serializeDate(value, toShortISO8601String); } export function serializeString(value: string | undefined): string | undefined { diff --git a/server/sonar-web/src/main/js/helpers/tokens.ts b/server/sonar-web/src/main/js/helpers/tokens.ts index 811716765f9..5852cfa8928 100644 --- a/server/sonar-web/src/main/js/helpers/tokens.ts +++ b/server/sonar-web/src/main/js/helpers/tokens.ts @@ -20,7 +20,7 @@ import { getAllValues } from '../api/settings'; import { SettingsKey } from '../types/settings'; import { TokenExpiration, UserToken } from '../types/token'; -import { now, toShortNotSoISOString } from './dates'; +import { now, toShortISO8601String } from './dates'; import { translate } from './l10n'; export const EXPIRATION_OPTIONS = [ @@ -67,7 +67,7 @@ export async function getAvailableExpirationOptions() { export function computeTokenExpirationDate(days: number) { const expirationDate = now(); expirationDate.setDate(expirationDate.getDate() + days); - return toShortNotSoISOString(expirationDate); + return toShortISO8601String(expirationDate); } export function getNextTokenName(tokenNameBase: string, tokens: UserToken[]) { -- 2.39.5