From 044ec4011251694089e23f5c8d9055e80a1cadfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 18 Mar 2020 13:00:14 +0100 Subject: [PATCH] Graphite: Rollup Indicator (#22738) * WIP: Rollup indiator progress * Progress * Progress, can now open inspector with right tab * changed type and made inspect * Showing stats * Progress * Progress * Getting ready for v1 * Added option and fixed some strict nulls * Updated * Fixed test --- packages/grafana-data/src/types/data.ts | 46 +++-- packages/grafana-data/src/types/panel.ts | 17 ++ packages/grafana-data/src/utils/datasource.ts | 7 +- .../src/components/BarGauge/BarGauge.tsx | 2 +- .../grafana-ui/src/components/Table/Table.tsx | 23 ++- .../components/Inspector/InspectHeader.tsx | 24 ++- .../components/Inspector/PanelInspector.tsx | 113 ++++++----- .../dashboard/dashgrid/DashboardPanel.tsx | 7 +- .../dashboard/dashgrid/PanelChrome.tsx | 10 +- .../dashboard/dashgrid/PanelChromeAngular.tsx | 10 +- .../dashgrid/PanelHeader/PanelHeader.tsx | 56 +++++- .../graphite/MetricTankMetaInspector.tsx | 179 ++++++++++++++---- .../configuration/ConfigEditor.styles.ts | 10 - .../graphite/configuration/ConfigEditor.tsx | 70 ++++--- .../plugins/datasource/graphite/datasource.ts | 54 +++++- .../app/plugins/datasource/graphite/meta.ts | 45 ++++- .../graphite/specs/datasource.test.ts | 98 ++++++++-- .../app/plugins/datasource/graphite/types.ts | 7 +- .../panel/bargauge/BarGaugePanel.test.tsx | 32 +++- .../plugins/panel/news/NewsPanelEditor.tsx | 44 +++-- public/app/plugins/panel/singlestat/module.ts | 3 +- 21 files changed, 631 insertions(+), 226 deletions(-) delete mode 100644 public/app/plugins/datasource/graphite/configuration/ConfigEditor.styles.ts diff --git a/packages/grafana-data/src/types/data.ts b/packages/grafana-data/src/types/data.ts index 0c2568a4b33..ecf355c8f43 100644 --- a/packages/grafana-data/src/types/data.ts +++ b/packages/grafana-data/src/types/data.ts @@ -1,3 +1,4 @@ +import { FieldConfig } from './dataFrame'; import { DataTransformerConfig } from './transformations'; import { ApplyFieldOverrideOptions } from './fieldOverrides'; @@ -15,19 +16,40 @@ export enum LoadingState { } export interface QueryResultMeta { - [key: string]: any; - - // Match the result to the query - requestId?: string; - - // Used in Explore for highlighting - searchWords?: string[]; - - // Used in Explore to show limit applied to search result - limit?: number; - - // DatasSource Specific Values + /** DatasSource Specific Values */ custom?: Record; + + /** Stats */ + stats?: QueryResultMetaStat[]; + + /** Meta Notices */ + notices?: QueryResultMetaNotice[]; + + /** Used to track transformation ids that where part of the processing */ + transformations?: string[]; + + /** + * Legacy data source specific, should be moved to custom + * */ + gmdMeta?: any[]; // used by cloudwatch + rawQuery?: string; // used by stackdriver + alignmentPeriod?: string; // used by stackdriver + query?: string; // used by azure log + searchWords?: string[]; // used by log models and loki + limit?: number; // used by log models and loki + json?: boolean; // used to keep track of old json doc values +} + +export interface QueryResultMetaStat extends FieldConfig { + title: string; + value: number; +} + +export interface QueryResultMetaNotice { + severity: 'info' | 'warning' | 'error'; + text: string; + url?: string; + inspect?: 'meta' | 'error' | 'data' | 'stats'; } export interface QueryResultBase { diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index c91afe8ba14..746dafc178f 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -16,14 +16,31 @@ export interface PanelPluginMeta extends PluginMeta { } export interface PanelData { + /** + * State of the data (loading, done, error, streaming) + */ state: LoadingState; + /** * Contains data frames with field overrides applied */ series: DataFrame[]; + + /** + * Request contains the queries and properties sent to the datasource + */ request?: DataQueryRequest; + + /** + * Timing measurements + */ timings?: DataQueryTimings; + + /** + * Any query errors + */ error?: DataQueryError; + /** * Contains the range from the request or a shifted time range if a request uses relative time */ diff --git a/packages/grafana-data/src/utils/datasource.ts b/packages/grafana-data/src/utils/datasource.ts index 07d26a24f09..6fad3a54b09 100644 --- a/packages/grafana-data/src/utils/datasource.ts +++ b/packages/grafana-data/src/utils/datasource.ts @@ -29,10 +29,9 @@ export const onUpdateDatasourceJsonDataOptionSelect = ( export const onUpdateDatasourceJsonDataOptionChecked = ( props: DataSourcePluginOptionsEditorProps, - key: K, - val: boolean -) => (event?: React.SyntheticEvent) => { - updateDatasourcePluginJsonDataOption(props, key, val); + key: K +) => (event: React.SyntheticEvent) => { + updateDatasourcePluginJsonDataOption(props, key, event.currentTarget.checked); }; export const onUpdateDatasourceSecureJsonDataOptionSelect = ( diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx index 0207411fb45..69829269864 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx @@ -37,7 +37,7 @@ export interface Props extends Themeable { height: number; width: number; field: FieldConfig; - display: DisplayProcessor; + display?: DisplayProcessor; value: DisplayValue; orientation: VizOrientation; itemSpacing?: number; diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index e6ee4dc1f0a..a5ad19da3c8 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -18,10 +18,11 @@ export interface Props { height: number; /** Minimal column width specified in pixels */ columnMinWidth?: number; + noHeader?: boolean; onCellClick?: TableFilterActionCallback; } -export const Table: FC = memo(({ data, height, onCellClick, width, columnMinWidth }) => { +export const Table: FC = memo(({ data, height, onCellClick, width, columnMinWidth, noHeader }) => { const theme = useTheme(); const [ref, headerRowMeasurements] = useMeasure(); const tableStyles = getTableStyles(theme); @@ -67,15 +68,17 @@ export const Table: FC = memo(({ data, height, onCellClick, width, column return (
-
- {headerGroups.map((headerGroup: any) => ( -
- {headerGroup.headers.map((column: any) => - renderHeaderCell(column, tableStyles.headerCell, data.fields[column.index]) - )} -
- ))} -
+ {!noHeader && ( +
+ {headerGroups.map((headerGroup: any) => ( +
+ {headerGroup.headers.map((column: any) => + renderHeaderCell(column, tableStyles.headerCell, data.fields[column.index]) + )} +
+ ))} +
+ )} ; - stats: { requestTime: number; queries: number; dataSources: number }; + panelData: PanelData; panel: PanelModel; isExpanded: boolean; - onSelectTab: (tab: SelectableValue) => void; onClose: () => void; onToggleExpand: () => void; @@ -24,7 +23,7 @@ export const InspectHeader: FC = ({ onClose, onToggleExpand, panel, - stats, + panelData, isExpanded, }) => { const theme = useTheme(); @@ -42,7 +41,7 @@ export const InspectHeader: FC = ({

{panel.title}

-
{formatStats(stats)}
+
{formatStats(panelData)}
{tabs.map((t, index) => { @@ -95,10 +94,15 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { }; }); -function formatStats(stats: { requestTime: number; queries: number; dataSources: number }) { - const queries = `${stats.queries} ${stats.queries === 1 ? 'query' : 'queries'}`; - const dataSources = `${stats.dataSources} ${stats.dataSources === 1 ? 'data source' : 'data sources'}`; - const requestTime = `${stats.requestTime === -1 ? 'N/A' : stats.requestTime}ms`; +function formatStats(panelData: PanelData) { + const { request } = panelData; + if (!request) { + return ''; + } - return `${queries} - ${dataSources} - ${requestTime}`; + const queryCount = request.targets.length; + const requestTime = request.endTime ? request.endTime - request.startTime : 0; + const formatted = formattedValueToString(getValueFormat('ms')(requestTime)); + + return `${queryCount} queries with total query time of ${formatted}`; } diff --git a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx index 3dd66406fd4..f1fcd5fd536 100644 --- a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx +++ b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx @@ -16,7 +16,9 @@ import { toCSV, DataQueryError, PanelData, - DataQuery, + getValueFormat, + formattedValueToString, + QueryResultMetaStat, } from '@grafana/data'; import { config } from 'app/core/config'; @@ -51,8 +53,6 @@ interface State { // If the datasource supports custom metadata metaDS?: DataSourceApi; - stats: { requestTime: number; queries: number; dataSources: number; processingTime: number }; - drawerWidth: string; } @@ -65,7 +65,6 @@ export class PanelInspector extends PureComponent { selected: 0, tab: props.selectedTab || InspectTab.Data, drawerWidth: '50%', - stats: { requestTime: 0, queries: 0, dataSources: 0, processingTime: 0 }, }; } @@ -89,23 +88,13 @@ export class PanelInspector extends PureComponent { const error = lastResult.error; const targets = lastResult.request?.targets || []; - const requestTime = lastResult.request?.endTime ? lastResult.request?.endTime - lastResult.request.startTime : -1; - const dataSources = new Set(targets.map(t => t.datasource)).size; - const processingTime = lastResult.timings?.dataProcessingTime || -1; // Find the first DataSource wanting to show custom metadata if (data && targets.length) { - const queries: Record = {}; - - for (const target of targets) { - queries[target.refId] = target; - } - for (const frame of data) { - const q = queries[frame.refId]; - - if (q && frame.meta && frame.meta.custom) { - const dataSource = await getDataSourceSrv().get(q.datasource); + if (frame.meta && frame.meta.custom) { + // get data source from first query + const dataSource = await getDataSourceSrv().get(targets[0].datasource); if (dataSource && dataSource.components?.MetadataInspector) { metaDS = dataSource; @@ -121,12 +110,6 @@ export class PanelInspector extends PureComponent { data, metaDS, tab: error ? InspectTab.Error : prevState.tab, - stats: { - requestTime, - queries: targets.length, - dataSources, - processingTime, - }, })); } @@ -184,7 +167,6 @@ export class PanelInspector extends PureComponent { }; }); - // Apply dummy styles const processed = applyFieldOverrides({ data, theme: config.theme, @@ -251,29 +233,66 @@ export class PanelInspector extends PureComponent { } renderStatsTab() { - const { stats } = this.state; + const { last } = this.state; + const { request } = last; + + if (!request) { + return null; + } + + let stats: QueryResultMetaStat[] = []; + + const requestTime = request.endTime ? request.endTime - request.startTime : -1; + const processingTime = last.timings?.dataProcessingTime || -1; + let dataRows = 0; + + for (const frame of last.series) { + dataRows += frame.length; + } + + stats.push({ title: 'Total request time', value: requestTime, unit: 'ms' }); + stats.push({ title: 'Data processing time', value: processingTime, unit: 'ms' }); + stats.push({ title: 'Number of queries', value: request.targets.length }); + stats.push({ title: 'Total number rows', value: dataRows }); + + let dataStats: QueryResultMetaStat[] = []; + + for (const series of last.series) { + if (series.meta && series.meta.stats) { + dataStats = dataStats.concat(series.meta.stats); + } + } + return ( - - - - - - - - - - - -
Query time{`${stats.requestTime === -1 ? 'N/A' : stats.requestTime + 'ms'}`}
Data processing time{`${ - stats.processingTime === -1 - ? 'N/A' - : Math.round((stats.processingTime + Number.EPSILON) * 100) / 100 + 'ms' - }`}
+ <> + {this.renderStatsTable('Stats', stats)} + {dataStats.length && this.renderStatsTable('Data source stats', dataStats)} + + ); + } + + renderStatsTable(name: string, stats: QueryResultMetaStat[]) { + return ( +
+
{name}
+ + + {stats.map(stat => { + return ( + + + + + ); + })} + +
{stat.title}{formatStat(stat.value, stat.unit)}
+
); } drawerHeader = () => { - const { tab, last, stats } = this.state; + const { tab, last } = this.state; const error = last?.error; const tabs = []; @@ -296,7 +315,7 @@ export class PanelInspector extends PureComponent { { } } +function formatStat(value: any, unit?: string): string { + if (unit) { + return formattedValueToString(getValueFormat(unit)(value)); + } else { + return value; + } +} + const getStyles = stylesFactory(() => { return { toolbar: css` diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 7439012da55..68d7852ead0 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -12,6 +12,7 @@ import { PanelChromeAngular } from './PanelChromeAngular'; // Actions import { initDashboardPanel } from '../state/actions'; +import { updateLocation } from 'app/core/reducers/location'; // Types import { PanelModel, DashboardModel } from '../state'; @@ -33,6 +34,7 @@ export interface ConnectedProps { export interface DispatchProps { initDashboardPanel: typeof initDashboardPanel; + updateLocation: typeof updateLocation; } export type Props = OwnProps & ConnectedProps & DispatchProps; @@ -72,7 +74,7 @@ export class DashboardPanelUnconnected extends PureComponent { }; renderPanel(plugin: PanelPlugin) { - const { dashboard, panel, isFullscreen, isInView, isInEditMode } = this.props; + const { dashboard, panel, isFullscreen, isInView, isInEditMode, updateLocation } = this.props; return ( @@ -105,6 +107,7 @@ export class DashboardPanelUnconnected extends PureComponent { isInEditMode={isInEditMode} width={width} height={height} + updateLocation={updateLocation} /> ); }} @@ -170,6 +173,6 @@ const mapStateToProps: MapStateToProps = ( }; }; -const mapDispatchToProps: MapDispatchToProps = { initDashboardPanel }; +const mapDispatchToProps: MapDispatchToProps = { initDashboardPanel, updateLocation }; export const DashboardPanel = connect(mapStateToProps, mapDispatchToProps)(DashboardPanelUnconnected); diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index ff59e4e129b..98d7ccd9aad 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -11,6 +11,7 @@ import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; import { profiler } from 'app/core/profiler'; import { getProcessedDataFrames } from '../state/runRequest'; import config from 'app/core/config'; +import { updateLocation } from 'app/core/actions'; // Types import { DashboardModel, PanelModel } from '../state'; import { PANEL_BORDER } from 'app/core/constants'; @@ -36,6 +37,7 @@ export interface Props { isInEditMode?: boolean; width: number; height: number; + updateLocation: typeof updateLocation; } export interface State { @@ -43,8 +45,6 @@ export interface State { renderCounter: number; errorMessage?: string; refreshWhenInView: boolean; - - // Current state of all events data: PanelData; } @@ -312,7 +312,7 @@ export class PanelChrome extends PureComponent { } render() { - const { dashboard, panel, isFullscreen, width, height } = this.props; + const { dashboard, panel, isFullscreen, width, height, updateLocation } = this.props; const { errorMessage, data } = this.state; const { transparent } = panel; @@ -328,14 +328,14 @@ export class PanelChrome extends PureComponent { {({ error }) => { diff --git a/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx b/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx index 730602270a9..1dd7bb66582 100644 --- a/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx @@ -17,6 +17,7 @@ import config from 'app/core/config'; import { DashboardModel, PanelModel } from '../state'; import { StoreState } from 'app/types'; import { LoadingState, DefaultTimeRange, PanelData, PanelPlugin, PanelEvents } from '@grafana/data'; +import { updateLocation } from 'app/core/actions'; import { PANEL_BORDER } from 'app/core/constants'; interface OwnProps { @@ -35,6 +36,7 @@ interface ConnectedProps { interface DispatchProps { setPanelAngularComponent: typeof setPanelAngularComponent; + updateLocation: typeof updateLocation; } export type Props = OwnProps & ConnectedProps & DispatchProps; @@ -215,7 +217,7 @@ export class PanelChromeAngularUnconnected extends PureComponent { } render() { - const { dashboard, panel, isFullscreen, plugin, angularComponent } = this.props; + const { dashboard, panel, isFullscreen, plugin, angularComponent, updateLocation } = this.props; const { errorMessage, data, alertState } = this.state; const { transparent } = panel; @@ -238,7 +240,6 @@ export class PanelChromeAngularUnconnected extends PureComponent { { links={panel.links} error={errorMessage} isFullscreen={isFullscreen} - isLoading={data.state === LoadingState.Loading} + data={data} + updateLocation={updateLocation} />
(this.element = element)} className="panel-height-helper" /> @@ -262,6 +264,6 @@ const mapStateToProps: MapStateToProps = ( }; }; -const mapDispatchToProps: MapDispatchToProps = { setPanelAngularComponent }; +const mapDispatchToProps: MapDispatchToProps = { setPanelAngularComponent, updateLocation }; export const PanelChromeAngular = connect(mapStateToProps, mapDispatchToProps)(PanelChromeAngularUnconnected); diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx index 72f411ea079..2859481cfd0 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx @@ -1,9 +1,9 @@ import React, { Component } from 'react'; import classNames from 'classnames'; import { isEqual } from 'lodash'; -import { DataLink, ScopedVars, PanelMenuItem } from '@grafana/data'; +import { DataLink, ScopedVars, PanelMenuItem, PanelData, LoadingState, QueryResultMetaNotice } from '@grafana/data'; import { AngularComponent } from '@grafana/runtime'; -import { ClickOutsideWrapper } from '@grafana/ui'; +import { ClickOutsideWrapper, Tooltip } from '@grafana/ui'; import { e2e } from '@grafana/e2e'; import PanelHeaderCorner from './PanelHeaderCorner'; @@ -14,11 +14,11 @@ import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers'; import { getPanelMenu } from 'app/features/dashboard/utils/getPanelMenu'; +import { updateLocation } from 'app/core/actions'; export interface Props { panel: PanelModel; dashboard: DashboardModel; - timeInfo?: string; title?: string; description?: string; scopedVars?: ScopedVars; @@ -26,7 +26,8 @@ export interface Props { links?: DataLink[]; error?: string; isFullscreen: boolean; - isLoading: boolean; + data: PanelData; + updateLocation: typeof updateLocation; } interface ClickCoordinates { @@ -92,8 +93,35 @@ export class PanelHeader extends Component { ); } + openInspect = (e: React.SyntheticEvent, tab: string) => { + const { updateLocation, panel } = this.props; + + e.stopPropagation(); + + updateLocation({ + query: { inspect: panel.id, tab }, + partial: true, + }); + }; + + renderNotice = (notice: QueryResultMetaNotice) => { + return ( + + {notice.inspect ? ( +
this.openInspect(e, notice.inspect)}> + +
+ ) : ( + + + + )} +
+ ); + }; + render() { - const { panel, timeInfo, scopedVars, error, isFullscreen, isLoading } = this.props; + const { panel, scopedVars, error, isFullscreen, data } = this.props; const { menuItems } = this.state; const title = templateSrv.replaceWithText(panel.title, scopedVars); @@ -102,9 +130,20 @@ export class PanelHeader extends Component { 'grid-drag-handle': !isFullscreen, }); + // dedupe on severity + const notices: Record = {}; + + for (const series of data.series) { + if (series.meta && series.meta.notices) { + for (const notice of series.meta.notices) { + notices[notice.severity] = notice; + } + } + } + return ( <> - {isLoading && this.renderLoadingState()} + {data.state === LoadingState.Loading && this.renderLoadingState()}
{ aria-label={e2e.pages.Dashboard.Panels.Panel.selectors.title(title)} >
+ {Object.values(notices).map(this.renderNotice)} {title} @@ -130,9 +170,9 @@ export class PanelHeader extends Component { )} - {timeInfo && ( + {data.request && data.request.timeInfo && ( - {timeInfo} + {data.request.timeInfo} )}
diff --git a/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx b/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx index 0842f2cb73e..85db719847b 100644 --- a/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx +++ b/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx @@ -1,8 +1,12 @@ +import { css, cx } from 'emotion'; import React, { PureComponent } from 'react'; -import { MetadataInspectorProps, DataFrame } from '@grafana/data'; +import { MetadataInspectorProps } from '@grafana/data'; import { GraphiteDatasource } from './datasource'; -import { GraphiteQuery, GraphiteOptions, MetricTankMeta, MetricTankResultMeta } from './types'; -import { parseSchemaRetentions } from './meta'; +import { GraphiteQuery, GraphiteOptions, MetricTankSeriesMeta } from './types'; +import { parseSchemaRetentions, getRollupNotice, getRuntimeConsolidationNotice } from './meta'; +import { stylesFactory } from '@grafana/ui'; +import { config } from 'app/core/config'; +import kbn from 'app/core/utils/kbn'; export type Props = MetadataInspectorProps; @@ -11,48 +15,159 @@ export interface State { } export class MetricTankMetaInspector extends PureComponent { - state = { index: 0 }; + renderMeta(meta: MetricTankSeriesMeta, key: string) { + const styles = getStyles(); + const buckets = parseSchemaRetentions(meta['schema-retentions']); + const rollupNotice = getRollupNotice([meta]); + const runtimeNotice = getRuntimeConsolidationNotice([meta]); + const normFunc = (meta['consolidate-normfetch'] || '').replace('Consolidator', ''); + + let totalSeconds = 0; + + for (const bucket of buckets) { + totalSeconds += kbn.interval_to_seconds(bucket.retention); + } - renderInfo = (info: MetricTankResultMeta, frame: DataFrame) => { - const buckets = parseSchemaRetentions(info['schema-retentions']); return ( -
-

Info

- - - {buckets.map(row => ( - - - - - - - - ))} - -
{row.interval}  {row.retention}  {row.chunkspan}  {row.numchunks}  {row.ready}  
-
{JSON.stringify(info, null, 2)}
+
+
Schema: {meta['schema-name']}
+
+
+
Step 1: Fetch
+
+ First data is fetched, either from raw data archive or a rollup archive +
+ + {rollupNotice &&

{rollupNotice.text}

} + {!rollupNotice &&

No rollup archive was used

} + +
+ {buckets.map((bucket, index) => { + const bucketLength = kbn.interval_to_seconds(bucket.retention); + const lengthPercent = (bucketLength / totalSeconds) * 100; + const isActive = index === meta['archive-read']; + + return ( +
+
{bucket.interval}
+
+
{bucket.retention}
+
+ ); + })} +
+
+ +
+
Step 2: Normalization
+
+ Normalization happens when series with different intervals between points are combined. +
+ + {meta['aggnum-norm'] > 1 &&

Normalization did occur using {normFunc}

} + {meta['aggnum-norm'] === 1 &&

No normalization was needed

} +
+ +
+
Step 3: Runtime consolidation
+
+ If there are too many data points at this point Metrictank will consolidate them down to below max data + points (set in queries tab). +
+ + {runtimeNotice &&

{runtimeNotice.text}

} + {!runtimeNotice &&

No runtime consolidation

} +
+
); - }; + } render() { const { data } = this.props; - if (!data || !data.length) { - return
No Metadata
; + + // away to dedupe them + const seriesMetas: Record = {}; + + for (const series of data) { + if (series.meta && series.meta.custom) { + for (const metaItem of series.meta.custom.seriesMetaList as MetricTankSeriesMeta[]) { + // key is to dedupe as many series will have identitical meta + const key = `${metaItem['schema-name']}-${metaItem['archive-read']}`; + seriesMetas[key] = metaItem; + } + } } - const frame = data[this.state.index]; - const meta = frame.meta?.custom as MetricTankMeta; - if (!meta || !meta.info) { - return <>No Metadatata on DataFrame; + if (Object.keys(seriesMetas).length === 0) { + return
No response meta data
; } + return (
-

MetricTank Request

-
{JSON.stringify(meta.request, null, 2)}
- {meta.info.map(info => this.renderInfo(info, frame))} +

Aggregation & rollup

+ {Object.keys(seriesMetas).map(key => this.renderMeta(seriesMetas[key], key))}
); } } + +const getStyles = stylesFactory(() => { + const { theme } = config; + const borderColor = theme.isDark ? theme.colors.gray25 : theme.colors.gray85; + const background = theme.isDark ? theme.colors.dark1 : theme.colors.white; + const headerBg = theme.isDark ? theme.colors.gray15 : theme.colors.gray85; + + return { + metaItem: css` + background: ${background}; + border: 1px solid ${borderColor}; + margin-bottom: ${theme.spacing.md}; + `, + metaItemHeader: css` + background: ${headerBg}; + padding: ${theme.spacing.xs} ${theme.spacing.md}; + font-size: ${theme.typography.size.md}; + `, + metaItemBody: css` + padding: ${theme.spacing.md}; + `, + stepHeading: css` + font-size: ${theme.typography.size.md}; + `, + stepDescription: css` + font-size: ${theme.typography.size.sm}; + color: ${theme.colors.textWeak}; + margin-bottom: ${theme.spacing.sm}; + `, + step: css` + margin-bottom: ${theme.spacing.lg}; + + &:last-child { + margin-bottom: 0; + } + `, + bucket: css` + display: flex; + margin-bottom: ${theme.spacing.sm}; + border-radius: ${theme.border.radius.md}; + `, + bucketInterval: css` + flex-grow: 0; + width: 60px; + `, + bucketRetention: css` + background: linear-gradient(0deg, ${theme.colors.blue85}, ${theme.colors.blue95}); + text-align: center; + color: ${theme.colors.white}; + margin-right: ${theme.spacing.md}; + border-radius: ${theme.border.radius.md}; + `, + bucketRetentionActive: css` + background: linear-gradient(0deg, ${theme.colors.greenBase}, ${theme.colors.greenShade}); + `, + }; +}); diff --git a/public/app/plugins/datasource/graphite/configuration/ConfigEditor.styles.ts b/public/app/plugins/datasource/graphite/configuration/ConfigEditor.styles.ts deleted file mode 100644 index be9256dccdc..00000000000 --- a/public/app/plugins/datasource/graphite/configuration/ConfigEditor.styles.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { css } from 'emotion'; - -const styles = { - helpbtn: css` - margin-left: 8px; - margin-top: 5px; - `, -}; - -export default styles; diff --git a/public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx index 265185cd4be..c04a990475e 100644 --- a/public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx @@ -1,8 +1,11 @@ import React, { PureComponent } from 'react'; -import { DataSourceHttpSettings, FormLabel, Button, Select } from '@grafana/ui'; -import { DataSourcePluginOptionsEditorProps, onUpdateDatasourceJsonDataOptionSelect } from '@grafana/data'; +import { DataSourceHttpSettings, FormLabel, Select, Switch } from '@grafana/ui'; +import { + DataSourcePluginOptionsEditorProps, + onUpdateDatasourceJsonDataOptionSelect, + onUpdateDatasourceJsonDataOptionChecked, +} from '@grafana/data'; import { GraphiteOptions, GraphiteType } from '../types'; -import styles from './ConfigEditor.styles'; const graphiteVersions = [ { label: '0.9.x', value: '0.9' }, @@ -17,22 +20,27 @@ const graphiteTypes = Object.entries(GraphiteType).map(([label, value]) => ({ export type Props = DataSourcePluginOptionsEditorProps; -interface State { - showMetricTankHelp: boolean; -} - -export class ConfigEditor extends PureComponent { +export class ConfigEditor extends PureComponent { constructor(props: Props) { super(props); - - this.state = { - showMetricTankHelp: false, - }; } + renderTypeHelp = () => { + return ( +

+ There are different types of Graphite compatible backends. Here you can specify the type you are using. If you + are using{' '} + + Metrictank + {' '} + then select that here. This will enable Metrictank specific features like query processing meta data. Metrictank + is a multi-tenant timeseries engine for Graphite and friends. +

+ ); + }; + render() { const { options, onOptionsChange } = this.props; - const { showMetricTankHelp } = this.state; const currentVersion = graphiteVersions.find(item => item.value === options.jsonData.graphiteVersion) ?? graphiteVersions[2]; @@ -61,39 +69,25 @@ export class ConfigEditor extends PureComponent {
- Type + Type