aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2018-11-07 11:23:43 +0100
committerSonarTech <sonartech@sonarsource.com>2018-11-20 20:20:59 +0100
commitc9e3474940286b65bd94d14eeacef982407a65af (patch)
tree601a704d493a9f38abcc2bf4659f9bb62cacd7c3 /server/sonar-web/src
parent5a5c9bfb9fb4c3591759ef444524c7d8097198a6 (diff)
downloadsonarqube-c9e3474940286b65bd94d14eeacef982407a65af.tar.gz
sonarqube-c9e3474940286b65bd94d14eeacef982407a65af.zip
SONAR-10770 display definition change events for applications (#918)
Diffstat (limited to 'server/sonar-web/src')
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx21
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContext.tsx31
-rw-r--r--server/sonar-web/src/main/js/app/types.ts8
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/DefinitionChangeEventInner.tsx168
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/DefinitionChangeEventInner-test.tsx65
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap275
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageHeader-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/utils.ts2
-rw-r--r--server/sonar-web/src/main/js/helpers/l10n.ts6
13 files changed, 584 insertions, 19 deletions
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
index 5314aed9833..faea1ccdef2 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -21,6 +21,7 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { differenceBy } from 'lodash';
+import { ComponentContext } from './ComponentContext';
import ComponentContainerNotFound from './ComponentContainerNotFound';
import ComponentNav from './nav/component/ComponentNav';
import { Component, BranchLike, Measure, Task } from '../types';
@@ -365,15 +366,17 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
<i className="spinner" />
</div>
) : (
- React.cloneElement(this.props.children, {
- branchLike,
- branchLikes,
- component,
- isInProgress,
- isPending,
- onBranchesChange: this.handleBranchesChange,
- onComponentChange: this.handleComponentChange
- })
+ <ComponentContext.Provider value={{ branchLike, component }}>
+ {React.cloneElement(this.props.children, {
+ branchLike,
+ branchLikes,
+ component,
+ isInProgress,
+ isPending,
+ onBranchesChange: this.handleBranchesChange,
+ onComponentChange: this.handleComponentChange
+ })}
+ </ComponentContext.Provider>
)}
</div>
);
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContext.tsx b/server/sonar-web/src/main/js/app/components/ComponentContext.tsx
new file mode 100644
index 00000000000..b62a351cd3f
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/ComponentContext.tsx
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { Component, BranchLike } from '../types';
+
+interface ComponentContextType {
+ branchLike: BranchLike | undefined;
+ component: Component | undefined;
+}
+
+export const ComponentContext = React.createContext<ComponentContextType>({
+ branchLike: undefined,
+ component: undefined
+});
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index 8774f66a0ec..a9fc074cc19 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -62,6 +62,14 @@ export interface AnalysisEvent {
status: string;
stillFailing: boolean;
};
+ projects?: Array<{
+ branch?: string;
+ changeType: string;
+ key: string;
+ name: string;
+ newBranch?: string;
+ oldBranch?: string;
+ }>;
}
export interface AppState {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/DefinitionChangeEventInner.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/DefinitionChangeEventInner.tsx
new file mode 100644
index 00000000000..5a7f71acfc9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/DefinitionChangeEventInner.tsx
@@ -0,0 +1,168 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { Link } from 'react-router';
+import { FormattedMessage } from 'react-intl';
+import * as classNames from 'classnames';
+import { AnalysisEvent, BranchLike } from '../../../app/types';
+import DropdownIcon from '../../../components/icons-components/DropdownIcon';
+import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
+import { ResetButtonLink } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
+import { getProjectUrl } from '../../../helpers/urls';
+import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon';
+import { isMainBranch } from '../../../helpers/branches';
+
+export type DefinitionChangeEvent = AnalysisEvent & Required<Pick<AnalysisEvent, 'projects'>>;
+
+export function isDefinitionChangeEvent(event: AnalysisEvent): event is DefinitionChangeEvent {
+ return event.category === 'DEFINITION_CHANGE' && event.projects !== undefined;
+}
+
+interface Props {
+ branchLike: BranchLike | undefined;
+ event: DefinitionChangeEvent;
+}
+
+interface State {
+ expanded: boolean;
+}
+
+export class DefinitionChangeEventInner extends React.PureComponent<Props, State> {
+ state: State = { expanded: false };
+
+ toggleProjectsList = () => {
+ this.setState(state => ({ expanded: !state.expanded }));
+ };
+
+ renderProjectLink = (project: { key: string; name: string }, branch: string | undefined) => (
+ <Link onClick={e => e.stopPropagation()} to={getProjectUrl(project.key, branch)}>
+ {project.name}
+ </Link>
+ );
+
+ renderBranch = (branch: string | undefined) => (
+ <span className="nowrap">
+ <LongLivingBranchIcon className="little-spacer-left text-text-top" />
+ {branch}
+ </span>
+ );
+
+ renderProjectChange(project: {
+ changeType: string;
+ key: string;
+ name: string;
+ branch?: string;
+ newBranch?: string;
+ oldBranch?: string;
+ }) {
+ const mainBranch = !this.props.branchLike || isMainBranch(this.props.branchLike);
+
+ if (project.changeType === 'ADDED') {
+ const message = mainBranch
+ ? 'event.definition_change.added'
+ : 'event.definition_change.branch_added';
+ return (
+ <div className="text-ellipsis">
+ <FormattedMessage
+ defaultMessage={translate(message)}
+ id={message}
+ values={{
+ project: this.renderProjectLink(project, project.branch),
+ branch: this.renderBranch(project.branch)
+ }}
+ />
+ </div>
+ );
+ } else if (project.changeType === 'REMOVED') {
+ const message = mainBranch
+ ? 'event.definition_change.removed'
+ : 'event.definition_change.branch_removed';
+ return (
+ <div className="text-ellipsis">
+ <FormattedMessage
+ defaultMessage={translate(message)}
+ id={message}
+ values={{
+ project: this.renderProjectLink(project, project.branch),
+ branch: this.renderBranch(project.branch)
+ }}
+ />
+ </div>
+ );
+ } else if (project.changeType === 'BRANCH_CHANGED') {
+ return (
+ <FormattedMessage
+ defaultMessage={translate('event.definition_change.branch_replaced')}
+ id={'event.definition_change.branch_replaced'}
+ values={{
+ project: this.renderProjectLink(project, project.newBranch),
+ oldBranch: this.renderBranch(project.oldBranch),
+ newBranch: this.renderBranch(project.newBranch)
+ }}
+ />
+ );
+ }
+
+ return null;
+ }
+
+ render() {
+ const { event } = this.props;
+ const { expanded } = this.state;
+ return (
+ <div className="project-activity-event-inner">
+ <div className="project-activity-event-inner-main">
+ <ProjectEventIcon
+ className={classNames(
+ 'project-activity-event-icon',
+ 'little-spacer-right',
+ event.category
+ )}
+ />
+
+ <div className="project-activity-event-inner-text flex-1">
+ <span className="note little-spacer-right">
+ {translate('event.category', event.category)}
+ </span>
+ </div>
+
+ <ResetButtonLink
+ className="project-activity-event-inner-more-link"
+ onClick={this.toggleProjectsList}
+ stopPropagation={true}>
+ {expanded ? translate('hide') : translate('more')}
+ <DropdownIcon className="little-spacer-left" turned={expanded} />
+ </ResetButtonLink>
+ </div>
+
+ {expanded && (
+ <ul>
+ {event.projects.map(project => (
+ <li className="display-flex-center little-spacer-top" key={project.key}>
+ {this.renderProjectChange(project)}
+ </li>
+ ))}
+ </ul>
+ )}
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx
index 4d66330e64e..69369b52d4a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx
@@ -20,7 +20,9 @@
import * as React from 'react';
import * as classNames from 'classnames';
import { isRichQualityGateEvent, RichQualityGateEventInner } from './RichQualityGateEventInner';
+import { isDefinitionChangeEvent, DefinitionChangeEventInner } from './DefinitionChangeEventInner';
import { AnalysisEvent } from '../../../app/types';
+import { ComponentContext } from '../../../app/components/ComponentContext';
import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
import { translate } from '../../../helpers/l10n';
@@ -31,6 +33,12 @@ interface Props {
export default function EventInner({ event }: Props) {
if (isRichQualityGateEvent(event)) {
return <RichQualityGateEventInner event={event} />;
+ } else if (isDefinitionChangeEvent(event)) {
+ return (
+ <ComponentContext.Consumer>
+ {({ branchLike }) => <DefinitionChangeEventInner branchLike={branchLike} event={event} />}
+ </ComponentContext.Consumer>
+ );
} else {
return (
<div className="project-activity-event-inner">
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.tsx
index c2519a32c71..8136cfcc71a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import * as classNames from 'classnames';
import ProjectActivityEventSelectOption from './ProjectActivityEventSelectOption';
import ProjectActivityEventSelectValue from './ProjectActivityEventSelectValue';
import ProjectActivityDateInput from './ProjectActivityDateInput';
@@ -39,8 +40,8 @@ export default class ProjectActivityPageHeader extends React.PureComponent<Props
this.props.updateQuery({ category: option ? option.value : '' });
render() {
- const eventTypes =
- this.props.project.qualifier === 'APP' ? APPLICATION_EVENT_TYPES : EVENT_TYPES;
+ const isApp = this.props.project.qualifier === 'APP';
+ const eventTypes = isApp ? APPLICATION_EVENT_TYPES : EVENT_TYPES;
const options = eventTypes.map(category => ({
label: translate('event.category', category),
value: category
@@ -50,7 +51,10 @@ export default class ProjectActivityPageHeader extends React.PureComponent<Props
<header className="page-header">
{!['VW', 'SVW'].includes(this.props.project.qualifier) && (
<Select
- className="input-medium pull-left big-spacer-right"
+ className={classNames('pull-left big-spacer-right', {
+ 'input-medium': !isApp,
+ 'input-large': isApp
+ })}
clearable={true}
onChange={this.handleCategoryChange}
optionComponent={ProjectActivityEventSelectOption}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.tsx
index 780668cf39d..74e02a77421 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.tsx
@@ -29,8 +29,7 @@ import Level from '../../../components/ui/Level';
import { translate } from '../../../helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls';
-export type RichQualityGateEvent = Exclude<AnalysisEvent, 'qualityGate'> &
- Required<Pick<AnalysisEvent, 'qualityGate'>>;
+export type RichQualityGateEvent = AnalysisEvent & Required<Pick<AnalysisEvent, 'qualityGate'>>;
export function isRichQualityGateEvent(event: AnalysisEvent): event is RichQualityGateEvent {
return event.category === 'QUALITY_GATE' && event.qualityGate !== undefined;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/DefinitionChangeEventInner-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/DefinitionChangeEventInner-test.tsx
new file mode 100644
index 00000000000..5e5a626df17
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/DefinitionChangeEventInner-test.tsx
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import { DefinitionChangeEventInner, DefinitionChangeEvent } from '../DefinitionChangeEventInner';
+import { click } from '../../../../helpers/testUtils';
+import { LongLivingBranch, BranchType } from '../../../../app/types';
+
+it('should render', () => {
+ const event: DefinitionChangeEvent = {
+ category: 'DEFINITION_CHANGE',
+ key: 'foo1234',
+ name: '',
+ projects: [
+ { changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'master' },
+ { changeType: 'REMOVED', key: 'bar', name: 'Bar', branch: 'master' }
+ ]
+ };
+ const wrapper = shallow(<DefinitionChangeEventInner branchLike={undefined} event={event} />);
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('.project-activity-event-inner-more-link'));
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render for a branch', () => {
+ const branch: LongLivingBranch = { name: 'feature-x', isMain: false, type: BranchType.LONG };
+ const event: DefinitionChangeEvent = {
+ category: 'DEFINITION_CHANGE',
+ key: 'foo1234',
+ name: '',
+ projects: [
+ { changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'feature-x' },
+ {
+ changeType: 'BRANCH_CHANGED',
+ key: 'bar',
+ name: 'Bar',
+ oldBranch: 'master',
+ newBranch: 'feature-y'
+ }
+ ]
+ };
+ const wrapper = shallow(<DefinitionChangeEventInner branchLike={branch} event={event} />);
+ click(wrapper.find('.project-activity-event-inner-more-link'));
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap
new file mode 100644
index 00000000000..ce671b44ab4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap
@@ -0,0 +1,275 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+ className="project-activity-event-inner"
+>
+ <div
+ className="project-activity-event-inner-main"
+ >
+ <ProjectEventIcon
+ className="project-activity-event-icon little-spacer-right DEFINITION_CHANGE"
+ />
+ <div
+ className="project-activity-event-inner-text flex-1"
+ >
+ <span
+ className="note little-spacer-right"
+ >
+ event.category.DEFINITION_CHANGE
+ </span>
+ </div>
+ <ResetButtonLink
+ className="project-activity-event-inner-more-link"
+ onClick={[Function]}
+ stopPropagation={true}
+ >
+ more
+ <DropdownIcon
+ className="little-spacer-left"
+ turned={false}
+ />
+ </ResetButtonLink>
+ </div>
+</div>
+`;
+
+exports[`should render 2`] = `
+<div
+ className="project-activity-event-inner"
+>
+ <div
+ className="project-activity-event-inner-main"
+ >
+ <ProjectEventIcon
+ className="project-activity-event-icon little-spacer-right DEFINITION_CHANGE"
+ />
+ <div
+ className="project-activity-event-inner-text flex-1"
+ >
+ <span
+ className="note little-spacer-right"
+ >
+ event.category.DEFINITION_CHANGE
+ </span>
+ </div>
+ <ResetButtonLink
+ className="project-activity-event-inner-more-link"
+ onClick={[Function]}
+ stopPropagation={true}
+ >
+ hide
+ <DropdownIcon
+ className="little-spacer-left"
+ turned={true}
+ />
+ </ResetButtonLink>
+ </div>
+ <ul>
+ <li
+ className="display-flex-center little-spacer-top"
+ key="foo"
+ >
+ <div
+ className="text-ellipsis"
+ >
+ <FormattedMessage
+ defaultMessage="event.definition_change.added"
+ id="event.definition_change.added"
+ values={
+ Object {
+ "branch": <span
+ className="nowrap"
+ >
+ <LongLivingBranchIcon
+ className="little-spacer-left text-text-top"
+ />
+ master
+ </span>,
+ "project": <Link
+ onClick={[Function]}
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": "master",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ Foo
+ </Link>,
+ }
+ }
+ />
+ </div>
+ </li>
+ <li
+ className="display-flex-center little-spacer-top"
+ key="bar"
+ >
+ <div
+ className="text-ellipsis"
+ >
+ <FormattedMessage
+ defaultMessage="event.definition_change.removed"
+ id="event.definition_change.removed"
+ values={
+ Object {
+ "branch": <span
+ className="nowrap"
+ >
+ <LongLivingBranchIcon
+ className="little-spacer-left text-text-top"
+ />
+ master
+ </span>,
+ "project": <Link
+ onClick={[Function]}
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": "master",
+ "id": "bar",
+ },
+ }
+ }
+ >
+ Bar
+ </Link>,
+ }
+ }
+ />
+ </div>
+ </li>
+ </ul>
+</div>
+`;
+
+exports[`should render for a branch 1`] = `
+<div
+ className="project-activity-event-inner"
+>
+ <div
+ className="project-activity-event-inner-main"
+ >
+ <ProjectEventIcon
+ className="project-activity-event-icon little-spacer-right DEFINITION_CHANGE"
+ />
+ <div
+ className="project-activity-event-inner-text flex-1"
+ >
+ <span
+ className="note little-spacer-right"
+ >
+ event.category.DEFINITION_CHANGE
+ </span>
+ </div>
+ <ResetButtonLink
+ className="project-activity-event-inner-more-link"
+ onClick={[Function]}
+ stopPropagation={true}
+ >
+ hide
+ <DropdownIcon
+ className="little-spacer-left"
+ turned={true}
+ />
+ </ResetButtonLink>
+ </div>
+ <ul>
+ <li
+ className="display-flex-center little-spacer-top"
+ key="foo"
+ >
+ <div
+ className="text-ellipsis"
+ >
+ <FormattedMessage
+ defaultMessage="event.definition_change.branch_added"
+ id="event.definition_change.branch_added"
+ values={
+ Object {
+ "branch": <span
+ className="nowrap"
+ >
+ <LongLivingBranchIcon
+ className="little-spacer-left text-text-top"
+ />
+ feature-x
+ </span>,
+ "project": <Link
+ onClick={[Function]}
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": "feature-x",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ Foo
+ </Link>,
+ }
+ }
+ />
+ </div>
+ </li>
+ <li
+ className="display-flex-center little-spacer-top"
+ key="bar"
+ >
+ <FormattedMessage
+ defaultMessage="event.definition_change.branch_replaced"
+ id="event.definition_change.branch_replaced"
+ values={
+ Object {
+ "newBranch": <span
+ className="nowrap"
+ >
+ <LongLivingBranchIcon
+ className="little-spacer-left text-text-top"
+ />
+ feature-y
+ </span>,
+ "oldBranch": <span
+ className="nowrap"
+ >
+ <LongLivingBranchIcon
+ className="little-spacer-left text-text-top"
+ />
+ master
+ </span>,
+ "project": <Link
+ onClick={[Function]}
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": "feature-y",
+ "id": "bar",
+ },
+ }
+ }
+ >
+ Bar
+ </Link>,
+ }
+ }
+ />
+ </li>
+ </ul>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageHeader-test.tsx.snap
index a8f48a01a16..b114a57e84a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityPageHeader-test.tsx.snap
@@ -5,7 +5,7 @@ exports[`should render correctly the list of series 1`] = `
className="page-header"
>
<Select
- className="input-medium pull-left big-spacer-right"
+ className="pull-left big-spacer-right input-medium"
clearable={true}
onChange={[Function]}
optionComponent={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
index ca57363cb5b..f95eaf09b8c 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
@@ -182,6 +182,10 @@
color: #cccccc;
}
+.project-activity-event-icon.DEFINITION_CHANGE {
+ color: #33a759;
+}
+
.project-activity-event-icon.OTHER {
color: #442d1b;
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/utils.ts b/server/sonar-web/src/main/js/apps/projectActivity/utils.ts
index 1e8cc597218..ce069f69369 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/utils.ts
+++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.ts
@@ -67,7 +67,7 @@ export interface MeasureHistory {
}
export const EVENT_TYPES = ['VERSION', 'QUALITY_GATE', 'QUALITY_PROFILE', 'OTHER'];
-export const APPLICATION_EVENT_TYPES = ['QUALITY_GATE', 'OTHER'];
+export const APPLICATION_EVENT_TYPES = ['QUALITY_GATE', 'DEFINITION_CHANGE', 'OTHER'];
export const DEFAULT_GRAPH = 'issues';
export const GRAPH_TYPES = ['issues', 'coverage', 'duplications', 'custom'];
export const GRAPHS_METRICS_DISPLAYED: { [x: string]: string[] } = {
diff --git a/server/sonar-web/src/main/js/helpers/l10n.ts b/server/sonar-web/src/main/js/helpers/l10n.ts
index 19014bcb8b0..dd2d8868403 100644
--- a/server/sonar-web/src/main/js/helpers/l10n.ts
+++ b/server/sonar-web/src/main/js/helpers/l10n.ts
@@ -158,14 +158,14 @@ export function getLocalizedMetricName(
short?: boolean
): string {
const bundleKey = `metric.${metric.key}.${short ? 'short_name' : 'name'}`;
- const fromBundle = translate(bundleKey);
- if (fromBundle === bundleKey) {
+ if (hasMessage(bundleKey)) {
+ return translate(bundleKey);
+ } else {
if (short) {
return getLocalizedMetricName(metric);
}
return metric.name || metric.key;
}
- return fromBundle;
}
export function getLocalizedCategoryMetricName(metric: { key: string; name?: string }) {