mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DataLinks: enable data links in Gauge, BarGauge and SingleStat2 panel (#18605)
* datalink on field * add dataFrame to view * Use scoped variables to pass series name and value time to data links interpolation * Use scoped variables to pass series name and value time to data links interpolation * Enable value specific variable suggestions when Gauge is displaying values * Fix prettier * Add basic context menu with data links to GaugePanel * Fix incorrect import in grafana/ui * Add custom cursor indicating datalinks available via context menu (in Gauge only now) * Add data links to SingleStat2 * Minor refactor * Retrieve data links in a lazy way * Update test to respect links retrieval being lazy * delay link creation * cleanup * Add origin to LinkModel and introduce field & panel links suppliers * Add value time and series name field link supplier * Remove links prop from visualization and implement common UI for data links context menu * Update snapshot * Rename className prop to clickTargetClassName * Simplify condition * Updated drilldown dashboard and minor changes * Use class name an onClick handler on the top level dom element in visualization * Enable series name interpolation when presented value is a calculation
This commit is contained in:
committed by
Dominik Prokop
parent
e1924608a2
commit
ff6b8c5adc
@@ -26,6 +26,8 @@ export interface Props extends Themeable {
|
||||
orientation: VizOrientation;
|
||||
itemSpacing?: number;
|
||||
displayMode: 'basic' | 'lcd' | 'gradient';
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export class BarGauge extends PureComponent<Props> {
|
||||
@@ -43,16 +45,20 @@ export class BarGauge extends PureComponent<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onClick, className } = this.props;
|
||||
const { title } = this.props.value;
|
||||
|
||||
if (!title) {
|
||||
return this.renderBarAndValue();
|
||||
}
|
||||
|
||||
const styles = getTitleStyles(this.props);
|
||||
|
||||
if (!title) {
|
||||
return (
|
||||
<div style={styles.wrapper} onClick={onClick} className={className}>
|
||||
{this.renderBarAndValue()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.wrapper}>
|
||||
<div style={styles.wrapper} onClick={onClick} className={className}>
|
||||
<div style={styles.title}>{title}</div>
|
||||
{this.renderBarAndValue()}
|
||||
</div>
|
||||
|
||||
@@ -4,41 +4,51 @@ exports[`BarGauge Render with basic options should render 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
"flexDirection": "row-reverse",
|
||||
"justifyContent": "flex-end",
|
||||
"flexDirection": "column",
|
||||
"overflow": "hidden",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="bar-gauge__value"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"color": "#73BF69",
|
||||
"display": "flex",
|
||||
"fontSize": "27.2727px",
|
||||
"height": "300px",
|
||||
"lineHeight": 1,
|
||||
"paddingLeft": "10px",
|
||||
"width": "60px",
|
||||
"flexDirection": "row-reverse",
|
||||
"justifyContent": "flex-end",
|
||||
}
|
||||
}
|
||||
>
|
||||
25
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"background": "rgba(115, 191, 105, 0.25)",
|
||||
"borderRadius": "3px",
|
||||
"borderRight": "2px solid #73BF69",
|
||||
"height": "300px",
|
||||
"transition": "width 1s",
|
||||
"width": "60px",
|
||||
<div
|
||||
className="bar-gauge__value"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"color": "#73BF69",
|
||||
"display": "flex",
|
||||
"fontSize": "27.2727px",
|
||||
"height": "300px",
|
||||
"lineHeight": 1,
|
||||
"paddingLeft": "10px",
|
||||
"width": "60px",
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
25
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"background": "rgba(115, 191, 105, 0.25)",
|
||||
"borderRadius": "3px",
|
||||
"borderRight": "2px solid #73BF69",
|
||||
"height": "300px",
|
||||
"transition": "width 1s",
|
||||
"width": "60px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Library
|
||||
import React, { PureComponent, ReactNode, CSSProperties } from 'react';
|
||||
import $ from 'jquery';
|
||||
import { css } from 'emotion';
|
||||
import { css, cx } from 'emotion';
|
||||
import { DisplayValue } from '@grafana/data';
|
||||
|
||||
// Utils
|
||||
@@ -27,6 +27,8 @@ export interface Props extends Themeable {
|
||||
suffix?: DisplayValue;
|
||||
sparkline?: BigValueSparkline;
|
||||
backgroundColor?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -119,15 +121,19 @@ export class BigValue extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { height, width, value, prefix, suffix, sparkline, backgroundColor } = this.props;
|
||||
const { height, width, value, prefix, suffix, sparkline, backgroundColor, onClick, className } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css({
|
||||
position: 'relative',
|
||||
display: 'table',
|
||||
})}
|
||||
className={cx(
|
||||
css({
|
||||
position: 'relative',
|
||||
display: 'table',
|
||||
}),
|
||||
className
|
||||
)}
|
||||
style={{ width, height, backgroundColor }}
|
||||
onClick={onClick}
|
||||
>
|
||||
{value.title && (
|
||||
<div
|
||||
@@ -143,6 +149,7 @@ export class BigValue extends PureComponent<Props> {
|
||||
{value.title}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<span
|
||||
className={css({
|
||||
lineHeight: 1,
|
||||
|
||||
@@ -3,10 +3,11 @@ import { css, cx } from 'emotion';
|
||||
import useClickAway from 'react-use/lib/useClickAway';
|
||||
import { GrafanaTheme, selectThemeVariant, ThemeContext } from '../../index';
|
||||
import { Portal, List } from '../index';
|
||||
import { LinkTarget } from '@grafana/data';
|
||||
|
||||
export interface ContextMenuItem {
|
||||
label: string;
|
||||
target?: string;
|
||||
target?: LinkTarget;
|
||||
icon?: string;
|
||||
url?: string;
|
||||
onClick?: (event?: React.SyntheticEvent<HTMLElement>) => void;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ContextMenu, ContextMenuGroup } from '../ContextMenu/ContextMenu';
|
||||
|
||||
interface WithContextMenuProps {
|
||||
children: (props: { openMenu: React.MouseEventHandler<HTMLElement> }) => JSX.Element;
|
||||
getContextMenuItems: () => ContextMenuGroup[];
|
||||
}
|
||||
|
||||
export const WithContextMenu: React.FC<WithContextMenuProps> = ({ children, getContextMenuItems }) => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [menuPosition, setMenuPositon] = useState({ x: 0, y: 0 });
|
||||
|
||||
return (
|
||||
<>
|
||||
{children({
|
||||
openMenu: e => {
|
||||
setIsMenuOpen(true);
|
||||
setMenuPositon({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
});
|
||||
},
|
||||
})}
|
||||
|
||||
{isMenuOpen && (
|
||||
<ContextMenu
|
||||
onClose={() => setIsMenuOpen(false)}
|
||||
x={menuPosition.x}
|
||||
y={menuPosition.y}
|
||||
items={getContextMenuItems()}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -59,6 +59,7 @@ export const DataLinkEditor: React.FC<DataLinkEditorProps> = React.memo(
|
||||
onBlur={onTitleBlur}
|
||||
inputWidth={15}
|
||||
labelWidth={5}
|
||||
placeholder="Show details"
|
||||
/>
|
||||
|
||||
<FormField
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { WithContextMenu } from '../ContextMenu/WithContextMenu';
|
||||
import { LinkModelSupplier } from '@grafana/data';
|
||||
import { linkModelToContextMenuItems } from '../../utils/dataLinks';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface DataLinksContextMenuProps {
|
||||
children: (props: { openMenu?: React.MouseEventHandler<HTMLElement>; targetClassName?: string }) => JSX.Element;
|
||||
links?: LinkModelSupplier<any>;
|
||||
}
|
||||
|
||||
export const DataLinksContextMenu: React.FC<DataLinksContextMenuProps> = ({ children, links }) => {
|
||||
if (!links) {
|
||||
return children({});
|
||||
}
|
||||
|
||||
const getDataLinksContextMenuItems = () => {
|
||||
return [{ items: linkModelToContextMenuItems(links), label: 'Data links' }];
|
||||
};
|
||||
|
||||
// Use this class name (exposed via render prop) to add context menu indicator to the click target of the visualization
|
||||
const targetClassName = css`
|
||||
cursor: context-menu;
|
||||
`;
|
||||
|
||||
return (
|
||||
<WithContextMenu getContextMenuItems={getDataLinksContextMenuItems}>
|
||||
{({ openMenu }) => {
|
||||
return children({ openMenu, targetClassName });
|
||||
}}
|
||||
</WithContextMenu>
|
||||
);
|
||||
};
|
||||
@@ -68,7 +68,7 @@ export const DataLinksEditor: FC<DataLinksEditorProps> = React.memo(({ value, on
|
||||
|
||||
{(!value || (value && value.length < (maxLinks || Infinity))) && (
|
||||
<Button variant="inverse" icon="fa fa-plus" onClick={() => onAdd()}>
|
||||
Create link
|
||||
Add link
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
margin-bottom: $space-xxs;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface Props extends Themeable {
|
||||
showThresholdLabels: boolean;
|
||||
width: number;
|
||||
value: DisplayValue;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const FONT_SCALE = 1;
|
||||
@@ -133,24 +135,16 @@ export class Gauge extends PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { width, value, height } = this.props;
|
||||
renderVisualization = () => {
|
||||
const { width, value, height, onClick } = this.props;
|
||||
const autoProps = calculateGaugeAutoProps(width, height, value.title);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<div
|
||||
style={{ height: `${autoProps.gaugeHeight}px`, width: '100%' }}
|
||||
ref={element => (this.canvasElement = element)}
|
||||
onClick={onClick}
|
||||
/>
|
||||
{autoProps.showLabel && (
|
||||
<div
|
||||
@@ -163,11 +157,30 @@ export class Gauge extends PureComponent<Props> {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
top: '-4px',
|
||||
cursor: 'default',
|
||||
}}
|
||||
>
|
||||
{value.title}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
className={this.props.className}
|
||||
>
|
||||
{this.renderVisualization()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('Next id to add', () => {
|
||||
it('should be 4', () => {
|
||||
const { instance } = setup();
|
||||
|
||||
instance.addMapping();
|
||||
instance.onAddMapping();
|
||||
|
||||
expect(instance.state.nextIdToAdd).toEqual(4);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
|
||||
import MappingRow from './MappingRow';
|
||||
import { MappingType, ValueMapping } from '@grafana/data';
|
||||
import { Button } from '../index';
|
||||
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
|
||||
|
||||
export interface Props {
|
||||
@@ -30,7 +31,7 @@ export class ValueMappingsEditor extends PureComponent<Props, State> {
|
||||
return Math.max.apply(null, mappings.map(mapping => mapping.id).map(m => m)) + 1;
|
||||
}
|
||||
|
||||
addMapping = () =>
|
||||
onAddMapping = () =>
|
||||
this.setState(prevState => ({
|
||||
valueMappings: [
|
||||
...prevState.valueMappings,
|
||||
@@ -81,16 +82,21 @@ export class ValueMappingsEditor extends PureComponent<Props, State> {
|
||||
const { valueMappings } = this.state;
|
||||
|
||||
return (
|
||||
<PanelOptionsGroup title="Add value mapping" onAdd={this.addMapping}>
|
||||
{valueMappings.length > 0 &&
|
||||
valueMappings.map((valueMapping, index) => (
|
||||
<MappingRow
|
||||
key={`${valueMapping.text}-${index}`}
|
||||
valueMapping={valueMapping}
|
||||
updateValueMapping={this.updateGauge}
|
||||
removeValueMapping={() => this.onRemoveMapping(valueMapping.id)}
|
||||
/>
|
||||
))}
|
||||
<PanelOptionsGroup title="Value mappings">
|
||||
<div>
|
||||
{valueMappings.length > 0 &&
|
||||
valueMappings.map((valueMapping, index) => (
|
||||
<MappingRow
|
||||
key={`${valueMapping.text}-${index}`}
|
||||
valueMapping={valueMapping}
|
||||
updateValueMapping={this.updateGauge}
|
||||
removeValueMapping={() => this.onRemoveMapping(valueMapping.id)}
|
||||
/>
|
||||
))}
|
||||
<Button variant="inverse" icon="fa fa-plus" onClick={this.onAddMapping}>
|
||||
Add mapping
|
||||
</Button>
|
||||
</div>
|
||||
</PanelOptionsGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,37 +2,45 @@
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<Component
|
||||
onAdd={[Function]}
|
||||
title="Add value mapping"
|
||||
title="Value mappings"
|
||||
>
|
||||
<MappingRow
|
||||
key="Ok-0"
|
||||
removeValueMapping={[Function]}
|
||||
updateValueMapping={[Function]}
|
||||
valueMapping={
|
||||
Object {
|
||||
"id": 1,
|
||||
"operator": "",
|
||||
"text": "Ok",
|
||||
"type": 1,
|
||||
"value": "20",
|
||||
<div>
|
||||
<MappingRow
|
||||
key="Ok-0"
|
||||
removeValueMapping={[Function]}
|
||||
updateValueMapping={[Function]}
|
||||
valueMapping={
|
||||
Object {
|
||||
"id": 1,
|
||||
"operator": "",
|
||||
"text": "Ok",
|
||||
"type": 1,
|
||||
"value": "20",
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
<MappingRow
|
||||
key="Meh-1"
|
||||
removeValueMapping={[Function]}
|
||||
updateValueMapping={[Function]}
|
||||
valueMapping={
|
||||
Object {
|
||||
"from": "21",
|
||||
"id": 2,
|
||||
"operator": "",
|
||||
"text": "Meh",
|
||||
"to": "30",
|
||||
"type": 2,
|
||||
/>
|
||||
<MappingRow
|
||||
key="Meh-1"
|
||||
removeValueMapping={[Function]}
|
||||
updateValueMapping={[Function]}
|
||||
valueMapping={
|
||||
Object {
|
||||
"from": "21",
|
||||
"id": 2,
|
||||
"operator": "",
|
||||
"text": "Meh",
|
||||
"to": "30",
|
||||
"type": 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
/>
|
||||
<Button
|
||||
icon="fa fa-plus"
|
||||
onClick={[Function]}
|
||||
variant="inverse"
|
||||
>
|
||||
Add mapping
|
||||
</Button>
|
||||
</div>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
@@ -76,4 +76,5 @@ export { CallToActionCard } from './CallToActionCard/CallToActionCard';
|
||||
export { ContextMenu, ContextMenuItem, ContextMenuGroup, ContextMenuProps } from './ContextMenu/ContextMenu';
|
||||
export { VariableSuggestion, VariableOrigin } from './DataLinks/DataLinkSuggestions';
|
||||
export { DataLinksEditor } from './DataLinks/DataLinksEditor';
|
||||
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
|
||||
export { SeriesIcon } from './Legend/SeriesIcon';
|
||||
|
||||
24
packages/grafana-ui/src/utils/dataLinks.ts
Normal file
24
packages/grafana-ui/src/utils/dataLinks.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ContextMenuItem } from '../components/ContextMenu/ContextMenu';
|
||||
import { LinkModelSupplier } from '@grafana/data';
|
||||
|
||||
export const DataLinkBuiltInVars = {
|
||||
keepTime: '__url_time_range',
|
||||
includeVars: '__all_variables',
|
||||
seriesName: '__series_name',
|
||||
valueTime: '__value_time',
|
||||
};
|
||||
|
||||
/**
|
||||
* Delays creating links until we need to open the ContextMenu
|
||||
*/
|
||||
export const linkModelToContextMenuItems: (links: LinkModelSupplier<any>) => ContextMenuItem[] = links => {
|
||||
return links.getLinks().map(link => {
|
||||
return {
|
||||
label: link.title,
|
||||
// TODO: rename to href
|
||||
url: link.href,
|
||||
target: link.target,
|
||||
icon: `fa ${link.target === '_self' ? 'fa-link' : 'fa-external-link'}`,
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
FieldConfig,
|
||||
DisplayValue,
|
||||
GraphSeriesValue,
|
||||
DataFrameView,
|
||||
} from '@grafana/data';
|
||||
|
||||
import toNumber from 'lodash/toNumber';
|
||||
@@ -14,6 +15,7 @@ import toString from 'lodash/toString';
|
||||
import { GrafanaTheme, InterpolateFunction, ScopedVars } from '../types/index';
|
||||
import { getDisplayProcessor } from './displayProcessor';
|
||||
import { getFlotPairs } from './flotPairs';
|
||||
import { DataLinkBuiltInVars } from '../utils/dataLinks';
|
||||
|
||||
export interface FieldDisplayOptions {
|
||||
values?: boolean; // If true show each row value
|
||||
@@ -23,7 +25,7 @@ export interface FieldDisplayOptions {
|
||||
defaults: FieldConfig; // Use these values unless otherwise stated
|
||||
override: FieldConfig; // Set these values regardless of the source
|
||||
}
|
||||
|
||||
// TODO: use built in variables, same as for data links?
|
||||
export const VAR_SERIES_NAME = '__series_name';
|
||||
export const VAR_FIELD_NAME = '__field_name';
|
||||
export const VAR_CALC = '__calc';
|
||||
@@ -59,10 +61,15 @@ function getTitleTemplate(title: string | undefined, stats: string[], data?: Dat
|
||||
}
|
||||
|
||||
export interface FieldDisplay {
|
||||
name: string; // NOT title!
|
||||
name: string; // The field name (title is in display)
|
||||
field: FieldConfig;
|
||||
display: DisplayValue;
|
||||
sparkline?: GraphSeriesValue[][];
|
||||
|
||||
// Expose to the original values for delayed inspection (DataLinks etc)
|
||||
view?: DataFrameView;
|
||||
column?: number; // The field column index
|
||||
row?: number; // only filled in when the value is from a row (ie, not a reduction)
|
||||
}
|
||||
|
||||
export interface GetFieldDisplayValuesOptions {
|
||||
@@ -75,8 +82,19 @@ export interface GetFieldDisplayValuesOptions {
|
||||
|
||||
export const DEFAULT_FIELD_DISPLAY_VALUES_LIMIT = 25;
|
||||
|
||||
const getTimeColumnIdx = (series: DataFrame) => {
|
||||
let timeColumn = -1;
|
||||
for (let i = 0; i < series.fields.length; i++) {
|
||||
if (series.fields[i].type === FieldType.time) {
|
||||
timeColumn = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return timeColumn;
|
||||
};
|
||||
|
||||
export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): FieldDisplay[] => {
|
||||
const { data, replaceVariables, fieldOptions, sparkline } = options;
|
||||
const { data, replaceVariables, fieldOptions } = options;
|
||||
const { defaults, override } = fieldOptions;
|
||||
const calcs = fieldOptions.calcs.length ? fieldOptions.calcs : [ReducerID.last];
|
||||
|
||||
@@ -96,17 +114,11 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
name: series.refId ? series.refId : `Series[${s}]`,
|
||||
};
|
||||
}
|
||||
scopedVars[VAR_SERIES_NAME] = { text: 'Series', value: series.name };
|
||||
|
||||
let timeColumn = -1;
|
||||
if (sparkline) {
|
||||
for (let i = 0; i < series.fields.length; i++) {
|
||||
if (series.fields[i].type === FieldType.time) {
|
||||
timeColumn = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
scopedVars[DataLinkBuiltInVars.seriesName] = { text: 'Series', value: series.name };
|
||||
|
||||
const timeColumn = getTimeColumnIdx(series);
|
||||
const view = new DataFrameView(series);
|
||||
|
||||
for (let i = 0; i < series.fields.length && !hitLimit; i++) {
|
||||
const field = series.fields[i];
|
||||
@@ -131,7 +143,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
|
||||
const title = config.title ? config.title : defaultTitle;
|
||||
|
||||
// Show all number fields
|
||||
// Show all rows
|
||||
if (fieldOptions.values) {
|
||||
const usesCellValues = title.indexOf(VAR_CELL_PREFIX) >= 0;
|
||||
|
||||
@@ -154,6 +166,9 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
name,
|
||||
field: config,
|
||||
display: displayValue,
|
||||
view,
|
||||
column: i,
|
||||
row: j,
|
||||
});
|
||||
|
||||
if (values.length >= limit) {
|
||||
@@ -166,15 +181,15 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
field,
|
||||
reducers: calcs, // The stats to calculate
|
||||
});
|
||||
let sparkline: GraphSeriesValue[][] | undefined = undefined;
|
||||
|
||||
// Single sparkline for a field
|
||||
const points =
|
||||
timeColumn < 0
|
||||
? undefined
|
||||
: getFlotPairs({
|
||||
xField: series.fields[timeColumn],
|
||||
yField: series.fields[i],
|
||||
});
|
||||
// Single sparkline for every reducer
|
||||
if (options.sparkline && timeColumn >= 0) {
|
||||
sparkline = getFlotPairs({
|
||||
xField: series.fields[timeColumn],
|
||||
yField: series.fields[i],
|
||||
});
|
||||
}
|
||||
|
||||
for (const calc of calcs) {
|
||||
scopedVars[VAR_CALC] = { value: calc, text: calc };
|
||||
@@ -184,7 +199,9 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
name,
|
||||
field: config,
|
||||
display: displayValue,
|
||||
sparkline: points,
|
||||
sparkline,
|
||||
view,
|
||||
column: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export * from './fieldDisplay';
|
||||
export * from './validate';
|
||||
export { getFlotPairs } from './flotPairs';
|
||||
export * from './slate';
|
||||
export * from './dataLinks';
|
||||
export { default as ansicolor } from './ansicolor';
|
||||
|
||||
// Export with a namespace
|
||||
|
||||
Reference in New Issue
Block a user