DataSourceInsights.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import { cx } from '@emotion/css';
  2. import React, { PureComponent } from 'react';
  3. import { connect, ConnectedProps } from 'react-redux';
  4. import AutoSizer from 'react-virtualized-auto-sizer';
  5. import { ArrayVector, DataFrame, dateTime, FieldType, TimeRange } from '@grafana/data';
  6. import { featureEnabled } from '@grafana/runtime';
  7. import { LegendDisplayMode, TooltipDisplayMode } from '@grafana/schema';
  8. import { Graph, InfoBox, Themeable2, VizTooltip, withTheme2 } from '@grafana/ui';
  9. import PageHeader from 'app/core/components/PageHeader/PageHeader';
  10. import { UpgradeBox } from 'app/core/components/Upgrade/UpgradeBox';
  11. import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
  12. import { getNavModel } from 'app/core/selectors/navModel';
  13. import { highlightTrial } from 'app/features/admin/utils';
  14. import { loadDataSource, loadDataSourceMeta } from 'app/features/datasources/state/actions';
  15. import { getDataSourceLoadingNav } from 'app/features/datasources/state/navModel';
  16. import { EnterpriseStoreState } from '../../types';
  17. import { SeriesOptions } from '../../types/flotgraph';
  18. import { getGraphSeriesModel } from '../GraphSeriesModel';
  19. import { DAILY_SUMMARY_DATE_FORMAT, DataSourceDailySummaryDTO, getDataSourceDailySummaries } from '../api';
  20. import { getInsightsStyles, InsightsStyles } from '../styles';
  21. interface RouteProps extends GrafanaRouteComponentProps<{ uid: string }> {}
  22. const mapStateToProps = (state: EnterpriseStoreState, props: RouteProps) => {
  23. const dataSourceUid = props.match.params.uid;
  24. const dataSourceLoadingNav = getDataSourceLoadingNav('insights');
  25. return {
  26. navModel: getNavModel(state.navIndex, `datasource-insights-${dataSourceUid}`, dataSourceLoadingNav),
  27. dataSourceUid,
  28. };
  29. };
  30. const mapDispatchToProps = {
  31. loadDataSource,
  32. loadDataSourceMeta,
  33. };
  34. export const connector = connect(mapStateToProps, mapDispatchToProps);
  35. export type Props = Themeable2 & ConnectedProps<typeof connector>;
  36. interface State {
  37. dailySummaries: DataSourceDailySummaryDTO[];
  38. from: string;
  39. to: string;
  40. }
  41. interface ChartConfig {
  42. title: string | JSX.Element;
  43. valueField: keyof DataSourceDailySummaryDTO;
  44. fieldType: FieldType;
  45. width: number;
  46. timeRange: TimeRange;
  47. color: string;
  48. showBars: boolean;
  49. showLines: boolean;
  50. }
  51. class DataSourceInsights extends PureComponent<Props, State> {
  52. constructor(props: Props) {
  53. super(props);
  54. this.state = {
  55. dailySummaries: [],
  56. from: '',
  57. to: '',
  58. };
  59. }
  60. async componentDidMount(): Promise<void> {
  61. const { dataSourceUid, loadDataSource, loadDataSourceMeta } = this.props;
  62. loadDataSource(dataSourceUid).then(loadDataSourceMeta);
  63. if (featureEnabled('analytics')) {
  64. let from = dateTime().subtract(30, 'days').format(DAILY_SUMMARY_DATE_FORMAT);
  65. let to = dateTime().format(DAILY_SUMMARY_DATE_FORMAT);
  66. const dailySummaries = await getDataSourceDailySummaries(dataSourceUid, from, to);
  67. this.setState({ dailySummaries, from, to });
  68. }
  69. }
  70. buildTimeRange(): TimeRange {
  71. const { from, to } = this.state;
  72. const timeRangeFrom = dateTime(from);
  73. const timeRangeTo = dateTime(to).add(24, 'hours');
  74. return {
  75. from: timeRangeFrom,
  76. to: timeRangeTo,
  77. raw: { from, to },
  78. };
  79. }
  80. convertDailySummariesToDataFrame(
  81. data: DataSourceDailySummaryDTO[],
  82. valueField: keyof DataSourceDailySummaryDTO,
  83. valueFieldType: FieldType
  84. ): DataFrame {
  85. const time = new ArrayVector<number>([]);
  86. const values = new ArrayVector<any>([]);
  87. data.forEach((dailySummary) => {
  88. time.buffer.push(dateTime(dailySummary.day, DAILY_SUMMARY_DATE_FORMAT).valueOf());
  89. let value = dailySummary[valueField];
  90. if (valueField === 'loadDuration') {
  91. value = dailySummary.queries ? dailySummary.loadDuration / (dailySummary.queries * 1000000) : 0;
  92. }
  93. values.buffer.push(value);
  94. });
  95. return {
  96. name: valueField,
  97. fields: [
  98. { name: 'Time', type: FieldType.time, config: {}, values: time },
  99. { name: valueField, type: valueFieldType, config: {}, values: values },
  100. ],
  101. length: data.length,
  102. };
  103. }
  104. renderChart(config: ChartConfig, styles: InsightsStyles) {
  105. const { dailySummaries } = this.state;
  106. const { color, fieldType, showBars, showLines, timeRange, title, valueField, width } = config;
  107. let dataFrame = this.convertDailySummariesToDataFrame(dailySummaries, valueField, fieldType);
  108. const seriesOptions: SeriesOptions = { [valueField]: { color: color } };
  109. const series = getGraphSeriesModel(
  110. [dataFrame],
  111. 'browser',
  112. seriesOptions,
  113. { showBars: showBars, showLines: showLines, showPoints: false },
  114. { placement: 'bottom', displayMode: LegendDisplayMode.Hidden }
  115. );
  116. return (
  117. <div
  118. className={cx(styles.graphContainer, 'panel-container')}
  119. aria-label="Graph container"
  120. key={config.valueField}
  121. >
  122. <div className="panel-title">{title}</div>
  123. <div className={cx('panel-content', styles.panelContent)}>
  124. <Graph
  125. height={150}
  126. width={width}
  127. timeRange={timeRange}
  128. showBars={showBars}
  129. showLines={showLines}
  130. showPoints={false}
  131. series={series}
  132. timeZone="browser"
  133. >
  134. <VizTooltip mode={TooltipDisplayMode.Multi} />
  135. </Graph>
  136. </div>
  137. </div>
  138. );
  139. }
  140. renderContent() {
  141. const { theme } = this.props;
  142. const styles = getInsightsStyles(theme);
  143. const { dailySummaries } = this.state;
  144. const timeRange = this.buildTimeRange();
  145. return dailySummaries?.length > 0 ? (
  146. <AutoSizer disableHeight>
  147. {({ width }) => {
  148. const charts: ChartConfig[] = [
  149. {
  150. title: 'Queries last 30 days',
  151. valueField: 'queries',
  152. fieldType: FieldType.number,
  153. width,
  154. timeRange,
  155. color: '',
  156. showBars: true,
  157. showLines: false,
  158. },
  159. {
  160. title: 'Errors last 30 days',
  161. valueField: 'errors',
  162. fieldType: FieldType.number,
  163. width,
  164. timeRange,
  165. color: theme.colors.error.border,
  166. showBars: true,
  167. showLines: false,
  168. },
  169. {
  170. title: 'Average load duration last 30 days (ms)',
  171. valueField: 'loadDuration',
  172. fieldType: FieldType.number,
  173. width,
  174. timeRange,
  175. color: theme.colors.primary.border,
  176. showBars: true,
  177. showLines: false,
  178. },
  179. ];
  180. return <main style={{ width }}>{charts.map((chart) => this.renderChart(chart, styles))}</main>;
  181. }}
  182. </AutoSizer>
  183. ) : (
  184. <span>No available data for this data source.</span>
  185. );
  186. }
  187. render() {
  188. const { navModel } = this.props;
  189. return (
  190. <>
  191. <PageHeader model={navModel} />
  192. <div className="page-container page-body">
  193. {featureEnabled('analytics.writers') && !featureEnabled('analytics') ? (
  194. <InfoBox
  195. title="Feature not available with an expired license"
  196. url="https://grafana.com/docs/grafana/latest/enterprise/license-expiration/"
  197. urlTitle="Read more on license expiration"
  198. >
  199. <span>
  200. Data source insights are not available with an expired license. Data will continue to be collected but
  201. you need to update your license to see this page.
  202. </span>
  203. </InfoBox>
  204. ) : (
  205. <>
  206. {highlightTrial() && (
  207. <UpgradeBox
  208. featureId={'data-source-insights'}
  209. eventVariant={'trial'}
  210. featureName={'data source usage insights'}
  211. text={'Enable data source usage insights for free during your trial of Grafana Pro.'}
  212. />
  213. )}
  214. {this.renderContent()}
  215. </>
  216. )}
  217. </div>
  218. </>
  219. );
  220. }
  221. }
  222. export default connector(withTheme2(DataSourceInsights));