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.

AddGraphMetric.tsx 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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 { ButtonSecondary, ChevronDownIcon, Dropdown, TextMuted } from 'design-system';
  21. import { sortBy } from 'lodash';
  22. import * as React from 'react';
  23. import { CCT_SOFTWARE_QUALITY_METRICS, HIDDEN_METRICS } from '../../helpers/constants';
  24. import { getLocalizedMetricName, translate } from '../../helpers/l10n';
  25. import { isDiffMetric } from '../../helpers/measures';
  26. import { MetricKey, MetricType } from '../../types/metrics';
  27. import { Metric } from '../../types/types';
  28. import AddGraphMetricPopup from './AddGraphMetricPopup';
  29. interface Props {
  30. metrics: Metric[];
  31. metricsTypeFilter?: string[];
  32. onAddMetric: (metric: string) => void;
  33. onRemoveMetric: (metric: string) => void;
  34. selectedMetrics: string[];
  35. }
  36. interface State {
  37. metrics: string[];
  38. query: string;
  39. selectedMetrics: string[];
  40. }
  41. export default class AddGraphMetric extends React.PureComponent<Props, State> {
  42. state: State = {
  43. metrics: [],
  44. query: '',
  45. selectedMetrics: [],
  46. };
  47. filterSelected = (query: string, selectedElements: string[]) => {
  48. return selectedElements.filter((element) =>
  49. this.getLocalizedMetricNameFromKey(element).toLowerCase().includes(query.toLowerCase()),
  50. );
  51. };
  52. filterMetricsElements = (
  53. { metricsTypeFilter, metrics, selectedMetrics }: Props,
  54. query: string,
  55. ) => {
  56. return metrics
  57. .filter((metric) => {
  58. if (metric.hidden) {
  59. return false;
  60. }
  61. if (isDiffMetric(metric.key)) {
  62. return false;
  63. }
  64. if (
  65. [MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType) &&
  66. !CCT_SOFTWARE_QUALITY_METRICS.includes(metric.key as MetricKey)
  67. ) {
  68. return false;
  69. }
  70. if (HIDDEN_METRICS.includes(metric.key as MetricKey)) {
  71. return false;
  72. }
  73. if (
  74. selectedMetrics.includes(metric.key) ||
  75. !getLocalizedMetricName(metric).toLowerCase().includes(query.toLowerCase())
  76. ) {
  77. return false;
  78. }
  79. if (metricsTypeFilter && metricsTypeFilter.length > 0) {
  80. return metricsTypeFilter.includes(metric.type);
  81. }
  82. return true;
  83. })
  84. .map((metric) => metric.key);
  85. };
  86. getSelectedMetricsElements = (metrics: Metric[], selectedMetrics: string[]) => {
  87. return metrics
  88. .filter((metric) => selectedMetrics.includes(metric.key))
  89. .map((metric) => metric.key);
  90. };
  91. getLocalizedMetricNameFromKey = (key: string) => {
  92. const metric = this.props.metrics.find((m) => m.key === key);
  93. return metric === undefined ? key : getLocalizedMetricName(metric);
  94. };
  95. onSearch = (query: string) => {
  96. this.setState({ query });
  97. return Promise.resolve();
  98. };
  99. onSelect = (metric: string) => {
  100. this.props.onAddMetric(metric);
  101. this.setState((state) => {
  102. return {
  103. selectedMetrics: sortBy([...state.selectedMetrics, metric]),
  104. metrics: this.filterMetricsElements(this.props, state.query),
  105. };
  106. });
  107. };
  108. onUnselect = (metric: string) => {
  109. this.props.onRemoveMetric(metric);
  110. this.setState((state) => {
  111. return {
  112. metrics: sortBy([...state.metrics, metric]),
  113. selectedMetrics: state.selectedMetrics.filter((selected) => selected !== metric),
  114. };
  115. });
  116. };
  117. render() {
  118. const { query } = this.state;
  119. const filteredMetrics = this.filterMetricsElements(this.props, query);
  120. const selectedMetrics = this.getSelectedMetricsElements(
  121. this.props.metrics,
  122. this.props.selectedMetrics,
  123. );
  124. return (
  125. <Dropdown
  126. allowResizing
  127. size="large"
  128. closeOnClick={false}
  129. id="activity-graph-custom-metric-selector"
  130. overlay={
  131. <AddGraphMetricPopup
  132. elements={filteredMetrics}
  133. filterSelected={this.filterSelected}
  134. metricsTypeFilter={this.props.metricsTypeFilter}
  135. onSearch={this.onSearch}
  136. onSelect={this.onSelect}
  137. onUnselect={this.onUnselect}
  138. selectedElements={selectedMetrics}
  139. />
  140. }
  141. >
  142. <ButtonSecondary
  143. className={
  144. 'sw-ml-2 sw-body-sm sw-flex sw-flex-row sw-justify-between sw-pl-3 sw-pr-2 sw-w-32 ' +
  145. 'sw-z-normal' // needed because the legends overlap part of the button
  146. }
  147. >
  148. <TextMuted text={translate('project_activity.graphs.custom.add')} />
  149. <ChevronDownIcon className="sw-ml-1 sw-mr-0 sw-pr-0" />
  150. </ButtonSecondary>
  151. </Dropdown>
  152. );
  153. }
  154. }