aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <stas-vilchik@users.noreply.github.com>2017-02-07 13:11:07 +0100
committerGitHub <noreply@github.com>2017-02-07 13:11:07 +0100
commitc54e107bb8305de177d8c805d0368eaf7df3bc37 (patch)
treefb994bad6f25ac5c563c0097502e20b0460f287e
parent929dcab3e90bc534193441f97cf6006d9495e448 (diff)
downloadsonarqube-c54e107bb8305de177d8c805d0368eaf7df3bc37.tar.gz
sonarqube-c54e107bb8305de177d8c805d0368eaf7df3bc37.zip
SONAR-8669 Display organizations on the "Notifications" page (#1633)
-rw-r--r--server/sonar-web/src/main/js/api/notifications.js9
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/ProjectNotifications.js22
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/Projects.js42
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap18
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Projects-test.js.snap21
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/actions.js20
-rw-r--r--server/sonar-web/src/main/js/components/shared/Organization.js13
-rw-r--r--server/sonar-web/src/main/js/helpers/urls.js7
-rw-r--r--server/sonar-web/src/main/js/store/notifications/duck.js11
9 files changed, 119 insertions, 44 deletions
diff --git a/server/sonar-web/src/main/js/api/notifications.js b/server/sonar-web/src/main/js/api/notifications.js
index 4ca5b24dd65..ec433d1e1b3 100644
--- a/server/sonar-web/src/main/js/api/notifications.js
+++ b/server/sonar-web/src/main/js/api/notifications.js
@@ -24,8 +24,9 @@ export type GetNotificationsResponse = {
notifications: Array<{
channel: string,
type: string,
- project: string | null,
- projectName: string | null
+ organization?: string,
+ project?: string,
+ projectName?: string
}>,
channels: Array<string>,
globalTypes: Array<string>,
@@ -36,7 +37,7 @@ export const getNotifications = (): Promise<GetNotificationsResponse> => (
getJSON('/api/notifications/list')
);
-export const addNotification = (channel: string, type: string, project: string | null): Promise<*> => {
+export const addNotification = (channel: string, type: string, project?: string): Promise<*> => {
const data: Object = { channel, type };
if (project) {
Object.assign(data, { project });
@@ -44,7 +45,7 @@ export const addNotification = (channel: string, type: string, project: string |
return post('/api/notifications/add', data);
};
-export const removeNotification = (channel: string, type: string, project: string | null): Promise<*> => {
+export const removeNotification = (channel: string, type: string, project?: string): Promise<*> => {
const data: Object = { channel, type };
if (project) {
Object.assign(data, { project });
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/ProjectNotifications.js b/server/sonar-web/src/main/js/apps/account/notifications/ProjectNotifications.js
index 828e4ec3eba..55279c4ed96 100644
--- a/server/sonar-web/src/main/js/apps/account/notifications/ProjectNotifications.js
+++ b/server/sonar-web/src/main/js/apps/account/notifications/ProjectNotifications.js
@@ -19,20 +19,23 @@
*/
import React from 'react';
import { connect } from 'react-redux';
+import { Link } from 'react-router';
import NotificationsList from './NotificationsList';
+import Organization from '../../../components/shared/Organization';
import { translate } from '../../../helpers/l10n';
import {
- getProjectNotifications,
- getNotificationChannels,
- getNotificationPerProjectTypes
+getProjectNotifications,
+getNotificationChannels,
+getNotificationPerProjectTypes
} from '../../../store/rootReducer';
import type {
- Notification,
- NotificationsState,
- ChannelsState,
- TypesState
+Notification,
+NotificationsState,
+ChannelsState,
+TypesState
} from '../../../store/notifications/duck';
import { addNotification, removeNotification } from './actions';
+import { getProjectUrl } from '../../../helpers/urls';
class ProjectNotifications extends React.Component {
props: {
@@ -72,7 +75,10 @@ class ProjectNotifications extends React.Component {
<thead>
<tr>
<th>
- <h4 className="display-inline-block">{project.name}</h4>
+ <span className="text-normal"><Organization organizationKey={project.organization}/></span>
+ <h4 className="display-inline-block">
+ <Link to={getProjectUrl(project.key)}>{project.name}</Link>
+ </h4>
</th>
{channels.map(channel => (
<th key={channel} className="text-center">
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/Projects.js b/server/sonar-web/src/main/js/apps/account/notifications/Projects.js
index b9c189b4002..72153b35b29 100644
--- a/server/sonar-web/src/main/js/apps/account/notifications/Projects.js
+++ b/server/sonar-web/src/main/js/apps/account/notifications/Projects.js
@@ -22,8 +22,9 @@ import Select from 'react-select';
import { connect } from 'react-redux';
import differenceBy from 'lodash/differenceBy';
import ProjectNotifications from './ProjectNotifications';
+import Organization from '../../../components/shared/Organization';
import { translate } from '../../../helpers/l10n';
-import { getComponents } from '../../../api/components';
+import { getSuggestions } from '../../../api/components';
import { getProjectsWithNotifications } from '../../../store/rootReducer';
type Props = {
@@ -60,19 +61,38 @@ class Projects extends React.Component {
}
}
- loadOptions = query => {
- // TODO filter existing out
- return getComponents({ qualifiers: 'TRK', q: query })
- .then(r => r.components)
+ renderOption = option => {
+ return (
+ <span>
+ <Organization organizationKey={option.organization} link={false}/>
+ <strong>{option.label}</strong>
+ </span>
+ );
+ }
+
+ loadOptions = (query, cb) => {
+ if (query.length < 2) {
+ cb(null, { options: [] });
+ return;
+ }
+
+ getSuggestions(query)
+ .then(r => {
+ const projects = r.results.find(domain => domain.q === 'TRK');
+ return projects ? projects.items : [];
+ })
.then(projects => projects.map(project => ({
value: project.key,
- label: project.name
+ label: project.name,
+ organization: project.organization
})))
- .then(options => ({ options }));
+ .then(options => {
+ cb(null, { options });
+ });
};
handleAddProject = selected => {
- const project = { key: selected.value, name: selected.label };
+ const project = { key: selected.value, name: selected.label, organization: selected.organization };
this.setState({
addedProjects: [...this.state.addedProjects, project]
});
@@ -102,13 +122,15 @@ class Projects extends React.Component {
Set notifications for:
</span>
<Select.Async
+ autoload={false}
+ cache={false}
name="new_project"
style={{ width: '300px' }}
loadOptions={this.loadOptions}
minimumInput={2}
+ optionRenderer={this.renderOption}
onChange={this.handleAddProject}
- placeholder="Search Project"
- searchPromptText="Type at least 2 characters to search"/>
+ placeholder="Search Project"/>
</div>
</section>
);
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap
index 44df6e92f1a..13344ac72ae 100644
--- a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap
@@ -4,9 +4,25 @@ exports[`test should match snapshot 1`] = `
<thead>
<tr>
<th>
+ <span
+ className="text-normal">
+ <Connect(Organization) />
+ </span>
<h4
className="display-inline-block">
- Foo
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }>
+ Foo
+ </Link>
</h4>
</th>
<th
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Projects-test.js.snap b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Projects-test.js.snap
index 3c6d90e870b..c1108cb2f21 100644
--- a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Projects-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Projects-test.js.snap
@@ -25,8 +25,8 @@ exports[`test should render projects 1`] = `
Set notifications for:
</span>
<Async
- autoload={true}
- cache={Object {}}
+ autoload={false}
+ cache={false}
ignoreAccents={true}
ignoreCase={true}
loadOptions={[Function]}
@@ -34,9 +34,10 @@ exports[`test should render projects 1`] = `
minimumInput={2}
name="new_project"
onChange={[Function]}
+ optionRenderer={[Function]}
options={Array []}
placeholder="Search Project"
- searchPromptText="Type at least 2 characters to search"
+ searchPromptText="Type to search"
style={
Object {
"width": "300px",
@@ -80,8 +81,8 @@ exports[`test should render projects 2`] = `
Set notifications for:
</span>
<Async
- autoload={true}
- cache={Object {}}
+ autoload={false}
+ cache={false}
ignoreAccents={true}
ignoreCase={true}
loadOptions={[Function]}
@@ -89,9 +90,10 @@ exports[`test should render projects 2`] = `
minimumInput={2}
name="new_project"
onChange={[Function]}
+ optionRenderer={[Function]}
options={Array []}
placeholder="Search Project"
- searchPromptText="Type at least 2 characters to search"
+ searchPromptText="Type to search"
style={
Object {
"width": "300px",
@@ -135,8 +137,8 @@ exports[`test should render projects 3`] = `
Set notifications for:
</span>
<Async
- autoload={true}
- cache={Object {}}
+ autoload={false}
+ cache={false}
ignoreAccents={true}
ignoreCase={true}
loadOptions={[Function]}
@@ -144,9 +146,10 @@ exports[`test should render projects 3`] = `
minimumInput={2}
name="new_project"
onChange={[Function]}
+ optionRenderer={[Function]}
options={Array []}
placeholder="Search Project"
- searchPromptText="Type at least 2 characters to search"
+ searchPromptText="Type to search"
style={
Object {
"width": "300px",
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/actions.js b/server/sonar-web/src/main/js/apps/account/notifications/actions.js
index 32d10641df4..c30af822106 100644
--- a/server/sonar-web/src/main/js/apps/account/notifications/actions.js
+++ b/server/sonar-web/src/main/js/apps/account/notifications/actions.js
@@ -20,7 +20,7 @@
// @flow
import * as api from '../../../api/notifications';
import type { GetNotificationsResponse } from '../../../api/notifications';
-import { onFail } from '../../../store/rootActions';
+import { onFail, fetchOrganizations } from '../../../store/rootActions';
import {
receiveNotifications,
addNotification as addNotificationAction,
@@ -30,12 +30,18 @@ import type { Notification } from '../../../store/notifications/duck';
export const fetchNotifications = () => (dispatch: Function) => {
const onFulfil = (response: GetNotificationsResponse) => {
- dispatch(receiveNotifications(
- response.notifications,
- response.channels,
- response.globalTypes,
- response.perProjectTypes
- ));
+ const organizations = response.notifications
+ .filter(n => n.organization)
+ .map(n => n.organization);
+
+ dispatch(fetchOrganizations(organizations)).then(() => {
+ dispatch(receiveNotifications(
+ response.notifications,
+ response.channels,
+ response.globalTypes,
+ response.perProjectTypes
+ ));
+ });
};
return api.getNotifications().then(onFulfil, onFail(dispatch));
diff --git a/server/sonar-web/src/main/js/components/shared/Organization.js b/server/sonar-web/src/main/js/components/shared/Organization.js
index f803359455e..4835871864b 100644
--- a/server/sonar-web/src/main/js/components/shared/Organization.js
+++ b/server/sonar-web/src/main/js/components/shared/Organization.js
@@ -28,17 +28,22 @@ type OwnProps = {
};
type Props = {
+ link?: boolean,
organizationKey: string,
organization: null | {
key: string,
name: string
},
- shouldBeDisplayed: boolean
+ shouldBeDisplayed: boolean,
};
class Organization extends React.Component {
props: Props;
+ static defaultProps = {
+ link: true
+ };
+
render () {
const { organization, shouldBeDisplayed } = this.props;
@@ -48,7 +53,11 @@ class Organization extends React.Component {
return (
<span>
- <OrganizationLink organization={organization}>{organization.name}</OrganizationLink>
+ {this.props.link ? (
+ <OrganizationLink organization={organization}>{organization.name}</OrganizationLink>
+ ) : (
+ organization.name
+ )}
<span className="slash-separator"/>
</span>
);
diff --git a/server/sonar-web/src/main/js/helpers/urls.js b/server/sonar-web/src/main/js/helpers/urls.js
index 6dddb630c09..42416ae5cf4 100644
--- a/server/sonar-web/src/main/js/helpers/urls.js
+++ b/server/sonar-web/src/main/js/helpers/urls.js
@@ -26,6 +26,13 @@ export function getComponentUrl (componentKey) {
return window.baseUrl + '/dashboard?id=' + encodeURIComponent(componentKey);
}
+export function getProjectUrl (key) {
+ return {
+ pathname: '/dashboard',
+ query: { id: key }
+ };
+}
+
/**
* Generate URL for a global issues page
* @param {object} query
diff --git a/server/sonar-web/src/main/js/store/notifications/duck.js b/server/sonar-web/src/main/js/store/notifications/duck.js
index b53948850f9..e5e6a8d4520 100644
--- a/server/sonar-web/src/main/js/store/notifications/duck.js
+++ b/server/sonar-web/src/main/js/store/notifications/duck.js
@@ -25,8 +25,9 @@ import uniqWith from 'lodash/uniqWith';
export type Notification = {
channel: string,
type: string,
- project: string | null,
- projectName: string | null
+ project?: string,
+ projectName?: string,
+ organization?: string
};
export type NotificationsState = Array<Notification>;
@@ -147,7 +148,11 @@ export const getGlobal = (state: State): NotificationsState => (
export const getProjects = (state: State): Array<string> => (
uniqBy(
- state.notifications.filter(n => n.project).map(n => ({ key: n.project, name: n.projectName })),
+ state.notifications.filter(n => n.project).map(n => ({
+ key: n.project,
+ name: n.projectName,
+ organization: n.organization
+ })),
project => project.key
)
);