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.

Sidebar.tsx 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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 { BasicSeparator, FlagMessage, Link } from 'design-system';
  21. import * as React from 'react';
  22. import { FormattedMessage } from 'react-intl';
  23. import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
  24. import { isBranch, isPullRequest } from '../../../helpers/branch-like';
  25. import { translate } from '../../../helpers/l10n';
  26. import { AppState } from '../../../types/appstate';
  27. import { BranchLike } from '../../../types/branch-like';
  28. import {
  29. ComponentQualifier,
  30. isApplication,
  31. isPortfolioLike,
  32. isProject,
  33. isView,
  34. } from '../../../types/component';
  35. import {
  36. Facet,
  37. ReferencedComponent,
  38. ReferencedLanguage,
  39. ReferencedRule,
  40. } from '../../../types/issues';
  41. import { GlobalSettingKeys } from '../../../types/settings';
  42. import { Component, Dict } from '../../../types/types';
  43. import { UserBase } from '../../../types/users';
  44. import { Query } from '../utils';
  45. import { AssigneeFacet } from './AssigneeFacet';
  46. import { AttributeCategoryFacet } from './AttributeCategoryFacet';
  47. import { AuthorFacet } from './AuthorFacet';
  48. import { CreationDateFacet } from './CreationDateFacet';
  49. import { DirectoryFacet } from './DirectoryFacet';
  50. import { FileFacet } from './FileFacet';
  51. import { LanguageFacet } from './LanguageFacet';
  52. import { PeriodFilter } from './PeriodFilter';
  53. import { ProjectFacet } from './ProjectFacet';
  54. import { ResolutionFacet } from './ResolutionFacet';
  55. import { RuleFacet } from './RuleFacet';
  56. import { ScopeFacet } from './ScopeFacet';
  57. import { SeverityFacet } from './SeverityFacet';
  58. import { SoftwareQualityFacet } from './SoftwareQualityFacet';
  59. import { StandardFacet } from './StandardFacet';
  60. import { StatusFacet } from './StatusFacet';
  61. import { TagFacet } from './TagFacet';
  62. import { TypeFacet } from './TypeFacet';
  63. import { VariantFacet } from './VariantFacet';
  64. export interface Props {
  65. appState: AppState;
  66. branchLike?: BranchLike;
  67. component: Component | undefined;
  68. createdAfterIncludesTime: boolean;
  69. facets: Dict<Facet | undefined>;
  70. loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>;
  71. loadingFacets: Dict<boolean>;
  72. myIssues: boolean;
  73. onFacetToggle: (property: string) => void;
  74. onFilterChange: (changes: Partial<Query>) => void;
  75. openFacets: Dict<boolean>;
  76. showVariantsFilter: boolean;
  77. query: Query;
  78. referencedComponentsById: Dict<ReferencedComponent>;
  79. referencedComponentsByKey: Dict<ReferencedComponent>;
  80. referencedLanguages: Dict<ReferencedLanguage>;
  81. referencedRules: Dict<ReferencedRule>;
  82. referencedUsers: Dict<UserBase>;
  83. }
  84. export class SidebarClass extends React.PureComponent<Props> {
  85. renderComponentFacets() {
  86. const { component, facets, loadingFacets, openFacets, query, branchLike, showVariantsFilter } =
  87. this.props;
  88. const hasFileOrDirectory =
  89. !isApplication(component?.qualifier) && !isPortfolioLike(component?.qualifier);
  90. if (!component || !hasFileOrDirectory) {
  91. return null;
  92. }
  93. const commonProps = {
  94. componentKey: component.key,
  95. loadSearchResultCount: this.props.loadSearchResultCount,
  96. onChange: this.props.onFilterChange,
  97. onToggle: this.props.onFacetToggle,
  98. query,
  99. };
  100. return (
  101. <>
  102. {showVariantsFilter && isProject(component?.qualifier) && (
  103. <>
  104. <BasicSeparator className="sw-my-4" />
  105. <VariantFacet
  106. fetching={loadingFacets.codeVariants === true}
  107. open={!!openFacets.codeVariants}
  108. stats={facets.codeVariants}
  109. values={query.codeVariants}
  110. {...commonProps}
  111. />
  112. </>
  113. )}
  114. {component.qualifier !== ComponentQualifier.Directory && (
  115. <>
  116. <BasicSeparator className="sw-my-4" />
  117. <DirectoryFacet
  118. branchLike={branchLike}
  119. directories={query.directories}
  120. fetching={loadingFacets.directories === true}
  121. open={!!openFacets.directories}
  122. stats={facets.directories}
  123. {...commonProps}
  124. />
  125. </>
  126. )}
  127. <BasicSeparator className="sw-my-4" />
  128. <FileFacet
  129. branchLike={branchLike}
  130. fetching={loadingFacets.files === true}
  131. files={query.files}
  132. open={!!openFacets.files}
  133. stats={facets.files}
  134. {...commonProps}
  135. />
  136. </>
  137. );
  138. }
  139. render() {
  140. const {
  141. appState: { settings },
  142. component,
  143. createdAfterIncludesTime,
  144. facets,
  145. openFacets,
  146. query,
  147. branchLike,
  148. } = this.props;
  149. const disableDeveloperAggregatedInfo =
  150. settings[GlobalSettingKeys.DeveloperAggregatedInfoDisabled] === 'true';
  151. const branch =
  152. (isBranch(branchLike) && branchLike.name) ||
  153. (isPullRequest(branchLike) && branchLike.branch) ||
  154. undefined;
  155. const displayPeriodFilter = component !== undefined && !isPortfolioLike(component.qualifier);
  156. const displayProjectsFacet = !component || isView(component.qualifier);
  157. const needIssueSync = component?.needIssueSync;
  158. return (
  159. <>
  160. {displayPeriodFilter && (
  161. <PeriodFilter
  162. onChange={this.props.onFilterChange}
  163. newCodeSelected={query.inNewCodePeriod}
  164. />
  165. )}
  166. {!needIssueSync && (
  167. <>
  168. <AttributeCategoryFacet
  169. fetching={this.props.loadingFacets.cleanCodeAttributeCategories === true}
  170. needIssueSync={needIssueSync}
  171. onChange={this.props.onFilterChange}
  172. onToggle={this.props.onFacetToggle}
  173. open={!!openFacets.cleanCodeAttributeCategories}
  174. stats={facets.cleanCodeAttributeCategories}
  175. categories={query.cleanCodeAttributeCategories}
  176. />
  177. <BasicSeparator className="sw-my-4" />
  178. <SoftwareQualityFacet
  179. fetching={this.props.loadingFacets.impactSoftwareQualities === true}
  180. needIssueSync={needIssueSync}
  181. onChange={this.props.onFilterChange}
  182. onToggle={this.props.onFacetToggle}
  183. open={!!openFacets.impactSoftwareQualities}
  184. stats={facets.impactSoftwareQualities}
  185. qualities={query.impactSoftwareQualities}
  186. />
  187. <BasicSeparator className="sw-my-4" />
  188. <SeverityFacet
  189. fetching={this.props.loadingFacets.impactSeverities === true}
  190. onChange={this.props.onFilterChange}
  191. onToggle={this.props.onFacetToggle}
  192. open={!!openFacets.impactSeverities}
  193. severities={query.impactSeverities}
  194. stats={facets.impactSeverities}
  195. />
  196. <BasicSeparator className="sw-my-4" />
  197. </>
  198. )}
  199. <TypeFacet
  200. fetching={this.props.loadingFacets.types === true}
  201. needIssueSync={needIssueSync}
  202. onChange={this.props.onFilterChange}
  203. onToggle={this.props.onFacetToggle}
  204. open={!!openFacets.types}
  205. stats={facets.types}
  206. types={query.types}
  207. />
  208. {!needIssueSync && (
  209. <>
  210. <BasicSeparator className="sw-my-4" />
  211. <ScopeFacet
  212. fetching={this.props.loadingFacets.scopes === true}
  213. onChange={this.props.onFilterChange}
  214. onToggle={this.props.onFacetToggle}
  215. open={!!openFacets.scopes}
  216. stats={facets.scopes}
  217. scopes={query.scopes}
  218. />
  219. <BasicSeparator className="sw-my-4" />
  220. <ResolutionFacet
  221. fetching={this.props.loadingFacets.resolutions === true}
  222. onChange={this.props.onFilterChange}
  223. onToggle={this.props.onFacetToggle}
  224. open={!!openFacets.resolutions}
  225. resolutions={query.resolutions}
  226. resolved={query.resolved}
  227. stats={facets.resolutions}
  228. />
  229. <BasicSeparator className="sw-my-4" />
  230. <StatusFacet
  231. fetching={this.props.loadingFacets.statuses === true}
  232. onChange={this.props.onFilterChange}
  233. onToggle={this.props.onFacetToggle}
  234. open={!!openFacets.statuses}
  235. stats={facets.statuses}
  236. statuses={query.statuses}
  237. />
  238. <BasicSeparator className="sw-my-4" />
  239. <StandardFacet
  240. cwe={query.cwe}
  241. cweOpen={!!openFacets.cwe}
  242. cweStats={facets.cwe}
  243. fetchingCwe={this.props.loadingFacets.cwe === true}
  244. fetchingOwaspTop10={this.props.loadingFacets.owaspTop10 === true}
  245. fetchingOwaspTop10-2021={this.props.loadingFacets['owaspTop10-2021'] === true}
  246. fetchingSonarSourceSecurity={this.props.loadingFacets.sonarsourceSecurity === true}
  247. loadSearchResultCount={this.props.loadSearchResultCount}
  248. onChange={this.props.onFilterChange}
  249. onToggle={this.props.onFacetToggle}
  250. open={!!openFacets.standards}
  251. owaspTop10={query.owaspTop10}
  252. owaspTop10Open={!!openFacets.owaspTop10}
  253. owaspTop10Stats={facets.owaspTop10}
  254. owaspTop10-2021={query['owaspTop10-2021']}
  255. owaspTop10-2021Open={!!openFacets['owaspTop10-2021']}
  256. owaspTop10-2021Stats={facets['owaspTop10-2021']}
  257. query={query}
  258. sonarsourceSecurity={query.sonarsourceSecurity}
  259. sonarsourceSecurityOpen={!!openFacets.sonarsourceSecurity}
  260. sonarsourceSecurityStats={facets.sonarsourceSecurity}
  261. />
  262. <BasicSeparator className="sw-my-4" />
  263. <CreationDateFacet
  264. component={component}
  265. createdAfter={query.createdAfter}
  266. createdAfterIncludesTime={createdAfterIncludesTime}
  267. createdAt={query.createdAt}
  268. createdBefore={query.createdBefore}
  269. createdInLast={query.createdInLast}
  270. fetching={this.props.loadingFacets.createdAt === true}
  271. onChange={this.props.onFilterChange}
  272. onToggle={this.props.onFacetToggle}
  273. open={!!openFacets.createdAt}
  274. inNewCodePeriod={query.inNewCodePeriod}
  275. stats={facets.createdAt}
  276. />
  277. <BasicSeparator className="sw-my-4" />
  278. <LanguageFacet
  279. fetching={this.props.loadingFacets.languages === true}
  280. loadSearchResultCount={this.props.loadSearchResultCount}
  281. onChange={this.props.onFilterChange}
  282. onToggle={this.props.onFacetToggle}
  283. open={!!openFacets.languages}
  284. query={query}
  285. referencedLanguages={this.props.referencedLanguages}
  286. selectedLanguages={query.languages}
  287. stats={facets.languages}
  288. />
  289. <BasicSeparator className="sw-my-4" />
  290. <RuleFacet
  291. fetching={this.props.loadingFacets.rules === true}
  292. loadSearchResultCount={this.props.loadSearchResultCount}
  293. onChange={this.props.onFilterChange}
  294. onToggle={this.props.onFacetToggle}
  295. open={!!openFacets.rules}
  296. query={query}
  297. referencedRules={this.props.referencedRules}
  298. stats={facets.rules}
  299. />
  300. {!disableDeveloperAggregatedInfo && (
  301. <>
  302. <BasicSeparator className="sw-my-4" />
  303. <TagFacet
  304. component={component}
  305. branch={branch}
  306. fetching={this.props.loadingFacets.tags === true}
  307. loadSearchResultCount={this.props.loadSearchResultCount}
  308. onChange={this.props.onFilterChange}
  309. onToggle={this.props.onFacetToggle}
  310. open={!!openFacets.tags}
  311. query={query}
  312. stats={facets.tags}
  313. tags={query.tags}
  314. />
  315. {displayProjectsFacet && (
  316. <>
  317. <BasicSeparator className="sw-my-4" />
  318. <ProjectFacet
  319. component={component}
  320. fetching={this.props.loadingFacets.projects === true}
  321. loadSearchResultCount={this.props.loadSearchResultCount}
  322. onChange={this.props.onFilterChange}
  323. onToggle={this.props.onFacetToggle}
  324. open={!!openFacets.projects}
  325. projects={query.projects}
  326. query={query}
  327. referencedComponents={this.props.referencedComponentsByKey}
  328. stats={facets.projects}
  329. />
  330. </>
  331. )}
  332. {this.renderComponentFacets()}
  333. {!this.props.myIssues && (
  334. <>
  335. <BasicSeparator className="sw-my-4" />
  336. <AssigneeFacet
  337. assigned={query.assigned}
  338. assignees={query.assignees}
  339. fetching={this.props.loadingFacets.assignees === true}
  340. loadSearchResultCount={this.props.loadSearchResultCount}
  341. onChange={this.props.onFilterChange}
  342. onToggle={this.props.onFacetToggle}
  343. open={!!openFacets.assignees}
  344. query={query}
  345. referencedUsers={this.props.referencedUsers}
  346. stats={facets.assignees}
  347. />
  348. </>
  349. )}
  350. <BasicSeparator className="sw-my-4" />
  351. <AuthorFacet
  352. author={query.author}
  353. component={component}
  354. fetching={this.props.loadingFacets.author === true}
  355. loadSearchResultCount={this.props.loadSearchResultCount}
  356. onChange={this.props.onFilterChange}
  357. onToggle={this.props.onFacetToggle}
  358. open={!!openFacets.author}
  359. query={query}
  360. stats={facets.author}
  361. />
  362. </>
  363. )}
  364. </>
  365. )}
  366. {needIssueSync && (
  367. <>
  368. <BasicSeparator className="sw-my-4" />
  369. <FlagMessage className="sw-my-6" variant="info">
  370. <div>
  371. {translate('indexation.page_unavailable.description')}
  372. <span className="sw-ml-1">
  373. <FormattedMessage
  374. defaultMessage={translate('indexation.filters_unavailable')}
  375. id="indexation.filters_unavailable"
  376. values={{
  377. link: (
  378. <Link to="https://docs.sonarsource.com/sonarqube/latest/instance-administration/reindexing/">
  379. {translate('learn_more')}
  380. </Link>
  381. ),
  382. }}
  383. />
  384. </span>
  385. </div>
  386. </FlagMessage>
  387. </>
  388. )}
  389. </>
  390. );
  391. }
  392. }
  393. export const Sidebar = withAppStateContext(SidebarClass);