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

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