mirror of https://github.com/grafana/grafana
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 calculationpull/16756/head
parent
e1924608a2
commit
ff6b8c5adc
@ -1,5 +1,30 @@ |
||||
/** |
||||
* Link configuration. The values may contain variables that need to be |
||||
* processed before running |
||||
*/ |
||||
export interface DataLink { |
||||
url: string; |
||||
title: string; |
||||
targetBlank?: boolean; |
||||
} |
||||
|
||||
export type LinkTarget = '_blank' | '_self'; |
||||
|
||||
/** |
||||
* Processed Link Model. The values are ready to use |
||||
*/ |
||||
export interface LinkModel<T> { |
||||
href: string; |
||||
title: string; |
||||
target: LinkTarget; |
||||
origin: T; |
||||
} |
||||
|
||||
/** |
||||
* Provides a way to produce links on demand |
||||
* |
||||
* TODO: ScopedVars in in GrafanaUI package! |
||||
*/ |
||||
export interface LinkModelSupplier<T extends object> { |
||||
getLinks(scopedVars?: any): Array<LinkModel<T>>; |
||||
} |
||||
|
||||
@ -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()} |
||||
/> |
||||
)} |
||||
</> |
||||
); |
||||
}; |
||||
@ -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> |
||||
); |
||||
}; |
||||
@ -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'}`, |
||||
}; |
||||
}); |
||||
}; |
||||
@ -0,0 +1,66 @@ |
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel'; |
||||
import { FieldDisplay, ScopedVars, DataLinkBuiltInVars } from '@grafana/ui'; |
||||
import { LinkModelSupplier, DataFrameHelper, FieldType } from '@grafana/data'; |
||||
import { getLinkSrv } from './link_srv'; |
||||
|
||||
/** |
||||
* Link suppliers creates link models based on a link origin |
||||
*/ |
||||
|
||||
export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<FieldDisplay> | undefined => { |
||||
const links = value.field.links; |
||||
if (!links || links.length === 0) { |
||||
return undefined; |
||||
} |
||||
return { |
||||
getLinks: (_scopedVars?: any) => { |
||||
const scopedVars: ScopedVars = {}; |
||||
// TODO, add values to scopedVars and/or pass objects to event listeners
|
||||
if (value.view) { |
||||
scopedVars[DataLinkBuiltInVars.seriesName] = { |
||||
text: 'Series', |
||||
value: value.view.dataFrame.name, |
||||
}; |
||||
const field = value.column ? value.view.dataFrame.fields[value.column] : undefined; |
||||
if (field) { |
||||
console.log('Full Field Info:', field); |
||||
} |
||||
if (value.row) { |
||||
const row = value.view.get(value.row); |
||||
console.log('ROW:', row); |
||||
const dataFrame = new DataFrameHelper(value.view.dataFrame); |
||||
|
||||
const timeField = dataFrame.getFirstFieldOfType(FieldType.time); |
||||
if (timeField) { |
||||
scopedVars[DataLinkBuiltInVars.valueTime] = { |
||||
text: 'Value time', |
||||
value: timeField.values.get(value.row), |
||||
}; |
||||
} |
||||
} |
||||
} else { |
||||
console.log('VALUE', value); |
||||
} |
||||
|
||||
return links.map(link => { |
||||
return getLinkSrv().getDataLinkUIModel(link, scopedVars, value); |
||||
}); |
||||
}, |
||||
}; |
||||
}; |
||||
|
||||
export const getPanelLinksSupplier = (value: PanelModel): LinkModelSupplier<PanelModel> => { |
||||
const links = value.links; |
||||
|
||||
if (!links || links.length === 0) { |
||||
return undefined; |
||||
} |
||||
|
||||
return { |
||||
getLinks: () => { |
||||
return links.map(link => { |
||||
return getLinkSrv().getDataLinkUIModel(link, value.scopedVars, value); |
||||
}); |
||||
}, |
||||
}; |
||||
}; |
||||
Loading…
Reference in new issue