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.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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 { 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 ([MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType)) {
  65. return false;
  66. }
  67. if (HIDDEN_METRICS.includes(metric.key as MetricKey)) {
  68. return false;
  69. }
  70. if (
  71. selectedMetrics.includes(metric.key) ||
  72. !getLocalizedMetricName(metric).toLowerCase().includes(query.toLowerCase())
  73. ) {
  74. return false;
  75. }
  76. if (metricsTypeFilter && metricsTypeFilter.length > 0) {
  77. return metricsTypeFilter.includes(metric.type);
  78. }
  79. return true;
  80. })
  81. .map((metric) => metric.key);
  82. };
  83. getSelectedMetricsElements = (metrics: Metric[], selectedMetrics: string[]) => {
  84. return metrics
  85. .filter((metric) => selectedMetrics.includes(metric.key))
  86. .map((metric) => metric.key);
  87. };
  88. getLocalizedMetricNameFromKey = (key: string) => {
  89. const metric = this.props.metrics.find((m) => m.key === key);
  90. return metric === undefined ? key : getLocalizedMetricName(metric);
  91. };
  92. onSearch = (query: string) => {
  93. this.setState({ query });
  94. return Promise.resolve();
  95. };
  96. onSelect = (metric: string) => {
  97. this.props.onAddMetric(metric);
  98. this.setState((state) => {
  99. return {
  100. selectedMetrics: sortBy([...state.selectedMetrics, metric]),
  101. metrics: this.filterMetricsElements(this.props, state.query),
  102. };
  103. });
  104. };
  105. onUnselect = (metric: string) => {
  106. this.props.onRemoveMetric(metric);
  107. this.setState((state) => {
  108. return {
  109. metrics: sortBy([...state.metrics, metric]),
  110. selectedMetrics: state.selectedMetrics.filter((selected) => selected !== metric),
  111. };
  112. });
  113. };
  114. render() {
  115. const { query } = this.state;
  116. const filteredMetrics = this.filterMetricsElements(this.props, query);
  117. const selectedMetrics = this.getSelectedMetricsElements(
  118. this.props.metrics,
  119. this.props.selectedMetrics,
  120. );
  121. return (
  122. <Dropdown
  123. allowResizing
  124. size="large"
  125. closeOnClick={false}
  126. id="activity-graph-custom-metric-selector"
  127. overlay={
  128. <AddGraphMetricPopup
  129. elements={filteredMetrics}
  130. filterSelected={this.filterSelected}
  131. metricsTypeFilter={this.props.metricsTypeFilter}
  132. onSearch={this.onSearch}
  133. onSelect={this.onSelect}
  134. onUnselect={this.onUnselect}
  135. selectedElements={selectedMetrics}
  136. />
  137. }
  138. >
  139. <ButtonSecondary
  140. className={
  141. 'sw-ml-2 sw-body-sm sw-flex sw-flex-row sw-justify-between sw-pl-3 sw-pr-2 sw-w-32 ' +
  142. 'sw-z-normal' // needed because the legends overlap part of the button
  143. }
  144. >
  145. <TextMuted text={translate('project_activity.graphs.custom.add')} />
  146. <ChevronDownIcon className="sw-ml-1 sw-mr-0 sw-pr-0" />
  147. </ButtonSecondary>
  148. </Dropdown>
  149. );
  150. }
  151. }