} from '../../types/clean-code-taxonomy';
import { SearchRulesResponse } from '../../types/coding-rules';
import { IssueSeverity } from '../../types/issues';
+import { QualityProfileChangelogFilterMode } from '../../types/quality-profiles';
import { SearchRulesQuery } from '../../types/rules';
import { Dict, Paging, ProfileInheritanceDetails, RuleDetails } from '../../types/types';
import {
- CompareResponse,
- Profile,
- ProfileProject,
- SearchQualityProfilesParameters,
- SearchQualityProfilesResponse,
activateRule,
addGroup,
addUser,
associateProject,
changeProfileParent,
compareProfiles,
+ CompareResponse,
copyProfile,
createQualityProfile,
deactivateRule,
getProfileProjects,
getQualityProfile,
getQualityProfileExporterUrl,
+ Profile,
+ ProfileProject,
removeGroup,
removeUser,
renameProfile,
restoreQualityProfile,
searchGroups,
searchQualityProfiles,
+ SearchQualityProfilesParameters,
+ SearchQualityProfilesResponse,
searchUsers,
setDefaultProfile,
} from '../quality-profiles';
credentialWords: 'foo,bar',
},
}),
+ mockQualityProfileChangelogEvent({
+ date: '2019-02-23T03:12:32+0100',
+ action: 'UPDATED',
+ ruleKey: 'c:rule4',
+ ruleName: 'Rule 5',
+ params: {
+ newCleanCodeAttribute: CleanCodeAttribute.Complete,
+ newCleanCodeAttributeCategory: CleanCodeAttributeCategory.Intentional,
+ oldCleanCodeAttribute: CleanCodeAttribute.Lawful,
+ oldCleanCodeAttributeCategory: CleanCodeAttributeCategory.Responsible,
+ impactChanges: [
+ {
+ newSeverity: SoftwareImpactSeverity.Medium,
+ newSoftwareQuality: SoftwareQuality.Reliability,
+ },
+ {
+ oldSeverity: SoftwareImpactSeverity.High,
+ oldSoftwareQuality: SoftwareQuality.Maintainability,
+ },
+ ],
+ },
+ }),
+ mockQualityProfileChangelogEvent({
+ date: '2019-01-23T03:12:32+0100',
+ action: 'UPDATED',
+ ruleKey: 'c:rule6',
+ ruleName: 'Rule 6',
+ params: {
+ severity: IssueSeverity.Critical,
+ },
+ }),
];
}
return this.reply({});
};
- handleGetProfileChangelog: typeof getProfileChangelog = (since, to, { language }, page) => {
+ handleGetProfileChangelog: typeof getProfileChangelog = (data) => {
+ const {
+ profile: { language },
+ since,
+ to,
+ page,
+ filterMode = QualityProfileChangelogFilterMode.MQR,
+ } = data;
const PAGE_SIZE = 50;
- const p = page || 1;
+ const p = page ?? 1;
const events = this.changelogEvents.filter((event) => {
if (event.ruleKey.split(':')[0] !== language) {
return false;
if (to && new Date(to) <= new Date(event.date)) {
return false;
}
+ if (
+ filterMode === QualityProfileChangelogFilterMode.MQR &&
+ event.action === 'UPDATED' &&
+ event.params &&
+ !Object.keys(event.params).includes('impactChanges')
+ ) {
+ return false;
+ }
+
+ if (
+ filterMode === QualityProfileChangelogFilterMode.STANDARD &&
+ event.action === 'UPDATED' &&
+ !event.params?.severity
+ ) {
+ return false;
+ }
+
return true;
});
SoftwareImpactSeverity,
SoftwareQuality,
} from '../types/clean-code-taxonomy';
+import { QualityProfileChangelogFilterMode } from '../types/quality-profiles';
import { Dict, Paging, ProfileInheritanceDetails, UserSelected } from '../types/types';
export interface ProfileActions {
}: Pick<Profile, 'language' | 'name'>): Promise<{
ancestors: ProfileInheritanceDetails[];
children: ProfileInheritanceDetails[];
- profile: ProfileInheritanceDetails;
+ profile: ProfileInheritanceDetails | null;
}> {
return getJSON('/api/qualityprofiles/inheritance', {
language,
paging: Paging;
}
-export function getProfileChangelog(
- since: any,
- to: any,
- { language, name: qualityProfile }: Profile,
- page?: number,
-): Promise<ChangelogResponse> {
+interface ChangelogData {
+ filterMode: QualityProfileChangelogFilterMode;
+ page?: number;
+ profile: Profile;
+ since: string;
+ to: string;
+}
+
+export function getProfileChangelog(data: ChangelogData): Promise<ChangelogResponse> {
+ const {
+ filterMode,
+ page,
+ profile: { language, name: qualityProfile },
+ since,
+ to,
+ } = data;
return getJSON('/api/qualityprofiles/changelog', {
since,
to,
language,
qualityProfile,
+ filterMode,
p: page,
});
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { LinkStandalone } from '@sonarsource/echoes-react';
import classNames from 'classnames';
import { isSameMinute } from 'date-fns';
import {
CellComponent,
ContentCell,
FlagMessage,
- Link,
Note,
Table,
TableRow,
import { FormattedMessage, useIntl } from 'react-intl';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import { parseDate } from '../../../helpers/dates';
+import { isDefined } from '../../../helpers/types';
import { getRulesUrl } from '../../../helpers/urls';
import { ProfileChangelogEvent } from '../types';
import ChangesList from './ChangesList';
events: ProfileChangelogEvent[];
}
-export default function Changelog(props: Props) {
+export default function Changelog(props: Readonly<Props>) {
const intl = useIntl();
const sortedRows = sortBy(
<CellComponent
className={classNames('sw-align-top', { 'sw-border-transparent': !shouldDisplayDate })}
>
- {event.ruleName && (
- <Link to={getRulesUrl({ rule_key: event.ruleKey })}>{event.ruleName}</Link>
+ {isDefined(event.ruleName) && (
+ <LinkStandalone to={getRulesUrl({ rule_key: event.ruleKey })}>
+ {event.ruleName}
+ </LinkStandalone>
)}
</CellComponent>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Button } from '@sonarsource/echoes-react';
-import { Spinner } from 'design-system';
+import { Button, Spinner } from '@sonarsource/echoes-react';
import * as React from 'react';
-import { withRouter } from '~sonar-aligned/components/hoc/withRouter';
-import { Location, Router } from '~sonar-aligned/types/router';
-import { ChangelogResponse, getProfileChangelog } from '../../../api/quality-profiles';
+import { useLocation, useRouter } from '~sonar-aligned/components/hoc/withRouter';
import { parseDate, toISO8601WithOffsetString } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
+import { isDefined } from '../../../helpers/types';
+import { useGetQualityProfileChangelog } from '../../../queries/quality-profiles';
+import { useStandardExperienceMode } from '../../../queries/settings';
+import { QualityProfileChangelogFilterMode } from '../../../types/quality-profiles';
import { withQualityProfilesContext } from '../qualityProfilesContext';
-import { Profile, ProfileChangelogEvent } from '../types';
+import { Profile } from '../types';
import { getProfileChangelogPath } from '../utils';
import Changelog from './Changelog';
import ChangelogEmpty from './ChangelogEmpty';
import ChangelogSearch from './ChangelogSearch';
interface Props {
- location: Location;
profile: Profile;
- router: Router;
}
-interface State {
- events?: ProfileChangelogEvent[];
- loading: boolean;
- page?: number;
- total?: number;
-}
-
-class ChangelogContainer extends React.PureComponent<Props, State> {
- mounted = false;
- state: State = { loading: true };
-
- componentDidMount() {
- this.mounted = true;
- this.loadChangelog();
- }
-
- componentDidUpdate(prevProps: Props) {
- if (prevProps.location !== this.props.location) {
- this.loadChangelog();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- stopLoading() {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
-
- loadChangelog() {
- this.setState({ loading: true });
- const {
- location: { query },
- profile,
- } = this.props;
-
- getProfileChangelog(query.since, query.to, profile)
- .then((r: ChangelogResponse) => {
- if (this.mounted) {
- this.setState({
- events: r.events,
- total: r.paging.total,
- page: r.paging.pageIndex,
- loading: false,
- });
- }
- })
- .catch(this.stopLoading);
- }
-
- loadMore(event: React.SyntheticEvent<HTMLElement>) {
- event.preventDefault();
- event.currentTarget.blur();
-
- if (this.state.page != null) {
- this.setState({ loading: true });
- const {
- location: { query },
- profile,
- } = this.props;
-
- getProfileChangelog(query.since, query.to, profile, this.state.page + 1)
- .then((r: ChangelogResponse) => {
- if (this.mounted && this.state.events) {
- this.setState(({ events = [] }) => ({
- events: [...events, ...r.events],
- total: r.paging.total,
- page: r.paging.pageIndex,
- loading: false,
- }));
- }
- })
- .catch(this.stopLoading);
- }
- }
-
- handleDateRangeChange = ({ from, to }: { from?: Date; to?: Date }) => {
- const path = getProfileChangelogPath(this.props.profile.name, this.props.profile.language, {
+function ChangelogContainer(props: Readonly<Props>) {
+ const { profile } = props;
+ const { data: isStandardMode } = useStandardExperienceMode();
+ const router = useRouter();
+ const {
+ query: { since, to },
+ } = useLocation();
+
+ const filterMode = isStandardMode
+ ? QualityProfileChangelogFilterMode.STANDARD
+ : QualityProfileChangelogFilterMode.MQR;
+
+ const {
+ data: changeLogResponse,
+ isLoading,
+ fetchNextPage,
+ } = useGetQualityProfileChangelog({
+ since,
+ to,
+ profile,
+ filterMode,
+ });
+
+ const events = changeLogResponse?.pages.flatMap((page) => page.events) ?? [];
+ const total = changeLogResponse?.pages[0].paging.total;
+
+ const handleDateRangeChange = ({ from, to }: { from?: Date; to?: Date }) => {
+ const path = getProfileChangelogPath(profile.name, profile.language, {
since: from && toISO8601WithOffsetString(from),
to: to && toISO8601WithOffsetString(to),
});
- this.props.router.push(path);
+ router.push(path);
};
- handleReset = () => {
- const path = getProfileChangelogPath(this.props.profile.name, this.props.profile.language);
- this.props.router.push(path);
+ const handleReset = () => {
+ const path = getProfileChangelogPath(profile.name, profile.language);
+ router.replace(path);
};
- render() {
- const { query } = this.props.location;
-
- const shouldDisplayFooter =
- this.state.events != null &&
- this.state.total != null &&
- this.state.events.length < this.state.total;
-
- return (
- <div className="sw-mt-4">
- <div className="sw-mb-2 sw-flex sw-gap-4 sw-items-center">
- <ChangelogSearch
- dateRange={{
- from: query.since ? parseDate(query.since) : undefined,
- to: query.to ? parseDate(query.to) : undefined,
- }}
- onDateRangeChange={this.handleDateRangeChange}
- onReset={this.handleReset}
- />
- <Spinner loading={this.state.loading} />
- </div>
+ const shouldDisplayFooter = isDefined(events) && isDefined(total) && events.length < total;
+
+ return (
+ <div className="sw-mt-4">
+ <div className="sw-mb-2 sw-flex sw-gap-4 sw-items-center">
+ <ChangelogSearch
+ dateRange={{
+ from: since ? parseDate(since) : undefined,
+ to: to ? parseDate(to) : undefined,
+ }}
+ onDateRangeChange={handleDateRangeChange}
+ onReset={handleReset}
+ />
+ <Spinner isLoading={isLoading} />
+ </div>
- {this.state.events != null && this.state.events.length === 0 && <ChangelogEmpty />}
+ {isDefined(events) && events.length === 0 && <ChangelogEmpty />}
- {this.state.events != null && this.state.events.length > 0 && (
- <Changelog events={this.state.events} />
- )}
+ {isDefined(events) && events.length > 0 && <Changelog events={events} />}
- {shouldDisplayFooter && (
- <footer className="sw-text-center sw-mt-2">
- <Button onClick={this.loadMore.bind(this)}>{translate('show_more')}</Button>
- </footer>
- )}
- </div>
- );
- }
+ {shouldDisplayFooter && (
+ <footer className="sw-text-center sw-mt-2">
+ <Button onClick={() => fetchNextPage()}>{translate('show_more')}</Button>
+ </footer>
+ )}
+ </div>
+ );
}
-export default withQualityProfilesContext(withRouter(ChangelogContainer));
+export default withQualityProfilesContext(ChangelogContainer);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { useStandardExperienceMode } from '../../../queries/settings';
import { ProfileChangelogEvent } from '../types';
import CleanCodeAttributeChange from './CleanCodeAttributeChange';
import ParameterChange from './ParameterChange';
changes: ProfileChangelogEvent['params'];
}
-export default function ChangesList({ changes }: Props) {
+export default function ChangesList({ changes }: Readonly<Props>) {
const {
severity,
oldCleanCodeAttribute,
...rest
} = changes ?? {};
+ const { data: isStandardMode } = useStandardExperienceMode();
+
return (
<ul className="sw-w-full sw-flex sw-flex-col sw-gap-1">
- {severity && (
+ {severity && isStandardMode && (
<li>
<SeverityChange severity={severity} />
</li>
)}
- {oldCleanCodeAttribute &&
+ {!isStandardMode &&
+ oldCleanCodeAttribute &&
oldCleanCodeAttributeCategory &&
newCleanCodeAttribute &&
newCleanCodeAttributeCategory && (
</li>
)}
- {impactChanges?.map((impactChange, index) => (
- <li key={index}>
- <SoftwareImpactChange impactChange={impactChange} />
- </li>
- ))}
+ {!isStandardMode &&
+ impactChanges?.map((impactChange, index) => (
+ <li key={index}>
+ <SoftwareImpactChange impactChange={impactChange} />
+ </li>
+ ))}
{Object.keys(rest).map((key) => (
<li key={key}>
severity: string;
}
-export default function SeverityChange({ severity }: Props) {
+export default function SeverityChange({ severity }: Readonly<Props>) {
return (
<div className="sw-whitespace-nowrap">
- {translate('quality_profiles.deprecated_severity_set_to')}{' '}
- <SeverityHelper severity={severity} />
+ {translate('quality_profiles.severity_set_to')} <SeverityHelper severity={severity} />
</div>
);
}
import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock';
import { mockQualityProfileChangelogEvent } from '../../../../helpers/testMocks';
import { renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
+import { SettingsKey } from '../../../../types/settings';
import routes from '../../routes';
jest.mock('../../../../api/quality-profiles');
settingsMock.reset();
});
-it('should see the changelog', async () => {
+it('should see the changelog in MQR', async () => {
const user = userEvent.setup();
renderChangeLog();
const rows = await ui.row.findAll();
- expect(rows).toHaveLength(6);
+ expect(rows).toHaveLength(7);
expect(ui.emptyPage.query()).not.toBeInTheDocument();
ui.checkRow(1, 'May 23, 2019', 'System', 'quality_profiles.changelog.ACTIVATED', 'Rule 0');
ui.checkRow(2, 'April 23, 2019', 'System', 'quality_profiles.changelog.DEACTIVATED', 'Rule 0', [
- /quality_profiles.deprecated_severity_set_to severity.MAJOR/,
+ /^$/, // Should be empty
]);
ui.checkRow(3, '', '', '', 'Rule 1', [
- /quality_profiles.deprecated_severity_set_to severity.CRITICAL/,
+ /quality_profiles.changelog.cca_and_category_changed.*COMPLETE.*INTENTIONAL.*LAWFUL.*RESPONSIBLE/,
+ /quality_profiles.changelog.impact_added.severity_impact.*MEDIUM.*RELIABILITY/,
+ /quality_profiles.changelog.impact_removed.severity_impact.HIGH.*MAINTAINABILITY/,
+ ]);
+ ui.checkRow(6, 'February 23, 2019', 'System', 'quality_profiles.changelog.UPDATED', 'Rule 5', [
/quality_profiles.changelog.cca_and_category_changed.*COMPLETE.*INTENTIONAL.*LAWFUL.*RESPONSIBLE/,
/quality_profiles.changelog.impact_added.severity_impact.*MEDIUM.*RELIABILITY/,
/quality_profiles.changelog.impact_removed.severity_impact.HIGH.*MAINTAINABILITY/,
expect(screen.getByText('/coding_rules?rule_key=c%3Arule0')).toBeInTheDocument();
});
+it('should return standard mode changelogs only', async () => {
+ settingsMock.set(SettingsKey.MQRMode, 'false');
+ renderChangeLog();
+
+ const rows = await ui.row.findAll();
+ expect(rows).toHaveLength(7);
+ ui.checkRow(1, 'May 23, 2019', 'System', 'quality_profiles.changelog.ACTIVATED', 'Rule 0', [
+ /^$/,
+ ]);
+ ui.checkRow(2, 'April 23, 2019', 'System', 'quality_profiles.changelog.DEACTIVATED', 'Rule 0', [
+ /quality_profiles.severity_set_to severity.MAJOR/,
+ ]);
+ ui.checkRow(6, 'January 23, 2019', 'System', 'quality_profiles.changelog.UPDATED', 'Rule 6', [
+ /quality_profiles.severity_set_to severity.CRITICAL/,
+ ]);
+});
+
it('should filter the changelog', async () => {
const user = userEvent.setup();
renderChangeLog();
- expect(await ui.row.findAll()).toHaveLength(6);
+ expect(await ui.row.findAll()).toHaveLength(7);
await user.click(ui.startDate.get());
await user.click(screen.getByRole('gridcell', { name: '20' }));
await user.click(document.body);
await user.click(document.body);
expect(await ui.row.findAll()).toHaveLength(4);
await user.click(ui.reset.get());
- expect(await ui.row.findAll()).toHaveLength(6);
+ expect(await ui.row.findAll()).toHaveLength(7);
});
it('should load more', async () => {
await user.click(ui.showMore.get());
expect(await ui.row.findAll()).toHaveLength(101);
await user.click(ui.reset.get());
- expect(await ui.row.findAll()).toHaveLength(51);
+ expect(await ui.row.findAll()).toHaveLength(101); // Reset should not reset the page
});
it('should see short changelog for php', async () => {
const rows = await ui.row.findAll();
expect(rows).toHaveLength(2);
ui.checkRow(1, 'May 23, 2019', 'System', 'quality_profiles.changelog.DEACTIVATED', 'PHP Rule', [
- /quality_profiles.deprecated_severity_set_to severity.CRITICAL/,
+ /^((?!severity.CRITICAL).)*$/, // Should not contain severity.CRITICAL
/quality_profiles.changelog.cca_and_category_changed.*COMPLETE.*INTENTIONAL.*CLEAR.*RESPONSIBLE/,
]);
expect(ui.showMore.query()).not.toBeInTheDocument();
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import {
- UseQueryResult,
+ infiniteQueryOptions,
queryOptions,
useMutation,
useQuery,
addUser,
compareProfiles,
deactivateRule,
+ getProfileChangelog,
getProfileInheritance,
getQualityProfile,
} from '../api/quality-profiles';
-import { ProfileInheritanceDetails } from '../types/types';
-import { createQueryHook } from './common';
+import { getNextPageParam, getPreviousPageParam } from '../helpers/react-query';
+import { isDefined } from '../helpers/types';
+import { QualityProfileChangelogFilterMode } from '../types/quality-profiles';
+import { createInfiniteQueryHook, createQueryHook } from './common';
-export function useProfileInheritanceQuery(
- profile?: Pick<Profile, 'language' | 'name' | 'parentKey'>,
-): UseQueryResult<{
- ancestors: ProfileInheritanceDetails[];
- children: ProfileInheritanceDetails[];
- profile: ProfileInheritanceDetails | null;
-}> {
- const { language, name, parentKey } = profile ?? {};
- return useQuery({
- queryKey: ['quality-profiles', 'inheritance', language, name, parentKey],
- queryFn: async ({ queryKey: [, , language, name] }) => {
- if (!language || !name) {
- return { ancestors: [], children: [], profile: null };
- }
- const response = await getProfileInheritance({ language, name });
- response.ancestors.reverse();
- return response;
- },
- });
-}
+const qualityProfileQueryKeys = {
+ all: () => ['quality-profiles'],
+ inheritance: (language?: string, name?: string, parentKey?: string) => [
+ ...qualityProfileQueryKeys.all(),
+ 'inheritance',
+ language,
+ name,
+ parentKey,
+ ],
+ profile: (profile: Profile, compareToSonarWay = false) => [
+ ...qualityProfileQueryKeys.all(),
+ 'details',
+ profile,
+ compareToSonarWay,
+ ],
+ changelog: (
+ language: string,
+ name: string,
+ since: string,
+ to: string,
+ filterMode: QualityProfileChangelogFilterMode,
+ ) => [...qualityProfileQueryKeys.all(), 'changelog', language, name, since, to, filterMode],
+ compare: (leftKey: string, rightKey: string) => [
+ ...qualityProfileQueryKeys.all(),
+ 'compare',
+ leftKey,
+ rightKey,
+ ],
+};
+
+export const useProfileInheritanceQuery = createQueryHook(
+ (profile?: Pick<Profile, 'language' | 'name' | 'parentKey'>) => {
+ const { language, name, parentKey } = profile ?? {};
+ return queryOptions({
+ queryKey: qualityProfileQueryKeys.inheritance(language, name, parentKey),
+ queryFn: () => {
+ if (!isDefined(language) || !isDefined(name)) {
+ return { ancestors: [], children: [], profile: null };
+ }
+ return getProfileInheritance({ language, name });
+ },
+ });
+ },
+);
export const useGetQualityProfile = createQueryHook(
(data: Parameters<typeof getQualityProfile>[0]) => {
return queryOptions({
- queryKey: ['quality-profile', 'details', data.profile, data.compareToSonarWay],
+ queryKey: qualityProfileQueryKeys.profile(data.profile, data.compareToSonarWay),
queryFn: () => {
return getQualityProfile(data);
},
},
);
+export const useGetQualityProfileChangelog = createInfiniteQueryHook(
+ (data: Parameters<typeof getProfileChangelog>[0]) => {
+ return infiniteQueryOptions({
+ queryKey: qualityProfileQueryKeys.changelog(
+ data.profile.language,
+ data.profile.name,
+ data.since,
+ data.to,
+ data.filterMode,
+ ),
+ queryFn: ({ pageParam }) => {
+ return getProfileChangelog({ ...data, page: pageParam });
+ },
+ getNextPageParam: (data) => getNextPageParam({ page: data.paging }),
+ getPreviousPageParam: (data) => getPreviousPageParam({ page: data.paging }),
+ initialPageParam: 1,
+ });
+ },
+);
+
export function useProfilesCompareQuery(leftKey: string, rightKey: string) {
return useQuery({
- queryKey: ['quality-profiles', 'compare', leftKey, rightKey],
- queryFn: ({ queryKey: [, , leftKey, rightKey] }) => {
- if (!leftKey || !rightKey) {
+ queryKey: qualityProfileQueryKeys.compare(leftKey, rightKey),
+ queryFn: ({ queryKey: [_1, _2, leftKey, rightKey] }) => {
+ if (!isDefined(leftKey) || !isDefined(rightKey)) {
return null;
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+export enum QualityProfileChangelogFilterMode {
+ MQR = 'MQR',
+ STANDARD = 'STANDARD',
+}
quality_profiles.extend_x_title=Extend Profile "{0}" - {1}
quality_profiles.rename_x_title=Rename Profile {0} - {1}
quality_profiles.deprecated=deprecated
-quality_profiles.deprecated_severity_set_to=Old severity set to
+quality_profiles.severity_set_to=Severity set to
quality_profiles.changelog.ACTIVATED=Activated
quality_profiles.changelog.DEACTIVATED=Deactivated
quality_profiles.changelog.UPDATED=Updated