mirror of
https://github.com/grafana/grafana.git
synced 2024-12-02 05:29:42 -06:00
Value mapping/add icon support (#44503)
Co-authored-by: Ryan McKinley <ryantxu@users.noreply.github.com> Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
e62e9904ee
commit
11aa6a3e8f
@ -13,7 +13,7 @@ import {
|
||||
ValueMappingFieldConfigSettings,
|
||||
valueMappingsOverrideProcessor,
|
||||
} from '@grafana/data';
|
||||
import { ValueMappingsValueEditor } from 'app/features/dimensions/editors/ValueMappingsEditor/mappings';
|
||||
import { ValueMappingsEditor } from 'app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor';
|
||||
import { ThresholdsValueEditor } from 'app/features/dimensions/editors/ThresholdsEditor/thresholds';
|
||||
|
||||
/**
|
||||
@ -31,7 +31,7 @@ export const getAllOptionEditors = () => {
|
||||
id: 'mappings',
|
||||
name: 'Mappings',
|
||||
description: 'Allows defining value mappings',
|
||||
editor: ValueMappingsValueEditor as any,
|
||||
editor: ValueMappingsEditor as any,
|
||||
};
|
||||
|
||||
const thresholds: StandardEditorsRegistryItem<ThresholdsConfig> = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DataFrame, Field, getFieldColorModeForField, getScaleCalculator, GrafanaTheme2 } from '@grafana/data';
|
||||
import { DataFrame, Field, getDisplayProcessor, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data';
|
||||
import { ColorDimensionConfig, DimensionSupplier } from './types';
|
||||
import { findField, getLastNotNullFieldValue } from './utils';
|
||||
|
||||
@ -20,7 +20,7 @@ export function getColorDimensionForField(
|
||||
theme: GrafanaTheme2
|
||||
): DimensionSupplier<string> {
|
||||
if (!field) {
|
||||
const v = theme.visualization.getColorByName(config.fixed) ?? 'grey';
|
||||
const v = theme.visualization.getColorByName(config.fixed ?? 'grey');
|
||||
return {
|
||||
isAssumed: Boolean(config.field?.length) || !config.fixed,
|
||||
fixed: v,
|
||||
@ -28,23 +28,28 @@ export function getColorDimensionForField(
|
||||
get: (i) => v,
|
||||
};
|
||||
}
|
||||
|
||||
// Use the expensive color calculation by value
|
||||
const mode = getFieldColorModeForField(field);
|
||||
if (!mode.isByValue) {
|
||||
const fixed = mode.getCalculator(field, theme)(0, 0);
|
||||
if (mode.isByValue || field.config.mappings?.length) {
|
||||
const disp = getDisplayProcessor({ field, theme });
|
||||
const getColor = (value: any): string => {
|
||||
return disp(value).color ?? '#ccc';
|
||||
};
|
||||
|
||||
return {
|
||||
fixed,
|
||||
value: () => fixed,
|
||||
get: (i) => fixed,
|
||||
field,
|
||||
get: (index: number): string => getColor(field.values.get(index)),
|
||||
value: () => getColor(getLastNotNullFieldValue(field)),
|
||||
};
|
||||
}
|
||||
const scale = getScaleCalculator(field, theme);
|
||||
|
||||
// Typically series or fixed color (does not depend on value)
|
||||
const fixed = mode.getCalculator(field, theme)(0, 0);
|
||||
return {
|
||||
get: (i) => {
|
||||
const val = field.values.get(i);
|
||||
return scale(val).color;
|
||||
},
|
||||
fixed,
|
||||
value: () => fixed,
|
||||
get: (i) => fixed,
|
||||
field,
|
||||
value: () => scale(getLastNotNullFieldValue(field)).color,
|
||||
};
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export const ColorDimensionEditor: FC<StandardEditorProps<ColorDimensionConfig,
|
||||
field,
|
||||
});
|
||||
} else {
|
||||
const fixed = value.fixed ?? defaultColor;
|
||||
const fixed = value?.fixed ?? defaultColor;
|
||||
onChange({
|
||||
...value,
|
||||
field: undefined,
|
||||
|
@ -3,7 +3,13 @@ import { FieldNamePickerConfigSettings, StandardEditorProps, StandardEditorsRegi
|
||||
import { InlineField, InlineFieldRow, RadioButtonGroup } from '@grafana/ui';
|
||||
import { FieldNamePicker } from '@grafana/ui/src/components/MatchersUI/FieldNamePicker';
|
||||
|
||||
import { MediaType, ResourceDimensionConfig, ResourceDimensionMode, ResourceDimensionOptions } from '../types';
|
||||
import {
|
||||
MediaType,
|
||||
ResourceDimensionConfig,
|
||||
ResourceDimensionMode,
|
||||
ResourceDimensionOptions,
|
||||
ResourcePickerSize,
|
||||
} from '../types';
|
||||
import { getPublicOrAbsoluteUrl, ResourceFolderName } from '..';
|
||||
import { ResourcePicker } from './ResourcePicker';
|
||||
|
||||
@ -102,6 +108,7 @@ export const ResourceDimensionEditor: FC<
|
||||
name={niceName(value?.fixed) ?? ''}
|
||||
mediaType={mediaType}
|
||||
folderName={folderName}
|
||||
size={ResourcePickerSize.NORMAL}
|
||||
/>
|
||||
)}
|
||||
{mode === ResourceDimensionMode.Mapping && (
|
||||
|
@ -1,34 +1,83 @@
|
||||
import React, { createRef } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { Button, InlineField, InlineFieldRow, Input, Popover, PopoverController, useStyles2 } from '@grafana/ui';
|
||||
import {
|
||||
Button,
|
||||
InlineField,
|
||||
InlineFieldRow,
|
||||
Input,
|
||||
LinkButton,
|
||||
Popover,
|
||||
PopoverController,
|
||||
useStyles2,
|
||||
useTheme2,
|
||||
} from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import SVG from 'react-inlinesvg';
|
||||
|
||||
import { MediaType, ResourceFolderName } from '../types';
|
||||
import { MediaType, ResourceFolderName, ResourcePickerSize } from '../types';
|
||||
import { closePopover } from '@grafana/ui/src/utils/closePopover';
|
||||
import { ResourcePickerPopover } from './ResourcePickerPopover';
|
||||
import { getPublicOrAbsoluteUrl } from '../resource';
|
||||
|
||||
interface Props {
|
||||
onChange: (value?: string) => void;
|
||||
mediaType: MediaType;
|
||||
folderName: ResourceFolderName;
|
||||
size: ResourcePickerSize;
|
||||
onClear?: (event: React.MouseEvent) => void;
|
||||
value?: string; //img/icons/unicons/0-plus.svg
|
||||
src?: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
onChange: (value?: string) => void;
|
||||
onClear: (event: React.MouseEvent) => void;
|
||||
mediaType: MediaType;
|
||||
folderName: ResourceFolderName;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export const ResourcePicker = (props: Props) => {
|
||||
const { value, src, name, placeholder, onChange, onClear, mediaType, folderName } = props;
|
||||
const { value, src, name, placeholder, onChange, onClear, mediaType, folderName, size, color } = props;
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
const theme = useTheme2();
|
||||
|
||||
const pickerTriggerRef = createRef<any>();
|
||||
const popoverElement = (
|
||||
<ResourcePickerPopover onChange={onChange} value={value} mediaType={mediaType} folderName={folderName} />
|
||||
);
|
||||
|
||||
let sanitizedSrc = src;
|
||||
if (!sanitizedSrc && value) {
|
||||
sanitizedSrc = getPublicOrAbsoluteUrl(value);
|
||||
}
|
||||
|
||||
const colorStyle = color && {
|
||||
fill: theme.visualization.getColorByName(color),
|
||||
};
|
||||
|
||||
const renderSmallResourcePicker = () => {
|
||||
if (value && sanitizedSrc) {
|
||||
return <SVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />;
|
||||
} else {
|
||||
return (
|
||||
<LinkButton variant="primary" fill="text" size="sm">
|
||||
Set icon
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const renderNormalResourcePicker = () => (
|
||||
<InlineFieldRow>
|
||||
<InlineField label={null} grow>
|
||||
<Input
|
||||
value={name}
|
||||
placeholder={placeholder}
|
||||
readOnly={true}
|
||||
prefix={sanitizedSrc && <SVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />}
|
||||
suffix={<Button icon="times" variant="secondary" fill="text" size="sm" onClick={onClear} />}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
);
|
||||
|
||||
return (
|
||||
<PopoverController content={popoverElement}>
|
||||
{(showPopper, hidePopper, popperProps) => {
|
||||
@ -45,18 +94,9 @@ export const ResourcePicker = (props: Props) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div ref={pickerTriggerRef} onClick={showPopper}>
|
||||
<InlineFieldRow className={styles.pointer}>
|
||||
<InlineField label={null} grow>
|
||||
<Input
|
||||
value={name}
|
||||
placeholder={placeholder}
|
||||
readOnly={true}
|
||||
prefix={src && <SVG src={src} className={styles.icon} />}
|
||||
suffix={<Button icon="times" variant="secondary" fill="text" size="sm" onClick={onClear} />}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<div ref={pickerTriggerRef} onClick={showPopper} className={styles.pointer}>
|
||||
{size === ResourcePickerSize.SMALL && renderSmallResourcePicker()}
|
||||
{size === ResourcePickerSize.NORMAL && renderNormalResourcePicker()}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -76,6 +116,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
max-width: 25px;
|
||||
width: 25px;
|
||||
`,
|
||||
});
|
||||
|
@ -3,6 +3,8 @@ import { GrafanaTheme2, MappingType, SpecialValueMatch, SelectableValue, ValueMa
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { css } from '@emotion/css';
|
||||
import { useStyles2, Icon, Select, HorizontalGroup, ColorPicker, IconButton, Input, Button } from '@grafana/ui';
|
||||
import { ResourcePicker } from '../ResourcePicker';
|
||||
import { ResourcePickerSize, ResourceFolderName, MediaType } from '../../types';
|
||||
|
||||
export interface ValueMappingEditRowModel {
|
||||
type: MappingType;
|
||||
@ -21,9 +23,10 @@ interface Props {
|
||||
onChange: (index: number, mapping: ValueMappingEditRowModel) => void;
|
||||
onRemove: (index: number) => void;
|
||||
onDuplicate: (index: number) => void;
|
||||
showIconPicker?: boolean;
|
||||
}
|
||||
|
||||
export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDuplicate: onDuplicate }: Props) {
|
||||
export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDuplicate, showIconPicker }: Props) {
|
||||
const { key, result } = mapping;
|
||||
const styles = useStyles2(getStyles);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
@ -63,6 +66,18 @@ export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDupl
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeIcon = (icon?: string) => {
|
||||
update((mapping) => {
|
||||
mapping.result.icon = icon;
|
||||
});
|
||||
};
|
||||
|
||||
const onClearIcon = () => {
|
||||
update((mapping) => {
|
||||
mapping.result.icon = undefined;
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateMatchValue = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
update((mapping) => {
|
||||
mapping.key = event.currentTarget.value;
|
||||
@ -183,6 +198,24 @@ export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDupl
|
||||
</ColorPicker>
|
||||
)}
|
||||
</td>
|
||||
{showIconPicker && (
|
||||
<td className={styles.textAlignCenter}>
|
||||
<HorizontalGroup spacing="sm" justify="center">
|
||||
<ResourcePicker
|
||||
onChange={onChangeIcon}
|
||||
onClear={onClearIcon}
|
||||
value={result.icon}
|
||||
size={ResourcePickerSize.SMALL}
|
||||
folderName={ResourceFolderName.Icon}
|
||||
mediaType={MediaType.Icon}
|
||||
color={result.color}
|
||||
/>
|
||||
{result.icon && (
|
||||
<IconButton name="times" onClick={onClearIcon} tooltip="Remove icon" tooltipPlacement="top" />
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
</td>
|
||||
)}
|
||||
<td className={styles.textAlignCenter}>
|
||||
<HorizontalGroup spacing="sm">
|
||||
<IconButton name="copy" onClick={() => onDuplicate(index)} data-testid="duplicate-value-mapping" />
|
||||
|
@ -26,6 +26,8 @@ const setup = (spy?: any, propOverrides?: object) => {
|
||||
},
|
||||
},
|
||||
],
|
||||
item: {} as any,
|
||||
context: {} as any,
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
@ -39,4 +41,23 @@ describe('Render', () => {
|
||||
const button = screen.getByText('Edit value mappings');
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render icon picker when icon exists and icon setting is set to true', () => {
|
||||
const propOverrides = {
|
||||
item: { settings: { icon: true } },
|
||||
value: [
|
||||
{
|
||||
type: MappingType.ValueToText,
|
||||
options: {
|
||||
'20': { text: 'Ok', icon: 'test' },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
setup({}, propOverrides);
|
||||
|
||||
const iconPicker = screen.getByTestId('iconPicker');
|
||||
|
||||
expect(iconPicker).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -1,16 +1,20 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { GrafanaTheme2, MappingType, ValueMapping } from '@grafana/data';
|
||||
import { GrafanaTheme2, MappingType, StandardEditorProps, ValueMapping } from '@grafana/data';
|
||||
import { css } from '@emotion/css';
|
||||
import { buildEditRowModels, editModelToSaveModel, ValueMappingsEditorModal } from './ValueMappingsEditorModal';
|
||||
import { useStyles2, VerticalGroup, Icon, ColorPicker, Button, Modal } from '@grafana/ui';
|
||||
import { MediaType, ResourceFolderName, ResourcePickerSize } from '../../types';
|
||||
import { ResourcePicker } from '../ResourcePicker';
|
||||
|
||||
export interface Props {
|
||||
value: ValueMapping[];
|
||||
onChange: (valueMappings: ValueMapping[]) => void;
|
||||
export interface Props extends StandardEditorProps<ValueMapping[], any, any> {
|
||||
showIcon?: boolean;
|
||||
}
|
||||
|
||||
export const ValueMappingsEditor = React.memo(({ value, onChange }: Props) => {
|
||||
export const ValueMappingsEditor = React.memo((props: Props) => {
|
||||
const { value, onChange, item } = props;
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
const showIconPicker = item.settings?.icon;
|
||||
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
||||
const onCloseEditor = useCallback(() => {
|
||||
setIsEditorOpen(false);
|
||||
@ -26,6 +30,14 @@ export const ValueMappingsEditor = React.memo(({ value, onChange }: Props) => {
|
||||
[rows, onChange]
|
||||
);
|
||||
|
||||
const onChangeIcon = useCallback(
|
||||
(icon: string | undefined, index: number) => {
|
||||
rows[index].result.icon = icon;
|
||||
onChange(editModelToSaveModel(rows));
|
||||
},
|
||||
[rows, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<VerticalGroup>
|
||||
<table className={styles.compactTable}>
|
||||
@ -46,15 +58,27 @@ export const ValueMappingsEditor = React.memo(({ value, onChange }: Props) => {
|
||||
<Icon name="arrow-right" />
|
||||
</td>
|
||||
<td>{row.result.text}</td>
|
||||
<td>
|
||||
{row.result.color && (
|
||||
{row.result.color && (
|
||||
<td>
|
||||
<ColorPicker
|
||||
color={row.result.color}
|
||||
onChange={(color) => onChangeColor(color, rowIndex)}
|
||||
enableNamedColors={true}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</td>
|
||||
)}
|
||||
{showIconPicker && row.result.icon && (
|
||||
<td data-testid="iconPicker">
|
||||
<ResourcePicker
|
||||
onChange={(icon) => onChangeIcon(icon, rowIndex)}
|
||||
value={row.result.icon}
|
||||
size={ResourcePickerSize.SMALL}
|
||||
folderName={ResourceFolderName.Icon}
|
||||
mediaType={MediaType.Icon}
|
||||
color={row.result.color}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@ -71,7 +95,12 @@ export const ValueMappingsEditor = React.memo(({ value, onChange }: Props) => {
|
||||
className={styles.modal}
|
||||
closeOnBackdropClick={false}
|
||||
>
|
||||
<ValueMappingsEditorModal value={value} onChange={onChange} onClose={onCloseEditor} />
|
||||
<ValueMappingsEditorModal
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onClose={onCloseEditor}
|
||||
showIconPicker={showIconPicker}
|
||||
/>
|
||||
</Modal>
|
||||
</VerticalGroup>
|
||||
);
|
||||
|
@ -9,9 +9,10 @@ export interface Props {
|
||||
value: ValueMapping[];
|
||||
onChange: (valueMappings: ValueMapping[]) => void;
|
||||
onClose: () => void;
|
||||
showIconPicker?: boolean;
|
||||
}
|
||||
|
||||
export function ValueMappingsEditorModal({ value, onChange, onClose }: Props) {
|
||||
export function ValueMappingsEditorModal({ value, onChange, onClose, showIconPicker }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const [rows, updateRows] = useState<ValueMappingEditRowModel[]>([]);
|
||||
|
||||
@ -98,6 +99,7 @@ export function ValueMappingsEditorModal({ value, onChange, onClose }: Props) {
|
||||
</th>
|
||||
<th style={{ textAlign: 'left' }}>Display text</th>
|
||||
<th style={{ width: '10%' }}>Color</th>
|
||||
{showIconPicker && <th style={{ width: '10%' }}>Icon</th>}
|
||||
<th style={{ width: '1%' }}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -113,6 +115,7 @@ export function ValueMappingsEditorModal({ value, onChange, onClose }: Props) {
|
||||
onChange={onChangeMapping}
|
||||
onRemove={onRemoveRow}
|
||||
onDuplicate={onDuplicateMapping}
|
||||
showIconPicker={showIconPicker}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
@ -240,38 +243,40 @@ export function editModelToSaveModel(rows: ValueMappingEditRowModel[]) {
|
||||
export function buildEditRowModels(value: ValueMapping[]) {
|
||||
const editRows: ValueMappingEditRowModel[] = [];
|
||||
|
||||
for (const mapping of value) {
|
||||
switch (mapping.type) {
|
||||
case MappingType.ValueToText:
|
||||
for (const key of Object.keys(mapping.options)) {
|
||||
if (value) {
|
||||
for (const mapping of value) {
|
||||
switch (mapping.type) {
|
||||
case MappingType.ValueToText:
|
||||
for (const key of Object.keys(mapping.options)) {
|
||||
editRows.push({
|
||||
type: mapping.type,
|
||||
result: mapping.options[key],
|
||||
key,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case MappingType.RangeToText:
|
||||
editRows.push({
|
||||
type: mapping.type,
|
||||
result: mapping.options[key],
|
||||
key,
|
||||
result: mapping.options.result,
|
||||
from: mapping.options.from ?? 0,
|
||||
to: mapping.options.to ?? 0,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case MappingType.RangeToText:
|
||||
editRows.push({
|
||||
type: mapping.type,
|
||||
result: mapping.options.result,
|
||||
from: mapping.options.from ?? 0,
|
||||
to: mapping.options.to ?? 0,
|
||||
});
|
||||
break;
|
||||
case MappingType.RegexToText:
|
||||
editRows.push({
|
||||
type: mapping.type,
|
||||
result: mapping.options.result,
|
||||
pattern: mapping.options.pattern,
|
||||
});
|
||||
break;
|
||||
case MappingType.SpecialValue:
|
||||
editRows.push({
|
||||
type: mapping.type,
|
||||
result: mapping.options.result,
|
||||
specialMatch: mapping.options.match ?? SpecialValueMatch.Null,
|
||||
});
|
||||
break;
|
||||
case MappingType.RegexToText:
|
||||
editRows.push({
|
||||
type: mapping.type,
|
||||
result: mapping.options.result,
|
||||
pattern: mapping.options.pattern,
|
||||
});
|
||||
break;
|
||||
case MappingType.SpecialValue:
|
||||
editRows.push({
|
||||
type: mapping.type,
|
||||
result: mapping.options.result,
|
||||
specialMatch: mapping.options.match ?? SpecialValueMatch.Null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FieldConfigEditorProps, ValueMapping, ValueMappingFieldConfigSettings } from '@grafana/data';
|
||||
import { ValueMappingsEditor } from '../ValueMappingsEditor/ValueMappingsEditor';
|
||||
|
||||
export class ValueMappingsValueEditor extends React.PureComponent<
|
||||
FieldConfigEditorProps<ValueMapping[], ValueMappingFieldConfigSettings>
|
||||
> {
|
||||
constructor(props: FieldConfigEditorProps<ValueMapping[], ValueMappingFieldConfigSettings>) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onChange } = this.props;
|
||||
let value = this.props.value;
|
||||
if (!value) {
|
||||
value = [];
|
||||
}
|
||||
|
||||
return <ValueMappingsEditor value={value} onChange={onChange} />;
|
||||
}
|
||||
}
|
@ -47,9 +47,14 @@ export function getResourceDimension(
|
||||
};
|
||||
}
|
||||
|
||||
const getIcon = (value: any): string => {
|
||||
const disp = field.display!;
|
||||
return getPublicOrAbsoluteUrl(disp(value).icon ?? '');
|
||||
};
|
||||
|
||||
return {
|
||||
field,
|
||||
get: field.values.get,
|
||||
value: () => getLastNotNullFieldValue(field),
|
||||
get: (index: number): string => getIcon(field.values.get(index)),
|
||||
value: () => getIcon(getLastNotNullFieldValue(field)),
|
||||
};
|
||||
}
|
||||
|
@ -127,3 +127,8 @@ export enum PickerTabType {
|
||||
Folder = 'folder',
|
||||
URL = 'url',
|
||||
}
|
||||
|
||||
export enum ResourcePickerSize {
|
||||
SMALL = 'small',
|
||||
NORMAL = 'normal',
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
|
||||
import { IconPanel } from './IconPanel';
|
||||
import { defaultPanelOptions, PanelOptions } from './models.gen';
|
||||
@ -8,7 +8,15 @@ import { CanvasElementOptions } from 'app/features/canvas';
|
||||
|
||||
export const plugin = new PanelPlugin<PanelOptions>(IconPanel)
|
||||
.setNoPadding() // extend to panel edges
|
||||
.useFieldConfig()
|
||||
.useFieldConfig({
|
||||
standardOptions: {
|
||||
[FieldConfigProperty.Mappings]: {
|
||||
settings: {
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.setPanelOptions((builder) => {
|
||||
builder.addNestedOptions<CanvasElementOptions<IconConfig>>({
|
||||
category: ['Icon'],
|
||||
|
Loading…
Reference in New Issue
Block a user