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.

Search.tsx 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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 QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
  22. import { sortBy } from 'lodash';
  23. import { translate } from 'sonar-ui-common/helpers/l10n';
  24. import { Button } from 'sonar-ui-common/components/controls/buttons';
  25. import Checkbox from 'sonar-ui-common/components/controls/Checkbox';
  26. import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
  27. import SearchBox from 'sonar-ui-common/components/controls/SearchBox';
  28. import Select from 'sonar-ui-common/components/controls/Select';
  29. import BulkApplyTemplateModal from './BulkApplyTemplateModal';
  30. import DeleteModal from './DeleteModal';
  31. import DateInput from '../../components/controls/DateInput';
  32. import { Project } from '../../api/components';
  33. export interface Props {
  34. analyzedBefore: Date | undefined;
  35. onAllDeselected: () => void;
  36. onAllSelected: () => void;
  37. onDateChanged: (analyzedBefore: Date | undefined) => void;
  38. onDeleteProjects: () => void;
  39. onProvisionedChanged: (provisioned: boolean) => void;
  40. onQualifierChanged: (qualifier: string) => void;
  41. onVisibilityChanged: (qualifier: string) => void;
  42. onSearch: (query: string) => void;
  43. organization: T.Organization;
  44. projects: Project[];
  45. provisioned: boolean;
  46. qualifiers: string;
  47. query: string;
  48. ready: boolean;
  49. selection: any[];
  50. topLevelQualifiers: string[];
  51. total: number;
  52. visibility?: T.Visibility;
  53. }
  54. interface State {
  55. bulkApplyTemplateModal: boolean;
  56. deleteModal: boolean;
  57. }
  58. const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP'];
  59. export default class Search extends React.PureComponent<Props, State> {
  60. mounted = false;
  61. state: State = { bulkApplyTemplateModal: false, deleteModal: false };
  62. getQualifierOptions = () => {
  63. const options = this.props.topLevelQualifiers.map(q => ({
  64. label: translate('qualifiers', q),
  65. value: q
  66. }));
  67. return sortBy(options, option => QUALIFIERS_ORDER.indexOf(option.value));
  68. };
  69. onCheck = (checked: boolean) => {
  70. if (checked) {
  71. this.props.onAllSelected();
  72. } else {
  73. this.props.onAllDeselected();
  74. }
  75. };
  76. handleDeleteClick = () => {
  77. this.setState({ deleteModal: true });
  78. };
  79. closeDeleteModal = () => {
  80. this.setState({ deleteModal: false });
  81. };
  82. handleDeleteConfirm = () => {
  83. this.closeDeleteModal();
  84. this.props.onDeleteProjects();
  85. };
  86. handleBulkApplyTemplateClick = () => {
  87. this.setState({ bulkApplyTemplateModal: true });
  88. };
  89. closeBulkApplyTemplateModal = () => {
  90. this.setState({ bulkApplyTemplateModal: false });
  91. };
  92. handleQualifierChange = ({ value }: { value: string }) => this.props.onQualifierChanged(value);
  93. handleVisibilityChange = ({ value }: { value: string }) => this.props.onVisibilityChanged(value);
  94. renderCheckbox = () => {
  95. const isAllChecked =
  96. this.props.projects.length > 0 && this.props.selection.length === this.props.projects.length;
  97. const thirdState =
  98. this.props.projects.length > 0 &&
  99. this.props.selection.length > 0 &&
  100. this.props.selection.length < this.props.projects.length;
  101. const checked = isAllChecked || thirdState;
  102. return (
  103. <Checkbox
  104. checked={checked}
  105. id="projects-selection"
  106. onCheck={this.onCheck}
  107. thirdState={thirdState}
  108. />
  109. );
  110. };
  111. renderQualifierOption = (option: { label: string; value: string }) => (
  112. <span>
  113. <QualifierIcon className="little-spacer-right" qualifier={option.value} />
  114. {option.label}
  115. </span>
  116. );
  117. renderQualifierFilter = () => {
  118. const options = this.getQualifierOptions();
  119. if (options.length < 2) {
  120. return null;
  121. }
  122. return (
  123. <td className="thin nowrap text-middle">
  124. <Select
  125. className="input-medium"
  126. clearable={false}
  127. disabled={!this.props.ready}
  128. name="projects-qualifier"
  129. onChange={this.handleQualifierChange}
  130. optionRenderer={this.renderQualifierOption}
  131. options={this.getQualifierOptions()}
  132. searchable={false}
  133. value={this.props.qualifiers}
  134. valueRenderer={this.renderQualifierOption}
  135. />
  136. </td>
  137. );
  138. };
  139. renderVisibilityFilter = () => {
  140. return (
  141. <td className="thin nowrap text-middle">
  142. <Select
  143. className="input-small"
  144. clearable={false}
  145. disabled={!this.props.ready}
  146. name="projects-visibility"
  147. onChange={this.handleVisibilityChange}
  148. options={[
  149. { value: 'all', label: translate('visibility.both') },
  150. { value: 'public', label: translate('visibility.public') },
  151. { value: 'private', label: translate('visibility.private') }
  152. ]}
  153. searchable={false}
  154. value={this.props.visibility || 'all'}
  155. />
  156. </td>
  157. );
  158. };
  159. renderTypeFilter = () =>
  160. this.props.qualifiers === 'TRK' ? (
  161. <td className="thin nowrap text-middle">
  162. <Checkbox
  163. checked={this.props.provisioned}
  164. className="link-checkbox-control"
  165. id="projects-provisioned"
  166. onCheck={this.props.onProvisionedChanged}>
  167. <span className="text-middle little-spacer-left">
  168. {translate('provisioning.only_provisioned')}
  169. </span>
  170. </Checkbox>
  171. <HelpTooltip
  172. className="spacer-left"
  173. overlay={translate('provisioning.only_provisioned.tooltip')}
  174. />
  175. </td>
  176. ) : null;
  177. renderDateFilter = () => {
  178. return (
  179. <td className="thin nowrap text-middle">
  180. <DateInput
  181. inputClassName="input-medium"
  182. name="analyzed-before"
  183. onChange={this.props.onDateChanged}
  184. placeholder={translate('last_analysis_before')}
  185. value={this.props.analyzedBefore}
  186. />
  187. </td>
  188. );
  189. };
  190. render() {
  191. return (
  192. <div className="big-spacer-bottom">
  193. <table className="data">
  194. <tbody>
  195. <tr>
  196. <td className="thin text-middle">
  197. {this.props.ready ? this.renderCheckbox() : <i className="spinner" />}
  198. </td>
  199. {this.renderQualifierFilter()}
  200. {this.renderDateFilter()}
  201. {this.renderVisibilityFilter()}
  202. {this.renderTypeFilter()}
  203. <td className="text-middle">
  204. <SearchBox
  205. minLength={3}
  206. onChange={this.props.onSearch}
  207. placeholder={translate('search.search_by_name_or_key')}
  208. value={this.props.query}
  209. />
  210. </td>
  211. <td className="thin nowrap text-middle">
  212. <Button
  213. className="js-bulk-apply-permission-template"
  214. disabled={this.props.selection.length === 0}
  215. onClick={this.handleBulkApplyTemplateClick}>
  216. {translate('permission_templates.bulk_apply_permission_template')}
  217. </Button>
  218. {this.props.qualifiers === 'TRK' && (
  219. <Button
  220. className="js-delete spacer-left button-red"
  221. disabled={this.props.selection.length === 0}
  222. onClick={this.handleDeleteClick}
  223. title={
  224. this.props.selection.length === 0
  225. ? translate('permission_templates.select_to_delete')
  226. : translate('permission_templates.delete_selected')
  227. }>
  228. {translate('delete')}
  229. </Button>
  230. )}
  231. </td>
  232. </tr>
  233. </tbody>
  234. </table>
  235. {this.state.bulkApplyTemplateModal && (
  236. <BulkApplyTemplateModal
  237. analyzedBefore={this.props.analyzedBefore}
  238. onClose={this.closeBulkApplyTemplateModal}
  239. organization={this.props.organization.key}
  240. provisioned={this.props.provisioned}
  241. qualifier={this.props.qualifiers}
  242. query={this.props.query}
  243. selection={this.props.selection}
  244. total={this.props.total}
  245. />
  246. )}
  247. {this.state.deleteModal && (
  248. <DeleteModal
  249. analyzedBefore={this.props.analyzedBefore}
  250. onClose={this.closeDeleteModal}
  251. onConfirm={this.handleDeleteConfirm}
  252. organization={this.props.organization.key}
  253. provisioned={this.props.provisioned}
  254. qualifier={this.props.qualifiers}
  255. query={this.props.query}
  256. selection={this.props.selection}
  257. total={this.props.total}
  258. />
  259. )}
  260. </div>
  261. );
  262. }
  263. }