dashboardWidgetQuerySelectorModal.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import * as React from 'react';
  2. import {Link} from 'react-router';
  3. import {css} from '@emotion/react';
  4. import styled from '@emotion/styled';
  5. import {ModalRenderProps} from 'sentry/actionCreators/modal';
  6. import {Client} from 'sentry/api';
  7. import Button from 'sentry/components/button';
  8. import Input from 'sentry/components/forms/controls/input';
  9. import {IconChevron, IconSearch} from 'sentry/icons';
  10. import {t} from 'sentry/locale';
  11. import space from 'sentry/styles/space';
  12. import {Organization, PageFilters} from 'sentry/types';
  13. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  14. import {DisplayModes} from 'sentry/utils/discover/types';
  15. import withApi from 'sentry/utils/withApi';
  16. import withPageFilters from 'sentry/utils/withPageFilters';
  17. import {Widget} from 'sentry/views/dashboardsV2/types';
  18. import {eventViewFromWidget} from 'sentry/views/dashboardsV2/utils';
  19. import {DisplayType} from 'sentry/views/dashboardsV2/widgetBuilder/utils';
  20. export type DashboardWidgetQuerySelectorModalOptions = {
  21. organization: Organization;
  22. widget: Widget;
  23. };
  24. type Props = ModalRenderProps &
  25. DashboardWidgetQuerySelectorModalOptions & {
  26. api: Client;
  27. organization: Organization;
  28. selection: PageFilters;
  29. };
  30. class DashboardWidgetQuerySelectorModal extends React.Component<Props> {
  31. renderQueries() {
  32. const {organization, widget, selection} = this.props;
  33. const querySearchBars = widget.queries.map((query, index) => {
  34. const eventView = eventViewFromWidget(
  35. widget.title,
  36. query,
  37. selection,
  38. widget.displayType
  39. );
  40. const discoverLocation = eventView.getResultsViewUrlTarget(organization.slug);
  41. // Pull a max of 3 valid Y-Axis from the widget
  42. const yAxisOptions = eventView.getYAxisOptions().map(({value}) => value);
  43. discoverLocation.query.yAxis = [
  44. ...new Set(query.fields.filter(field => yAxisOptions.includes(field))),
  45. ].slice(0, 3);
  46. switch (widget.displayType) {
  47. case DisplayType.BAR:
  48. discoverLocation.query.display = DisplayModes.BAR;
  49. break;
  50. default:
  51. break;
  52. }
  53. return (
  54. <React.Fragment key={index}>
  55. <QueryContainer>
  56. <Container>
  57. <SearchLabel htmlFor="smart-search-input" aria-label={t('Search events')}>
  58. <IconSearch />
  59. </SearchLabel>
  60. <StyledInput value={query.conditions} disabled />
  61. </Container>
  62. <Link to={discoverLocation}>
  63. <OpenInDiscoverButton
  64. priority="primary"
  65. icon={<IconChevron size="xs" direction="right" />}
  66. onClick={() => {
  67. trackAdvancedAnalyticsEvent(
  68. 'dashboards_views.query_selector.selected',
  69. {
  70. organization,
  71. widget_type: widget.displayType,
  72. }
  73. );
  74. }}
  75. aria-label={t('Open in Discover')}
  76. />
  77. </Link>
  78. </QueryContainer>
  79. </React.Fragment>
  80. );
  81. });
  82. return querySearchBars;
  83. }
  84. render() {
  85. const {Body, Header, widget} = this.props;
  86. return (
  87. <React.Fragment>
  88. <Header closeButton>
  89. <h4>{widget.title}</h4>
  90. </Header>
  91. <Body>
  92. <p>
  93. {t(
  94. 'Multiple queries were used to create this widget visualization. Which query would you like to view in Discover?'
  95. )}
  96. </p>
  97. {this.renderQueries()}
  98. </Body>
  99. </React.Fragment>
  100. );
  101. }
  102. }
  103. const StyledInput = styled(Input)`
  104. text-overflow: ellipsis;
  105. padding: 0px;
  106. box-shadow: none;
  107. height: auto;
  108. &:disabled {
  109. border: none;
  110. cursor: default;
  111. }
  112. `;
  113. const QueryContainer = styled('div')`
  114. display: flex;
  115. margin-bottom: ${space(1)};
  116. `;
  117. const OpenInDiscoverButton = styled(Button)`
  118. margin-left: ${space(1)};
  119. `;
  120. const Container = styled('div')`
  121. border: 1px solid ${p => p.theme.border};
  122. box-shadow: inset ${p => p.theme.dropShadowLight};
  123. background: ${p => p.theme.backgroundSecondary};
  124. padding: 7px ${space(1)};
  125. position: relative;
  126. display: grid;
  127. grid-template-columns: max-content 1fr max-content;
  128. gap: ${space(1)};
  129. align-items: start;
  130. flex-grow: 1;
  131. border-radius: ${p => p.theme.borderRadius};
  132. `;
  133. const SearchLabel = styled('label')`
  134. display: flex;
  135. padding: ${space(0.5)} 0;
  136. margin: 0;
  137. color: ${p => p.theme.gray300};
  138. `;
  139. export const modalCss = css`
  140. width: 100%;
  141. max-width: 700px;
  142. margin: 70px auto;
  143. `;
  144. export default withApi(withPageFilters(DashboardWidgetQuerySelectorModal));