queries.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import {useCallback, useLayoutEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import * as echarts from 'echarts/core';
  5. import {Button} from 'sentry/components/button';
  6. import {IconAdd} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import {trackAnalytics} from 'sentry/utils/analytics';
  10. import {MetricWidgetQueryParams} from 'sentry/utils/metrics';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import usePageFilters from 'sentry/utils/usePageFilters';
  13. import {DDM_CHART_GROUP} from 'sentry/views/ddm/constants';
  14. import {useDDMContext} from 'sentry/views/ddm/context';
  15. import {MetricQueryContextMenu} from 'sentry/views/ddm/contextMenu';
  16. import {QueryBuilder} from 'sentry/views/ddm/queryBuilder';
  17. import {QuerySymbol} from 'sentry/views/ddm/querySymbol';
  18. export function Queries() {
  19. const organization = useOrganization();
  20. const {widgets, updateWidget, addWidget, setSelectedWidgetIndex} = useDDMContext();
  21. const {selection} = usePageFilters();
  22. const hasEmptyWidget = widgets.length === 0 || widgets.some(widget => !widget.mri);
  23. // Make sure all charts are connected to the same group whenever the widgets definition changes
  24. useLayoutEffect(() => {
  25. echarts.connect(DDM_CHART_GROUP);
  26. }, [widgets]);
  27. const handleChange = useCallback(
  28. (index: number, widget: Partial<MetricWidgetQueryParams>) => {
  29. updateWidget(index, widget);
  30. },
  31. [updateWidget]
  32. );
  33. return (
  34. <Wrapper>
  35. {widgets.map((widget, index) => (
  36. <Row key={index} onFocusCapture={() => setSelectedWidgetIndex(index)}>
  37. <StyledQuerySymbol index={index} />
  38. <QueryBuilder
  39. onChange={data => handleChange(index, data)}
  40. metricsQuery={{
  41. mri: widget.mri,
  42. op: widget.op,
  43. groupBy: widget.groupBy,
  44. title: widget.title,
  45. }}
  46. displayType={widget.displayType}
  47. isEdit
  48. projects={selection.projects}
  49. />
  50. <MetricQueryContextMenu
  51. displayType={widget.displayType}
  52. widgetIndex={index}
  53. metricsQuery={{
  54. mri: widget.mri,
  55. query: widget.query,
  56. op: widget.op,
  57. groupBy: widget.groupBy,
  58. projects: selection.projects,
  59. datetime: selection.datetime,
  60. environments: selection.environments,
  61. title: widget.title,
  62. }}
  63. />
  64. </Row>
  65. ))}
  66. {/* placeholder for first grid column */}
  67. <div />
  68. <div>
  69. <Button
  70. disabled={hasEmptyWidget}
  71. icon={<IconAdd size="xs" isCircled />}
  72. onClick={() => {
  73. trackAnalytics('ddm.widget.add', {
  74. organization,
  75. });
  76. Sentry.metrics.increment('ddm.widget.add');
  77. addWidget();
  78. }}
  79. >
  80. {t('Add Query')}
  81. </Button>
  82. </div>
  83. </Wrapper>
  84. );
  85. }
  86. const StyledQuerySymbol = styled(QuerySymbol)`
  87. margin-top: 10px;
  88. `;
  89. const Wrapper = styled('div')`
  90. padding-bottom: ${space(2)};
  91. display: grid;
  92. grid-template-columns: min-content 1fr max-content;
  93. gap: ${space(1)};
  94. `;
  95. const Row = styled('div')`
  96. display: contents;
  97. `;