aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>2024-01-17 16:33:08 +0100
committersonartech <sonartech@sonarsource.com>2024-01-19 20:02:55 +0000
commit792921dfe50178050aa591ebcd4d7f62325684f0 (patch)
tree29aa2970a4d5d93729fbe5eb75542462747abee6
parente608a535ede1786dffff2b1771b5ff7bb13ee15b (diff)
downloadsonarqube-792921dfe50178050aa591ebcd4d7f62325684f0.tar.gz
sonarqube-792921dfe50178050aa591ebcd4d7f62325684f0.zip
SONAR-21420 Migrate audit logs page to new UI
-rw-r--r--server/sonar-web/design-system/src/components/buttons/Button.tsx15
-rw-r--r--server/sonar-web/design-system/src/components/buttons/__tests__/ButtonPrimary-test.tsx36
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx143
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/components/DownloadButton.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx45
6 files changed, 171 insertions, 84 deletions
diff --git a/server/sonar-web/design-system/src/components/buttons/Button.tsx b/server/sonar-web/design-system/src/components/buttons/Button.tsx
index 80e58555a0b..6d17cd784f2 100644
--- a/server/sonar-web/design-system/src/components/buttons/Button.tsx
+++ b/server/sonar-web/design-system/src/components/buttons/Button.tsx
@@ -149,6 +149,21 @@ export const buttonStyle = (props: ThemedProps) => css`
const BaseButtonLink = styled(BaseLink)`
${buttonStyle}
+
+ /*
+ Workaround to apply disable style to button-link
+ as link does not have disabled attribute, using props instead
+ */
+
+ ${({ disabled, theme }) =>
+ disabled
+ ? `&, &:hover, &:focus, &:active {
+ color: ${themeContrast('buttonDisabled')({ theme })};
+ background-color: ${themeColor('buttonDisabled')({ theme })};
+ border: ${themeBorder('default', 'buttonDisabledBorder')({ theme })};
+ cursor: not-allowed;
+ }`
+ : undefined};
`;
const BaseButton = styled.button`
diff --git a/server/sonar-web/design-system/src/components/buttons/__tests__/ButtonPrimary-test.tsx b/server/sonar-web/design-system/src/components/buttons/__tests__/ButtonPrimary-test.tsx
new file mode 100644
index 00000000000..af81f1dd7d1
--- /dev/null
+++ b/server/sonar-web/design-system/src/components/buttons/__tests__/ButtonPrimary-test.tsx
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { render, screen } from '@testing-library/react';
+import { ButtonPrimary } from '../ButtonPrimary';
+
+it('renders ButtonPrimary correctly', () => {
+ render(<ButtonPrimary>Hello</ButtonPrimary>);
+ expect(screen.getByRole('button', { name: 'Hello' })).toBeInTheDocument();
+});
+
+it('renders ButtonPrimary correctly when to is defined', () => {
+ render(
+ <ButtonPrimary download="http://link.com" to="http://link.com">
+ Hello
+ </ButtonPrimary>,
+ );
+ expect(screen.queryByRole('button', { name: 'Hello' })).not.toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'Hello' })).toBeInTheDocument();
+});
diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
index 9e7b58e11f7..f08f3ccb9b1 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
@@ -83,6 +83,7 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = [
'/admin/users',
'/admin/settings/encryption',
'/admin/extension/license/support',
+ '/admin/audit',
];
export default function GlobalContainer() {
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 84c58aa20f8..7242e023fd0 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
@@ -17,13 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { subDays } from 'date-fns';
+import {
+ DateRangePicker,
+ LargeCenteredLayout,
+ Link,
+ PageContentFontWrapper,
+ PopupZLevel,
+ RadioButton,
+ Title,
+} from 'design-system';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { FormattedMessage } from 'react-intl';
-import Link from '../../../components/common/Link';
-import DateRangeInput from '../../../components/controls/DateRangeInput';
-import Radio from '../../../components/controls/Radio';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import { now } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
@@ -72,68 +79,74 @@ export default function AuditAppRenderer(props: AuditAppRendererProps) {
const { dateRange, downloadStarted, housekeepingPolicy, selection } = props;
return (
- <main className="page page-limited" id="marketplace-page">
- <Suggestions suggestions="audit-logs" />
- <Helmet title={translate('audit_logs.page')} />
-
- <header className="page-header">
- <h2 className="page-title">{translate('audit_logs.page')}</h2>
- </header>
-
- <p className="big-spacer-bottom">
- {translate('audit_logs.page.description.1')}
- <br />
- <FormattedMessage
- id="audit_logs.page.description.2"
- defaultMessage={translate('audit_logs.page.description.2')}
- values={{
- housekeeping: translate('audit_logs.housekeeping_policy', housekeepingPolicy),
- link: (
- <Link
- to={{
- pathname: '/admin/settings',
- search: queryToSearch({ category: 'housekeeping' }),
- hash: '#auditLogs',
- }}
- >
- {translate('audit_logs.page.description.link')}
- </Link>
- ),
- }}
- />
- </p>
-
- <div className="huge-spacer-bottom">
- <h3 className="big-spacer-bottom">{translate('audit_logs.download')}</h3>
-
- <ul>
- {getRangeOptions(housekeepingPolicy).map((option) => (
- <li key={option} className="spacer-bottom">
- <Radio
- checked={selection === option}
- onCheck={props.handleOptionSelection}
- value={option}
- >
- {translate('audit_logs.range_option', option)}
- </Radio>
- </li>
- ))}
- </ul>
-
- <DateRangeInput
- onChange={props.handleDateSelection}
- minDate={subDays(now(), HOUSEKEEPING_POLICY_VALUES[housekeepingPolicy])}
- maxDate={now()}
- value={dateRange}
+ <LargeCenteredLayout as="main" id="audit-logs-page">
+ <PageContentFontWrapper className="sw-body-sm sw-my-8">
+ <Suggestions suggestions="audit-logs" />
+ <Helmet title={translate('audit_logs.page')} />
+
+ <Title>{translate('audit_logs.page')}</Title>
+
+ <p className="sw-mb-4">
+ {translate('audit_logs.page.description.1')}
+ <br />
+ <FormattedMessage
+ id="audit_logs.page.description.2"
+ defaultMessage={translate('audit_logs.page.description.2')}
+ values={{
+ housekeeping: translate('audit_logs.housekeeping_policy', housekeepingPolicy),
+ link: (
+ <Link
+ to={{
+ pathname: '/admin/settings',
+ search: queryToSearch({ category: 'housekeeping' }),
+ hash: '#auditLogs',
+ }}
+ >
+ {translate('audit_logs.page.description.link')}
+ </Link>
+ ),
+ }}
+ />
+ </p>
+
+ <div className="sw-mb-6">
+ <h3 className="sw-mb-4">{translate('audit_logs.download')}</h3>
+
+ <ul>
+ {getRangeOptions(housekeepingPolicy).map((option) => (
+ <li key={option} className="sw-mb-2">
+ <RadioButton
+ checked={selection === option}
+ onCheck={props.handleOptionSelection}
+ value={option}
+ >
+ {translate('audit_logs.range_option', option)}
+ </RadioButton>
+ </li>
+ ))}
+ </ul>
+
+ <DateRangePicker
+ className="sw-w-abs-350 sw-mt-4"
+ clearButtonLabel={translate('clear')}
+ fromLabel={translate('start_date')}
+ onChange={props.handleDateSelection}
+ separatorText={translate('to_')}
+ toLabel={translate('end_date')}
+ value={dateRange}
+ minDate={subDays(now(), HOUSEKEEPING_POLICY_VALUES[housekeepingPolicy])}
+ maxDate={now()}
+ zLevel={PopupZLevel.Content}
+ />
+ </div>
+
+ <DownloadButton
+ dateRange={dateRange}
+ downloadStarted={downloadStarted}
+ onStartDownload={props.handleStartDownload}
+ selection={selection}
/>
- </div>
-
- <DownloadButton
- dateRange={dateRange}
- downloadStarted={downloadStarted}
- onStartDownload={props.handleStartDownload}
- selection={selection}
- />
- </main>
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/DownloadButton.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/DownloadButton.tsx
index 132960224bc..718bc6ee853 100644
--- a/server/sonar-web/src/main/js/apps/audit-logs/components/DownloadButton.tsx
+++ b/server/sonar-web/src/main/js/apps/audit-logs/components/DownloadButton.tsx
@@ -17,8 +17,8 @@
* 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 { endOfDay, startOfDay, subDays } from 'date-fns';
+import { ButtonPrimary } from 'design-system/lib';
import * as React from 'react';
import { now } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
@@ -63,7 +63,7 @@ function getRangeParams(selection: RangeOption, dateRange?: { from?: Date; to?:
}).toString();
}
-export default function DownloadButton(props: DownloadButtonProps) {
+export default function DownloadButton(props: Readonly<DownloadButtonProps>) {
const { dateRange, downloadStarted, selection } = props;
const downloadDisabled =
@@ -77,20 +77,19 @@ export default function DownloadButton(props: DownloadButtonProps) {
return (
<>
- <a
- className={classNames('button button-primary', { disabled: downloadDisabled })}
+ <ButtonPrimary
download="audit_logs.json"
+ disabled={downloadDisabled}
aria-disabled={downloadDisabled}
onClick={downloadDisabled ? undefined : props.onStartDownload}
- href={downloadUrl}
- rel="noopener noreferrer"
+ to={downloadUrl}
target="_blank"
>
{translate('download_verb')}
- </a>
+ </ButtonPrimary>
{downloadStarted && (
- <div className="spacer-top">
+ <div className="sw-mt-2">
<p>{translate('audit_logs.download_start.sentence.1')}</p>
<p>{translate('audit_logs.download_start.sentence.2')}</p>
<br />
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx
index 83ad423c2ce..31bd0f534ac 100644
--- a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx
@@ -17,14 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { getDate, getMonth, getYear, subDays } from 'date-fns';
import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock';
import { now } from '../../../../helpers/dates';
-import { getMonthName } from '../../../../helpers/l10n';
+import { getShortMonthName } from '../../../../helpers/l10n';
import { renderAppWithAdminContext } from '../../../../helpers/testReactTestingUtils';
-import { byPlaceholderText, byRole, byText } from '../../../../helpers/testSelector';
+import { byPlaceholderText, byRole, byTestId, byText } from '../../../../helpers/testSelector';
import { AdminPageExtension } from '../../../../types/extension';
import { SettingsKey } from '../../../../types/settings';
import routes from '../../routes';
@@ -64,8 +63,8 @@ const ui = {
downloadSentenceStart: byText('audit_logs.download_start.sentence.1'),
startDateInput: byPlaceholderText('start_date'),
endDateInput: byPlaceholderText('end_date'),
- dateInputMonthSelect: byRole('combobox', { name: 'Month:' }),
- dateInputYearSelect: byRole('combobox', { name: 'Year:' }),
+ dateInputMonthSelect: byTestId('month-select'),
+ dateInputYearSelect: byTestId('year-select'),
};
let handler: SettingsServiceMock;
@@ -113,15 +112,39 @@ it('should handle download button click', async () => {
await user.click(ui.customRadio.get());
expect(ui.downloadButton.get()).toHaveAttribute('aria-disabled', 'true');
await user.click(ui.startDateInput.get());
+ const monthSelector = ui.dateInputMonthSelect.byRole('combobox').get();
+ await user.click(monthSelector);
+ await user.click(
+ ui.dateInputMonthSelect
+ .byText(getShortMonthName(getMonth(startDay)))
+ .getAll()
+ .slice(-1)[0],
+ );
+
+ const yearSelector = ui.dateInputYearSelect.byRole('combobox').get();
+ await user.click(yearSelector);
+ await user.click(
+ ui.dateInputYearSelect.byText(getYear(startDay).toString()).getAll().slice(-1)[0],
+ );
+
+ await user.click(byText(getDate(startDay), { selector: 'button' }).get());
- await user.selectOptions(ui.dateInputMonthSelect.get(), getMonthName(getMonth(startDay)));
- await user.selectOptions(ui.dateInputYearSelect.get(), getYear(startDay).toString());
- await user.click(screen.getByText(getDate(startDay)));
await user.click(ui.endDateInput.get());
- await user.selectOptions(ui.dateInputMonthSelect.get(), getMonthName(getMonth(endDate)));
- await user.selectOptions(ui.dateInputYearSelect.get(), getYear(endDate).toString());
- await user.click(screen.getByText(getDate(endDate)));
+ await user.click(monthSelector);
+ await user.click(
+ ui.dateInputMonthSelect
+ .byText(getShortMonthName(getMonth(endDate)))
+ .getAll()
+ .slice(-1)[0],
+ );
+
+ await user.click(yearSelector);
+ await user.click(
+ ui.dateInputYearSelect.byText(getYear(endDate).toString()).getAll().slice(-1)[0],
+ );
+
+ await user.click(byText(getDate(endDate), { selector: 'button' }).get());
expect(await ui.downloadButton.find()).toHaveAttribute('aria-disabled', 'false');
await user.click(downloadButton);