ServiceGraphSection.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import { css } from '@emotion/css';
  2. import React, { useEffect, useState } from 'react';
  3. import useAsync from 'react-use/lib/useAsync';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { Alert, InlineField, InlineFieldRow, useStyles2 } from '@grafana/ui';
  6. import { AdHocFilter } from '../../../../features/variables/adhoc/picker/AdHocFilter';
  7. import { AdHocVariableFilter } from '../../../../features/variables/types';
  8. import { PrometheusDatasource } from '../../prometheus/datasource';
  9. import { TempoQuery } from '../datasource';
  10. import { getDS } from './utils';
  11. export function ServiceGraphSection({
  12. graphDatasourceUid,
  13. query,
  14. onChange,
  15. }: {
  16. graphDatasourceUid?: string;
  17. query: TempoQuery;
  18. onChange: (value: TempoQuery) => void;
  19. }) {
  20. const styles = useStyles2(getStyles);
  21. const dsState = useAsync(() => getDS(graphDatasourceUid), [graphDatasourceUid]);
  22. // Check if service graph metrics are being collected. If not, displays a warning
  23. const [hasKeys, setHasKeys] = useState<boolean | undefined>(undefined);
  24. useEffect(() => {
  25. async function fn(ds: PrometheusDatasource) {
  26. const keys = await ds.getTagKeys({
  27. series: [
  28. 'traces_service_graph_request_server_seconds_sum',
  29. 'traces_service_graph_request_total',
  30. 'traces_service_graph_request_failed_total',
  31. ],
  32. });
  33. setHasKeys(Boolean(keys.length));
  34. }
  35. if (!dsState.loading && dsState.value) {
  36. fn(dsState.value as PrometheusDatasource);
  37. }
  38. }, [dsState]);
  39. if (dsState.loading) {
  40. return null;
  41. }
  42. const ds = dsState.value as PrometheusDatasource;
  43. if (!graphDatasourceUid) {
  44. return <div className="text-warning">Please set up a service graph datasource in the datasource settings.</div>;
  45. }
  46. if (graphDatasourceUid && !ds) {
  47. return (
  48. <div className="text-warning">
  49. Service graph datasource is configured but the data source no longer exists. Please configure existing data
  50. source to use the service graph functionality.
  51. </div>
  52. );
  53. }
  54. const filters = queryToFilter(query.serviceMapQuery || '');
  55. return (
  56. <div>
  57. <InlineFieldRow>
  58. <InlineField label="Filter" labelWidth={14} grow>
  59. <AdHocFilter
  60. datasource={{ uid: graphDatasourceUid }}
  61. filters={filters}
  62. getTagKeysOptions={{
  63. series: [
  64. 'traces_service_graph_request_server_seconds_sum',
  65. 'traces_service_graph_request_total',
  66. 'traces_service_graph_request_failed_total',
  67. ],
  68. }}
  69. addFilter={(filter: AdHocVariableFilter) => {
  70. onChange({
  71. ...query,
  72. serviceMapQuery: filtersToQuery([...filters, filter]),
  73. });
  74. }}
  75. removeFilter={(index: number) => {
  76. const newFilters = [...filters];
  77. newFilters.splice(index, 1);
  78. onChange({ ...query, serviceMapQuery: filtersToQuery(newFilters) });
  79. }}
  80. changeFilter={(index: number, filter: AdHocVariableFilter) => {
  81. const newFilters = [...filters];
  82. newFilters.splice(index, 1, filter);
  83. onChange({ ...query, serviceMapQuery: filtersToQuery(newFilters) });
  84. }}
  85. />
  86. </InlineField>
  87. </InlineFieldRow>
  88. {hasKeys === false ? (
  89. <Alert title="No service graph data found" severity="info" className={styles.alert}>
  90. Please ensure that service graph metrics are set up correctly according to the{' '}
  91. <a
  92. target="_blank"
  93. rel="noreferrer noopener"
  94. href="https://grafana.com/docs/tempo/next/grafana-agent/service-graphs/"
  95. >
  96. Tempo documentation
  97. </a>
  98. .
  99. </Alert>
  100. ) : null}
  101. </div>
  102. );
  103. }
  104. function queryToFilter(query: string): AdHocVariableFilter[] {
  105. let match;
  106. let filters: AdHocVariableFilter[] = [];
  107. const re = /([\w_]+)(=|!=|<|>|=~|!~)"(.*?)"/g;
  108. while ((match = re.exec(query)) !== null) {
  109. filters.push({
  110. key: match[1],
  111. operator: match[2],
  112. value: match[3],
  113. condition: '',
  114. });
  115. }
  116. return filters;
  117. }
  118. function filtersToQuery(filters: AdHocVariableFilter[]): string {
  119. return `{${filters.map((f) => `${f.key}${f.operator}"${f.value}"`).join(',')}}`;
  120. }
  121. const getStyles = (theme: GrafanaTheme2) => ({
  122. alert: css`
  123. max-width: 75ch;
  124. margin-top: ${theme.spacing(2)};
  125. `,
  126. });