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.

ProfileFacet.tsx 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 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 styled from '@emotion/styled';
  21. import { FacetBox, FacetItem, HelperHintIcon, Note, themeColor } from 'design-system';
  22. import { sortBy } from 'lodash';
  23. import * as React from 'react';
  24. import { Profile } from '../../../api/quality-profiles';
  25. import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
  26. import { translate } from '../../../helpers/l10n';
  27. import { Dict } from '../../../types/types';
  28. import { FacetItemsList } from '../../issues/sidebar/FacetItemsList';
  29. import { FacetKey, Query } from '../query';
  30. interface Props {
  31. activation: boolean | undefined;
  32. compareToProfile: string | undefined;
  33. languages: string[];
  34. onChange: (changes: Partial<Query>) => void;
  35. onToggle: (facet: FacetKey) => void;
  36. open: boolean;
  37. referencedProfiles: Dict<Profile>;
  38. value: string | undefined;
  39. }
  40. export default class ProfileFacet extends React.PureComponent<Props> {
  41. handleItemClick = (selected: string) => {
  42. const newValue = this.props.value === selected ? '' : selected;
  43. this.props.onChange({
  44. activation: this.props.activation === undefined ? true : this.props.activation,
  45. compareToProfile: undefined,
  46. profile: newValue,
  47. });
  48. };
  49. handleHeaderClick = () => this.props.onToggle('profile');
  50. handleClear = () =>
  51. this.props.onChange({
  52. activation: undefined,
  53. compareToProfile: undefined,
  54. inheritance: undefined,
  55. profile: undefined,
  56. });
  57. handleActiveClick = (event: React.SyntheticEvent<HTMLElement>) => {
  58. this.stopPropagation(event);
  59. this.props.onChange({ activation: true, compareToProfile: undefined });
  60. };
  61. handleInactiveClick = (event: React.SyntheticEvent<HTMLElement>) => {
  62. this.stopPropagation(event);
  63. this.props.onChange({ activation: false, compareToProfile: undefined });
  64. };
  65. stopPropagation = (event: React.SyntheticEvent<HTMLElement>) => {
  66. event.preventDefault();
  67. event.stopPropagation();
  68. event.currentTarget.blur();
  69. };
  70. getTextValue = () => {
  71. const { referencedProfiles, value } = this.props;
  72. if (value) {
  73. const profile = referencedProfiles[value];
  74. const name = (profile && `${profile.name} ${profile.languageName}`) || value;
  75. return [name];
  76. }
  77. return [];
  78. };
  79. getTooltip = (profile: Profile) => {
  80. const base = `${profile.name} ${profile.languageName}`;
  81. return profile.isBuiltIn ? `${base} (${translate('quality_profiles.built_in')})` : base;
  82. };
  83. renderName = (profile: Profile) => (
  84. <>
  85. {profile.name}
  86. <Note className="sw-ml-1">
  87. {profile.languageName}
  88. {profile.isBuiltIn && ` (${translate('quality_profiles.built_in')})`}
  89. </Note>
  90. </>
  91. );
  92. renderActivation = (profile: Profile) => {
  93. const isCompare = profile.key === this.props.compareToProfile;
  94. const activation = isCompare ? true : this.props.activation;
  95. return (
  96. <>
  97. <FacetToggleActiveStyle
  98. selected={!!activation}
  99. aria-checked={activation}
  100. className="js-active sw-body-xs"
  101. onClick={isCompare ? this.stopPropagation : this.handleActiveClick}
  102. role="radio"
  103. tabIndex={-1}
  104. >
  105. active
  106. </FacetToggleActiveStyle>
  107. <FacetToggleInActiveStyle
  108. selected={!activation}
  109. aria-checked={!activation}
  110. className="js-inactive sw-body-xs sw-ml-1"
  111. onClick={isCompare ? this.stopPropagation : this.handleInactiveClick}
  112. role="radio"
  113. tabIndex={-1}
  114. >
  115. inactive
  116. </FacetToggleInActiveStyle>
  117. </>
  118. );
  119. };
  120. renderItem = (profile: Profile) => {
  121. const active = [this.props.value, this.props.compareToProfile].includes(profile.key);
  122. return (
  123. <FacetItem
  124. active={active}
  125. className="it__search-navigator-facet"
  126. key={profile.key}
  127. name={this.renderName(profile)}
  128. onClick={this.handleItemClick}
  129. stat={active ? this.renderActivation(profile) : null}
  130. tooltip={this.getTooltip(profile)}
  131. value={profile.key}
  132. />
  133. );
  134. };
  135. render() {
  136. const { languages, open, referencedProfiles, value } = this.props;
  137. let profiles = Object.values(referencedProfiles);
  138. if (languages.length > 0) {
  139. profiles = profiles.filter((profile) => languages.includes(profile.language));
  140. }
  141. profiles = sortBy(
  142. profiles,
  143. (profile) => profile.name.toLowerCase(),
  144. (profile) => profile.languageName,
  145. );
  146. const property = 'profile';
  147. const headerId = `facet_${property}`;
  148. const count = value ? 1 : undefined;
  149. return (
  150. <FacetBox
  151. className="it__search-navigator-facet-box"
  152. data-property={property}
  153. id={headerId}
  154. name={translate('coding_rules.facet.qprofile')}
  155. onClear={this.handleClear}
  156. onClick={this.handleHeaderClick}
  157. open={open}
  158. clearIconLabel={translate('clear')}
  159. count={count}
  160. help={
  161. <DocumentationTooltip
  162. content={translate('coding_rules.facet.qprofile.help')}
  163. links={[
  164. {
  165. href: '/instance-administration/quality-profiles/',
  166. label: translate('coding_rules.facet.qprofile.link'),
  167. },
  168. ]}
  169. >
  170. <HelperHintIcon />
  171. </DocumentationTooltip>
  172. }
  173. >
  174. {open && (
  175. <FacetItemsList labelledby={headerId}>{profiles.map(this.renderItem)}</FacetItemsList>
  176. )}
  177. </FacetBox>
  178. );
  179. }
  180. }
  181. const FacetToggleActiveStyle = styled.span<{ selected: boolean }>`
  182. background-color: ${(props) =>
  183. props.selected ? themeColor('facetToggleActive') : 'transparent'};
  184. color: ${(props) => (props.selected ? '#fff' : undefined)};
  185. padding: 2px;
  186. border-radius: 4px;
  187. `;
  188. const FacetToggleInActiveStyle = styled.span<{ selected: boolean }>`
  189. background-color: ${(props) =>
  190. props.selected ? themeColor('facetToggleInactive') : 'transparent'};
  191. color: ${(props) => (props.selected ? '#fff' : undefined)};
  192. padding: 2px;
  193. border-radius: 4px;
  194. `;