diff options
20 files changed, 401 insertions, 277 deletions
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx index 668cb99c8e4..d191cc46fb3 100644 --- a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx @@ -119,7 +119,6 @@ export default function AuditAppRenderer(props: AuditAppRendererProps) { </ul> <DateRangeInput - className="big-spacer-left" onChange={props.handleDateSelection} minDate={subDays(now(), HOUSEKEEPING_POLICY_VALUES[housekeepingPolicy])} maxDate={now()} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css b/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css index 5fb4b9ad748..f08c5d60023 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css +++ b/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css @@ -26,10 +26,6 @@ margin-left: 16px; } -.bt-search-form-label { - margin-bottom: 4px; -} - .bt-search-form-field { padding: 4px 0; } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.tsx index a6ab2a113b6..d9343452025 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.tsx @@ -22,25 +22,29 @@ import Checkbox from '../../../components/controls/Checkbox'; import { translate } from '../../../helpers/l10n'; import { CURRENTS } from '../constants'; -interface Props { +interface CurrentsFilterProps { value?: string; + id: string; onChange: (value: string) => void; } -export default class CurrentsFilter extends React.PureComponent<Props> { - handleChange = (value: boolean) => { - const newValue = value ? CURRENTS.ONLY_CURRENTS : CURRENTS.ALL; - this.props.onChange(newValue); - }; +export default function CurrentsFilter(props: CurrentsFilterProps) { + const { id, value, onChange } = props; + const checked = value === CURRENTS.ONLY_CURRENTS; - render() { - const checked = this.props.value === CURRENTS.ONLY_CURRENTS; - return ( - <div className="bt-search-form-field"> - <Checkbox checked={checked} onCheck={this.handleChange}> - <span className="little-spacer-left">{translate('yes')}</span> - </Checkbox> - </div> - ); - } + const handleChange = React.useCallback( + (value: boolean) => { + const newValue = value ? CURRENTS.ONLY_CURRENTS : CURRENTS.ALL; + onChange(newValue); + }, + [onChange] + ); + + return ( + <div className="bt-search-form-field"> + <Checkbox id={id} checked={checked} onCheck={handleChange}> + <span className="little-spacer-left">{translate('yes')}</span> + </Checkbox> + </div> + ); } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Search.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Search.tsx index 198317542dc..bede447ef59 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Search.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Search.tsx @@ -102,29 +102,49 @@ export default class Search extends React.PureComponent<Props> { <section className="big-spacer-top big-spacer-bottom"> <ul className="bt-search-form"> <li> - <h6 id="background-task-status-filter-label" className="bt-search-form-label"> - {translate('status')} - </h6> - <StatusFilter onChange={this.handleStatusChange} value={status} /> + <div className="display-flex-column"> + <label + id="background-task-status-filter-label" + className="text-bold little-spacer-bottom" + htmlFor="status-filter"> + {translate('status')} + </label> + <StatusFilter id="status-filter" onChange={this.handleStatusChange} value={status} /> + </div> </li> {types.length > 1 && ( <li> - <h6 id="background-task-type-filter-label" className="bt-search-form-label"> - {translate('type')} - </h6> - <TypesFilter onChange={this.handleTypeChange} types={types} value={taskType} /> + <div className="display-flex-column"> + <label + id="background-task-type-filter-label" + className="text-bold little-spacer-bottom" + htmlFor="types-filter"> + {translate('type')} + </label> + <TypesFilter + id="types-filter" + onChange={this.handleTypeChange} + types={types} + value={taskType} + /> + </div> </li> )} {!component && ( <li> - <h6 className="bt-search-form-label"> - {translate('background_tasks.currents_filter.ONLY_CURRENTS')} - </h6> - <CurrentsFilter onChange={this.handleCurrentsChange} value={currents} /> + <div className="display-flex-column"> + <label className="text-bold little-spacer-bottom" htmlFor="currents-filter"> + {translate('background_tasks.currents_filter.ONLY_CURRENTS')} + </label> + <CurrentsFilter + id="currents-filter" + onChange={this.handleCurrentsChange} + value={currents} + /> + </div> </li> )} <li> - <h6 className="bt-search-form-label">{translate('date')}</h6> <DateFilter maxExecutedAt={maxExecutedAt} minSubmittedAt={minSubmittedAt} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx index 6e248342f54..f797b433719 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx @@ -23,39 +23,44 @@ import { translate } from '../../../helpers/l10n'; import { TaskStatuses } from '../../../types/tasks'; import { STATUSES } from '../constants'; -interface Props { +interface StatusFilterProps { value?: string; + id: string; onChange: (value?: string) => void; } -export default class StatusFilter extends React.PureComponent<Props> { - handleChange = ({ value }: BasicSelectOption) => { - this.props.onChange(value); - }; +export default function StatusFilter(props: StatusFilterProps) { + const { id, value, onChange } = props; - render() { - const options: BasicSelectOption[] = [ - { value: STATUSES.ALL, label: translate('background_task.status.ALL') }, - { - value: STATUSES.ALL_EXCEPT_PENDING, - label: translate('background_task.status.ALL_EXCEPT_PENDING') - }, - { value: TaskStatuses.Pending, label: translate('background_task.status.PENDING') }, - { value: TaskStatuses.InProgress, label: translate('background_task.status.IN_PROGRESS') }, - { value: TaskStatuses.Success, label: translate('background_task.status.SUCCESS') }, - { value: TaskStatuses.Failed, label: translate('background_task.status.FAILED') }, - { value: TaskStatuses.Canceled, label: translate('background_task.status.CANCELED') } - ]; + const options: BasicSelectOption[] = [ + { value: STATUSES.ALL, label: translate('background_task.status.ALL') }, + { + value: STATUSES.ALL_EXCEPT_PENDING, + label: translate('background_task.status.ALL_EXCEPT_PENDING') + }, + { value: TaskStatuses.Pending, label: translate('background_task.status.PENDING') }, + { value: TaskStatuses.InProgress, label: translate('background_task.status.IN_PROGRESS') }, + { value: TaskStatuses.Success, label: translate('background_task.status.SUCCESS') }, + { value: TaskStatuses.Failed, label: translate('background_task.status.FAILED') }, + { value: TaskStatuses.Canceled, label: translate('background_task.status.CANCELED') } + ]; - return ( - <Select - aria-labelledby="background-task-status-filter-label" - className="input-medium" - onChange={this.handleChange} - options={options} - value={options.find(o => o.value === this.props.value)} - isSearchable={false} - /> - ); - } + const handleChange = React.useCallback( + ({ value }: BasicSelectOption) => { + onChange(value); + }, + [onChange] + ); + + return ( + <Select + aria-labelledby="background-task-status-filter-label" + className="input-medium" + id={id} + onChange={handleChange} + options={options} + value={options.find(o => o.value === value)} + isSearchable={false} + /> + ); } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx index b84b2150539..4ef1522f76d 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx @@ -24,6 +24,7 @@ import { ALL_TYPES } from '../constants'; interface Props { value: string; + id: string; onChange: Function; types: string[]; } @@ -34,7 +35,7 @@ export default class TypesFilter extends React.PureComponent<Props> { }; render() { - const { value, types } = this.props; + const { value, types, id } = this.props; const options = types.map(t => { return { value: t, @@ -51,6 +52,7 @@ export default class TypesFilter extends React.PureComponent<Props> { <Select aria-labelledby="background-task-type-filter-label" className="input-large" + id={id} isClearable={false} onChange={this.handleChange} options={allOptions} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx index b1fa4fa47ad..fe2c2d21886 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx @@ -29,7 +29,7 @@ import { Query } from '../utils'; import './projectActivity.css'; import ProjectActivityAnalysesList from './ProjectActivityAnalysesList'; import ProjectActivityGraphs from './ProjectActivityGraphs'; -import ProjectActivityPageHeader from './ProjectActivityPageHeader'; +import ProjectActivityPageFilters from './ProjectActivityPageFilters'; interface Props { addCustomEvent: (analysis: string, name: string, category?: string) => Promise<void>; @@ -62,7 +62,7 @@ export default function ProjectActivityApp(props: Props) { <A11ySkipTarget anchor="activity_main" /> - <ProjectActivityPageHeader + <ProjectActivityPageFilters category={query.category} from={query.from} project={props.project} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.tsx index e8ef04cf8c9..03fa34586d9 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.tsx @@ -40,7 +40,7 @@ export default class ProjectActivityDateInput extends React.PureComponent<Props> render() { return ( - <div> + <div className="display-flex-end"> <DateRangeInput onChange={this.handleChange} value={{ from: this.props.from, to: this.props.to }} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFilters.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFilters.tsx new file mode 100644 index 00000000000..4e6dd15c6f8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFilters.tsx @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import Select from '../../../components/controls/Select'; +import { translate } from '../../../helpers/l10n'; +import { ComponentQualifier } from '../../../types/component'; +import { Component } from '../../../types/types'; +import { APPLICATION_EVENT_TYPES, EVENT_TYPES, Query } from '../utils'; +import ProjectActivityDateInput from './ProjectActivityDateInput'; + +interface ProjectActivityPageFiltersProps { + category?: string; + from?: Date; + project: Pick<Component, 'qualifier'>; + to?: Date; + updateQuery: (changes: Partial<Query>) => void; +} + +export default function ProjectActivityPageFilters(props: ProjectActivityPageFiltersProps) { + const { project, category, from, to, updateQuery } = props; + + const isApp = project.qualifier === ComponentQualifier.Application; + const eventTypes = isApp ? APPLICATION_EVENT_TYPES : EVENT_TYPES; + const options = eventTypes.map(category => ({ + label: translate('event.category', category), + value: category + })); + + const handleCategoryChange = React.useCallback( + (option: { value: string } | null) => { + updateQuery({ category: option ? option.value : '' }); + }, + [updateQuery] + ); + + return ( + <div className="page-header display-flex-start"> + {!([ComponentQualifier.Portfolio, ComponentQualifier.SubPortfolio] as string[]).includes( + project.qualifier + ) && ( + <div className="display-flex-column big-spacer-right"> + <label className="text-bold little-spacer-bottom" htmlFor="filter-events"> + {translate('project_activity.filter_events')} + </label> + <Select + className={isApp ? 'input-large' : 'input-medium'} + id="filter-events" + isClearable={true} + isSearchable={false} + onChange={handleCategoryChange} + options={options} + value={options.filter(o => o.value === category)} + /> + </div> + )} + <ProjectActivityDateInput from={from} onChange={props.updateQuery} to={to} /> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.tsx deleted file mode 100644 index 57f22d99298..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import Select from '../../../components/controls/Select'; -import { translate } from '../../../helpers/l10n'; -import { Component } from '../../../types/types'; -import { APPLICATION_EVENT_TYPES, EVENT_TYPES, Query } from '../utils'; -import ProjectActivityDateInput from './ProjectActivityDateInput'; - -interface Props { - category?: string; - from?: Date; - project: Pick<Component, 'qualifier'>; - to?: Date; - updateQuery: (changes: Partial<Query>) => void; -} - -export default class ProjectActivityPageHeader extends React.PureComponent<Props> { - handleCategoryChange = (option: { value: string } | null) => - this.props.updateQuery({ category: option ? option.value : '' }); - - render() { - const isApp = this.props.project.qualifier === 'APP'; - const eventTypes = isApp ? APPLICATION_EVENT_TYPES : EVENT_TYPES; - const options = eventTypes.map(category => ({ - label: translate('event.category', category), - value: category - })); - - return ( - <header className="page-header"> - {!['VW', 'SVW'].includes(this.props.project.qualifier) && ( - <Select - className={classNames('pull-left big-spacer-right', { - 'input-medium': !isApp, - 'input-large': isApp - })} - placeholder={translate('project_activity.filter_events') + '...'} - isClearable={true} - isSearchable={false} - onChange={this.handleCategoryChange} - options={options} - value={options.filter(o => o.value === this.props.category)} - /> - )} - <ProjectActivityDateInput - from={this.props.from} - onChange={this.props.updateQuery} - to={this.props.to} - /> - </header> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityPageHeader-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityPageFilters-test.tsx index 209ef357749..54beadc9fc6 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityPageHeader-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityPageFilters-test.tsx @@ -20,12 +20,12 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { parseDate } from '../../../../helpers/dates'; -import ProjectActivityPageHeader from '../ProjectActivityPageHeader'; +import ProjectActivityPageFilters from '../ProjectActivityPageFilters'; it('should render correctly the list of series', () => { expect( shallow( - <ProjectActivityPageHeader + <ProjectActivityPageFilters category="" from={parseDate('2016-10-27T12:21:15+0200')} project={{ qualifier: 'TRK' }} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.tsx.snap index bcb41a66916..3f89e7becc1 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.tsx.snap @@ -17,7 +17,7 @@ exports[`should render correctly 1`] = ` <A11ySkipTarget anchor="activity_main" /> - <ProjectActivityPageHeader + <ProjectActivityPageFilters category="" project={ Object { diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.tsx.snap index d10a64ba95c..7ebe7e54805 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.tsx.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render correctly the date inputs 1`] = ` -<div> +<div + className="display-flex-end" +> <DateRangeInput onChange={[Function]} value={ diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageFilters-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageFilters-test.tsx.snap new file mode 100644 index 00000000000..56f0a23bc2f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageFilters-test.tsx.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly the list of series 1`] = ` +<div + className="page-header display-flex-start" +> + <div + className="display-flex-column big-spacer-right" + > + <label + className="text-bold little-spacer-bottom" + htmlFor="filter-events" + > + project_activity.filter_events + </label> + <Select + className="input-medium" + id="filter-events" + isClearable={true} + isSearchable={false} + onChange={[Function]} + options={ + Array [ + Object { + "label": "event.category.VERSION", + "value": "VERSION", + }, + Object { + "label": "event.category.QUALITY_GATE", + "value": "QUALITY_GATE", + }, + Object { + "label": "event.category.QUALITY_PROFILE", + "value": "QUALITY_PROFILE", + }, + Object { + "label": "event.category.OTHER", + "value": "OTHER", + }, + ] + } + value={Array []} + /> + </div> + <ProjectActivityDateInput + from={2016-10-27T10:21:15.000Z} + onChange={[Function]} + /> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageHeader-test.tsx.snap deleted file mode 100644 index 5659e62e451..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageHeader-test.tsx.snap +++ /dev/null @@ -1,40 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly the list of series 1`] = ` -<header - className="page-header" -> - <Select - className="pull-left big-spacer-right input-medium" - isClearable={true} - isSearchable={false} - onChange={[Function]} - options={ - Array [ - Object { - "label": "event.category.VERSION", - "value": "VERSION", - }, - Object { - "label": "event.category.QUALITY_GATE", - "value": "QUALITY_GATE", - }, - Object { - "label": "event.category.QUALITY_PROFILE", - "value": "QUALITY_PROFILE", - }, - Object { - "label": "event.category.OTHER", - "value": "OTHER", - }, - ] - } - placeholder="project_activity.filter_events..." - value={Array []} - /> - <ProjectActivityDateInput - from={2016-10-27T10:21:15.000Z} - onChange={[Function]} - /> -</header> -`; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx index 9b34dcf77f6..0a59ac50f23 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx @@ -22,21 +22,20 @@ import { Button } from '../../../components/controls/buttons'; import DateRangeInput from '../../../components/controls/DateRangeInput'; import { translate } from '../../../helpers/l10n'; -interface Props { +interface ChangelogSearchProps { dateRange: { from?: Date; to?: Date } | undefined; onDateRangeChange: (range: { from?: Date; to?: Date }) => void; onReset: () => void; } -export default class ChangelogSearch extends React.PureComponent<Props> { - render() { - return ( - <div className="display-inline-block" id="quality-profile-changelog-form"> - <DateRangeInput onChange={this.props.onDateRangeChange} value={this.props.dateRange} /> - <Button className="spacer-left text-top" onClick={this.props.onReset}> - {translate('reset_verb')} - </Button> - </div> - ); - } +export default function ChangelogSearch(props: ChangelogSearchProps) { + const { dateRange } = props; + return ( + <div className="display-flex-end" id="quality-profile-changelog-form"> + <DateRangeInput onChange={props.onDateRangeChange} value={dateRange} /> + <Button className="spacer-left text-top" onClick={props.onReset}> + {translate('reset_verb')} + </Button> + </div> + ); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/__snapshots__/ChangelogSearch-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/__snapshots__/ChangelogSearch-test.tsx.snap index 457151fcb76..91386121e4e 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/__snapshots__/ChangelogSearch-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/__snapshots__/ChangelogSearch-test.tsx.snap @@ -2,7 +2,7 @@ exports[`should render 1`] = ` <div - className="display-inline-block" + className="display-flex-end" id="quality-profile-changelog-form" > <DateRangeInput diff --git a/server/sonar-web/src/main/js/components/controls/DateInput.tsx b/server/sonar-web/src/main/js/components/controls/DateInput.tsx index 9ed6b165b10..166d7717aee 100644 --- a/server/sonar-web/src/main/js/components/controls/DateInput.tsx +++ b/server/sonar-web/src/main/js/components/controls/DateInput.tsx @@ -47,6 +47,7 @@ interface Props { maxDate?: Date; minDate?: Date; name?: string; + id?: string; onChange: (date: Date | undefined) => void; placeholder: string; value?: Date; @@ -128,6 +129,7 @@ export default class DateInput extends React.PureComponent<Props, State> { name, className, inputClassName, + id, placeholder } = this.props; const { lastHovered, currentMonth, open } = this.state; @@ -166,6 +168,7 @@ export default class DateInput extends React.PureComponent<Props, State> { className={classNames('date-input-control-input', inputClassName, { 'is-filled': value !== undefined })} + id={id} innerRef={(node: HTMLInputElement | null) => (this.input = node)} name={name} onFocus={this.openCalendar} diff --git a/server/sonar-web/src/main/js/components/controls/DateRangeInput.tsx b/server/sonar-web/src/main/js/components/controls/DateRangeInput.tsx index 4d3aee13173..a2f13e9b635 100644 --- a/server/sonar-web/src/main/js/components/controls/DateRangeInput.tsx +++ b/server/sonar-web/src/main/js/components/controls/DateRangeInput.tsx @@ -63,29 +63,43 @@ export default class DateRangeInput extends React.PureComponent<Props> { const { minDate, maxDate } = this.props; return ( - <div className={classNames('display-inline-flex-center', this.props.className)}> - <DateInput - currentMonth={this.to} - data-test="from" - highlightTo={this.to} - minDate={minDate} - maxDate={maxDate && this.to ? min([maxDate, this.to]) : maxDate || this.to} - onChange={this.handleFromChange} - placeholder={translate('start_date')} - value={this.from} - /> - <span className="note little-spacer-left little-spacer-right">{translate('to_')}</span> - <DateInput - currentMonth={this.from} - data-test="to" - highlightFrom={this.from} - minDate={minDate && this.from ? max([minDate, this.from]) : minDate || this.from} - maxDate={maxDate} - onChange={this.handleToChange} - placeholder={translate('end_date')} - ref={element => (this.toDateInput = element)} - value={this.to} - /> + <div className={classNames('display-flex-end', this.props.className)}> + <div className="display-flex-column"> + <label className="text-bold little-spacer-bottom" htmlFor="date-from"> + {translate('start_date')} + </label> + <DateInput + currentMonth={this.to} + data-test="from" + id="date-from" + highlightTo={this.to} + minDate={minDate} + maxDate={maxDate && this.to ? min([maxDate, this.to]) : maxDate || this.to} + onChange={this.handleFromChange} + placeholder={translate('start_date')} + value={this.from} + /> + </div> + <span className="note little-spacer-left little-spacer-right little-spacer-bottom"> + {translate('to_')} + </span> + <div className="display-flex-column"> + <label className="text-bold little-spacer-bottom" htmlFor="date-to"> + {translate('end_date')} + </label> + <DateInput + currentMonth={this.from} + data-test="to" + id="date-to" + highlightFrom={this.from} + minDate={minDate && this.from ? max([minDate, this.from]) : minDate || this.from} + maxDate={maxDate} + onChange={this.handleToChange} + placeholder={translate('end_date')} + ref={element => (this.toDateInput = element)} + value={this.to} + /> + </div> </div> ); } diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateRangeInput-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateRangeInput-test.tsx.snap index dbc2f7cb5a2..19e4e6ab487 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateRangeInput-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateRangeInput-test.tsx.snap @@ -2,88 +2,154 @@ exports[`should render 1`] = ` <div - className="display-inline-flex-center" + className="display-flex-end" > - <DateInput - currentMonth={2018-02-05T00:00:00.000Z} - data-test="from" - highlightTo={2018-02-05T00:00:00.000Z} - maxDate={2018-02-05T00:00:00.000Z} - onChange={[Function]} - placeholder="start_date" - value={2018-01-17T00:00:00.000Z} - /> + <div + className="display-flex-column" + > + <label + className="text-bold little-spacer-bottom" + htmlFor="date-from" + > + start_date + </label> + <DateInput + currentMonth={2018-02-05T00:00:00.000Z} + data-test="from" + highlightTo={2018-02-05T00:00:00.000Z} + id="date-from" + maxDate={2018-02-05T00:00:00.000Z} + onChange={[Function]} + placeholder="start_date" + value={2018-01-17T00:00:00.000Z} + /> + </div> <span - className="note little-spacer-left little-spacer-right" + className="note little-spacer-left little-spacer-right little-spacer-bottom" > to_ </span> - <DateInput - currentMonth={2018-01-17T00:00:00.000Z} - data-test="to" - highlightFrom={2018-01-17T00:00:00.000Z} - minDate={2018-01-17T00:00:00.000Z} - onChange={[Function]} - placeholder="end_date" - value={2018-02-05T00:00:00.000Z} - /> + <div + className="display-flex-column" + > + <label + className="text-bold little-spacer-bottom" + htmlFor="date-to" + > + end_date + </label> + <DateInput + currentMonth={2018-01-17T00:00:00.000Z} + data-test="to" + highlightFrom={2018-01-17T00:00:00.000Z} + id="date-to" + minDate={2018-01-17T00:00:00.000Z} + onChange={[Function]} + placeholder="end_date" + value={2018-02-05T00:00:00.000Z} + /> + </div> </div> `; exports[`should render: with min/max 1`] = ` <div - className="display-inline-flex-center" + className="display-flex-end" > - <DateInput - data-test="from" - maxDate={2018-02-05T00:00:00.000Z} - minDate={2018-01-17T00:00:00.000Z} - onChange={[Function]} - placeholder="start_date" - /> + <div + className="display-flex-column" + > + <label + className="text-bold little-spacer-bottom" + htmlFor="date-from" + > + start_date + </label> + <DateInput + data-test="from" + id="date-from" + maxDate={2018-02-05T00:00:00.000Z} + minDate={2018-01-17T00:00:00.000Z} + onChange={[Function]} + placeholder="start_date" + /> + </div> <span - className="note little-spacer-left little-spacer-right" + className="note little-spacer-left little-spacer-right little-spacer-bottom" > to_ </span> - <DateInput - data-test="to" - maxDate={2018-02-05T00:00:00.000Z} - minDate={2018-01-17T00:00:00.000Z} - onChange={[Function]} - placeholder="end_date" - /> + <div + className="display-flex-column" + > + <label + className="text-bold little-spacer-bottom" + htmlFor="date-to" + > + end_date + </label> + <DateInput + data-test="to" + id="date-to" + maxDate={2018-02-05T00:00:00.000Z} + minDate={2018-01-17T00:00:00.000Z} + onChange={[Function]} + placeholder="end_date" + /> + </div> </div> `; exports[`should render: with min/max and value 1`] = ` <div - className="display-inline-flex-center" + className="display-flex-end" > - <DateInput - currentMonth={2018-02-05T00:00:00.000Z} - data-test="from" - highlightTo={2018-02-05T00:00:00.000Z} - maxDate={2018-02-05T00:00:00.000Z} - minDate={2018-01-17T00:00:00.000Z} - onChange={[Function]} - placeholder="start_date" - value={2018-01-17T00:00:00.000Z} - /> + <div + className="display-flex-column" + > + <label + className="text-bold little-spacer-bottom" + htmlFor="date-from" + > + start_date + </label> + <DateInput + currentMonth={2018-02-05T00:00:00.000Z} + data-test="from" + highlightTo={2018-02-05T00:00:00.000Z} + id="date-from" + maxDate={2018-02-05T00:00:00.000Z} + minDate={2018-01-17T00:00:00.000Z} + onChange={[Function]} + placeholder="start_date" + value={2018-01-17T00:00:00.000Z} + /> + </div> <span - className="note little-spacer-left little-spacer-right" + className="note little-spacer-left little-spacer-right little-spacer-bottom" > to_ </span> - <DateInput - currentMonth={2018-01-17T00:00:00.000Z} - data-test="to" - highlightFrom={2018-01-17T00:00:00.000Z} - maxDate={2018-02-05T00:00:00.000Z} - minDate={2018-01-17T00:00:00.000Z} - onChange={[Function]} - placeholder="end_date" - value={2018-02-05T00:00:00.000Z} - /> + <div + className="display-flex-column" + > + <label + className="text-bold little-spacer-bottom" + htmlFor="date-to" + > + end_date + </label> + <DateInput + currentMonth={2018-01-17T00:00:00.000Z} + data-test="to" + highlightFrom={2018-01-17T00:00:00.000Z} + id="date-to" + maxDate={2018-02-05T00:00:00.000Z} + minDate={2018-01-17T00:00:00.000Z} + onChange={[Function]} + placeholder="end_date" + value={2018-02-05T00:00:00.000Z} + /> + </div> </div> `; |