diff options
author | Stas Vilchik <stas-vilchik@users.noreply.github.com> | 2017-02-07 13:11:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-07 13:11:07 +0100 |
commit | c54e107bb8305de177d8c805d0368eaf7df3bc37 (patch) | |
tree | fb994bad6f25ac5c563c0097502e20b0460f287e | |
parent | 929dcab3e90bc534193441f97cf6006d9495e448 (diff) | |
download | sonarqube-c54e107bb8305de177d8c805d0368eaf7df3bc37.tar.gz sonarqube-c54e107bb8305de177d8c805d0368eaf7df3bc37.zip |
SONAR-8669 Display organizations on the "Notifications" page (#1633)
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 ) ); |