mirror of
https://github.com/grafana/grafana.git
synced 2025-08-03 01:32:13 +08:00
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
This commit is contained in:
@ -52,3 +52,23 @@ export interface LinkModel<T> {
|
||||
export interface LinkModelSupplier<T extends object> {
|
||||
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',
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { MatcherConfig, FieldConfig, Field } from '../types';
|
||||
import { Registry, RegistryItem } from '../utils';
|
||||
import { ComponentType } from 'react';
|
||||
import { MatcherConfig, FieldConfig, Field, DataFrame, VariableSuggestion, VariableSuggestionsScope } from '../types';
|
||||
import { Registry, RegistryItem } from '../utils';
|
||||
import { InterpolateFunction } from './panel';
|
||||
import { DataFrame } from 'apache-arrow';
|
||||
|
||||
export interface DynamicConfigValue {
|
||||
prop: string;
|
||||
@ -26,18 +25,20 @@ export interface FieldConfigSource {
|
||||
export interface FieldConfigEditorProps<TValue, TSettings> {
|
||||
item: FieldPropertyEditorItem<TValue, TSettings>; // The property info
|
||||
value: TValue;
|
||||
context: FieldOverrideContext;
|
||||
onChange: (value?: TValue) => void;
|
||||
}
|
||||
|
||||
export interface FieldOverrideContext {
|
||||
field: Field;
|
||||
data: DataFrame;
|
||||
replaceVariables: InterpolateFunction;
|
||||
data: DataFrame[];
|
||||
field?: Field;
|
||||
replaceVariables?: InterpolateFunction;
|
||||
getSuggestions?: (scope?: VariableSuggestionsScope) => VariableSuggestion[];
|
||||
}
|
||||
|
||||
export interface FieldOverrideEditorProps<TValue, TSettings> {
|
||||
item: FieldPropertyEditorItem<TValue, TSettings>;
|
||||
value: any;
|
||||
value: TValue;
|
||||
context: FieldOverrideContext;
|
||||
onChange: (value?: any) => void;
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
import React, { ChangeEvent, useContext } from 'react';
|
||||
import { DataLink } from '@grafana/data';
|
||||
import { DataLink, VariableSuggestion, GrafanaTheme } from '@grafana/data';
|
||||
import { FormField, Switch } from '../index';
|
||||
import { VariableSuggestion } from './DataLinkSuggestions';
|
||||
import { css } from 'emotion';
|
||||
import { ThemeContext, stylesFactory } from '../../themes/index';
|
||||
import { DataLinkInput } from './DataLinkInput';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
interface DataLinkEditorProps {
|
||||
index: number;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useState, useMemo, useContext, useRef, RefObject, memo, useEffect } from 'react';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { VariableSuggestion, VariableOrigin, DataLinkSuggestions } from './DataLinkSuggestions';
|
||||
import { DataLinkSuggestions } from './DataLinkSuggestions';
|
||||
import { ThemeContext, DataLinkBuiltInVars, makeValue } from '../../index';
|
||||
import { SelectionReference } from './SelectionReference';
|
||||
import { Portal } from '../index';
|
||||
@ -14,7 +14,7 @@ import { css, cx } from 'emotion';
|
||||
import { SlatePrism } from '../../slate-plugins';
|
||||
import { SCHEMA } from '../../utils/slate';
|
||||
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);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { selectThemeVariant, ThemeContext } from '../../index';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { GrafanaTheme, VariableSuggestion } from '@grafana/data';
|
||||
import { css, cx } from 'emotion';
|
||||
import _ from 'lodash';
|
||||
import React, { useRef, useContext, useMemo } from 'react';
|
||||
@ -8,22 +8,6 @@ import { List } from '../index';
|
||||
import tinycolor from 'tinycolor2';
|
||||
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 {
|
||||
suggestions: VariableSuggestion[];
|
||||
activeIndex: number;
|
||||
|
@ -4,10 +4,10 @@ import React, { FC } from 'react';
|
||||
import Prism from 'prismjs';
|
||||
// Components
|
||||
import { css } from 'emotion';
|
||||
import { DataLink } from '@grafana/data';
|
||||
import { DataLink, VariableSuggestion } from '@grafana/data';
|
||||
import { Button } from '../index';
|
||||
import { DataLinkEditor } from './DataLinkEditor';
|
||||
import { VariableSuggestion } from './DataLinkSuggestions';
|
||||
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
|
||||
interface DataLinksEditorProps {
|
||||
|
@ -1,49 +0,0 @@
|
||||
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);
|
||||
},
|
||||
});
|
||||
});
|
225
packages/grafana-ui/src/components/FieldConfigs/links.tsx
Normal file
225
packages/grafana-ui/src/components/FieldConfigs/links.tsx
Normal file
@ -0,0 +1,225 @@
|
||||
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', () => {
|
||||
thresholds: {} as any,
|
||||
noValue: 'no value',
|
||||
unit: 'km/s',
|
||||
links: {} as any,
|
||||
};
|
||||
|
||||
it('make sure all fields have a valid name', () => {
|
||||
|
@ -1,4 +1,10 @@
|
||||
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 { NumberValueEditor, NumberOverrideEditor, numberOverrideProcessor, NumberFieldConfigSettings } from './number';
|
||||
import { UnitValueEditor, UnitOverrideEditor } from './units';
|
||||
@ -8,6 +14,7 @@ import {
|
||||
thresholdsOverrideProcessor,
|
||||
ThresholdsFieldConfigSettings,
|
||||
} from './thresholds';
|
||||
import { DataLinksValueEditor, DataLinksOverrideEditor, dataLinksOverrideProcessor } from './links';
|
||||
|
||||
const title: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
|
||||
id: 'title', // Match field properties
|
||||
@ -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>(
|
||||
() => {
|
||||
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';
|
||||
export * from './SingleStatShared/index';
|
||||
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 { DataLinkInput } from './DataLinks/DataLinkInput';
|
||||
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
|
||||
@ -118,7 +117,6 @@ export { Icon } from './Icon/Icon';
|
||||
export { Drawer } from './Drawer/Drawer';
|
||||
|
||||
// TODO: namespace!!
|
||||
export { FieldConfigEditor } from './FieldConfigs/FieldConfigEditor';
|
||||
export {
|
||||
StringValueEditor,
|
||||
StringOverrideEditor,
|
||||
@ -135,3 +133,5 @@ export {
|
||||
// Next-gen forms
|
||||
export { default as Forms } from './Forms';
|
||||
export { ValuePicker } from './ValuePicker/ValuePicker';
|
||||
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
|
||||
export { standardFieldConfigEditorRegistry } from './FieldConfigs/standardFieldConfigEditorRegistry';
|
||||
|
@ -6,13 +6,16 @@ import {
|
||||
DataFrame,
|
||||
FieldPropertyEditorItem,
|
||||
DynamicConfigValue,
|
||||
VariableSuggestionsScope,
|
||||
} from '@grafana/data';
|
||||
import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry';
|
||||
import Forms from '../Forms';
|
||||
import { fieldMatchersUI } from '../MatchersUI/fieldMatchersUI';
|
||||
import { ControlledCollapse } from '../Collapse/Collapse';
|
||||
import { ValuePicker } from '../ValuePicker/ValuePicker';
|
||||
|
||||
import {
|
||||
standardFieldConfigEditorRegistry,
|
||||
Forms,
|
||||
fieldMatchersUI,
|
||||
ControlledCollapse,
|
||||
ValuePicker,
|
||||
} from '@grafana/ui';
|
||||
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||
interface Props {
|
||||
config: FieldConfigSource;
|
||||
custom?: FieldConfigEditorRegistry; // custom fields
|
||||
@ -89,12 +92,21 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
||||
};
|
||||
|
||||
renderEditor(item: FieldPropertyEditorItem, custom: boolean) {
|
||||
const { data } = this.props;
|
||||
const config = this.props.config.defaults;
|
||||
const value = custom ? (config.custom ? config.custom[item.id] : undefined) : (config as any)[item.id];
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@ -169,7 +181,11 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
||||
this.onDynamicConfigValueChange(i, j, value);
|
||||
}}
|
||||
item={item}
|
||||
context={{} as any}
|
||||
context={{
|
||||
data,
|
||||
getSuggestions: (scope?: VariableSuggestionsScope) =>
|
||||
getDataLinksVariableSuggestions(data, scope),
|
||||
}}
|
||||
/>
|
||||
</Forms.Field>
|
||||
);
|
@ -1,4 +1,4 @@
|
||||
import React, { PureComponent, CSSProperties } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
GrafanaTheme,
|
||||
FieldConfigSource,
|
||||
@ -9,18 +9,10 @@ import {
|
||||
SelectableValue,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
stylesFactory,
|
||||
Forms,
|
||||
FieldConfigEditor,
|
||||
CustomScrollbar,
|
||||
selectThemeVariant,
|
||||
ControlledCollapse,
|
||||
} from '@grafana/ui';
|
||||
import { stylesFactory, Forms, CustomScrollbar, selectThemeVariant, ControlledCollapse } from '@grafana/ui';
|
||||
import { css, cx } from 'emotion';
|
||||
import config from 'app/core/config';
|
||||
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 { DashboardModel } from '../../state/DashboardModel';
|
||||
@ -36,6 +28,8 @@ import { DisplayMode, displayModes } from './types';
|
||||
import { PanelEditorTabs } from './PanelEditorTabs';
|
||||
import { DashNavTimeControls } from '../DashNav/DashNavTimeControls';
|
||||
import { LocationState, CoreEvents } from 'app/types';
|
||||
import { calculatePanelSize } from './utils';
|
||||
import { FieldConfigEditor } from './FieldConfigEditor';
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
const handleColor = selectThemeVariant(
|
||||
@ -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) => ({
|
||||
location: state.location,
|
||||
});
|
||||
|
@ -0,0 +1,26 @@
|
||||
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';
|
||||
import { sanitizeUrl } from 'app/core/utils/text';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { VariableSuggestion, VariableOrigin, DataLinkBuiltInVars } from '@grafana/ui';
|
||||
import { DataLinkBuiltInVars } from '@grafana/ui';
|
||||
import {
|
||||
DataLink,
|
||||
KeyValue,
|
||||
@ -16,6 +16,9 @@ import {
|
||||
ScopedVars,
|
||||
FieldType,
|
||||
Field,
|
||||
VariableSuggestion,
|
||||
VariableOrigin,
|
||||
VariableSuggestionsScope,
|
||||
} from '@grafana/data';
|
||||
|
||||
const timeRangeVars = [
|
||||
@ -180,21 +183,33 @@ const getDataFrameVars = (dataFrames: DataFrame[]) => {
|
||||
return suggestions;
|
||||
};
|
||||
|
||||
export const getDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => {
|
||||
export const getDataLinksVariableSuggestions = (
|
||||
dataFrames: DataFrame[],
|
||||
scope?: VariableSuggestionsScope
|
||||
): VariableSuggestion[] => {
|
||||
const valueTimeVar = {
|
||||
value: `${DataLinkBuiltInVars.valueTime}`,
|
||||
label: 'Time',
|
||||
documentation: 'Time value of the clicked datapoint (in ms epoch)',
|
||||
origin: VariableOrigin.Value,
|
||||
};
|
||||
return [
|
||||
...seriesVars,
|
||||
...getFieldVars(dataFrames),
|
||||
...valueVars,
|
||||
valueTimeVar,
|
||||
...getDataFrameVars(dataFrames),
|
||||
...getPanelLinksVariableSuggestions(),
|
||||
];
|
||||
const includeValueVars = scope === VariableSuggestionsScope.Values;
|
||||
|
||||
return includeValueVars
|
||||
? [
|
||||
...seriesVars,
|
||||
...getFieldVars(dataFrames),
|
||||
...valueVars,
|
||||
valueTimeVar,
|
||||
...getDataFrameVars(dataFrames),
|
||||
...getPanelLinksVariableSuggestions(),
|
||||
]
|
||||
: [
|
||||
...seriesVars,
|
||||
...getFieldVars(dataFrames),
|
||||
...getDataFrameVars(dataFrames),
|
||||
...getPanelLinksVariableSuggestions(),
|
||||
];
|
||||
};
|
||||
|
||||
export const getCalculationValueDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
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';
|
||||
|
||||
const getStyles = stylesFactory(() => ({
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { Button, DataLinkBuiltInVars, stylesFactory, useTheme, VariableOrigin } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { Button, DataLinkBuiltInVars, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { GrafanaTheme, VariableOrigin } from '@grafana/data';
|
||||
import { DataLinkConfig } from '../types';
|
||||
import { DataLink } from './DataLink';
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
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';
|
||||
|
||||
const getStyles = stylesFactory(() => ({
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { Button, DataLinkBuiltInVars, stylesFactory, useTheme, VariableOrigin } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { Button, DataLinkBuiltInVars, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { GrafanaTheme, VariableOrigin } from '@grafana/data';
|
||||
import { DerivedFieldConfig } from '../types';
|
||||
import { DerivedField } from './DerivedField';
|
||||
import { DebugSection } from './DebugSection';
|
||||
|
@ -11,9 +11,15 @@ import { DataProcessor } from './data_processor';
|
||||
import { axesEditorComponent } from './axes_editor';
|
||||
import config from 'app/core/config';
|
||||
import TimeSeries from 'app/core/time_series2';
|
||||
import { VariableSuggestion } from '@grafana/ui';
|
||||
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 { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
|
||||
|
Reference in New Issue
Block a user