queries.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import {useCallback, useLayoutEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as echarts from 'echarts/core';
  4. import {t} from 'sentry/locale';
  5. import {space} from 'sentry/styles/space';
  6. import type {MetricWidgetQueryParams} from 'sentry/utils/metrics/types';
  7. import usePageFilters from 'sentry/utils/usePageFilters';
  8. import {DDM_CHART_GROUP} from 'sentry/views/ddm/constants';
  9. import {useDDMContext} from 'sentry/views/ddm/context';
  10. import {MetricQueryContextMenu} from 'sentry/views/ddm/contextMenu';
  11. import {QueryBuilder} from 'sentry/views/ddm/queryBuilder';
  12. import {QuerySymbol} from 'sentry/views/ddm/querySymbol';
  13. export function Queries() {
  14. const {
  15. widgets,
  16. updateWidget,
  17. setSelectedWidgetIndex,
  18. showQuerySymbols,
  19. selectedWidgetIndex,
  20. } = useDDMContext();
  21. const {selection} = usePageFilters();
  22. // Make sure all charts are connected to the same group whenever the widgets definition changes
  23. useLayoutEffect(() => {
  24. echarts.connect(DDM_CHART_GROUP);
  25. }, [widgets]);
  26. const handleChange = useCallback(
  27. (index: number, widget: Partial<MetricWidgetQueryParams>) => {
  28. updateWidget(index, widget);
  29. },
  30. [updateWidget]
  31. );
  32. return (
  33. <Wrapper showQuerySymbols={showQuerySymbols}>
  34. {widgets.map((widget, index) => (
  35. <Row key={index} onFocusCapture={() => setSelectedWidgetIndex(index)}>
  36. <Query
  37. widget={widget}
  38. onChange={data => handleChange(index, data)}
  39. projects={selection.projects}
  40. symbol={
  41. showQuerySymbols && (
  42. <StyledQuerySymbol
  43. index={index}
  44. isSelected={index === selectedWidgetIndex}
  45. onClick={() => setSelectedWidgetIndex(index)}
  46. role="button"
  47. aria-label={t('Select query')}
  48. />
  49. )
  50. }
  51. contextMenu={
  52. <MetricQueryContextMenu
  53. displayType={widget.displayType}
  54. widgetIndex={index}
  55. metricsQuery={{
  56. mri: widget.mri,
  57. query: widget.query,
  58. op: widget.op,
  59. groupBy: widget.groupBy,
  60. projects: selection.projects,
  61. datetime: selection.datetime,
  62. environments: selection.environments,
  63. }}
  64. />
  65. }
  66. />
  67. </Row>
  68. ))}
  69. </Wrapper>
  70. );
  71. }
  72. interface Props {
  73. onChange: (data: Partial<MetricWidgetQueryParams>) => void;
  74. projects: number[];
  75. widget: MetricWidgetQueryParams;
  76. contextMenu?: React.ReactNode;
  77. symbol?: React.ReactNode;
  78. }
  79. export function Query({widget, projects, onChange, contextMenu, symbol}: Props) {
  80. return (
  81. <QueryWrapper hasSymbol={!!symbol}>
  82. {symbol}
  83. <QueryBuilder
  84. onChange={onChange}
  85. metricsQuery={{
  86. mri: widget.mri,
  87. op: widget.op,
  88. groupBy: widget.groupBy,
  89. query: widget.query,
  90. }}
  91. displayType={widget.displayType}
  92. isEdit
  93. projects={projects}
  94. />
  95. {contextMenu}
  96. </QueryWrapper>
  97. );
  98. }
  99. const QueryWrapper = styled('div')<{hasSymbol: boolean}>`
  100. display: grid;
  101. gap: ${space(1)};
  102. padding-bottom: ${space(1)};
  103. grid-template-columns: 1fr max-content;
  104. ${p => p.hasSymbol && `grid-template-columns: min-content 1fr max-content;`}
  105. `;
  106. const StyledQuerySymbol = styled(QuerySymbol)`
  107. margin-top: 10px;
  108. cursor: pointer;
  109. `;
  110. const Wrapper = styled('div')<{showQuerySymbols: boolean}>`
  111. padding-bottom: ${space(2)};
  112. `;
  113. const Row = styled('div')`
  114. display: contents;
  115. `;