You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Projects.tsx 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import * as React from 'react';
  21. import { differenceWith } from 'lodash';
  22. import { translate } from 'sonar-ui-common/helpers/l10n';
  23. import { AsyncSelect } from 'sonar-ui-common/components/controls/Select';
  24. import ProjectNotifications from './ProjectNotifications';
  25. import { NotificationProject } from './types';
  26. import { getSuggestions } from '../../../api/components';
  27. import Organization from '../../../components/shared/Organization';
  28. export interface Props {
  29. addNotification: (n: T.Notification) => void;
  30. channels: string[];
  31. notificationsByProject: T.Dict<T.Notification[]>;
  32. projects: NotificationProject[];
  33. removeNotification: (n: T.Notification) => void;
  34. types: string[];
  35. }
  36. interface State {
  37. addedProjects: NotificationProject[];
  38. }
  39. export default class Projects extends React.PureComponent<Props, State> {
  40. state: State = { addedProjects: [] };
  41. componentWillReceiveProps(nextProps: Props) {
  42. // remove all projects from `this.state.addedProjects`
  43. // that already exist in `nextProps.projects`
  44. this.setState(state => ({
  45. addedProjects: differenceWith(
  46. state.addedProjects,
  47. Object.keys(nextProps.projects),
  48. (stateProject, propsProjectKey) => stateProject.key !== propsProjectKey
  49. )
  50. }));
  51. }
  52. loadOptions = (query: string) => {
  53. if (query.length < 2) {
  54. return Promise.resolve({ options: [] });
  55. }
  56. return getSuggestions(query)
  57. .then(r => {
  58. const projects = r.results.find(domain => domain.q === 'TRK');
  59. return projects ? projects.items : [];
  60. })
  61. .then(projects => {
  62. return projects
  63. .filter(
  64. project =>
  65. !this.props.projects.find(p => p.key === project.key) &&
  66. !this.state.addedProjects.find(p => p.key === project.key)
  67. )
  68. .map(project => ({
  69. value: project.key,
  70. label: project.name,
  71. organization: project.organization
  72. }));
  73. })
  74. .then(options => {
  75. return { options };
  76. });
  77. };
  78. handleAddProject = (selected: { label: string; organization: string; value: string }) => {
  79. const project = {
  80. key: selected.value,
  81. name: selected.label,
  82. organization: selected.organization
  83. };
  84. this.setState(state => ({
  85. addedProjects: [...state.addedProjects, project]
  86. }));
  87. };
  88. renderOption = (option: { label: string; organization: string; value: string }) => {
  89. return (
  90. <span>
  91. <Organization link={false} organizationKey={option.organization} />
  92. <strong>{option.label}</strong>
  93. </span>
  94. );
  95. };
  96. render() {
  97. const allProjects = [...this.props.projects, ...this.state.addedProjects];
  98. return (
  99. <section className="boxed-group">
  100. <h2>{translate('my_profile.per_project_notifications.title')}</h2>
  101. <div className="boxed-group-inner">
  102. {allProjects.length === 0 && (
  103. <div className="note">{translate('my_account.no_project_notifications')}</div>
  104. )}
  105. {allProjects.map(project => (
  106. <ProjectNotifications
  107. addNotification={this.props.addNotification}
  108. channels={this.props.channels}
  109. key={project.key}
  110. notifications={this.props.notificationsByProject[project.key] || []}
  111. project={project}
  112. removeNotification={this.props.removeNotification}
  113. types={this.props.types}
  114. />
  115. ))}
  116. <div className="spacer-top panel bg-muted">
  117. <span className="text-middle spacer-right">
  118. {translate('my_account.set_notifications_for')}:
  119. </span>
  120. <AsyncSelect
  121. autoload={false}
  122. cache={false}
  123. className="input-super-large"
  124. loadOptions={this.loadOptions}
  125. name="new_project"
  126. onChange={this.handleAddProject}
  127. optionRenderer={this.renderOption}
  128. placeholder={translate('my_account.search_project')}
  129. />
  130. </div>
  131. </div>
  132. </section>
  133. );
  134. }
  135. }