MetricSelect.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { css } from '@emotion/css';
  2. import React, { useCallback, useState } from 'react';
  3. // @ts-ignore
  4. import Highlighter from 'react-highlight-words';
  5. import { SelectableValue, toOption, GrafanaTheme2 } from '@grafana/data';
  6. import { EditorField, EditorFieldGroup } from '@grafana/experimental';
  7. import { Select, FormatOptionLabelMeta, useStyles2 } from '@grafana/ui';
  8. import { PromVisualQuery } from '../types';
  9. // We are matching words split with space
  10. const splitSeparator = ' ';
  11. export interface Props {
  12. query: PromVisualQuery;
  13. onChange: (query: PromVisualQuery) => void;
  14. onGetMetrics: () => Promise<SelectableValue[]>;
  15. }
  16. export function MetricSelect({ query, onChange, onGetMetrics }: Props) {
  17. const styles = useStyles2(getStyles);
  18. const [state, setState] = useState<{
  19. metrics?: Array<SelectableValue<any>>;
  20. isLoading?: boolean;
  21. }>({});
  22. const customFilterOption = useCallback((option: SelectableValue<any>, searchQuery: string) => {
  23. const label = option.label ?? option.value;
  24. if (!label) {
  25. return false;
  26. }
  27. // custom value is not a string label but a react node
  28. if (!label.toLowerCase) {
  29. return true;
  30. }
  31. const searchWords = searchQuery.split(splitSeparator);
  32. return searchWords.reduce((acc, cur) => acc && label.toLowerCase().includes(cur.toLowerCase()), true);
  33. }, []);
  34. const formatOptionLabel = useCallback(
  35. (option: SelectableValue<any>, meta: FormatOptionLabelMeta<any>) => {
  36. // For newly created custom value we don't want to add highlight
  37. if (option['__isNew__']) {
  38. return option.label;
  39. }
  40. return (
  41. <Highlighter
  42. searchWords={meta.inputValue.split(splitSeparator)}
  43. textToHighlight={option.label ?? ''}
  44. highlightClassName={styles.highlight}
  45. />
  46. );
  47. },
  48. [styles.highlight]
  49. );
  50. return (
  51. <EditorFieldGroup>
  52. <EditorField label="Metric">
  53. <Select
  54. inputId="prometheus-metric-select"
  55. className={styles.select}
  56. value={query.metric ? toOption(query.metric) : undefined}
  57. placeholder="Select metric"
  58. allowCustomValue
  59. formatOptionLabel={formatOptionLabel}
  60. filterOption={customFilterOption}
  61. onOpenMenu={async () => {
  62. setState({ isLoading: true });
  63. const metrics = await onGetMetrics();
  64. setState({ metrics, isLoading: undefined });
  65. }}
  66. isLoading={state.isLoading}
  67. options={state.metrics}
  68. onChange={({ value }) => {
  69. if (value) {
  70. onChange({ ...query, metric: value });
  71. }
  72. }}
  73. />
  74. </EditorField>
  75. </EditorFieldGroup>
  76. );
  77. }
  78. const getStyles = (theme: GrafanaTheme2) => ({
  79. select: css`
  80. min-width: 125px;
  81. `,
  82. highlight: css`
  83. label: select__match-highlight;
  84. background: inherit;
  85. padding: inherit;
  86. color: ${theme.colors.warning.contrastText};
  87. background-color: ${theme.colors.warning.main};
  88. `,
  89. });