RuleViewerVisualization.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { css } from '@emotion/css';
  2. import React, { useCallback, useState } from 'react';
  3. import AutoSizer from 'react-virtualized-auto-sizer';
  4. import { DataSourceInstanceSettings, DateTime, dateTime, GrafanaTheme2, PanelData, urlUtil } from '@grafana/data';
  5. import { config, getDataSourceSrv, PanelRenderer } from '@grafana/runtime';
  6. import { Alert, CodeEditor, DateTimePicker, LinkButton, useStyles2, useTheme2 } from '@grafana/ui';
  7. import { isExpressionQuery } from 'app/features/expressions/guards';
  8. import { PanelOptions } from 'app/plugins/panel/table/models.gen';
  9. import { AccessControlAction } from 'app/types';
  10. import { AlertQuery } from 'app/types/unified-alerting-dto';
  11. import { TABLE, TIMESERIES } from '../../utils/constants';
  12. import { Authorize } from '../Authorize';
  13. import { PanelPluginsButtonGroup, SupportedPanelPlugins } from '../PanelPluginsButtonGroup';
  14. type RuleViewerVisualizationProps = {
  15. data?: PanelData;
  16. query: AlertQuery;
  17. onChangeQuery: (query: AlertQuery) => void;
  18. };
  19. const headerHeight = 4;
  20. export function RuleViewerVisualization(props: RuleViewerVisualizationProps): JSX.Element | null {
  21. const theme = useTheme2();
  22. const styles = useStyles2(getStyles);
  23. const { data, query, onChangeQuery } = props;
  24. const defaultPanel = isExpressionQuery(query.model) ? TABLE : TIMESERIES;
  25. const [panel, setPanel] = useState<SupportedPanelPlugins>(defaultPanel);
  26. const dsSettings = getDataSourceSrv().getInstanceSettings(query.datasourceUid);
  27. const relativeTimeRange = query.relativeTimeRange;
  28. const [options, setOptions] = useState<PanelOptions>({
  29. frameIndex: 0,
  30. showHeader: true,
  31. });
  32. const onTimeChange = useCallback(
  33. (newDateTime: DateTime) => {
  34. const now = dateTime().unix() - newDateTime.unix();
  35. if (relativeTimeRange) {
  36. const interval = relativeTimeRange.from - relativeTimeRange.to;
  37. onChangeQuery({
  38. ...query,
  39. relativeTimeRange: { from: now + interval, to: now },
  40. });
  41. }
  42. },
  43. [onChangeQuery, query, relativeTimeRange]
  44. );
  45. const setDateTime = useCallback((relativeTimeRangeTo: number) => {
  46. return relativeTimeRangeTo === 0 ? dateTime() : dateTime().subtract(relativeTimeRangeTo, 'seconds');
  47. }, []);
  48. if (!data) {
  49. return null;
  50. }
  51. if (!dsSettings) {
  52. return (
  53. <div className={styles.content}>
  54. <Alert title="Could not find datasource for query" />
  55. <CodeEditor
  56. width="100%"
  57. height="250px"
  58. language="json"
  59. showLineNumbers={false}
  60. showMiniMap={false}
  61. value={JSON.stringify(query, null, '\t')}
  62. readOnly={true}
  63. />
  64. </div>
  65. );
  66. }
  67. return (
  68. <div className={styles.content}>
  69. <AutoSizer>
  70. {({ width, height }) => {
  71. return (
  72. <div style={{ width, height }}>
  73. <div className={styles.header}>
  74. <div>
  75. {`Query ${query.refId}`}
  76. <span className={styles.dataSource}>({dsSettings.name})</span>
  77. </div>
  78. <div className={styles.actions}>
  79. {!isExpressionQuery(query.model) && relativeTimeRange ? (
  80. <DateTimePicker
  81. date={setDateTime(relativeTimeRange.to)}
  82. onChange={onTimeChange}
  83. maxDate={new Date()}
  84. />
  85. ) : null}
  86. <PanelPluginsButtonGroup onChange={setPanel} value={panel} size="md" />
  87. <Authorize actions={[AccessControlAction.DataSourcesExplore]}>
  88. {!isExpressionQuery(query.model) && (
  89. <>
  90. <div className={styles.spacing} />
  91. <LinkButton
  92. size="md"
  93. variant="secondary"
  94. icon="compass"
  95. target="_blank"
  96. href={createExploreLink(dsSettings, query)}
  97. >
  98. View in Explore
  99. </LinkButton>
  100. </>
  101. )}
  102. </Authorize>
  103. </div>
  104. </div>
  105. <PanelRenderer
  106. height={height - theme.spacing.gridSize * headerHeight}
  107. width={width}
  108. data={data}
  109. pluginId={panel}
  110. title=""
  111. onOptionsChange={setOptions}
  112. options={options}
  113. />
  114. </div>
  115. );
  116. }}
  117. </AutoSizer>
  118. </div>
  119. );
  120. }
  121. function createExploreLink(settings: DataSourceInstanceSettings, query: AlertQuery): string {
  122. const { name } = settings;
  123. const { refId, ...rest } = query.model;
  124. const queryParams = { ...rest, datasource: name };
  125. return urlUtil.renderUrl(`${config.appSubUrl}/explore`, {
  126. left: JSON.stringify(['now-1h', 'now', name, queryParams]),
  127. });
  128. }
  129. const getStyles = (theme: GrafanaTheme2) => {
  130. return {
  131. content: css`
  132. width: 100%;
  133. height: 250px;
  134. `,
  135. header: css`
  136. height: ${theme.spacing(headerHeight)};
  137. display: flex;
  138. align-items: center;
  139. justify-content: space-between;
  140. white-space: nowrap;
  141. `,
  142. refId: css`
  143. font-weight: ${theme.typography.fontWeightMedium};
  144. color: ${theme.colors.text.link};
  145. overflow: hidden;
  146. `,
  147. dataSource: css`
  148. margin-left: ${theme.spacing(1)};
  149. font-style: italic;
  150. color: ${theme.colors.text.secondary};
  151. `,
  152. actions: css`
  153. display: flex;
  154. align-items: center;
  155. `,
  156. spacing: css`
  157. padding: ${theme.spacing(0, 1, 0, 0)};
  158. `,
  159. errorMessage: css`
  160. white-space: pre-wrap;
  161. `,
  162. };
  163. };