Commit e612d7a2 by Dominik Prokop Committed by GitHub

New panel edit: data links edit (#22077)

* Move data links suggestions to grafana-data

* Data links -  field config and overrides

* Lint

* Fix test

* Add variable suggestions  to field override context

* Revert "Move data links suggestions to grafana-data"

This reverts commit 5d8d01a65eeda8db2379fddfc1223a9ec9a00c1d.

* Move FieldConfigEditor to core
parent 94b66258
...@@ -52,3 +52,23 @@ export interface LinkModel<T> { ...@@ -52,3 +52,23 @@ export interface LinkModel<T> {
export interface LinkModelSupplier<T extends object> { export interface LinkModelSupplier<T extends object> {
getLinks(scopedVars?: any): Array<LinkModel<T>>; getLinks(scopedVars?: any): Array<LinkModel<T>>;
} }
export enum VariableOrigin {
Series = 'series',
Field = 'field',
Fields = 'fields',
Value = 'value',
BuiltIn = 'built-in',
Template = 'template',
}
export interface VariableSuggestion {
value: string;
label: string;
documentation?: string;
origin: VariableOrigin;
}
export enum VariableSuggestionsScope {
Values = 'values',
}
import { MatcherConfig, FieldConfig, Field } from '../types';
import { Registry, RegistryItem } from '../utils';
import { ComponentType } from 'react'; import { ComponentType } from 'react';
import { MatcherConfig, FieldConfig, Field, DataFrame, VariableSuggestion, VariableSuggestionsScope } from '../types';
import { Registry, RegistryItem } from '../utils';
import { InterpolateFunction } from './panel'; import { InterpolateFunction } from './panel';
import { DataFrame } from 'apache-arrow';
export interface DynamicConfigValue { export interface DynamicConfigValue {
prop: string; prop: string;
...@@ -26,18 +25,20 @@ export interface FieldConfigSource { ...@@ -26,18 +25,20 @@ export interface FieldConfigSource {
export interface FieldConfigEditorProps<TValue, TSettings> { export interface FieldConfigEditorProps<TValue, TSettings> {
item: FieldPropertyEditorItem<TValue, TSettings>; // The property info item: FieldPropertyEditorItem<TValue, TSettings>; // The property info
value: TValue; value: TValue;
context: FieldOverrideContext;
onChange: (value?: TValue) => void; onChange: (value?: TValue) => void;
} }
export interface FieldOverrideContext { export interface FieldOverrideContext {
field: Field; data: DataFrame[];
data: DataFrame; field?: Field;
replaceVariables: InterpolateFunction; replaceVariables?: InterpolateFunction;
getSuggestions?: (scope?: VariableSuggestionsScope) => VariableSuggestion[];
} }
export interface FieldOverrideEditorProps<TValue, TSettings> { export interface FieldOverrideEditorProps<TValue, TSettings> {
item: FieldPropertyEditorItem<TValue, TSettings>; item: FieldPropertyEditorItem<TValue, TSettings>;
value: any; value: TValue;
context: FieldOverrideContext; context: FieldOverrideContext;
onChange: (value?: any) => void; onChange: (value?: any) => void;
} }
......
import React, { ChangeEvent, useContext } from 'react'; import React, { ChangeEvent, useContext } from 'react';
import { DataLink } from '@grafana/data'; import { DataLink, VariableSuggestion, GrafanaTheme } from '@grafana/data';
import { FormField, Switch } from '../index'; import { FormField, Switch } from '../index';
import { VariableSuggestion } from './DataLinkSuggestions';
import { css } from 'emotion'; import { css } from 'emotion';
import { ThemeContext, stylesFactory } from '../../themes/index'; import { ThemeContext, stylesFactory } from '../../themes/index';
import { DataLinkInput } from './DataLinkInput'; import { DataLinkInput } from './DataLinkInput';
import { GrafanaTheme } from '@grafana/data';
interface DataLinkEditorProps { interface DataLinkEditorProps {
index: number; index: number;
......
import React, { useState, useMemo, useContext, useRef, RefObject, memo, useEffect } from 'react'; import React, { useState, useMemo, useContext, useRef, RefObject, memo, useEffect } from 'react';
import usePrevious from 'react-use/lib/usePrevious'; import usePrevious from 'react-use/lib/usePrevious';
import { VariableSuggestion, VariableOrigin, DataLinkSuggestions } from './DataLinkSuggestions'; import { DataLinkSuggestions } from './DataLinkSuggestions';
import { ThemeContext, DataLinkBuiltInVars, makeValue } from '../../index'; import { ThemeContext, DataLinkBuiltInVars, makeValue } from '../../index';
import { SelectionReference } from './SelectionReference'; import { SelectionReference } from './SelectionReference';
import { Portal } from '../index'; import { Portal } from '../index';
...@@ -14,7 +14,7 @@ import { css, cx } from 'emotion'; ...@@ -14,7 +14,7 @@ import { css, cx } from 'emotion';
import { SlatePrism } from '../../slate-plugins'; import { SlatePrism } from '../../slate-plugins';
import { SCHEMA } from '../../utils/slate'; import { SCHEMA } from '../../utils/slate';
import { stylesFactory } from '../../themes'; import { stylesFactory } from '../../themes';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme, VariableSuggestion, VariableOrigin } from '@grafana/data';
const modulo = (a: number, n: number) => a - n * Math.floor(a / n); const modulo = (a: number, n: number) => a - n * Math.floor(a / n);
......
import { selectThemeVariant, ThemeContext } from '../../index'; import { selectThemeVariant, ThemeContext } from '../../index';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme, VariableSuggestion } from '@grafana/data';
import { css, cx } from 'emotion'; import { css, cx } from 'emotion';
import _ from 'lodash'; import _ from 'lodash';
import React, { useRef, useContext, useMemo } from 'react'; import React, { useRef, useContext, useMemo } from 'react';
...@@ -8,22 +8,6 @@ import { List } from '../index'; ...@@ -8,22 +8,6 @@ import { List } from '../index';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { stylesFactory } from '../../themes'; import { stylesFactory } from '../../themes';
export enum VariableOrigin {
Series = 'series',
Field = 'field',
Fields = 'fields',
Value = 'value',
BuiltIn = 'built-in',
Template = 'template',
}
export interface VariableSuggestion {
value: string;
label: string;
documentation?: string;
origin: VariableOrigin;
}
interface DataLinkSuggestionsProps { interface DataLinkSuggestionsProps {
suggestions: VariableSuggestion[]; suggestions: VariableSuggestion[];
activeIndex: number; activeIndex: number;
......
...@@ -4,10 +4,10 @@ import React, { FC } from 'react'; ...@@ -4,10 +4,10 @@ import React, { FC } from 'react';
import Prism from 'prismjs'; import Prism from 'prismjs';
// Components // Components
import { css } from 'emotion'; import { css } from 'emotion';
import { DataLink } from '@grafana/data'; import { DataLink, VariableSuggestion } from '@grafana/data';
import { Button } from '../index'; import { Button } from '../index';
import { DataLinkEditor } from './DataLinkEditor'; import { DataLinkEditor } from './DataLinkEditor';
import { VariableSuggestion } from './DataLinkSuggestions';
import { useTheme } from '../../themes/ThemeContext'; import { useTheme } from '../../themes/ThemeContext';
interface DataLinksEditorProps { interface DataLinksEditorProps {
......
import { storiesOf } from '@storybook/react';
import FieldConfigEditor from './FieldConfigEditor';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { FieldConfigSource, FieldConfigEditorRegistry, FieldPropertyEditorItem, Registry } from '@grafana/data';
import { NumberFieldConfigSettings, NumberValueEditor, NumberOverrideEditor, numberOverrideProcessor } from './number';
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
const FieldConfigStories = storiesOf('UI/FieldConfig', module);
FieldConfigStories.addDecorator(withCenteredStory);
const cfg: FieldConfigSource = {
defaults: {
title: 'Hello',
decimals: 3,
},
overrides: [],
};
const columWidth: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
id: 'width', // Match field properties
name: 'Column Width',
description: 'column width (for table)',
editor: NumberValueEditor,
override: NumberOverrideEditor,
process: numberOverrideProcessor,
settings: {
placeholder: 'auto',
min: 20,
max: 300,
},
};
export const customEditorRegistry: FieldConfigEditorRegistry = new Registry<FieldPropertyEditorItem>(() => {
return [columWidth];
});
FieldConfigStories.add('default', () => {
return renderComponentWithTheme(FieldConfigEditor, {
config: cfg,
data: [],
custom: customEditorRegistry,
onChange: (config: FieldConfigSource) => {
console.log('Data', config);
},
});
});
import {
FieldOverrideContext,
FieldConfigEditorProps,
DataLink,
FieldOverrideEditorProps,
DataFrame,
} from '@grafana/data';
import React, { FC, useState } from 'react';
import { css, cx } from 'emotion';
import Forms from '../Forms';
import { Modal } from '../Modal/Modal';
import { DataLinkEditor } from '../DataLinks/DataLinkEditor';
import cloneDeep from 'lodash/cloneDeep';
import { VariableSuggestion } from '@grafana/data';
export interface DataLinksFieldConfigSettings {}
export const dataLinksOverrideProcessor = (
value: any,
context: FieldOverrideContext,
_settings: DataLinksFieldConfigSettings
) => {
return value as DataLink[];
};
export const DataLinksValueEditor: React.FC<FieldConfigEditorProps<DataLink[], DataLinksFieldConfigSettings>> = ({
value,
onChange,
context,
}) => {
const onDataLinkChange = (index: number, link: DataLink) => {
const links = cloneDeep(value);
links[index] = link;
onChange(links);
};
const onDataLinkAdd = () => {
const links = cloneDeep(value);
links.push({
title: '',
url: '',
});
onChange(links);
};
return (
<>
{value.map((l, i) => {
return (
<DataLinksListItem
key={`${l.title}/${i}`}
index={i}
link={l}
onChange={onDataLinkChange}
data={context.data}
suggestions={context.getSuggestions ? context.getSuggestions() : []}
/>
);
})}
<Forms.Button size="sm" icon="fa fa-plus" onClick={onDataLinkAdd}>
Create data link
</Forms.Button>
</>
);
};
export const DataLinksOverrideEditor: React.FC<FieldOverrideEditorProps<DataLink[], DataLinksFieldConfigSettings>> = ({
value,
onChange,
context,
item,
}) => {
const onDataLinkChange = (index: number, link: DataLink) => {
const links = cloneDeep(value);
links[index] = link;
onChange(links);
};
const onDataLinkAdd = () => {
let links = cloneDeep(value);
if (links) {
links.push({
title: '',
url: '',
});
} else {
links = [
{
title: '',
url: '',
},
];
}
onChange(links);
};
return (
<>
{value &&
value.map((l, i) => {
return (
<DataLinksListItem
key={`${l.title}/${i}`}
index={i}
link={l}
onChange={onDataLinkChange}
data={context.data}
suggestions={context.getSuggestions ? context.getSuggestions() : []}
/>
);
})}
<Forms.Button size="sm" icon="fa fa-plus" onClick={onDataLinkAdd}>
Create data link
</Forms.Button>
</>
);
};
interface DataLinksListItemProps {
index: number;
link: DataLink;
data: DataFrame[];
onChange: (index: number, link: DataLink) => void;
suggestions: VariableSuggestion[];
}
const DataLinksListItem: FC<DataLinksListItemProps> = ({ index, link, data, onChange, suggestions }) => {
const [isEditing, setIsEditing] = useState(false);
const style = () => {
return {
wrapper: css`
display: flex;
justify-content: space-between;
`,
action: css`
flex-shrink: 0;
flex-grow: 0;
`,
noTitle: css`
font-style: italic;
`,
};
};
const styles = style();
const hasTitle = link.title.trim() !== '';
return (
<>
<div className={styles.wrapper}>
<div className={cx(!hasTitle && styles.noTitle)}>{hasTitle ? link.title : 'Edit data link'}</div>
<div>
<Forms.Button size="sm" icon="fa fa-pencil" variant="link" onClick={() => setIsEditing(true)} />
</div>
</div>
{isEditing && (
<Modal
title="Edit data link"
isOpen={isEditing}
onDismiss={() => {
setIsEditing(false);
}}
>
<DataLinkEditorModalContent
index={index}
link={link}
data={data}
onChange={onChange}
onClose={() => setIsEditing(false)}
suggestions={suggestions}
/>
</Modal>
)}
</>
);
};
interface DataLinkEditorModalContentProps {
link: DataLink;
index: number;
data: DataFrame[];
suggestions: VariableSuggestion[];
onChange: (index: number, ink: DataLink) => void;
onClose: () => void;
}
const DataLinkEditorModalContent: FC<DataLinkEditorModalContentProps> = ({
link,
index,
data,
suggestions,
onChange,
onClose,
}) => {
const [dirtyLink, setDirtyLink] = useState(link);
return (
<>
<DataLinkEditor
value={dirtyLink}
index={index}
isLast={false}
suggestions={suggestions}
onChange={(index, link) => {
setDirtyLink(link);
}}
onRemove={() => {}}
/>
<Forms.Button
onClick={() => {
onChange(index, dirtyLink);
onClose();
}}
>
Save
</Forms.Button>
<Forms.Button onClick={() => onClose()}>Cancel</Forms.Button>
</>
);
};
...@@ -10,6 +10,7 @@ describe('standardFieldConfigEditorRegistry', () => { ...@@ -10,6 +10,7 @@ describe('standardFieldConfigEditorRegistry', () => {
thresholds: {} as any, thresholds: {} as any,
noValue: 'no value', noValue: 'no value',
unit: 'km/s', unit: 'km/s',
links: {} as any,
}; };
it('make sure all fields have a valid name', () => { it('make sure all fields have a valid name', () => {
......
import { FieldConfigEditorRegistry, Registry, FieldPropertyEditorItem, ThresholdsConfig } from '@grafana/data'; import {
FieldConfigEditorRegistry,
Registry,
FieldPropertyEditorItem,
ThresholdsConfig,
DataLink,
} from '@grafana/data';
import { StringValueEditor, StringOverrideEditor, stringOverrideProcessor, StringFieldConfigSettings } from './string'; import { StringValueEditor, StringOverrideEditor, stringOverrideProcessor, StringFieldConfigSettings } from './string';
import { NumberValueEditor, NumberOverrideEditor, numberOverrideProcessor, NumberFieldConfigSettings } from './number'; import { NumberValueEditor, NumberOverrideEditor, numberOverrideProcessor, NumberFieldConfigSettings } from './number';
import { UnitValueEditor, UnitOverrideEditor } from './units'; import { UnitValueEditor, UnitOverrideEditor } from './units';
...@@ -8,6 +14,7 @@ import { ...@@ -8,6 +14,7 @@ import {
thresholdsOverrideProcessor, thresholdsOverrideProcessor,
ThresholdsFieldConfigSettings, ThresholdsFieldConfigSettings,
} from './thresholds'; } from './thresholds';
import { DataLinksValueEditor, DataLinksOverrideEditor, dataLinksOverrideProcessor } from './links';
const title: FieldPropertyEditorItem<string, StringFieldConfigSettings> = { const title: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
id: 'title', // Match field properties id: 'title', // Match field properties
...@@ -110,8 +117,20 @@ const noValue: FieldPropertyEditorItem<string, StringFieldConfigSettings> = { ...@@ -110,8 +117,20 @@ const noValue: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
}, },
}; };
const links: FieldPropertyEditorItem<DataLink[], StringFieldConfigSettings> = {
id: 'links', // Match field properties
name: 'DataLinks',
description: 'Manage date links',
editor: DataLinksValueEditor,
override: DataLinksOverrideEditor,
process: dataLinksOverrideProcessor,
settings: {
placeholder: '-',
},
};
export const standardFieldConfigEditorRegistry: FieldConfigEditorRegistry = new Registry<FieldPropertyEditorItem>( export const standardFieldConfigEditorRegistry: FieldConfigEditorRegistry = new Registry<FieldPropertyEditorItem>(
() => { () => {
return [title, unit, min, max, decimals, thresholds, noValue]; return [title, unit, min, max, decimals, thresholds, noValue, links];
} }
); );
...@@ -95,7 +95,6 @@ export { ClickOutsideWrapper } from './ClickOutsideWrapper/ClickOutsideWrapper'; ...@@ -95,7 +95,6 @@ export { ClickOutsideWrapper } from './ClickOutsideWrapper/ClickOutsideWrapper';
export * from './SingleStatShared/index'; export * from './SingleStatShared/index';
export { CallToActionCard } from './CallToActionCard/CallToActionCard'; export { CallToActionCard } from './CallToActionCard/CallToActionCard';
export { ContextMenu, ContextMenuItem, ContextMenuGroup, ContextMenuProps } from './ContextMenu/ContextMenu'; export { ContextMenu, ContextMenuItem, ContextMenuGroup, ContextMenuProps } from './ContextMenu/ContextMenu';
export { VariableSuggestion, VariableOrigin } from './DataLinks/DataLinkSuggestions';
export { DataLinksEditor } from './DataLinks/DataLinksEditor'; export { DataLinksEditor } from './DataLinks/DataLinksEditor';
export { DataLinkInput } from './DataLinks/DataLinkInput'; export { DataLinkInput } from './DataLinks/DataLinkInput';
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu'; export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
...@@ -118,7 +117,6 @@ export { Icon } from './Icon/Icon'; ...@@ -118,7 +117,6 @@ export { Icon } from './Icon/Icon';
export { Drawer } from './Drawer/Drawer'; export { Drawer } from './Drawer/Drawer';
// TODO: namespace!! // TODO: namespace!!
export { FieldConfigEditor } from './FieldConfigs/FieldConfigEditor';
export { export {
StringValueEditor, StringValueEditor,
StringOverrideEditor, StringOverrideEditor,
...@@ -135,3 +133,5 @@ export { ...@@ -135,3 +133,5 @@ export {
// Next-gen forms // Next-gen forms
export { default as Forms } from './Forms'; export { default as Forms } from './Forms';
export { ValuePicker } from './ValuePicker/ValuePicker'; export { ValuePicker } from './ValuePicker/ValuePicker';
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
export { standardFieldConfigEditorRegistry } from './FieldConfigs/standardFieldConfigEditorRegistry';
...@@ -6,13 +6,16 @@ import { ...@@ -6,13 +6,16 @@ import {
DataFrame, DataFrame,
FieldPropertyEditorItem, FieldPropertyEditorItem,
DynamicConfigValue, DynamicConfigValue,
VariableSuggestionsScope,
} from '@grafana/data'; } from '@grafana/data';
import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry'; import {
import Forms from '../Forms'; standardFieldConfigEditorRegistry,
import { fieldMatchersUI } from '../MatchersUI/fieldMatchersUI'; Forms,
import { ControlledCollapse } from '../Collapse/Collapse'; fieldMatchersUI,
import { ValuePicker } from '../ValuePicker/ValuePicker'; ControlledCollapse,
ValuePicker,
} from '@grafana/ui';
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
interface Props { interface Props {
config: FieldConfigSource; config: FieldConfigSource;
custom?: FieldConfigEditorRegistry; // custom fields custom?: FieldConfigEditorRegistry; // custom fields
...@@ -89,12 +92,21 @@ export class FieldConfigEditor extends React.PureComponent<Props> { ...@@ -89,12 +92,21 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
}; };
renderEditor(item: FieldPropertyEditorItem, custom: boolean) { renderEditor(item: FieldPropertyEditorItem, custom: boolean) {
const { data } = this.props;
const config = this.props.config.defaults; const config = this.props.config.defaults;
const value = custom ? (config.custom ? config.custom[item.id] : undefined) : (config as any)[item.id]; const value = custom ? (config.custom ? config.custom[item.id] : undefined) : (config as any)[item.id];
return ( return (
<Forms.Field label={item.name} description={item.description} key={`${item.id}/${custom}`}> <Forms.Field label={item.name} description={item.description} key={`${item.id}/${custom}`}>
<item.editor item={item} value={value} onChange={v => this.setDefaultValue(item.id, v, custom)} /> <item.editor
item={item}
value={value}
onChange={v => this.setDefaultValue(item.id, v, custom)}
context={{
data,
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
}}
/>
</Forms.Field> </Forms.Field>
); );
} }
...@@ -169,7 +181,11 @@ export class FieldConfigEditor extends React.PureComponent<Props> { ...@@ -169,7 +181,11 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
this.onDynamicConfigValueChange(i, j, value); this.onDynamicConfigValueChange(i, j, value);
}} }}
item={item} item={item}
context={{} as any} context={{
data,
getSuggestions: (scope?: VariableSuggestionsScope) =>
getDataLinksVariableSuggestions(data, scope),
}}
/> />
</Forms.Field> </Forms.Field>
); );
......
import React, { PureComponent, CSSProperties } from 'react'; import React, { PureComponent } from 'react';
import { import {
GrafanaTheme, GrafanaTheme,
FieldConfigSource, FieldConfigSource,
...@@ -9,18 +9,10 @@ import { ...@@ -9,18 +9,10 @@ import {
SelectableValue, SelectableValue,
TimeRange, TimeRange,
} from '@grafana/data'; } from '@grafana/data';
import { import { stylesFactory, Forms, CustomScrollbar, selectThemeVariant, ControlledCollapse } from '@grafana/ui';
stylesFactory,
Forms,
FieldConfigEditor,
CustomScrollbar,
selectThemeVariant,
ControlledCollapse,
} from '@grafana/ui';
import { css, cx } from 'emotion'; import { css, cx } from 'emotion';
import config from 'app/core/config'; import config from 'app/core/config';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
import { PanelModel } from '../../state/PanelModel'; import { PanelModel } from '../../state/PanelModel';
import { DashboardModel } from '../../state/DashboardModel'; import { DashboardModel } from '../../state/DashboardModel';
...@@ -36,6 +28,8 @@ import { DisplayMode, displayModes } from './types'; ...@@ -36,6 +28,8 @@ import { DisplayMode, displayModes } from './types';
import { PanelEditorTabs } from './PanelEditorTabs'; import { PanelEditorTabs } from './PanelEditorTabs';
import { DashNavTimeControls } from '../DashNav/DashNavTimeControls'; import { DashNavTimeControls } from '../DashNav/DashNavTimeControls';
import { LocationState, CoreEvents } from 'app/types'; import { LocationState, CoreEvents } from 'app/types';
import { calculatePanelSize } from './utils';
import { FieldConfigEditor } from './FieldConfigEditor';
const getStyles = stylesFactory((theme: GrafanaTheme) => { const getStyles = stylesFactory((theme: GrafanaTheme) => {
const handleColor = selectThemeVariant( const handleColor = selectThemeVariant(
...@@ -407,28 +401,6 @@ export class PanelEditor extends PureComponent<Props, State> { ...@@ -407,28 +401,6 @@ export class PanelEditor extends PureComponent<Props, State> {
} }
} }
function calculatePanelSize(mode: DisplayMode, width: number, height: number, panel: PanelModel): CSSProperties {
if (mode === DisplayMode.Fill) {
return { width, height };
}
const colWidth = (window.innerWidth - GRID_CELL_VMARGIN * 4) / GRID_COLUMN_COUNT;
const pWidth = colWidth * panel.gridPos.w;
const pHeight = GRID_CELL_HEIGHT * panel.gridPos.h;
const scale = Math.min(width / pWidth, height / pHeight);
if (mode === DisplayMode.Exact && pWidth <= width && pHeight <= height) {
return {
width: pWidth,
height: pHeight,
};
}
return {
width: pWidth * scale,
height: pHeight * scale,
};
}
const mapStateToProps = (state: StoreState) => ({ const mapStateToProps = (state: StoreState) => ({
location: state.location, location: state.location,
}); });
......
import { CSSProperties } from 'react';
import { PanelModel } from '../../state/PanelModel';
import { GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, GRID_CELL_HEIGHT } from 'app/core/constants';
import { DisplayMode } from './types';
export function calculatePanelSize(mode: DisplayMode, width: number, height: number, panel: PanelModel): CSSProperties {
if (mode === DisplayMode.Fill) {
return { width, height };
}
const colWidth = (window.innerWidth - GRID_CELL_VMARGIN * 4) / GRID_COLUMN_COUNT;
const pWidth = colWidth * panel.gridPos.w;
const pHeight = GRID_CELL_HEIGHT * panel.gridPos.h;
const scale = Math.min(width / pWidth, height / pHeight);
if (mode === DisplayMode.Exact && pWidth <= width && pHeight <= height) {
return {
width: pWidth,
height: pHeight,
};
}
return {
width: pWidth * scale,
height: pHeight * scale,
};
}
...@@ -6,7 +6,7 @@ import { appendQueryToUrl, toUrlParams } from 'app/core/utils/url'; ...@@ -6,7 +6,7 @@ import { appendQueryToUrl, toUrlParams } from 'app/core/utils/url';
import { sanitizeUrl } from 'app/core/utils/text'; import { sanitizeUrl } from 'app/core/utils/text';
import { getConfig } from 'app/core/config'; import { getConfig } from 'app/core/config';
import locationUtil from 'app/core/utils/location_util'; import locationUtil from 'app/core/utils/location_util';
import { VariableSuggestion, VariableOrigin, DataLinkBuiltInVars } from '@grafana/ui'; import { DataLinkBuiltInVars } from '@grafana/ui';
import { import {
DataLink, DataLink,
KeyValue, KeyValue,
...@@ -16,6 +16,9 @@ import { ...@@ -16,6 +16,9 @@ import {
ScopedVars, ScopedVars,
FieldType, FieldType,
Field, Field,
VariableSuggestion,
VariableOrigin,
VariableSuggestionsScope,
} from '@grafana/data'; } from '@grafana/data';
const timeRangeVars = [ const timeRangeVars = [
...@@ -180,21 +183,33 @@ const getDataFrameVars = (dataFrames: DataFrame[]) => { ...@@ -180,21 +183,33 @@ const getDataFrameVars = (dataFrames: DataFrame[]) => {
return suggestions; return suggestions;
}; };
export const getDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => { export const getDataLinksVariableSuggestions = (
dataFrames: DataFrame[],
scope?: VariableSuggestionsScope
): VariableSuggestion[] => {
const valueTimeVar = { const valueTimeVar = {
value: `${DataLinkBuiltInVars.valueTime}`, value: `${DataLinkBuiltInVars.valueTime}`,
label: 'Time', label: 'Time',
documentation: 'Time value of the clicked datapoint (in ms epoch)', documentation: 'Time value of the clicked datapoint (in ms epoch)',
origin: VariableOrigin.Value, origin: VariableOrigin.Value,
}; };
return [ const includeValueVars = scope === VariableSuggestionsScope.Values;
...seriesVars,
...getFieldVars(dataFrames), return includeValueVars
...valueVars, ? [
valueTimeVar, ...seriesVars,
...getDataFrameVars(dataFrames), ...getFieldVars(dataFrames),
...getPanelLinksVariableSuggestions(), ...valueVars,
]; valueTimeVar,
...getDataFrameVars(dataFrames),
...getPanelLinksVariableSuggestions(),
]
: [
...seriesVars,
...getFieldVars(dataFrames),
...getDataFrameVars(dataFrames),
...getPanelLinksVariableSuggestions(),
];
}; };
export const getCalculationValueDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => { export const getCalculationValueDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => {
......
import React from 'react'; import React from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { Button, FormField, VariableSuggestion, DataLinkInput, stylesFactory } from '@grafana/ui'; import { VariableSuggestion } from '@grafana/data';
import { Button, FormField, DataLinkInput, stylesFactory } from '@grafana/ui';
import { DataLinkConfig } from '../types'; import { DataLinkConfig } from '../types';
const getStyles = stylesFactory(() => ({ const getStyles = stylesFactory(() => ({
......
import React from 'react'; import React from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { Button, DataLinkBuiltInVars, stylesFactory, useTheme, VariableOrigin } from '@grafana/ui'; import { Button, DataLinkBuiltInVars, stylesFactory, useTheme } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme, VariableOrigin } from '@grafana/data';
import { DataLinkConfig } from '../types'; import { DataLinkConfig } from '../types';
import { DataLink } from './DataLink'; import { DataLink } from './DataLink';
......
import React from 'react'; import React from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { Button, FormField, VariableSuggestion, DataLinkInput, stylesFactory } from '@grafana/ui'; import { Button, FormField, DataLinkInput, stylesFactory } from '@grafana/ui';
import { VariableSuggestion } from '@grafana/data';
import { DerivedFieldConfig } from '../types'; import { DerivedFieldConfig } from '../types';
const getStyles = stylesFactory(() => ({ const getStyles = stylesFactory(() => ({
......
import React, { useState } from 'react'; import React, { useState } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { Button, DataLinkBuiltInVars, stylesFactory, useTheme, VariableOrigin } from '@grafana/ui'; import { Button, DataLinkBuiltInVars, stylesFactory, useTheme } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme, VariableOrigin } from '@grafana/data';
import { DerivedFieldConfig } from '../types'; import { DerivedFieldConfig } from '../types';
import { DerivedField } from './DerivedField'; import { DerivedField } from './DerivedField';
import { DebugSection } from './DebugSection'; import { DebugSection } from './DebugSection';
......
...@@ -11,9 +11,15 @@ import { DataProcessor } from './data_processor'; ...@@ -11,9 +11,15 @@ import { DataProcessor } from './data_processor';
import { axesEditorComponent } from './axes_editor'; import { axesEditorComponent } from './axes_editor';
import config from 'app/core/config'; import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2'; import TimeSeries from 'app/core/time_series2';
import { VariableSuggestion } from '@grafana/ui';
import { getProcessedDataFrames } from 'app/features/dashboard/state/runRequest'; import { getProcessedDataFrames } from 'app/features/dashboard/state/runRequest';
import { getColorFromHexRgbOrName, PanelEvents, DataFrame, DataLink, DateTimeInput } from '@grafana/data'; import {
getColorFromHexRgbOrName,
PanelEvents,
DataFrame,
DataLink,
DateTimeInput,
VariableSuggestion,
} from '@grafana/data';
import { GraphContextMenuCtrl } from './GraphContextMenuCtrl'; import { GraphContextMenuCtrl } from './GraphContextMenuCtrl';
import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv'; import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment