postJSON,
RequestData,
} from '../helpers/request';
-import { IssueResponse, ListIssuesResponse, RawIssuesResponse } from '../types/issues';
+import { FacetName, IssueResponse, ListIssuesResponse, RawIssuesResponse } from '../types/issues';
import { Dict, FacetValue, IssueChangelog, SnippetsByComponent, SourceLine } from '../types/types';
-type FacetName =
- | 'assigned_to_me'
- | 'assignees'
- | 'author'
- | 'codeVariants'
- | 'createdAt'
- | 'cwe'
- | 'directories'
- | 'files'
- | 'languages'
- | 'owaspTop10'
- | 'projects'
- | 'reporters'
- | 'resolutions'
- | 'rules'
- | 'severities'
- | 'statuses'
- | 'tags'
- | 'types';
-
export function searchIssues(query: RequestData): Promise<RawIssuesResponse> {
return getJSON('/api/issues/search', query).catch(throwGlobalError);
}
rule: string;
severity?: string;
}) => {
- const nextActivation = mockRuleActivation({
- qProfile: data.key,
- severity: data.severity,
- params: Object.entries(data.params ?? {}).map(([key, value]) => ({ key, value })),
- });
+ if (data.reset) {
+ const parentQP = this.qualityProfile.find((p) => p.key === data.key)?.parentKey!;
+ const parentActivation = this.rulesActivations[data.rule]?.find(
+ (activation) => activation.qProfile === parentQP,
+ )!;
+ const parentParams = parentActivation?.params ?? [];
+ const activation = this.rulesActivations[data.rule]?.find(
+ ({ qProfile }) => qProfile === data.key,
+ )!;
+ activation.inherit = 'INHERITED';
+ activation.params = parentParams;
+
+ return this.reply(undefined);
+ }
+
+ const nextActivations = [
+ mockRuleActivation({
+ qProfile: data.key,
+ severity: data.severity,
+ params: Object.entries(data.params ?? {}).map(([key, value]) => ({ key, value })),
+ }),
+ ];
+
+ const inheritingProfiles = this.qualityProfile.filter(
+ (p) => p.isInherited && p.parentKey === data.key,
+ );
+ nextActivations.push(
+ ...inheritingProfiles.map((profile) =>
+ mockRuleActivation({
+ qProfile: profile.key,
+ severity: data.severity,
+ inherit: 'INHERITED',
+ params: Object.entries(data.params ?? {}).map(([key, value]) => ({ key, value })),
+ }),
+ ),
+ );
if (!this.rulesActivations[data.rule]) {
- this.rulesActivations[data.rule] = [nextActivation];
+ this.rulesActivations[data.rule] = nextActivations;
return this.reply(undefined);
}
- const activationIndex = this.rulesActivations[data.rule]?.findIndex((activation) => {
- return activation.qProfile === data.key;
+ nextActivations.forEach((nextActivation) => {
+ const activationIndex = this.rulesActivations[data.rule]?.findIndex(
+ ({ qProfile }) => qProfile === nextActivation.qProfile,
+ );
+
+ if (activationIndex !== -1) {
+ this.rulesActivations[data.rule][activationIndex] = {
+ ...nextActivation,
+ inherit: 'OVERRIDES',
+ };
+ } else {
+ this.rulesActivations[data.rule].push(nextActivation);
+ }
});
- if (activationIndex !== -1) {
- this.rulesActivations[data.rule][activationIndex] = nextActivation;
- } else {
- this.rulesActivations[data.rule].push(nextActivation);
- }
return this.reply(undefined);
};
export const QP_2 = 'p2';
export const QP_3 = 'p3';
export const QP_4 = 'p4';
+export const QP_5 = 'p5';
// Issues.
export const ISSUE_0 = 'issue0';
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { mockQualityProfile } from '../../../helpers/testMocks';
-import { QP_1, QP_2, QP_3, QP_4 } from './ids';
+import { QP_1, QP_2, QP_3, QP_4, QP_5 } from './ids';
export function mockQualityProfilesList() {
return [
language: 'java',
languageName: 'Java',
}),
+ mockQualityProfile({
+ key: QP_5,
+ name: 'QP FooBaz',
+ language: 'java',
+ languageName: 'Java',
+ isInherited: true,
+ parentKey: QP_4,
+ parentName: 'QP FooBarBaz',
+ }),
];
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { get, remove, save } from '../../../helpers/storage';
+import { ComponentQualifier } from '../../../types/component';
import RecentHistory, { History } from '../RecentHistory';
jest.mock('../../../helpers/storage', () => ({
}));
beforeEach(() => {
- (get as jest.Mock).mockClear();
- (remove as jest.Mock).mockClear();
- (save as jest.Mock).mockClear();
+ jest.mocked(get).mockClear();
+ jest.mocked(remove).mockClear();
+ jest.mocked(save).mockClear();
});
it('should get existing history', () => {
- const history = [{ key: 'foo', name: 'Foo', icon: 'TRK' }];
- (get as jest.Mock).mockReturnValueOnce(JSON.stringify(history));
+ const history = [{ key: 'foo', name: 'Foo', icon: ComponentQualifier.Project }];
+ jest.mocked(get).mockReturnValueOnce(JSON.stringify(history));
expect(RecentHistory.get()).toEqual(history);
expect(get).toHaveBeenCalledWith('sonar_recent_history');
});
it('should get empty history', () => {
- (get as jest.Mock).mockReturnValueOnce(null);
+ jest.mocked(get).mockReturnValueOnce(null);
expect(RecentHistory.get()).toEqual([]);
expect(get).toHaveBeenCalledWith('sonar_recent_history');
});
it('should return [] and clear history in case of failure', () => {
- (get as jest.Mock).mockReturnValueOnce('not a json');
+ jest.mocked(get).mockReturnValueOnce('not a json');
expect(RecentHistory.get()).toEqual([]);
expect(get).toHaveBeenCalledWith('sonar_recent_history');
expect(remove).toHaveBeenCalledWith('sonar_recent_history');
});
it('should save history', () => {
- const history = [{ key: 'foo', name: 'Foo', icon: 'TRK' }];
+ const history = [{ key: 'foo', name: 'Foo', icon: ComponentQualifier.Project }];
RecentHistory.set(history);
expect(save).toHaveBeenCalledWith('sonar_recent_history', JSON.stringify(history));
});
});
it('should add item to history', () => {
- const history = [{ key: 'foo', name: 'Foo', icon: 'TRK' }];
- (get as jest.Mock).mockReturnValueOnce(JSON.stringify(history));
- RecentHistory.add('bar', 'Bar', 'VW');
+ const history = [{ key: 'foo', name: 'Foo', icon: ComponentQualifier.Project }];
+ jest.mocked(get).mockReturnValueOnce(JSON.stringify(history));
+ RecentHistory.add('bar', 'Bar', ComponentQualifier.Portfolio);
expect(save).toHaveBeenCalledWith(
'sonar_recent_history',
- JSON.stringify([{ key: 'bar', name: 'Bar', icon: 'VW' }, ...history]),
+ JSON.stringify([{ key: 'bar', name: 'Bar', icon: ComponentQualifier.Portfolio }, ...history]),
);
});
it('should keep 10 items maximum', () => {
const history: History = [];
for (let i = 0; i < 10; i++) {
- history.push({ key: `key-${i}`, name: `name-${i}`, icon: 'TRK' });
+ history.push({ key: `key-${i}`, name: `name-${i}`, icon: ComponentQualifier.Project });
}
- (get as jest.Mock).mockReturnValueOnce(JSON.stringify(history));
- RecentHistory.add('bar', 'Bar', 'VW');
+ jest.mocked(get).mockReturnValueOnce(JSON.stringify(history));
+ RecentHistory.add('bar', 'Bar', ComponentQualifier.Portfolio);
expect(save).toHaveBeenCalledWith(
'sonar_recent_history',
- JSON.stringify([{ key: 'bar', name: 'Bar', icon: 'VW' }, ...history.slice(0, 9)]),
+ JSON.stringify([
+ { key: 'bar', name: 'Bar', icon: ComponentQualifier.Portfolio },
+ ...history.slice(0, 9),
+ ]),
);
});
it('should remove component from history', () => {
const history: History = [];
for (let i = 0; i < 10; i++) {
- history.push({ key: `key-${i}`, name: `name-${i}`, icon: 'TRK' });
+ history.push({ key: `key-${i}`, name: `name-${i}`, icon: ComponentQualifier.Project });
}
- (get as jest.Mock).mockReturnValueOnce(JSON.stringify(history));
+ jest.mocked(get).mockReturnValueOnce(JSON.stringify(history));
RecentHistory.remove('key-5');
expect(save).toHaveBeenCalledWith(
'sonar_recent_history',
static shouldDisplayCompletedNotification() {
return JSON.parse(
- get(LS_INDEXATION_COMPLETED_NOTIFICATION_SHOULD_BE_DISPLAYED) || false.toString(),
+ get(LS_INDEXATION_COMPLETED_NOTIFICATION_SHOULD_BE_DISPLAYED) ?? false.toString(),
);
}
}
import { Alert, AlertVariant } from '../../../components/ui/Alert';
import DismissableAlert from '../../../components/ui/DismissableAlert';
import SystemUpgradeButton from '../../../components/upgrade/SystemUpgradeButton';
-import { sortUpgrades, UpdateUseCase } from '../../../components/upgrade/utils';
+import { UpdateUseCase, sortUpgrades } from '../../../components/upgrade/utils';
import { translate } from '../../../helpers/l10n';
import { hasGlobalPermission } from '../../../helpers/users';
import { AppState } from '../../../types/appstate';
const latest = [...upgrades].sort(
(upgrade1, upgrade2) =>
- new Date(upgrade2.releaseDate || '').getTime() -
- new Date(upgrade1.releaseDate || '').getTime(),
+ new Date(upgrade2.releaseDate ?? '').getTime() -
+ new Date(upgrade1.releaseDate ?? '').getTime(),
)[0];
const dismissKey = useCase + latest.version;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ONE_SECOND } from '../../helpers/constants';
import { toShortISO8601String } from '../../helpers/dates';
import { ActivityRequestParameters, Task, TaskStatuses } from '../../types/tasks';
import { ALL_TYPES, CURRENTS, STATUSES } from './constants';
return parameters;
}
-const ONE_SECOND = 1000;
const ONE_MINUTE = 60 * ONE_SECOND;
const ONE_HOUR = 60 * ONE_MINUTE;
*/
import { getBreadcrumbs, getChildren, getComponent } from '../../../api/components';
import { mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like';
+import { ComponentQualifier } from '../../../types/component';
import {
addComponent,
addComponentBreadcrumbs,
describe('getCodeMetrics', () => {
it('should return the right metrics for portfolios', () => {
- expect(getCodeMetrics('VW')).toMatchSnapshot();
- expect(getCodeMetrics('VW', undefined, { includeQGStatus: true })).toMatchSnapshot();
+ expect(getCodeMetrics(ComponentQualifier.Portfolio)).toMatchSnapshot();
expect(
- getCodeMetrics('VW', undefined, { includeQGStatus: true, newCode: true }),
+ getCodeMetrics(ComponentQualifier.Portfolio, undefined, { includeQGStatus: true }),
).toMatchSnapshot();
expect(
- getCodeMetrics('VW', undefined, { includeQGStatus: true, newCode: false }),
+ getCodeMetrics(ComponentQualifier.Portfolio, undefined, {
+ includeQGStatus: true,
+ newCode: true,
+ }),
+ ).toMatchSnapshot();
+ expect(
+ getCodeMetrics(ComponentQualifier.Portfolio, undefined, {
+ includeQGStatus: true,
+ newCode: false,
+ }),
).toMatchSnapshot();
});
it('should return the right metrics for apps', () => {
- expect(getCodeMetrics('APP')).toMatchSnapshot();
+ expect(getCodeMetrics(ComponentQualifier.Application)).toMatchSnapshot();
});
it('should return the right metrics for projects', () => {
- expect(getCodeMetrics('TRK', mockMainBranch())).toMatchSnapshot();
- expect(getCodeMetrics('TRK', mockPullRequest())).toMatchSnapshot();
+ expect(getCodeMetrics(ComponentQualifier.Project, mockMainBranch())).toMatchSnapshot();
+ expect(getCodeMetrics(ComponentQualifier.Project, mockPullRequest())).toMatchSnapshot();
});
});
paging: { total: 2, pageIndex: 0 },
});
- await retrieveComponentChildren('key', 'TRK', { mounted: true }, mockMainBranch());
+ await retrieveComponentChildren(
+ 'key',
+ ComponentQualifier.Project,
+ { mounted: true },
+ mockMainBranch(),
+ );
expect(addComponentChildren).toHaveBeenCalledWith('key', components, 2, 0);
expect(addComponent).toHaveBeenCalledTimes(2);
});
(getBreadcrumbs as jest.Mock).mockResolvedValueOnce([]);
- await retrieveComponent('key', 'TRK', { mounted: true }, mockMainBranch());
+ await retrieveComponent('key', ComponentQualifier.Project, { mounted: true }, mockMainBranch());
expect(addComponentChildren).toHaveBeenCalled();
expect(addComponent).toHaveBeenCalledTimes(3);
});
(getBreadcrumbs as jest.Mock).mockResolvedValueOnce([]);
- await retrieveComponent('key', 'TRK', { mounted: false }, mockMainBranch());
+ await retrieveComponent(
+ 'key',
+ ComponentQualifier.Project,
+ { mounted: false },
+ mockMainBranch(),
+ );
expect(addComponentChildren).not.toHaveBeenCalled();
expect(addComponent).not.toHaveBeenCalled();
paging: { total: 6, pageIndex: 1 },
});
- await loadMoreChildren('key', 1, 'TRK', { mounted: true }, mockMainBranch());
+ await loadMoreChildren(
+ 'key',
+ 1,
+ ComponentQualifier.Project,
+ { mounted: true },
+ mockMainBranch(),
+ );
expect(addComponentChildren).toHaveBeenCalledWith('key', components, 6, 1);
expect(addComponent).toHaveBeenCalledTimes(3);
function skipRootDir(breadcrumbs: ComponentMeasure[]) {
return breadcrumbs.filter((component) => {
- return !(component.qualifier === 'DIR' && component.name === '/');
+ return !(component.qualifier === ComponentQualifier.Directory && component.name === '/');
});
}
if (instance.mounted && isPortfolioLike(qualifier)) {
await Promise.all(
- result.components.map((c) => getComponentData({ component: c.refKey || c.key })),
+ result.components.map((c) => getComponentData({ component: c.refKey ?? c.key })),
).then(
(data) => {
data.forEach(({ component: { analysisDate } }, i) => {
// Activate rule in quality profile
await user.click(ui.activateButton.get());
await selectEvent.select(ui.qualityProfileSelect.get(), 'QP FooBar');
- await user.type(ui.paramInput('1').get(), 'paramInput');
await act(() => user.click(ui.activateButton.get(ui.activateQPDialog.get())));
expect(ui.qpLink('QP FooBar').get()).toBeInTheDocument();
- // Change rule details in quality profile
- await user.click(ui.changeButton('QP FooBar').get());
- await user.type(ui.paramInput('1').get(), 'New');
- await act(() => user.click(ui.saveButton.get(ui.changeQPDialog.get())));
- expect(screen.getByText('paramInputNew')).toBeInTheDocument();
-
// activate last java rule
await user.click(ui.activateButton.get());
+ await user.type(ui.paramInput('1').get(), 'paramInput');
await act(() => user.click(ui.activateButton.get(ui.activateQPDialog.get())));
- expect(ui.qpLink('QP FooBarBaz').get()).toBeInTheDocument();
+ expect(ui.qpLink('QP FooBarBaz').getAll()).toHaveLength(2);
+ expect(ui.qpLink('QP FooBaz').get()).toBeInTheDocument();
// Rule is activated in all quality profiles - show notification in dialog
await user.click(ui.activateButton.get());
expect(ui.activateButton.get(ui.activateQPDialog.get())).toBeDisabled();
await user.click(ui.cancelButton.get());
+ // Change rule details in quality profile
+ await user.click(ui.changeButton('QP FooBaz').get());
+ await user.type(ui.paramInput('1').get(), 'New');
+ await act(() => user.click(ui.saveButton.get(ui.changeQPDialog.get())));
+ expect(screen.getByText('paramInputNew')).toBeInTheDocument();
+
+ // Revert rule details in quality profile
+ await user.click(ui.revertToParentDefinitionButton.get());
+ await act(() => user.click(ui.yesButton.get()));
+ expect(screen.queryByText('paramInputNew')).not.toBeInTheDocument();
+
// Deactivate rule in quality profile
await user.click(ui.deactivateInQPButton('QP FooBar').get());
await act(() => user.click(ui.yesButton.get()));
*/
import * as React from 'react';
import { Profile } from '../../../api/quality-profiles';
-import { Button } from '../../../components/controls/buttons';
import Dropdown from '../../../components/controls/Dropdown';
import Tooltip from '../../../components/controls/Tooltip';
+import { Button } from '../../../components/controls/buttons';
import { PopupPlacement } from '../../../components/ui/popups';
import { translate } from '../../../helpers/l10n';
import { Dict } from '../../../types/types';
render() {
// show "Bulk Change" button only if user is admin of at least one QP
const canBulkChange = Object.values(this.props.referencedProfiles).some((profile) =>
- Boolean(profile.actions && profile.actions.edit),
+ Boolean(profile.actions?.edit),
);
if (!canBulkChange) {
return (
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { Languages } from '../../../types/languages';
+import { MetricType } from '../../../types/metrics';
import { Dict } from '../../../types/types';
import { Query, serializeQuery } from '../query';
profiles = profiles.filter((profile) => query.languages.includes(profile.language));
}
return profiles
- .filter((profile) => profile.actions && profile.actions.edit)
+ .filter((profile) => profile.actions?.edit)
.filter((profile) => !profile.isBuiltIn);
};
action === 'activate'
? `${translate('coding_rules.activate_in_quality_profile')} (${formatMeasure(
total,
- 'INT',
+ MetricType.Integer,
)} ${translate('coding_rules._rules')})`
: `${translate('coding_rules.deactivate_in_quality_profile')} (${formatMeasure(
total,
- 'INT',
+ MetricType.Integer,
)} ${translate('coding_rules._rules')})`;
return (
import { formatMeasure } from '../../../helpers/measures';
import { getIssuesUrl } from '../../../helpers/urls';
import { Feature } from '../../../types/features';
+import { FacetName } from '../../../types/issues';
+import { MetricType } from '../../../types/metrics';
import { RuleDetails } from '../../../types/types';
interface Props extends WithAvailableFeaturesProps {
resolved: 'false',
rules: key,
},
- 'projects',
+ FacetName.Projects,
).then(
({ facet, response }) => {
if (this.mounted) {
<tr key={project.key}>
<td className="coding-rules-detail-list-name">{project.name}</td>
<td className="coding-rules-detail-list-parameters">
- <Link to={path}>{formatMeasure(project.count, 'INT')}</Link>
+ <Link to={path}>{formatMeasure(project.count, MetricType.Integer)}</Link>
</td>
</tr>
);
handleActivate = () => this.props.onActivate();
handleDeactivate = (key?: string) => {
- if (key) {
+ if (key !== undefined) {
deactivateRule({
key,
rule: this.props.ruleDetails.key,
};
handleRevert = (key?: string) => {
- if (key) {
+ if (key !== undefined) {
activateRule({
key,
rule: this.props.ruleDetails.key,
};
renderParameter = (param: { key: string; value: string }, parentActivation?: RuleActivation) => {
- const originalParam =
- parentActivation && parentActivation.params.find((p) => p.key === param.key);
- const originalValue = originalParam && originalParam.value;
+ const originalParam = parentActivation?.params.find((p) => p.key === param.key);
+ const originalValue = originalParam?.value;
return (
<div className="coding-rules-detail-quality-profile-parameter" key={param.key}>
);
renderActions = (activation: RuleActivation, profile: Profile) => {
- const canEdit = profile.actions && profile.actions.edit && !profile.isBuiltIn;
+ const canEdit = profile.actions?.edit && !profile.isBuiltIn;
const { ruleDetails } = this.props;
const hasParent = activation.inherit !== 'NONE' && profile.parentKey;
return (
render() {
const { activations = [], referencedProfiles, ruleDetails } = this.props;
const canActivate = Object.values(referencedProfiles).some((profile) =>
- Boolean(profile.actions && profile.actions.edit && profile.language === ruleDetails.lang),
+ Boolean(profile.actions?.edit && profile.language === ruleDetails.lang),
);
return (
changeQPDialog: byRole('dialog', { name: 'coding_rules.change_details' }),
deactivateInQPButton: (profile: string) =>
byRole('button', { name: `coding_rules.deactivate_in_quality_profile_x.${profile}` }),
+ revertToParentDefinitionButton: byRole('button', {
+ name: 'coding_rules.revert_to_parent_definition',
+ }),
activaInAllQPs: byText('coding_rules.active_in_all_profiles'),
yesButton: byRole('button', { name: 'yes' }),
paramInput: (param: string) => byRole('textbox', { name: param }),
import * as React from 'react';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockQuery } from '../../../../helpers/mocks/issues';
+import {
+ renderOwaspTop102021Category,
+ renderOwaspTop10Category,
+ renderSonarSourceSecurityCategory,
+} from '../../../../helpers/security-standard';
import { mockAppState } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../../types/component';
import { GlobalSettingKeys } from '../../../../types/settings';
import { SidebarClass as Sidebar } from '../Sidebar';
+jest.mock('../../../../helpers/security-standard', () => {
+ return {
+ ...jest.requireActual('../../../../helpers/security-standard'),
+ renderOwaspTop10Category: jest.fn(),
+ renderOwaspTop102021Category: jest.fn(),
+ renderSonarSourceSecurityCategory: jest.fn(),
+ };
+});
+
it('should render correct facets for Application', () => {
renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.Application }) });
expect(screen.getByText(text)).toBeInTheDocument();
});
+it('should call functions from security-standard', () => {
+ renderSidebar({
+ component: mockComponent({ qualifier: ComponentQualifier.Application }),
+ query: {
+ ...mockQuery(),
+ owaspTop10: ['foo'],
+ 'owaspTop10-2021': ['bar'],
+ sonarsourceSecurity: ['baz'],
+ },
+ });
+
+ expect(renderOwaspTop10Category).toHaveBeenCalledTimes(1);
+ expect(renderOwaspTop102021Category).toHaveBeenCalledTimes(1);
+ expect(renderSonarSourceSecurityCategory).toHaveBeenCalledTimes(1);
+});
+
function renderSidebar(props: Partial<Sidebar['props']> = {}) {
return renderComponent(
<Sidebar
)}
aria-label={translateWithParameters(
'project_activity.show_analysis_X_on_graph',
- analysis.buildString || formatDate(parsedDate, formatterOption),
+ analysis.buildString ?? formatDate(parsedDate, formatterOption),
)}
onClick={() => props.onUpdateSelectedDate(analysis.date)}
ref={(ref) => (node = ref)}
<ActionsDropdown
ariaLabel={translateWithParameters(
'project_activity.analysis_X_actions',
- analysis.buildString || formatDate(parsedDate, formatterOption),
+ analysis.buildString ?? formatDate(parsedDate, formatterOption),
)}
buttonSize="small"
id="it__analysis-actions"
existingBranches: Array<string>,
) {
return (
- branch.newCodePeriod &&
- branch.newCodePeriod.value &&
+ branch.newCodePeriod?.value &&
branch.newCodePeriod.type === NewCodeDefinitionType.ReferenceBranch &&
!existingBranches.includes(branch.newCodePeriod.value)
);
} else if (referenceBranchDoesNotExist(branch, existingBranches)) {
settingWarning = translateWithParameters(
'baseline.reference_branch.does_not_exist',
- branch.newCodePeriod?.value || '',
+ branch.newCodePeriod?.value ?? '',
);
}
<td className="huge-spacer-right nowrap">
<Tooltip overlay={settingWarning}>
<span>
- {settingWarning && <WarningIcon className="little-spacer-right" />}
+ {settingWarning !== undefined && <WarningIcon className="little-spacer-right" />}
{branch.newCodePeriod
? renderNewCodePeriodSetting(branch.newCodePeriod)
: translate('branch_list.default_setting')}
checkPermissions() {
const { configuration } = this.props.component;
- const hasPermission = configuration && configuration.showQualityProfiles;
+ const hasPermission = configuration?.showQualityProfiles;
return !!hasPermission;
}
// If the profile is the default profile, all is good.
if (profile.isDefault) {
return { profile, selected: false };
- } else {
- // If it is neither the default, nor explicitly selected, it
- // means this is outdated information. This can only mean the
- // user wants to use the default profile, but it will only
- // be taken into account after a new analysis. Fetch the
- // default profile.
- const defaultProfile = allProfiles.find(
- (p) => p.isDefault && p.language === profile.language,
- );
- return (
- defaultProfile && {
- profile: defaultProfile,
- selected: false,
- }
- );
}
- } else {
- return undefined;
+
+ // If it is neither the default, nor explicitly selected, it
+ // means this is outdated information. This can only mean the
+ // user wants to use the default profile, but it will only
+ // be taken into account after a new analysis. Fetch the
+ // default profile.
+ const defaultProfile = allProfiles.find(
+ (p) => p.isDefault && p.language === profile.language,
+ );
+
+ return (
+ defaultProfile && {
+ profile: defaultProfile,
+ selected: false,
+ }
+ );
}
+
+ return undefined;
})
.filter(isDefined);
const { component } = this.props;
const { allProfiles = [], projectProfiles = [] } = this.state;
- const newProfile = newKey && allProfiles.find((p) => p.key === newKey);
+ const newProfile = newKey !== undefined && allProfiles.find((p) => p.key === newKey);
const oldProjectProfile = projectProfiles.find((p) => p.profile.key === oldKey);
const defaultProfile = allProfiles.find(
(p) => p.isDefault && p.language === oldProjectProfile?.profile.language,
export default function LanguageProfileSelectOption(props: LanguageProfileSelectOptionProps) {
const option = props.data;
+ const SelectOptionDisableTooltipOverlay = React.useCallback(
+ () => (
+ <>
+ <p>
+ {translate(
+ 'project_quality_profile.add_language_modal.profile_unavailable_no_active_rules',
+ )}
+ </p>
+ {option.label && option.language && (
+ <Link to={getQualityProfileUrl(option.label, option.language)}>
+ {translate('project_quality_profile.add_language_modal.go_to_profile')}
+ </Link>
+ )}
+ </>
+ ),
+ [option.label, option.language],
+ );
+
return (
<components.Option {...props}>
<div>
<DisableableSelectOption
option={option}
disabledReason={translate('project_quality_profile.add_language_modal.no_active_rules')}
- disableTooltipOverlay={() => (
- <>
- <p>
- {translate(
- 'project_quality_profile.add_language_modal.profile_unavailable_no_active_rules',
- )}
- </p>
- {option.label && option.language && (
- <Link to={getQualityProfileUrl(option.label, option.language)}>
- {translate('project_quality_profile.add_language_modal.go_to_profile')}
- </Link>
- )}
- </>
- )}
+ disableTooltipOverlay={SelectOptionDisableTooltipOverlay}
/>
</div>
</components.Option>
*/
import { searchProjects } from '../../../api/components';
+import { ONE_SECOND } from '../../../helpers/constants';
import { mockComponent } from '../../../helpers/mocks/component';
import { Component } from '../../../types/types';
import * as utils from '../utils';
});
describe('formatDuration', () => {
- const ONE_SECOND = 1000;
const ONE_MINUTE = 60 * ONE_SECOND;
const ONE_HOUR = 60 * ONE_MINUTE;
const ONE_DAY = 24 * ONE_HOUR;
ps: 50,
});
- await utils.fetchProjects({ isFavorite: false, pageIndex: 3, query: { view: 'leak' } });
+ await utils.fetchProjects({
+ isFavorite: false,
+ pageIndex: 3,
+ query: {
+ view: 'leak',
+ new_reliability: 6,
+ incorrect_property: 'should not appear in post data',
+ search: 'foo',
+ },
+ });
expect(searchProjects).toHaveBeenCalledWith({
f: 'analysisDate,leakPeriodDate',
facets: utils.LEAK_FACETS.join(),
+ filter: 'new_reliability_rating = 6 and query = "foo"',
p: 3,
ps: 50,
});
}
function getAsString(value: any): string | undefined {
- if (typeof value !== 'string' || !value) {
+ if (typeof value !== 'string' || value === '') {
return undefined;
}
return value;
}
function getAsStringArray(value: any): string[] | undefined {
- if (typeof value !== 'string' || !value) {
+ if (typeof value !== 'string' || value === '') {
return undefined;
}
return value.split(',');
function convertIssuesRating(metric: string, rating: number): string {
if (rating > 1 && rating < 5) {
return `${metric} >= ${rating}`;
- } else {
- return `${metric} = ${rating}`;
}
+
+ return `${metric} = ${rating}`;
}
function convertCoverage(metric: string, coverage: number): string {
query: Query,
property: string,
conditionsArray: string[],
- convertFunction: (metric: string, value: any) => string,
+ convertFunction: (metric: string, value: Query[string]) => string,
): void {
const metric = mapPropertyToMetric(property);
- if (query[property] != null && metric) {
+ if (query[property] !== undefined && metric !== undefined) {
conditionsArray.push(convertFunction(metric, query[property]));
}
}
describe('getLocalizedMetricNameNoDiffMetric', () => {
it('should return the correct corresponding metric', () => {
- expect(getLocalizedMetricNameNoDiffMetric(mockMetric(), {})).toBe('coverage');
- expect(getLocalizedMetricNameNoDiffMetric(mockMetric({ key: 'new_bugs' }), METRICS)).toBe(
- 'Bugs',
- );
+ expect(getLocalizedMetricNameNoDiffMetric(mockMetric(), {})).toBe(MetricKey.coverage);
+ expect(
+ getLocalizedMetricNameNoDiffMetric(mockMetric({ key: MetricKey.new_bugs }), METRICS),
+ ).toBe('Bugs');
expect(
getLocalizedMetricNameNoDiffMetric(
mockMetric({ key: 'new_custom_metric', name: 'Custom Metric on New Code' }),
).toBe('Custom Metric on New Code');
expect(
getLocalizedMetricNameNoDiffMetric(
- mockMetric({ key: 'new_maintainability_rating' }),
+ mockMetric({ key: MetricKey.new_maintainability_rating }),
METRICS,
),
).toBe('Maintainability Rating');
const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions);
const sortedWeakConditions = sortBy(
weakConditions,
- (condition) => metrics[condition.metric] && metrics[condition.metric].name,
+ (condition) => metrics[condition.metric]?.name,
);
const sortedMissingConditions = sortBy(
missingConditions,
- (condition) => metrics[condition.metric] && metrics[condition.metric].name,
+ (condition) => metrics[condition.metric]?.name,
);
return (
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import {
- getMaintainabilityGrid,
GRID_INDEX_OFFSET,
PERCENT_MULTIPLIER,
+ getMaintainabilityGrid,
} from '../../../helpers/ratings';
import { AppState } from '../../../types/appstate';
+import { MetricKey, MetricType } from '../../../types/metrics';
import { GlobalSettingKeys } from '../../../types/settings';
import { Condition, Metric } from '../../../types/types';
import { isCaycCondition } from '../utils';
metric,
className = '',
}: Props) {
- if (condition.metric === 'new_maintainability_rating') {
+ if (condition.metric === MetricKey.new_maintainability_rating) {
const maintainabilityGrid = getMaintainabilityGrid(
settings[GlobalSettingKeys.RatingGrid] ?? '',
);
const maintainabilityRatingThreshold =
maintainabilityGrid[Math.floor(Number(condition.error)) - GRID_INDEX_OFFSET];
- const ratingLetter = formatMeasure(condition.error, 'RATING');
+ const ratingLetter = formatMeasure(condition.error, MetricType.Rating);
return (
<span className={className}>
{condition.error === '1'
? translateWithParameters(
'quality_gates.cayc.new_maintainability_rating.A',
- formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, 'PERCENT'),
+ formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, MetricType.Percent),
)
: translateWithParameters(
'quality_gates.cayc.new_maintainability_rating',
ratingLetter,
- formatMeasure(maintainabilityRatingThreshold * PERCENT_MULTIPLIER, 'PERCENT'),
+ formatMeasure(
+ maintainabilityRatingThreshold * PERCENT_MULTIPLIER,
+ MetricType.Percent,
+ ),
)}
)
</span>
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { getRulesUrl } from '../../../helpers/urls';
+import { MetricType } from '../../../types/metrics';
import { Dict, Rule, RuleActivation } from '../../../types/types';
const RULES_LIMIT = 10;
function parseRules(rules: Rule[], actives?: Dict<RuleActivation[]>): ExtendedRule[] {
return rules.map((rule) => {
- const activations = actives && actives[rule.key];
+ const activations = actives?.[rule.key];
return { ...rule, activations: activations ? activations.length : 0 };
});
}
const newRulesUrl = getRulesUrl({ available_since: this.periodStartDate });
const seeAllRulesText = `${translate('see_all')} ${formatMeasure(
latestRulesTotal,
- 'SHORT_INT',
- null,
+ MetricType.ShortInteger,
)}`;
return (
function orderLocations(locations: FlowLocation[]) {
return sortBy(
locations,
- (location) => location.textRange && location.textRange.startLine,
- (location) => location.textRange && location.textRange.startOffset,
+ (location) => location.textRange?.startLine,
+ (location) => location.textRange?.startOffset,
);
}
return domains.map((domain) => {
const deprecated = getLatestDeprecatedAction(domain);
const internal = !domain.actions.find((action: any) => !action.internal);
- return { ...domain, deprecatedSince: deprecated && deprecated.deprecatedSince, internal };
+ return { ...domain, deprecatedSince: deprecated?.deprecatedSince, internal };
});
}
const action = domain.actions.find(
(action) => getActionKey(domain.path, action.key) === splat,
);
- const internal = Boolean(!query.internal && (domain.internal || (action && action.internal)));
+ const internal = Boolean(!query.internal && (domain.internal || action?.internal));
const deprecated = Boolean(
- !query.deprecated && (domain.deprecatedSince || (action && action.deprecatedSince)),
+ !query.deprecated && (domain.deprecatedSince || action?.deprecatedSince),
);
if (internal || deprecated) {
this.updateQuery({ internal, deprecated });
const query = parseQuery(this.props.location.query);
const value = !query[flag];
- if (domain && domain[domainFlag] && !value) {
+ if (domain?.[domainFlag] && !value) {
this.props.router.push({
pathname: '/web_api',
query: serializeQuery({ ...query, [flag]: false }),
<p className="spacer-bottom">
{translateWithParameters(
'webhooks.delivery.response_x',
- delivery.httpStatus || translate('webhooks.delivery.server_unreachable'),
+ delivery.httpStatus ?? translate('webhooks.delivery.server_unreachable'),
)}
</p>
<p className="spacer-bottom">
</p>
<p className="spacer-bottom">{translate('webhooks.delivery.payload')}</p>
<Spinner className="spacer-left spacer-top" loading={loading}>
- {payload && <CodeSnippet noCopy snippet={formatPayload(payload)} />}
+ {payload !== undefined && <CodeSnippet noCopy snippet={formatPayload(payload)} />}
</Spinner>
</div>
);
import { getMeasures } from '../../../api/measures';
import { getAllMetrics } from '../../../api/metrics';
import Link from '../../../components/common/Link';
-import { ResetButtonLink } from '../../../components/controls/buttons';
import Modal from '../../../components/controls/Modal';
+import { ResetButtonLink } from '../../../components/controls/buttons';
import IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
import QualifierIcon from '../../../components/icons/QualifierIcon';
import TagsIcon from '../../../components/icons/TagsIcon';
} from '../../../helpers/measures';
import { getBranchLikeUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
-import { IssueSeverity, IssueType as IssueTypeEnum } from '../../../types/issues';
+import { ComponentQualifier } from '../../../types/component';
+import { FacetName, IssueSeverity, IssueType as IssueTypeEnum } from '../../../types/issues';
import { MetricType } from '../../../types/metrics';
import { FacetValue, IssueType, MeasureEnhanced, SourceViewerFile } from '../../../types/types';
import Measure from '../../measure/Measure';
resolved: 'false',
...getBranchLikeQuery(this.props.branchLike),
},
- ['types', 'severities', 'tags'],
+ [FacetName.Types, FacetName.Severities, FacetName.Tags],
).then(({ facets }) => {
const severitiesFacet = facets.find((f) => f.property === 'severities');
const tagsFacet = facets.find((f) => f.property === 'tags');
const typesFacet = facets.find((f) => f.property === 'types');
return {
- severitiesFacet: severitiesFacet && severitiesFacet.values,
- tagsFacet: tagsFacet && tagsFacet.values,
+ severitiesFacet: severitiesFacet?.values,
+ tagsFacet: tagsFacet?.values,
typesFacet: typesFacet && (typesFacet.values as FacetValue<IssueType>[]),
};
});
<div className="modal-body modal-container">
<div className="measures-viewer-header big-spacer-bottom">
<div>
- <QualifierIcon className="little-spacer-right" qualifier="TRK" />
+ <QualifierIcon
+ className="little-spacer-right"
+ qualifier={ComponentQualifier.Project}
+ />
<Link to={getBranchLikeUrl(sourceViewerFile.project, branchLike)}>
{sourceViewerFile.projectName}
</Link>
<i className="spinner" />
) : (
<>
- {sourceViewerFile.q === 'UTS' ? (
+ {sourceViewerFile.q === ComponentQualifier.TestFile ? (
this.renderTests()
) : (
<div className="measures-viewer">
import { renderComponent } from '../../../helpers/testReactTestingUtils';
import { byLabelText, byPlaceholderText, byRole, byText } from '../../../helpers/testSelector';
import { ComponentPropsType } from '../../../helpers/testUtils';
-import { MetricKey } from '../../../types/metrics';
+import { MetricKey, MetricType } from '../../../types/metrics';
import { GraphType, MeasureHistory } from '../../../types/project-activity';
import { Metric } from '../../../types/types';
import GraphsHeader from '../GraphsHeader';
metrics.push(
mockMetric({
key: metric,
- type: metric.includes('_density') || metric === MetricKey.coverage ? 'PERCENT' : 'INT',
+ type:
+ metric.includes('_density') || metric === MetricKey.coverage
+ ? MetricType.Percent
+ : MetricType.Integer,
}),
);
});
// The following should be filtered out, and not be suggested as options.
metrics.push(
- mockMetric({ key: MetricKey.new_bugs, type: 'INT' }),
- mockMetric({ key: MetricKey.burned_budget, type: 'DATA' }),
+ mockMetric({ key: MetricKey.new_bugs, type: MetricType.Integer }),
+ mockMetric({ key: MetricKey.burned_budget, type: MetricType.Data }),
);
// The following will not be filtered out, but has no values.
- metrics.push(mockMetric({ key: MetricKey.test_failures, type: 'INT' }));
+ metrics.push(mockMetric({ key: MetricKey.test_failures, type: MetricType.Integer }));
measuresHistory.push(
mockMeasureHistory({
metric: MetricKey.test_failures,
: 'component_report.unsubscribe_x_success';
const frequencyTranslation = translate(
'report.frequency',
- status?.componentFrequency || status?.globalFrequency || '',
+ status?.componentFrequency ?? status?.globalFrequency ?? '',
).toLowerCase();
const qualifierTranslation = translate('qualifier', component.qualifier).toLowerCase();
{total !== undefined
? translateWithParameters(
'x_of_y_shown',
- formatMeasure(count, MetricType.Integer, null),
- formatMeasure(total, MetricType.Integer, null),
+ formatMeasure(count, MetricType.Integer),
+ formatMeasure(total, MetricType.Integer),
)
- : translateWithParameters('x_show', formatMeasure(count, MetricType.Integer, null))}
+ : translateWithParameters('x_show', formatMeasure(count, MetricType.Integer))}
</span>
{button}
{/* eslint-disable local-rules/no-conditional-rendering-of-deferredspinner */}
return <components.MultiValueRemove {...props}>×</components.MultiValueRemove>;
}
-/* Keeping it as a class to simplify a dozen tests */
-export default class Select<
+export default function Select<
Option = LabelValueSelectOption,
IsMulti extends boolean = boolean,
Group extends GroupBase<Option> = GroupBase<Option>,
-> extends React.Component<NamedProps<Option, IsMulti, Group> & StyleExtensionProps> {
- render() {
- return (
- <ReactSelect<Option, IsMulti, Group>
- {...omit(this.props, 'className', 'large')}
- styles={selectStyle<Option, IsMulti, Group>(this.props)}
- className={classNames('react-select', this.props.className)}
- classNamePrefix="react-select"
- components={{
- ...this.props.components,
- DropdownIndicator: dropdownIndicator,
- ClearIndicator: clearIndicator,
- MultiValueRemove: multiValueRemove,
- }}
- />
- );
- }
+>(props: NamedProps<Option, IsMulti, Group> & StyleExtensionProps) {
+ return (
+ <ReactSelect<Option, IsMulti, Group>
+ {...omit(props, 'className', 'large')}
+ styles={selectStyle<Option, IsMulti, Group>(props)}
+ className={classNames('react-select', props.className)}
+ classNamePrefix="react-select"
+ components={{
+ ...props.components,
+ DropdownIndicator: dropdownIndicator,
+ ClearIndicator: clearIndicator,
+ MultiValueRemove: multiValueRemove,
+ }}
+ />
+ );
}
export function CreatableSelect<
import * as React from 'react';
import { createPortal, findDOMNode } from 'react-dom';
import { rawSizes } from '../../app/theme';
+import { ONE_SECOND } from '../../helpers/constants';
import { translate } from '../../helpers/l10n';
import EscKeydownHandler from './EscKeydownHandler';
import FocusOutHandler from './FocusOutHandler';
this.state = {
flipped: false,
placement: props.placement,
- visible: props.visible !== undefined ? props.visible : false,
+ visible: props.visible ?? false,
};
this.id = uniqueId('tooltip-');
this.throttledPositionTooltip = throttle(this.positionTooltip, 10);
};
isVisible = () => {
- return this.props.visible !== undefined ? this.props.visible : this.state.visible;
+ return this.props.visible ?? this.state.visible;
};
getPlacement = (): Placement => {
- return this.state.placement || 'bottom';
+ return this.state.placement ?? 'bottom';
};
tooltipNodeRef = (node: HTMLElement | null) => {
this.setState({ visible: true });
}
},
- (this.props.mouseEnterDelay || 0) * 1000,
+ (this.props.mouseEnterDelay ?? 0) * ONE_SECOND,
);
if (this.props.onShow) {
this.props.onHide();
}
},
- (this.props.mouseLeaveDelay || 0) * 1000,
+ (this.props.mouseLeaveDelay ?? 0) * ONE_SECOND,
);
}
};
import * as React from 'react';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { formatMeasure } from '../../helpers/measures';
+import { MetricType } from '../../types/metrics';
import { ButtonLink } from '../controls/buttons';
interface Props {
return (
<div className="note spacer-top spacer-bottom text-center">
- {translateWithParameters('x_show', formatMeasure(count, 'INT', null))}
+ {translateWithParameters('x_show', formatMeasure(count, MetricType.Integer))}
{hasMore && (
<ButtonLink
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { translate } from '../../../helpers/l10n';
-import { IssueComment } from '../../../types/types';
-import CommentTile from './CommentTile';
-
-interface CommentListProps {
- comments?: IssueComment[];
- deleteComment: (comment: string) => void;
- onEdit: (comment: string, text: string) => void;
-}
-
-export default function CommentList(props: CommentListProps) {
- const { comments } = props;
- if (!comments || comments.length === 0) {
- return (
- <div className="note spacer-top spacer-bottom">{translate('issue.comment.empty.list')}</div>
- );
- }
-
- // sorting comment i.e showing newest on top
- const sortedComments = [...comments]?.sort(
- (com1, com2) =>
- new Date(com2.createdAt || '').getTime() - new Date(com1.createdAt || '').getTime(),
- );
- return (
- <div className="issue-comment-list-wrapper spacer-bottom">
- {sortedComments?.map((c) => (
- <CommentTile
- comment={c}
- key={c.key}
- handleDelete={props.deleteComment}
- onEdit={props.onEdit}
- />
- ))}
- </div>
- );
-}
getMaintainabilityGrid,
} from '../../helpers/ratings';
import { AppState } from '../../types/appstate';
-import { MetricKey } from '../../types/metrics';
+import { MetricKey, MetricType } from '../../types/metrics';
import { GlobalSettingKeys } from '../../types/settings';
import { KNOWN_RATINGS } from './utils';
}
const rating = Number(value);
- const ratingLetter = formatMeasure(value, 'RATING');
+ const ratingLetter = formatMeasure(value, MetricType.Rating);
if (finalMetricKey !== 'sqale_rating' && finalMetricKey !== 'maintainability_rating') {
return <>{translate('metric', finalMetricKey, 'tooltip', ratingLetter)}</>;
{rating === 1
? translateWithParameters(
'metric.sqale_rating.tooltip.A',
- formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, 'PERCENT'),
+ formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, MetricType.Percent),
)
: translateWithParameters(
'metric.sqale_rating.tooltip',
ratingLetter,
- formatMeasure(maintainabilityRatingThreshold * PERCENT_MULTIPLIER, 'PERCENT'),
+ formatMeasure(maintainabilityRatingThreshold * PERCENT_MULTIPLIER, MetricType.Percent),
)}
</>
);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { MetricKey, MetricType } from '../../../types/metrics';
import Measure from '../Measure';
it('renders trivial measure', () => {
expect(
- shallow(<Measure metricKey="coverage" metricType="PERCENT" value="73.0" />),
+ shallow(
+ <Measure metricKey={MetricKey.coverage} metricType={MetricType.Percent} value="73.0" />,
+ ),
).toMatchSnapshot();
});
it('renders leak measure', () => {
expect(
- shallow(<Measure metricKey="new_coverage" metricType="PERCENT" value="36.0" />),
+ shallow(
+ <Measure metricKey={MetricKey.new_coverage} metricType={MetricType.Percent} value="36.0" />,
+ ),
).toMatchSnapshot();
});
it('renders LEVEL', () => {
expect(
- shallow(<Measure metricKey="quality_gate_status" metricType="LEVEL" value="ERROR" />),
+ shallow(
+ <Measure metricKey="quality_gate_status" metricType={MetricType.Level} value="ERROR" />,
+ ),
).toMatchSnapshot();
});
it('renders RATING', () => {
expect(
- shallow(<Measure metricKey="sqale_rating" metricType="RATING" value="3" />),
+ shallow(
+ <Measure metricKey={MetricKey.sqale_rating} metricType={MetricType.Rating} value="3" />,
+ ),
).toMatchSnapshot();
});
it('renders undefined measure', () => {
expect(
- shallow(<Measure metricKey="foo" metricType="PERCENT" value={undefined} />),
+ shallow(<Measure metricKey="foo" metricType={MetricType.Percent} value={undefined} />),
).toMatchSnapshot();
});
import { EditionKey } from '../../types/editions';
import { SystemUpgrade } from '../../types/system';
import Link from '../common/Link';
-import { ResetButtonLink } from '../controls/buttons';
import Modal from '../controls/Modal';
+import { ResetButtonLink } from '../controls/buttons';
import { Alert, AlertVariant } from '../ui/Alert';
import SystemUpgradeItem from './SystemUpgradeItem';
-import { UpdateUseCase } from './utils';
+import { SYSTEM_VERSION_REGEXP, UpdateUseCase } from './utils';
interface Props {
appState: AppState;
[UpdateUseCase.PreviousLTS]: 'error',
};
-interface State {
- upgrading: boolean;
-}
+export function SystemUpgradeForm(props: Props) {
+ const { appState, latestLTS, onClose, updateUseCase, systemUpgrades } = props;
+
+ let systemUpgradesWithPatch: SystemUpgrade[][] = [];
+ const alertVariant = updateUseCase ? MAP_ALERT[updateUseCase] : undefined;
+ const header = translate('system.system_upgrade');
+ const parsedVersion = SYSTEM_VERSION_REGEXP.exec(appState.version);
+ let patches: SystemUpgrade[] = [];
+
+ if (updateUseCase === UpdateUseCase.NewPatch && parsedVersion !== null) {
+ const [, major, minor] = parsedVersion;
+ const majoMinorVersion = `${major}.${minor}`;
-export class SystemUpgradeForm extends React.PureComponent<Props, State> {
- versionParser = /^(\d+)\.(\d+)(\.(\d+))?/;
- state: State = { upgrading: false };
+ patches = flatMap(systemUpgrades, (upgrades) =>
+ filter(upgrades, (upgrade) => upgrade.version.startsWith(majoMinorVersion)),
+ );
+ systemUpgradesWithPatch = systemUpgrades
+ .map((upgrades) =>
+ upgrades.filter((upgrade) => !upgrade.version.startsWith(majoMinorVersion)),
+ )
+ .filter(negate(isEmpty));
+
+ systemUpgradesWithPatch.push(patches);
+ } else {
+ let untilLTS = false;
- render() {
- const { upgrading } = this.state;
- const { appState, systemUpgrades, latestLTS, updateUseCase } = this.props;
- let systemUpgradesWithPatch: SystemUpgrade[][] = [];
- const alertVariant = updateUseCase ? MAP_ALERT[updateUseCase] : undefined;
- const header = translate('system.system_upgrade');
- const parsedVersion = this.versionParser.exec(appState.version);
- let patches: SystemUpgrade[] = [];
- if (updateUseCase === UpdateUseCase.NewPatch && parsedVersion !== null) {
- const [, major, minor] = parsedVersion;
- const majoMinorVersion = `${major}.${minor}`;
- patches = flatMap(systemUpgrades, (upgrades) =>
- filter(upgrades, (upgrade) => upgrade.version.startsWith(majoMinorVersion)),
- );
- systemUpgradesWithPatch = systemUpgrades
- .map((upgrades) =>
- upgrades.filter((upgrade) => !upgrade.version.startsWith(majoMinorVersion)),
- )
- .filter(negate(isEmpty));
- systemUpgradesWithPatch.push(patches);
- } else {
- let untilLTS = false;
- for (const upgrades of systemUpgrades) {
- if (untilLTS === false) {
- systemUpgradesWithPatch.push(upgrades);
- untilLTS = upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS));
- }
+ for (const upgrades of systemUpgrades) {
+ if (untilLTS === false) {
+ systemUpgradesWithPatch.push(upgrades);
+ untilLTS = upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS));
}
}
+ }
- return (
- <Modal contentLabel={header} onRequestClose={this.props.onClose}>
- <div className="modal-head">
- <h2>{header}</h2>
- </div>
+ return (
+ <Modal contentLabel={header} onRequestClose={onClose}>
+ <div className="modal-head">
+ <h2>{header}</h2>
+ </div>
- <div className="modal-body">
- {alertVariant && updateUseCase && (
- <Alert variant={alertVariant} className={`it__upgrade-alert-${updateUseCase}`}>
- {translate('admin_notification.update', updateUseCase)}
- </Alert>
- )}
- {systemUpgradesWithPatch.map((upgrades) => (
- <SystemUpgradeItem
- edition={
- appState.edition as EditionKey /* TODO: Fix once AppState is no longer ambiant. */
- }
- key={upgrades[upgrades.length - 1].version}
- systemUpgrades={upgrades}
- isPatch={upgrades === patches}
- isLTSVersion={upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS))}
- />
- ))}
- </div>
- <div className="modal-foot">
- {upgrading && <i className="spinner spacer-right" />}
- <Link
- className="pull-left link-no-underline display-flex-center"
- to="https://www.sonarqube.org/downloads/?referrer=sonarqube"
- target="_blank"
- >
- {translate('system.see_sonarqube_downloads')}
- </Link>
- <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
- </div>
- </Modal>
- );
- }
+ <div className="modal-body">
+ {alertVariant && updateUseCase && (
+ <Alert variant={alertVariant} className={`it__upgrade-alert-${updateUseCase}`}>
+ {translate('admin_notification.update', updateUseCase)}
+ </Alert>
+ )}
+ {systemUpgradesWithPatch.map((upgrades) => (
+ <SystemUpgradeItem
+ edition={
+ appState.edition as EditionKey /* TODO: Fix once AppState is no longer ambiant. */
+ }
+ key={upgrades[upgrades.length - 1].version}
+ systemUpgrades={upgrades}
+ isPatch={upgrades === patches}
+ isLTSVersion={upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS))}
+ />
+ ))}
+ </div>
+ <div className="modal-foot">
+ <Link
+ className="pull-left link-no-underline display-flex-center"
+ to="https://www.sonarqube.org/downloads/?referrer=sonarqube"
+ target="_blank"
+ >
+ {translate('system.see_sonarqube_downloads')}
+ </Link>
+ <ResetButtonLink onClick={onClose}>{translate('cancel')}</ResetButtonLink>
+ </div>
+ </Modal>
+ );
}
export default withAppStateContext(SystemUpgradeForm);
PreviousLTS = 'previous_lts',
}
+export const SYSTEM_VERSION_REGEXP = /^(\d+)\.(\d+)(\.(\d+))?/;
+
export function sortUpgrades(upgrades: SystemUpgrade[]): SystemUpgrade[] {
return sortBy(upgrades, [
(upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[0]),
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { MetricKey, MetricType } from '../../types/metrics';
import { Dict } from '../../types/types';
import { getMessages } from '../l10nBundle';
import { enhanceConditionWithMeasure, formatMeasure, isPeriodBestValue } from '../measures';
describe('enhanceConditionWithMeasure', () => {
it('should correctly map enhance conditions with measure data', () => {
const measures = [
- mockMeasureEnhanced({ metric: mockMetric({ key: 'bugs' }), period: undefined }),
- mockMeasureEnhanced({ metric: mockMetric({ key: 'new_bugs' }) }),
+ mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }), period: undefined }),
+ mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_bugs }) }),
];
expect(
- enhanceConditionWithMeasure(mockQualityGateStatusCondition({ metric: 'bugs' }), measures),
+ enhanceConditionWithMeasure(
+ mockQualityGateStatusCondition({ metric: MetricKey.bugs }),
+ measures,
+ ),
).toMatchObject({
- measure: expect.objectContaining({ metric: expect.objectContaining({ key: 'bugs' }) }),
+ measure: expect.objectContaining({
+ metric: expect.objectContaining({ key: MetricKey.bugs }),
+ }),
});
expect(
- enhanceConditionWithMeasure(mockQualityGateStatusCondition({ metric: 'new_bugs' }), measures),
+ enhanceConditionWithMeasure(
+ mockQualityGateStatusCondition({ metric: MetricKey.new_bugs }),
+ measures,
+ ),
).toMatchObject({
measure: expect.objectContaining({
- metric: expect.objectContaining({ key: 'new_bugs' }),
+ metric: expect.objectContaining({ key: MetricKey.new_bugs }),
}),
period: 1,
});
describe('#formatMeasure()', () => {
it('should format INT', () => {
- expect(formatMeasure(0, 'INT')).toBe('0');
- expect(formatMeasure(1, 'INT')).toBe('1');
- expect(formatMeasure(-5, 'INT')).toBe('-5');
- expect(formatMeasure(999, 'INT')).toBe('999');
- expect(formatMeasure(1000, 'INT')).toBe('1,000');
- expect(formatMeasure(1529, 'INT')).toBe('1,529');
- expect(formatMeasure(10000, 'INT')).toBe('10,000');
- expect(formatMeasure(1234567890, 'INT')).toBe('1,234,567,890');
+ expect(formatMeasure(0, MetricType.Integer)).toBe('0');
+ expect(formatMeasure(1, MetricType.Integer)).toBe('1');
+ expect(formatMeasure(-5, MetricType.Integer)).toBe('-5');
+ expect(formatMeasure(999, MetricType.Integer)).toBe('999');
+ expect(formatMeasure(1000, MetricType.Integer)).toBe('1,000');
+ expect(formatMeasure(1529, MetricType.Integer)).toBe('1,529');
+ expect(formatMeasure(10000, MetricType.Integer)).toBe('10,000');
+ expect(formatMeasure(1234567890, MetricType.Integer)).toBe('1,234,567,890');
});
it('should format SHORT_INT', () => {
- expect(formatMeasure(0, 'SHORT_INT')).toBe('0');
- expect(formatMeasure(1, 'SHORT_INT')).toBe('1');
- expect(formatMeasure(999, 'SHORT_INT')).toBe('999');
- expect(formatMeasure(1000, 'SHORT_INT')).toBe('1k');
- expect(formatMeasure(1529, 'SHORT_INT')).toBe('1.5k');
- expect(formatMeasure(10000, 'SHORT_INT')).toBe('10k');
- expect(formatMeasure(10678, 'SHORT_INT')).toBe('11k');
- expect(formatMeasure(9467890, 'SHORT_INT')).toBe('9.5M');
- expect(formatMeasure(994567890, 'SHORT_INT')).toBe('995M');
- expect(formatMeasure(999000001, 'SHORT_INT')).toBe('999M');
- expect(formatMeasure(999567890, 'SHORT_INT')).toBe('1G');
- expect(formatMeasure(1234567890, 'SHORT_INT')).toBe('1.2G');
- expect(formatMeasure(11234567890, 'SHORT_INT')).toBe('11G');
+ expect(formatMeasure(0, MetricType.ShortInteger)).toBe('0');
+ expect(formatMeasure(1, MetricType.ShortInteger)).toBe('1');
+ expect(formatMeasure(999, MetricType.ShortInteger)).toBe('999');
+ expect(formatMeasure(1000, MetricType.ShortInteger)).toBe('1k');
+ expect(formatMeasure(1529, MetricType.ShortInteger)).toBe('1.5k');
+ expect(formatMeasure(10000, MetricType.ShortInteger)).toBe('10k');
+ expect(formatMeasure(10678, MetricType.ShortInteger)).toBe('11k');
+ expect(formatMeasure(9467890, MetricType.ShortInteger)).toBe('9.5M');
+ expect(formatMeasure(994567890, MetricType.ShortInteger)).toBe('995M');
+ expect(formatMeasure(999000001, MetricType.ShortInteger)).toBe('999M');
+ expect(formatMeasure(999567890, MetricType.ShortInteger)).toBe('1G');
+ expect(formatMeasure(1234567890, MetricType.ShortInteger)).toBe('1.2G');
+ expect(formatMeasure(11234567890, MetricType.ShortInteger)).toBe('11G');
});
it('should format FLOAT', () => {
});
it('should format PERCENT', () => {
- expect(formatMeasure(0.0, 'PERCENT')).toBe('0.0%');
- expect(formatMeasure(1.0, 'PERCENT')).toBe('1.0%');
- expect(formatMeasure(1.3, 'PERCENT')).toBe('1.3%');
- expect(formatMeasure(1.34, 'PERCENT')).toBe('1.3%');
- expect(formatMeasure(50.89, 'PERCENT')).toBe('50.9%');
- expect(formatMeasure(100.0, 'PERCENT')).toBe('100%');
- expect(formatMeasure(50.89, 'PERCENT', { decimals: 0 })).toBe('50.9%');
- expect(formatMeasure(50.89, 'PERCENT', { decimals: 1 })).toBe('50.9%');
- expect(formatMeasure(50.89, 'PERCENT', { decimals: 2 })).toBe('50.89%');
- expect(formatMeasure(50.89, 'PERCENT', { decimals: 3 })).toBe('50.890%');
- expect(formatMeasure(50, 'PERCENT', { decimals: 0, omitExtraDecimalZeros: true })).toBe(
- '50.0%',
- );
- expect(formatMeasure(50, 'PERCENT', { decimals: 1, omitExtraDecimalZeros: true })).toBe(
- '50.0%',
- );
- expect(formatMeasure(50, 'PERCENT', { decimals: 3, omitExtraDecimalZeros: true })).toBe(
- '50.0%',
- );
- expect(formatMeasure(50.89, 'PERCENT', { decimals: 3, omitExtraDecimalZeros: true })).toBe(
- '50.89%',
- );
+ expect(formatMeasure(0.0, MetricType.Percent)).toBe('0.0%');
+ expect(formatMeasure(1.0, MetricType.Percent)).toBe('1.0%');
+ expect(formatMeasure(1.3, MetricType.Percent)).toBe('1.3%');
+ expect(formatMeasure(1.34, MetricType.Percent)).toBe('1.3%');
+ expect(formatMeasure(50.89, MetricType.Percent)).toBe('50.9%');
+ expect(formatMeasure(100.0, MetricType.Percent)).toBe('100%');
+ expect(formatMeasure(50.89, MetricType.Percent, { decimals: 0 })).toBe('50.9%');
+ expect(formatMeasure(50.89, MetricType.Percent, { decimals: 1 })).toBe('50.9%');
+ expect(formatMeasure(50.89, MetricType.Percent, { decimals: 2 })).toBe('50.89%');
+ expect(formatMeasure(50.89, MetricType.Percent, { decimals: 3 })).toBe('50.890%');
+ expect(
+ formatMeasure(50, MetricType.Percent, { decimals: 0, omitExtraDecimalZeros: true }),
+ ).toBe('50.0%');
+ expect(
+ formatMeasure(50, MetricType.Percent, { decimals: 1, omitExtraDecimalZeros: true }),
+ ).toBe('50.0%');
+ expect(
+ formatMeasure(50, MetricType.Percent, { decimals: 3, omitExtraDecimalZeros: true }),
+ ).toBe('50.0%');
+ expect(
+ formatMeasure(50.89, MetricType.Percent, { decimals: 3, omitExtraDecimalZeros: true }),
+ ).toBe('50.89%');
});
it('should format WORK_DUR', () => {
});
it('should format SHORT_WORK_DUR', () => {
- expect(formatMeasure(0, 'SHORT_WORK_DUR')).toBe('0');
- expect(formatMeasure(5 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('5d');
- expect(formatMeasure(2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('2h');
- expect(formatMeasure(ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('1min');
- expect(formatMeasure(40 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('40min');
- expect(formatMeasure(58 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('1h');
- expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('5d');
- expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('2h');
- expect(formatMeasure(ONE_HOUR + 55 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('2h');
- expect(formatMeasure(3 * ONE_DAY + 6 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('4d');
- expect(formatMeasure(7 * ONE_HOUR + 59 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('1d');
- expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('5d');
- expect(formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('15d');
- expect(formatMeasure(7 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('7min');
- expect(formatMeasure(-5 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('-5d');
- expect(formatMeasure(-2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('-2h');
- expect(formatMeasure(-1 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('-1min');
-
- expect(formatMeasure(1529 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('1.5kd');
- expect(formatMeasure(1234567 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('1.2Md');
- expect(formatMeasure(12345670 * ONE_DAY + 4 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('12Md');
+ expect(formatMeasure(0, MetricType.ShortWorkDuration)).toBe('0');
+ expect(formatMeasure(5 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('5d');
+ expect(formatMeasure(2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('2h');
+ expect(formatMeasure(ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1min');
+ expect(formatMeasure(40 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('40min');
+ expect(formatMeasure(58 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1h');
+ expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('5d');
+ expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('2h');
+ expect(formatMeasure(ONE_HOUR + 55 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('2h');
+ expect(formatMeasure(3 * ONE_DAY + 6 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('4d');
+ expect(formatMeasure(7 * ONE_HOUR + 59 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1d');
+ expect(
+ formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration),
+ ).toBe('5d');
+ expect(
+ formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration),
+ ).toBe('15d');
+ expect(formatMeasure(7 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('7min');
+ expect(formatMeasure(-5 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('-5d');
+ expect(formatMeasure(-2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('-2h');
+ expect(formatMeasure(-1 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('-1min');
+
+ expect(formatMeasure(1529 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('1.5kd');
+ expect(formatMeasure(1234567 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('1.2Md');
+ expect(formatMeasure(12345670 * ONE_DAY + 4 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe(
+ '12Md',
+ );
});
it('should format RATING', () => {
- expect(formatMeasure(1, 'RATING')).toBe('A');
- expect(formatMeasure(2, 'RATING')).toBe('B');
- expect(formatMeasure(3, 'RATING')).toBe('C');
- expect(formatMeasure(4, 'RATING')).toBe('D');
- expect(formatMeasure(5, 'RATING')).toBe('E');
+ expect(formatMeasure(1, MetricType.Rating)).toBe('A');
+ expect(formatMeasure(2, MetricType.Rating)).toBe('B');
+ expect(formatMeasure(3, MetricType.Rating)).toBe('C');
+ expect(formatMeasure(4, MetricType.Rating)).toBe('D');
+ expect(formatMeasure(5, MetricType.Rating)).toBe('E');
});
it('should format LEVEL', () => {
- expect(formatMeasure('ERROR', 'LEVEL')).toBe('Error');
- expect(formatMeasure('WARN', 'LEVEL')).toBe('Warning');
- expect(formatMeasure('OK', 'LEVEL')).toBe('Ok');
- expect(formatMeasure('UNKNOWN', 'LEVEL')).toBe('UNKNOWN');
+ expect(formatMeasure('ERROR', MetricType.Level)).toBe('Error');
+ expect(formatMeasure('WARN', MetricType.Level)).toBe('Warning');
+ expect(formatMeasure('OK', MetricType.Level)).toBe('Ok');
+ expect(formatMeasure('UNKNOWN', MetricType.Level)).toBe('UNKNOWN');
});
it('should format MILLISEC', () => {
});
it('should return null if value is empty string', () => {
- expect(formatMeasure('', 'PERCENT')).toBe('');
+ expect(formatMeasure('', MetricType.Percent)).toBe('');
});
it('should not fail with undefined', () => {
- expect(formatMeasure(undefined, 'INT')).toBe('');
+ expect(formatMeasure(undefined, MetricType.Integer)).toBe('');
});
});
];
export const GRADLE_SCANNER_VERSION = '4.3.1.3277';
+
+export const ONE_SECOND = 1000;
};
}
- const parsedFlows: FlowLocation[][] = (issue.flows || [])
+ const parsedFlows: FlowLocation[][] = (issue.flows ?? [])
.filter((flow) => flow.locations !== undefined)
.map((flow) => flow.locations!.filter((location) => location.textRange != null))
.map((flow) =>
flow.map((location) => {
const component = components.find((component) => component.key === location.component);
- return { ...location, componentName: component && component.name };
+ return { ...location, componentName: component?.name };
}),
);
function orderLocations(locations: FlowLocation[]) {
return sortBy(
locations,
- (location) => location.textRange && location.textRange.startLine,
- (location) => location.textRange && location.textRange.startOffset,
+ (location) => location.textRange?.startLine,
+ (location) => location.textRange?.startOffset,
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { MetricKey } from '../types/metrics';
+import { MetricKey, MetricType } from '../types/metrics';
import {
QualityGateStatusCondition,
QualityGateStatusConditionEnhanced,
} from '../types/quality-gates';
import { Dict, Measure, MeasureEnhanced, Metric } from '../types/types';
+import { ONE_SECOND } from './constants';
import { translate, translateWithParameters } from './l10n';
import { getCurrentLocale } from './l10nBundle';
import { isDefined } from './types';
// Make sure we have a period index. This is necessary when dealing with
// applications.
let { period } = condition;
- if (measure && measure.period && !period) {
+ if (measure?.period && !period) {
period = measure.period.index;
}
}
export function isPeriodBestValue(measure: Measure | MeasureEnhanced): boolean {
- return measure.period?.bestValue || false;
+ return measure.period?.bestValue ?? false;
}
/** Check if metric is differential */
export function isDiffMetric(metricKey: MetricKey | string): boolean {
- return metricKey.indexOf('new_') === 0;
+ return metricKey.startsWith('new_');
}
export function getDisplayMetrics(metrics: Metric[]) {
const HOURS_IN_DAY = 8;
-interface Formatter {
- (value: string | number, options?: any): string;
-}
+type Formatter = (value: string | number, options?: Dict<unknown>) => string;
/**
* Format a measure value for a given type
export function formatMeasure(
value: string | number | undefined,
type: string,
- options?: any,
+ options?: Dict<unknown>,
): string {
const formatter = getFormatter(type);
// eslint-disable-next-line react-hooks/rules-of-hooks
/** Return corresponding "short" for better display in UI */
export function getShortType(type: string): string {
- if (type === 'INT') {
- return 'SHORT_INT';
+ if (type === MetricType.Integer) {
+ return MetricType.ShortInteger;
} else if (type === 'WORK_DUR') {
- return 'SHORT_WORK_DUR';
+ return MetricType.ShortWorkDuration;
}
return type;
}
function useFormatter(
value: string | number | undefined,
formatter: Formatter,
- options?: any,
+ options?: Dict<unknown>,
): string {
return value !== undefined && value !== '' ? formatter(value, options) : '';
}
value: string | number,
option?: { roundingFunc?: (x: number) => number },
): string {
- const roundingFunc = (option && option.roundingFunc) || undefined;
+ const roundingFunc = option?.roundingFunc;
if (typeof value === 'string') {
value = parseFloat(value);
}
if (typeof value === 'string') {
value = parseInt(value, 10);
}
- const ONE_SECOND = 1000;
const ONE_MINUTE = 60 * ONE_SECOND;
if (value >= ONE_MINUTE) {
const minutes = Math.round(value / ONE_MINUTE);
): string {
if (shouldDisplayDaysInShortFormat(days)) {
const roundedDays = Math.round(days);
- const formattedDays = formatMeasure(isNegative ? -1 * roundedDays : roundedDays, 'SHORT_INT');
+ const formattedDays = formatMeasure(
+ isNegative ? -1 * roundedDays : roundedDays,
+ MetricType.ShortInteger,
+ );
return translateWithParameters('work_duration.x_days', formattedDays);
}
const roundedHours = Math.round(hours);
const formattedHours = formatMeasure(
isNegative ? -1 * roundedHours : roundedHours,
- 'SHORT_INT',
+ MetricType.ShortInteger,
);
return translateWithParameters('work_duration.x_hours', formattedHours);
}
- const formattedMinutes = formatMeasure(isNegative ? -1 * minutes : minutes, 'SHORT_INT');
+ const formattedMinutes = formatMeasure(
+ isNegative ? -1 * minutes : minutes,
+ MetricType.ShortInteger,
+ );
return translateWithParameters('work_duration.x_minutes', formattedMinutes);
}
const [provided, unknown] = partition<T>(links, isProvided);
return [
...sortBy(provided, (link) => PROVIDED_TYPES.indexOf(link.type)),
- ...sortBy(unknown, (link) => link.name && link.name.toLowerCase()),
+ ...sortBy(unknown, (link) => link.name?.toLowerCase()),
];
}
import { stringify } from './stringify-queryparams';
import { getBaseUrl } from './system';
+const FAST_RETRY_TIMEOUT = 500;
+const SLOW_RETRY_TIMEOUT = 3000;
+
export function getCSRFTokenName(): string {
return 'X-XSRF-TOKEN';
}
return window.fetch(getBaseUrl() + url, options);
}
- setMethod(method: string): Request {
+ setMethod(method: string): this {
this.options.method = method;
return this;
}
- setData(data?: RequestData, isJSON = false): Request {
+ setData(data?: RequestData, isJSON = false): this {
if (data) {
this.data = data;
this.isJSON = isJSON;
/**
* Check that response status is ok
*/
-export function checkStatus(response: Response, bypassRedirect?: boolean): Promise<Response> {
+export function checkStatus(response: Response, bypassRedirect = false): Promise<Response> {
return new Promise((resolve, reject) => {
if (response.status === HttpStatus.Unauthorized && !bypassRedirect) {
import('./handleRequiredAuthentication').then((i) => i.default()).then(reject, reject);
/**
* Shortcut to do a GET request and return a Response
*/
-export function get(url: string, data?: RequestData, bypassRedirect?: boolean): Promise<Response> {
+export function get(url: string, data?: RequestData, bypassRedirect = false): Promise<Response> {
return request(url)
.setData(data)
.submit()
/**
* Shortcut to do a GET request and return response json
*/
-export function getJSON(url: string, data?: RequestData, bypassRedirect?: boolean): Promise<any> {
+export function getJSON(url: string, data?: RequestData, bypassRedirect = false): Promise<any> {
return get(url, data, bypassRedirect).then(parseJSON);
}
/**
* Shortcut to do a GET request and return response text
*/
-export function getText(
- url: string,
- data?: RequestData,
- bypassRedirect?: boolean,
-): Promise<string> {
+export function getText(url: string, data?: RequestData, bypassRedirect = false): Promise<string> {
return get(url, data, bypassRedirect).then(parseText);
}
/**
* Shortcut to do a POST request and return response json
*/
-export function postJSON(url: string, data?: RequestData, bypassRedirect?: boolean): Promise<any> {
+export function postJSON(url: string, data?: RequestData, bypassRedirect = false): Promise<any> {
return request(url)
.setMethod('POST')
.setData(data)
export function postJSONBody(
url: string,
data?: RequestData,
- bypassRedirect?: boolean,
+ bypassRedirect = false,
): Promise<any> {
return request(url)
.setMethod('POST')
/**
* Shortcut to do a POST request
*/
-export function post(url: string, data?: RequestData, bypassRedirect?: boolean): Promise<void> {
+export function post(url: string, data?: RequestData, bypassRedirect = false): Promise<void> {
return new Promise((resolve, reject) => {
request(url)
.setMethod('POST')
return new Promise<T>((resolve) => {
setTimeout(
() => resolve(requestTryAndRepeatUntil(repeatAPICall, tries, stopRepeat, repeatErrors)),
- tries.max > tries.slowThreshold ? 500 : 3000,
+ tries.max > tries.slowThreshold ? FAST_RETRY_TIMEOUT : SLOW_RETRY_TIMEOUT,
);
});
}
export interface Facet {
[value: string]: number;
}
+
+export enum FacetName {
+ AssignedToMe = 'assigned_to_me',
+ Assignees = 'assignees',
+ Author = 'author',
+ CodeVariants = 'codeVariants',
+ CreatedAt = 'createdAt',
+ Cwe = 'cwe',
+ Directories = 'directories',
+ Files = 'files',
+ Languages = 'languages',
+ OwaspTop10 = 'owaspTop10',
+ Projects = 'projects',
+ Reporters = 'reporters',
+ Resolutions = 'resolutions',
+ Rules = 'rules',
+ Severities = 'severities',
+ Statuses = 'statuses',
+ Tags = 'tags',
+ Types = 'types',
+}