import { ThemeProvider } from '@emotion/react';
import styled from '@emotion/styled';
-import { TooltipProvider } from '@sonarsource/echoes-react';
+import { EchoesProvider } from '@sonarsource/echoes-react';
import { QueryClientProvider } from '@tanstack/react-query';
import { ToastMessageContainer, lightTheme } from 'design-system';
import * as React from 'react';
<ToastMessageContainer />
<Helmet titleTemplate={translate('page_title.template.default')} />
<StackContext>
- <TooltipProvider>
+ <EchoesProvider>
<RouterProvider router={router} />
- </TooltipProvider>
+ </EchoesProvider>
</StackContext>
</QueryClientProvider>
</ThemeProvider>
import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { times } from 'lodash';
-import selectEvent from 'react-select-event';
import { byLabelText, byRole, byTestId, byText } from '~sonar-aligned/helpers/testSelector';
import { ComponentQualifier } from '~sonar-aligned/types/component';
import { MetricKey } from '~sonar-aligned/types/metrics';
await user.click(
ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(),
);
- await waitFor(() => ui.changeViewToList());
+
+ // Click list option in view select
+ await user.click(ui.viewSelect.get());
+ await user.click(ui.selectOptions('component_measures.tab.list').get());
expect(
within(await ui.measuresRow('out.tsx').find()).getByRole('cell', { name: '2' }),
await user.click(ui.maintainabilityDomainBtn.get());
await user.click(ui.measureBtn('Maintainability Rating metric.has_rating_X.E').get());
- await waitFor(() => ui.changeViewToTreeMap());
+
+ // Click treemap option in view select
+ await user.click(ui.viewSelect.get());
+ await user.click(ui.selectOptions('component_measures.tab.treemap').get());
expect(await ui.treeMapCell(/folderA/).find()).toBeInTheDocument();
expect(ui.treeMapCell(/test1\.js/).get()).toBeInTheDocument();
name: 'component_measures.hidden_best_score_metrics_show_label',
}),
goToActivityLink: byRole('link', { name: 'component_measures.see_metric_history' }),
+ selectOptions: (name: string) => byRole('option', { name }),
};
const ui = {
expect(selectors.loading.query()).not.toBeInTheDocument();
});
},
- async changeViewToList() {
- await selectEvent.select(ui.viewSelect.get(), 'component_measures.tab.list');
- },
- async changeViewToTreeMap() {
- await selectEvent.select(ui.viewSelect.get(), 'component_measures.tab.treemap');
- },
async arrowDown() {
await user.keyboard('[ArrowDown]');
},
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { InputSelect } from 'design-system';
+import { Select } from '@sonarsource/echoes-react';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
import { MeasurePageView } from '../../../types/measures';
view: MeasurePageView;
}
-interface ViewOption {
- label: string;
- value: MeasurePageView;
-}
-
-export default function MeasureViewSelect(props: MeasureViewSelectProps) {
- const { metric, view, className } = props;
- const options = [];
- if (hasTree(metric.key)) {
- options.push({
- label: translate('component_measures.tab.tree'),
- value: MeasurePageView.tree,
- });
- }
- if (hasList(metric.key)) {
- options.push({
- label: translate('component_measures.tab.list'),
- value: MeasurePageView.list,
- });
- }
- if (hasTreemap(metric.key, metric.type)) {
- options.push({
- label: translate('component_measures.tab.treemap'),
- value: MeasurePageView.treemap,
- });
- }
+export default function MeasureViewSelect(props: Readonly<MeasureViewSelectProps>) {
+ const { metric, view, className, handleViewChange } = props;
- const handleChange = (option: ViewOption) => {
- return props.handleViewChange(option.value);
- };
+ const measureViewOptions = React.useMemo(() => {
+ const options = [];
+ if (hasTree(metric.key)) {
+ options.push({
+ label: translate('component_measures.tab.tree'),
+ value: MeasurePageView.tree,
+ });
+ }
+ if (hasList(metric.key)) {
+ options.push({
+ label: translate('component_measures.tab.list'),
+ value: MeasurePageView.list,
+ });
+ }
+ if (hasTreemap(metric.key, metric.type)) {
+ options.push({
+ label: translate('component_measures.tab.treemap'),
+ value: MeasurePageView.treemap,
+ });
+ }
+ return options;
+ }, [metric]);
return (
- <InputSelect
- size="small"
- aria-labelledby="measures-view-selection-label"
- blurInputOnSelect
+ <Select
+ ariaLabelledBy="measures-view-selection-label"
className={className}
- onChange={handleChange}
- options={options}
- isSearchable={false}
- value={options.find((o) => o.value === view)}
+ data={measureViewOptions}
+ isNotClearable
+ onChange={handleViewChange}
+ value={view}
/>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { InputSelect, LabelValueSelectOption, StyledPageTitle } from 'design-system';
+import { Select } from '@sonarsource/echoes-react';
+import { StyledPageTitle } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
import { VIEWS } from '../utils';
view: string;
}
-export interface PerspectiveOption {
- label: string;
- value: string;
-}
+export default function PerspectiveSelect(props: Readonly<Props>) {
+ const { onChange, view } = props;
+
+ const handleChange = React.useCallback(
+ (value: string) => {
+ onChange({ view: value });
+ },
+ [onChange],
+ );
-export default class PerspectiveSelect extends React.PureComponent<Props> {
- handleChange = (option: PerspectiveOption) => {
- this.props.onChange({ view: option.value });
- };
+ const options = React.useMemo(
+ () => VIEWS.map((opt) => ({ value: opt.value, label: translate('projects.view', opt.label) })),
+ [],
+ );
- render() {
- const { view } = this.props;
- const options: PerspectiveOption[] = [
- ...VIEWS.map((opt) => ({
- value: opt.value,
- label: translate('projects.view', opt.label),
- })),
- ];
- return (
- <div className="sw-flex sw-items-center">
- <StyledPageTitle
- id="aria-projects-perspective"
- as="label"
- className="sw-body-sm-highlight sw-mr-2"
- >
- {translate('projects.perspective')}
- </StyledPageTitle>
- <InputSelect
- aria-labelledby="aria-projects-perspective"
- className="sw-mr-4 sw-body-sm"
- onChange={(data: LabelValueSelectOption) => this.handleChange(data)}
- options={options}
- placeholder={translate('project_activity.filter_events')}
- size="small"
- value={options.find((option) => option.value === view)}
- />
- </div>
- );
- }
+ return (
+ <div className="sw-flex sw-items-center">
+ <StyledPageTitle
+ id="aria-projects-perspective"
+ as="label"
+ className="sw-body-sm-highlight sw-mr-2"
+ >
+ {translate('projects.perspective')}
+ </StyledPageTitle>
+ <Select
+ ariaLabelledBy="aria-projects-perspective"
+ className="sw-mr-4 sw-body-sm"
+ isNotClearable
+ onChange={handleChange}
+ data={options}
+ placeholder={translate('project_activity.filter_events')}
+ value={view}
+ />
+ </div>
+ );
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import {
- InputSelect,
- InteractiveIcon,
- LabelValueSelectOption,
- SortAscendIcon,
- SortDescendIcon,
- StyledPageTitle,
-} from 'design-system';
-import { omit, sortBy } from 'lodash';
+import { Select, Tooltip } from '@sonarsource/echoes-react';
+import classNames from 'classnames';
+import { InteractiveIcon, SortAscendIcon, SortDescendIcon, StyledPageTitle } from 'design-system';
+import { sortBy } from 'lodash';
import * as React from 'react';
-import { OptionProps, components } from 'react-select';
-import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';
import { SORTING_LEAK_METRICS, SORTING_METRICS, parseSorting } from '../utils';
interface Props {
- className?: string;
defaultOption: string;
onChange: (sort: string, desc: boolean) => void;
selectedSort: string;
}
export interface Option {
- className?: string;
label: string;
+ optionClass?: string;
short?: string;
value: string;
}
sortOrderButtonNode: HTMLElement | null = null;
getSorting = () => {
- const options = this.getOptions();
- const { sortDesc, sortValue } = parseSorting(this.props.selectedSort);
- return { sortDesc, value: options.find((o) => o.value === sortValue) };
+ return parseSorting(this.props.selectedSort);
};
getOptions = () => {
(option) => ({
value: option.value,
label: translate('projects.sorting', option.value),
- className: option.class,
+ optionClass: option.class,
}),
);
};
}
};
- handleSortChange = (option: Option) => {
- this.props.onChange(option.value, this.getSorting().sortDesc);
- };
-
- projectsSortingSelectOption = (props: OptionProps<Option, false>) => {
- const { data, children } = props;
- return (
- <components.Option
- {...omit(props, ['children'])}
- className={`it__project-sort-option-${data.value} ${data.className}`}
- >
- {data.short ? data.short : children}
- </components.Option>
- );
+ handleSortChange = (value: string) => {
+ this.props.onChange(value, this.getSorting().sortDesc);
};
render() {
- const { sortDesc, value } = this.getSorting();
+ const { sortDesc, sortValue } = this.getSorting();
return (
<div className="sw-flex sw-items-center">
>
{translate('projects.sort_by')}
</StyledPageTitle>
- <InputSelect
- aria-labelledby="aria-projects-sort"
+ <Select
+ ariaLabelledBy="aria-projects-sort"
className="sw-body-sm"
- onChange={(data: LabelValueSelectOption<string>) => this.handleSortChange(data)}
- options={this.getOptions()}
- components={{
- Option: this.projectsSortingSelectOption,
- }}
+ onChange={this.handleSortChange}
+ data={this.getOptions()}
+ optionComponent={ProjectsSortingSelectItem}
placeholder={translate('project_activity.filter_events')}
- size="small"
- value={value}
+ isNotClearable
+ value={sortValue}
/>
<Tooltip
- mouseLeaveDelay={1}
content={
sortDesc ? translate('projects.sort_descending') : translate('projects.sort_ascending')
}
);
}
}
+
+const ProjectsSortingSelectItem = React.forwardRef<HTMLDivElement, Option & { className: string }>(
+ ({ className, label, optionClass, short, value, ...props }, ref) => {
+ return (
+ <div
+ className={classNames(`it__project-sort-option-${value}`, className, optionClass)}
+ ref={ref}
+ {...props}
+ >
+ {short ?? label}
+ </div>
+ );
+ },
+);
+
+ProjectsSortingSelectItem.displayName = 'ProjectsSortingSelectItem';
import userEvent from '@testing-library/user-event';
import { last } from 'lodash';
import React from 'react';
-import selectEvent from 'react-select-event';
import { byLabelText, byRole, byText } from '~sonar-aligned/helpers/testSelector';
import SettingsServiceMock, {
DEFAULT_DEFINITIONS_MOCK,
jsonFormatStatus: byText('settings.json.format_error'),
jsonFormatButton: byRole('button', { name: 'settings.json.format' }),
toggleButton: byRole('switch'),
- selectOption: (value: string) => byText(value),
+ selectOption: (name: string) => byRole('option', { name }),
+ selectInput: byRole('searchbox', { name: 'property.test.single.select.list.name' }),
saveButton: byRole('button', { name: 'save' }),
cancelButton: byRole('button', { name: 'cancel' }),
changeButton: byRole('button', { name: 'change_verb' }),
it('renders definition for SettingType = SINGLE_SELECT_LIST and can do operations', async () => {
const user = userEvent.setup();
- renderDefinition({
+ const definition = {
+ ...DEFAULT_DEFINITIONS_MOCK[0],
+ key: 'test.single.select.list',
type: SettingType.SINGLE_SELECT_LIST,
- options: ['first', 'second'],
- });
+ defaultValue: 'default',
+ options: ['first', 'second', 'default'],
+ };
+ settingsMock.setDefinition(definition);
+ renderDefinition(definition, { key: definition.key, value: 'default' });
- expect(
- await ui.nameHeading('property.sonar.announcement.message.name').find(),
- ).toBeInTheDocument();
+ expect(await ui.nameHeading('property.test.single.select.list.name').find()).toBeInTheDocument();
// Can select option
- expect(ui.selectOption('Select...').get()).toBeInTheDocument();
- await selectEvent.select(ui.announcementInput.get(), 'first');
- expect(ui.selectOption('first').get()).toBeInTheDocument();
+ expect(ui.selectInput.get()).toHaveValue('default');
+ await user.click(ui.selectInput.get());
+ await user.click(ui.selectOption('first').get());
+ expect(ui.selectInput.get()).toHaveValue('first');
// Can cancel action
await user.click(ui.cancelButton.get());
- expect(ui.selectOption('Select...').get()).toBeInTheDocument();
+ expect(ui.selectInput.get()).toHaveValue('default');
// Can save
- await selectEvent.select(ui.announcementInput.get(), 'second');
+ await user.click(ui.selectInput.get());
+ await user.click(ui.selectOption('second').get());
await user.click(ui.saveButton.get());
expect(ui.savedMsg.get()).toBeInTheDocument();
// Can reset
await user.click(
- ui.resetButton('settings.definition.reset.property.sonar.announcement.message.name').get(),
+ ui.resetButton('settings.definition.reset.property.test.single.select.list.name').get(),
);
await user.click(ui.resetButton().get());
- expect(ui.selectOption('Select...').get()).toBeInTheDocument();
+ expect(ui.selectInput.get()).toHaveValue('default');
});
it('renders definition for SettingType = FORMATTED_TEXT and can do operations', async () => {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { InputSelect } from 'design-system';
+import { InputSize, Select } from '@sonarsource/echoes-react';
import * as React from 'react';
import { ExtendedSettingDefinition } from '../../../../types/settings';
import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
type Props = DefaultSpecializedInputProps & Pick<ExtendedSettingDefinition, 'options'>;
-export default class InputForSingleSelectList extends React.PureComponent<Props> {
- handleInputChange = ({ value }: { label: string; value: string }) => {
- this.props.onChange(value);
- };
+export default function InputForSingleSelectList(props: Readonly<Props>) {
+ const { name, options: opts, value, setting } = props;
- render() {
- const { options: opts, name, value, setting } = this.props;
+ const options = React.useMemo(
+ () => opts.map((option) => ({ label: option, value: option })),
+ [opts],
+ );
- const options = opts.map((option) => ({
- label: option,
- value: option,
- }));
-
- return (
- <InputSelect
- name={name}
- onChange={this.handleInputChange}
- aria-label={getPropertyName(setting.definition)}
- options={options}
- value={options.find((option) => option.value === value) ?? null}
- />
- );
- }
+ return (
+ <Select
+ ariaLabel={getPropertyName(setting.definition)}
+ data={options}
+ isNotClearable
+ name={name}
+ onChange={props.onChange}
+ size={InputSize.Large}
+ value={value}
+ />
+ );
}
name: 'Azure DevOps integration',
description: `azure devops integration configuration
Configuration name
- Give your configuration a clear and succinct name.
+ Give your configuration a clear and succinct name.
This name will be used at project level to identify the correct configured Azure instance for a project.
Azure DevOps URL
For Azure DevOps Server, provide the full collection URL:
For Azure DevOps Services, provide the full organization URL:
https://dev.azure.com/your_organization
Personal Access Token
- SonarQube needs a Personal Access Token to report the Quality Gate status on Pull Requests in Azure DevOps.
- To create this token, we recommend using a dedicated Azure DevOps account with administration permissions.
+ SonarQube needs a Personal Access Token to report the Quality Gate status on Pull Requests in Azure DevOps.
+ To create this token, we recommend using a dedicated Azure DevOps account with administration permissions.
The token itself needs Code > Read & Write permission.
`,
category: 'almintegration',
name: 'Bitbucket integration',
description: `bitbucket server cloud integration configuration
Configuration name
- Give your configuration a clear and succinct name.
+ Give your configuration a clear and succinct name.
This name will be used at project level to identify the correct configured Bitbucket instance for a project.
Bitbucket Server URL
Example: https://bitbucket-server.your-company.com
Personal Access Token
- SonarQube needs a Personal Access Token to report the Quality Gate status on Pull Requests in Bitbucket Server.
- To create this token, we recommend using a dedicated Bitbucket Server account with administration permissions.
+ SonarQube needs a Personal Access Token to report the Quality Gate status on Pull Requests in Bitbucket Server.
+ To create this token, we recommend using a dedicated Bitbucket Server account with administration permissions.
The token itself needs Read permission.
Workspace ID
The workspace ID is part of your bitbucket cloud URL https://bitbucket.org/{workspace}/{repository}
- SonarQube needs you to create an OAuth consumer in your Bitbucket Cloud workspace settings
- to report the Quality Gate status on Pull Requests.
- It needs to be a private consumer with Pull Requests: Read permission.
+ SonarQube needs you to create an OAuth consumer in your Bitbucket Cloud workspace settings
+ to report the Quality Gate status on Pull Requests.
+ It needs to be a private consumer with Pull Requests: Read permission.
An OAuth callback URL is required by Bitbucket Cloud but not used by SonarQube so any URL works.
OAuth Key
- Bitbucket automatically creates an OAuth key when you create your OAuth consumer.
+ Bitbucket automatically creates an OAuth key when you create your OAuth consumer.
You can find it in your Bitbucket Cloud workspace settings under OAuth consumers.
OAuth Secret
- Bitbucket automatically creates an OAuth secret when you create your OAuth consumer.
+ Bitbucket automatically creates an OAuth secret when you create your OAuth consumer.
You can find it in your Bitbucket Cloud workspace settings under OAuth consumers.
`,
category: 'almintegration',
name: 'GitHub integration',
description: `github integration configuration
Configuration name
- Give your configuration a clear and succinct name.
+ Give your configuration a clear and succinct name.
This name will be used at project level to identify the correct configured GitHub App for a project.
GitHub API URL
Example for Github Enterprise:
https://github.company.com/api/v3
If using GitHub.com:
https://api.github.com/
- You need to install a GitHub App with specific settings and permissions to enable
- Pull Request Decoration on your Organization or Repository.
+ You need to install a GitHub App with specific settings and permissions to enable
+ Pull Request Decoration on your Organization or Repository.
GitHub App ID
The App ID is found on your GitHub App's page on GitHub at Settings > Developer Settings > GitHub Apps
Client ID
Client Secret
The Client secret is found on your GitHub App's page.
Private Key
- Your GitHub App's private key. You can generate a .pem file from your GitHub App's page under Private keys.
- Copy and paste the whole contents of the file here.
+ Your GitHub App's private key. You can generate a .pem file from your GitHub App's page under Private keys.
+ Copy and paste the whole contents of the file here.
`,
category: 'almintegration',
key: `sonar.almintegration.${AlmKeys.GitHub}`,
name: 'Gitlab integration',
description: `gitlab integration configuration
Configuration name
- Give your configuration a clear and succinct name.
+ Give your configuration a clear and succinct name.
This name will be used at project level to identify the correct configured GitLab instance for a project.
GitLab API URL
Provide the GitLab API URL. For example:
https://gitlab.com/api/v4
Personal Access Token
- SonarQube needs a Personal Access Token to report the Quality Gate status on Merge Requests in GitLab.
- To create this token,
+ SonarQube needs a Personal Access Token to report the Quality Gate status on Merge Requests in GitLab.
+ To create this token,
we recommend using a dedicated GitLab account with Reporter permission to all target projects.
The token itself needs the api scope.
`,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { TooltipProvider } from '@sonarsource/echoes-react';
+import { EchoesProvider } from '@sonarsource/echoes-react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Matcher, RenderResult, render, screen, within } from '@testing-library/react';
import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
<AvailableFeaturesContext.Provider value={featureList}>
<CurrentUserContextProvider currentUser={currentUser}>
<AppStateContextProvider appState={appState}>
- <TooltipProvider delayDuration={0}>
+ <EchoesProvider tooltipsDelayDuration={0}>
<MemoryRouter initialEntries={[pathname]}>
<Routes>
<Route path="*" element={children} />
</Routes>
</MemoryRouter>
- </TooltipProvider>
+ </EchoesProvider>
</AppStateContextProvider>
</CurrentUserContextProvider>
</AvailableFeaturesContext.Provider>
<IndexationContextProvider>
<QueryClientProvider client={queryClient}>
<ToastMessageContainer />
-
- <TooltipProvider delayDuration={0}>
+ <EchoesProvider tooltipsDelayDuration={0}>
<RouterProvider router={router} />
- </TooltipProvider>
+ </EchoesProvider>
</QueryClientProvider>
</IndexationContextProvider>
</AppStateContextProvider>
languageName: node
linkType: hard
-"@floating-ui/core@npm:^1.6.0":
- version: 1.6.4
- resolution: "@floating-ui/core@npm:1.6.4"
- dependencies:
- "@floating-ui/utils": "npm:^0.2.4"
- checksum: 10/589430cbff4bac90b9b891e2c94c57dc113d39ac163552f547d9e4c7d21f09997b9d33e82ec717759caee678c47f845f14a3f28df6f029fcfcf3ad803ba4eb7c
- languageName: node
- linkType: hard
-
-"@floating-ui/dom@npm:^1.0.0":
+"@floating-ui/dom@npm:^1.0.0, @floating-ui/dom@npm:^1.2.1":
version: 1.6.5
resolution: "@floating-ui/dom@npm:1.6.5"
dependencies:
languageName: node
linkType: hard
-"@floating-ui/dom@npm:^1.2.1":
- version: 1.6.7
- resolution: "@floating-ui/dom@npm:1.6.7"
- dependencies:
- "@floating-ui/core": "npm:^1.6.0"
- "@floating-ui/utils": "npm:^0.2.4"
- checksum: 10/a6a42bfd243c311f6040043808a6549c1db45fa36138b81cb1e615170d61fd2daf4f37accc1df3e0189405d97e3d71b12de39879c9d58ccf181c982b69cf6cf9
- languageName: node
- linkType: hard
-
"@floating-ui/react-dom@npm:^1.3.0":
version: 1.3.0
resolution: "@floating-ui/react-dom@npm:1.3.0"
languageName: node
linkType: hard
-"@floating-ui/utils@npm:^0.2.4":
- version: 0.2.4
- resolution: "@floating-ui/utils@npm:0.2.4"
- checksum: 10/7662d7a4ae39c0287e026f666297a3d28c80e588251c8c59ff66938a0aead47d380bbb9018629bd63a98f399c3919ec689d5448a5c48ffc176d545ddef705df1
- languageName: node
- linkType: hard
-
"@formatjs/ecma402-abstract@npm:2.0.0":
version: 2.0.0
resolution: "@formatjs/ecma402-abstract@npm:2.0.0"