mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NewPanelEdit: Move field defaults to own tab, and merge general and panel options to first options tab (#23350)
* use dropdown * keep padding the same * keep padding the same * Refactoring and moving to components * Updated * Alt names Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
3fae28be52
commit
3ec88a013f
@ -1,105 +0,0 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { PanelModel } from '../../state';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Forms, Select, DataLinksInlineEditor, Input } from '@grafana/ui';
|
||||
import { OptionsGroup } from './OptionsGroup';
|
||||
import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||
import { getVariables } from '../../../variables/state/selectors';
|
||||
|
||||
export const GeneralPanelOptions: FC<{
|
||||
panel: PanelModel;
|
||||
onPanelConfigChange: (configKey: string, value: any) => void;
|
||||
}> = ({ panel, onPanelConfigChange }) => {
|
||||
const linkVariablesSuggestions = useMemo(() => getPanelLinksVariableSuggestions(), []);
|
||||
|
||||
const variableOptions = getVariableOptions();
|
||||
const directionOptions = [
|
||||
{ label: 'Horizontal', value: 'h' },
|
||||
{ label: 'Vertical', value: 'v' },
|
||||
];
|
||||
|
||||
const maxPerRowOptions = [2, 3, 4, 6, 8, 12].map(value => ({ label: value.toString(), value }));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<OptionsGroup title="Panel settings">
|
||||
<Forms.Field label="Panel title">
|
||||
<Input defaultValue={panel.title} onBlur={e => onPanelConfigChange('title', e.currentTarget.value)} />
|
||||
</Forms.Field>
|
||||
<Forms.Field label="Description" description="Panel description supports markdown and links.">
|
||||
<Forms.TextArea
|
||||
defaultValue={panel.description}
|
||||
onBlur={e => onPanelConfigChange('description', e.currentTarget.value)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
<Forms.Field label="Transparent" description="Display panel without background.">
|
||||
<Forms.Switch
|
||||
value={panel.transparent}
|
||||
onChange={e => onPanelConfigChange('transparent', e.currentTarget.checked)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
</OptionsGroup>
|
||||
<OptionsGroup title="Panel links">
|
||||
<DataLinksInlineEditor
|
||||
links={panel.links}
|
||||
onChange={links => onPanelConfigChange('links', links)}
|
||||
suggestions={linkVariablesSuggestions}
|
||||
data={[]}
|
||||
/>
|
||||
</OptionsGroup>
|
||||
<OptionsGroup title="Panel repeats">
|
||||
<Forms.Field
|
||||
label="Repeat by variable"
|
||||
description="Repeat this panel for each value in the selected variable.
|
||||
This is not visible while in edit mode. You need to go back to dashboard and then update the variable or
|
||||
reload the dashboard."
|
||||
>
|
||||
<Select
|
||||
value={panel.repeat}
|
||||
onChange={value => onPanelConfigChange('repeat', value.value)}
|
||||
options={variableOptions}
|
||||
/>
|
||||
</Forms.Field>
|
||||
{panel.repeat && (
|
||||
<Forms.Field label="Repeat direction">
|
||||
<Forms.RadioButtonGroup
|
||||
options={directionOptions}
|
||||
value={panel.repeatDirection || 'h'}
|
||||
onChange={value => onPanelConfigChange('repeatDirection', value)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
)}
|
||||
|
||||
{panel.repeat && panel.repeatDirection === 'h' && (
|
||||
<Forms.Field label="Max per row">
|
||||
<Select
|
||||
options={maxPerRowOptions}
|
||||
value={panel.maxPerRow}
|
||||
onChange={value => onPanelConfigChange('maxPerRow', value.value)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
)}
|
||||
</OptionsGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getVariableOptions(): Array<SelectableValue<string>> {
|
||||
const options = getVariables().map((item: any) => {
|
||||
return { label: item.name, value: item.name };
|
||||
});
|
||||
|
||||
if (options.length === 0) {
|
||||
options.unshift({
|
||||
label: 'No template variables found',
|
||||
value: null,
|
||||
});
|
||||
}
|
||||
|
||||
options.unshift({
|
||||
label: 'Disable repeating',
|
||||
value: null,
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
@ -5,10 +5,11 @@ import { useTheme, Icon, stylesFactory } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
defaultToClosed?: boolean;
|
||||
}
|
||||
|
||||
export const OptionsGroup: FC<Props> = ({ title, children }) => {
|
||||
const [isExpanded, toggleExpand] = useState(true);
|
||||
export const OptionsGroup: FC<Props> = ({ title, children, defaultToClosed }) => {
|
||||
const [isExpanded, toggleExpand] = useState(defaultToClosed ? false : true);
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useState, CSSProperties } from 'react';
|
||||
import Transition from 'react-transition-group/Transition';
|
||||
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin } from '@grafana/data';
|
||||
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import {
|
||||
CustomScrollbar,
|
||||
@ -8,22 +8,22 @@ import {
|
||||
Tab,
|
||||
TabContent,
|
||||
TabsBar,
|
||||
Select,
|
||||
useTheme,
|
||||
Container,
|
||||
Icon,
|
||||
Input,
|
||||
} from '@grafana/ui';
|
||||
import { DefaultFieldConfigEditor, OverrideFieldConfigEditor } from './FieldConfigEditor';
|
||||
import { AngularPanelOptions } from './AngularPanelOptions';
|
||||
import { css } from 'emotion';
|
||||
import { GeneralPanelOptions } from './GeneralPanelOptions';
|
||||
import { PanelOptionsEditor } from './PanelOptionsEditor';
|
||||
import { PanelOptionsTab } from './PanelOptionsTab';
|
||||
import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton';
|
||||
|
||||
export const OptionsPaneContent: React.FC<{
|
||||
plugin?: PanelPlugin;
|
||||
plugin: PanelPlugin;
|
||||
panel: PanelModel;
|
||||
data: PanelData;
|
||||
width: number;
|
||||
dashboard: DashboardModel;
|
||||
onClose: () => void;
|
||||
onFieldConfigsChange: (config: FieldConfigSource) => void;
|
||||
@ -33,6 +33,7 @@ export const OptionsPaneContent: React.FC<{
|
||||
plugin,
|
||||
panel,
|
||||
data,
|
||||
width,
|
||||
onFieldConfigsChange,
|
||||
onPanelOptionsChanged,
|
||||
onPanelConfigChange,
|
||||
@ -41,7 +42,7 @@ export const OptionsPaneContent: React.FC<{
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme);
|
||||
const [activeTab, setActiveTab] = useState('defaults');
|
||||
const [activeTab, setActiveTab] = useState('options');
|
||||
const [isSearching, setSearchMode] = useState(false);
|
||||
|
||||
const renderFieldOptions = useCallback(
|
||||
@ -54,7 +55,6 @@ export const OptionsPaneContent: React.FC<{
|
||||
|
||||
return (
|
||||
<Container padding="md">
|
||||
{renderCustomPanelSettings(plugin)}
|
||||
<DefaultFieldConfigEditor
|
||||
config={fieldConfig}
|
||||
plugin={plugin}
|
||||
@ -88,49 +88,54 @@ export const OptionsPaneContent: React.FC<{
|
||||
[data, plugin, panel, onFieldConfigsChange]
|
||||
);
|
||||
|
||||
const renderCustomPanelSettings = useCallback(
|
||||
(plugin: PanelPlugin) => {
|
||||
const editors: JSX.Element[] = [];
|
||||
if (plugin.editor && panel) {
|
||||
editors.push(
|
||||
<div className={styles.legacyOptions} key="plugin custom panel settings">
|
||||
<plugin.editor
|
||||
data={data}
|
||||
options={panel.getOptions()}
|
||||
onOptionsChange={onPanelOptionsChanged}
|
||||
fieldConfig={panel.getFieldConfig()}
|
||||
onFieldConfigChange={onFieldConfigsChange}
|
||||
return (
|
||||
<div className={styles.panelOptionsPane}>
|
||||
{plugin && (
|
||||
<div className={styles.wrapper}>
|
||||
<TabsBar className={styles.tabsBar}>
|
||||
<TabsBarContent
|
||||
width={width}
|
||||
isSearching={isSearching}
|
||||
styles={styles}
|
||||
activeTab={activeTab}
|
||||
onClose={onClose}
|
||||
setSearchMode={setSearchMode}
|
||||
setActiveTab={setActiveTab}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// When editor created declaratively
|
||||
if (plugin.optionEditors && panel) {
|
||||
editors.push(
|
||||
<PanelOptionsEditor
|
||||
key="panel options"
|
||||
options={panel.getOptions()}
|
||||
onChange={onPanelOptionsChanged}
|
||||
plugin={plugin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (editors.length > 0) {
|
||||
return editors;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.legacyOptions}>
|
||||
<AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
<CustomScrollbar>
|
||||
{activeTab === 'options' && (
|
||||
<PanelOptionsTab
|
||||
panel={panel}
|
||||
plugin={plugin}
|
||||
dashboard={dashboard}
|
||||
data={data}
|
||||
onPanelConfigChange={onPanelConfigChange}
|
||||
onFieldConfigsChange={onFieldConfigsChange}
|
||||
onPanelOptionsChanged={onPanelOptionsChanged}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'defaults' && renderFieldOptions(plugin)}
|
||||
{activeTab === 'overrides' && renderFieldOverrideOptions(plugin)}
|
||||
</CustomScrollbar>
|
||||
</TabContent>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[data, plugin, panel, onFieldConfigsChange]
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSearchInput = useCallback(() => {
|
||||
export const TabsBarContent: React.FC<{
|
||||
width: number;
|
||||
isSearching: boolean;
|
||||
activeTab: string;
|
||||
styles: OptionsPaneStyles;
|
||||
onClose: () => void;
|
||||
setSearchMode: (mode: boolean) => void;
|
||||
setActiveTab: (tab: string) => void;
|
||||
}> = ({ width, isSearching, activeTab, onClose, setSearchMode, setActiveTab, styles }) => {
|
||||
if (isSearching) {
|
||||
const defaultStyles = {
|
||||
transition: 'width 50ms ease-in-out',
|
||||
width: '50%',
|
||||
@ -163,56 +168,66 @@ export const OptionsPaneContent: React.FC<{
|
||||
}}
|
||||
</Transition>
|
||||
);
|
||||
}, []);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.panelOptionsPane}>
|
||||
{plugin && (
|
||||
<div className={styles.wrapper}>
|
||||
<TabsBar className={styles.tabsBar}>
|
||||
{isSearching && renderSearchInput()}
|
||||
{!isSearching && (
|
||||
<>
|
||||
<Tab label="Options" active={activeTab === 'defaults'} onChangeTab={() => setActiveTab('defaults')} />
|
||||
<Tab
|
||||
label="Overrides"
|
||||
active={activeTab === 'overrides'}
|
||||
onChangeTab={() => setActiveTab('overrides')}
|
||||
/>
|
||||
<Tab label="General" active={activeTab === 'panel'} onChangeTab={() => setActiveTab('panel')} />
|
||||
<div className="flex-grow-1" />
|
||||
<div className={styles.tabsButton}>
|
||||
<DashNavButton
|
||||
icon="fa fa-search"
|
||||
tooltip="Search all options"
|
||||
classSuffix="search-options"
|
||||
onClick={() => setSearchMode(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.tabsButton}>
|
||||
<DashNavButton
|
||||
icon="fa fa-chevron-right"
|
||||
tooltip="Close options pane"
|
||||
classSuffix="close-options"
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
<CustomScrollbar>
|
||||
{activeTab === 'defaults' && renderFieldOptions(plugin)}
|
||||
{activeTab === 'overrides' && renderFieldOverrideOptions(plugin)}
|
||||
{activeTab === 'panel' && <GeneralPanelOptions panel={panel} onPanelConfigChange={onPanelConfigChange} />}
|
||||
</CustomScrollbar>
|
||||
</TabContent>
|
||||
<>
|
||||
{width < 377 ? (
|
||||
<div className="flex-grow-1">
|
||||
<Select
|
||||
options={tabSelections}
|
||||
value={tabSelections.find(v => v.value === activeTab)}
|
||||
onChange={v => {
|
||||
setActiveTab(v.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{tabSelections.map(item => {
|
||||
return (
|
||||
<Tab label={item.label} active={activeTab === item.value} onChangeTab={() => setActiveTab(item.value)} />
|
||||
);
|
||||
})}
|
||||
<div className="flex-grow-1" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.tabsButton}>
|
||||
<DashNavButton
|
||||
icon="fa fa-search"
|
||||
tooltip="Search all options"
|
||||
classSuffix="search-options"
|
||||
onClick={() => setSearchMode(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.tabsButton}>
|
||||
<DashNavButton
|
||||
icon="fa fa-chevron-right"
|
||||
tooltip="Close options pane"
|
||||
classSuffix="close-options"
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const tabSelections: Array<SelectableValue<string>> = [
|
||||
{
|
||||
label: 'Panel',
|
||||
value: 'options',
|
||||
},
|
||||
{
|
||||
label: 'Data',
|
||||
value: 'defaults',
|
||||
},
|
||||
{
|
||||
label: 'Overrides',
|
||||
value: 'overrides',
|
||||
},
|
||||
];
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
return {
|
||||
wrapper: css`
|
||||
@ -274,3 +289,5 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
type OptionsPaneStyles = ReturnType<typeof getStyles>;
|
||||
|
@ -30,11 +30,6 @@ import { VariableModel } from 'app/features/templating/types';
|
||||
import { getVariables } from 'app/features/variables/state/selectors';
|
||||
import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems';
|
||||
|
||||
enum Pane {
|
||||
Right,
|
||||
Top,
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
dashboard: DashboardModel;
|
||||
sourcePanel: PanelModel;
|
||||
@ -255,7 +250,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
renderOptionsPane() {
|
||||
const { plugin, dashboard, data, panel } = this.props;
|
||||
const { plugin, dashboard, data, panel, uiState } = this.props;
|
||||
|
||||
if (!plugin) {
|
||||
return <div />;
|
||||
@ -267,6 +262,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
dashboard={dashboard}
|
||||
data={data}
|
||||
panel={panel}
|
||||
width={uiState.rightPaneSize as number}
|
||||
onClose={this.onTogglePanelOptions}
|
||||
onFieldConfigsChange={this.onFieldConfigChange}
|
||||
onPanelOptionsChanged={this.onPanelOptionsChanged}
|
||||
@ -342,6 +338,11 @@ const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
||||
|
||||
export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEditorUnconnected);
|
||||
|
||||
enum Pane {
|
||||
Right,
|
||||
Top,
|
||||
}
|
||||
|
||||
/*
|
||||
* Styles
|
||||
*/
|
||||
|
@ -0,0 +1,165 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { PanelModel, DashboardModel } from '../../state';
|
||||
import { SelectableValue, PanelPlugin, FieldConfigSource, PanelData } from '@grafana/data';
|
||||
import { Forms, Select, DataLinksInlineEditor, Input } from '@grafana/ui';
|
||||
import { OptionsGroup } from './OptionsGroup';
|
||||
import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||
import { getVariables } from '../../../variables/state/selectors';
|
||||
import { PanelOptionsEditor } from './PanelOptionsEditor';
|
||||
import { AngularPanelOptions } from '../../panel_editor/AngularPanelOptions';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
plugin: PanelPlugin;
|
||||
data: PanelData;
|
||||
dashboard: DashboardModel;
|
||||
onPanelConfigChange: (configKey: string, value: any) => void;
|
||||
onPanelOptionsChanged: (options: any) => void;
|
||||
onFieldConfigsChange: (config: FieldConfigSource) => void;
|
||||
}
|
||||
|
||||
export const PanelOptionsTab: FC<Props> = ({
|
||||
panel,
|
||||
plugin,
|
||||
data,
|
||||
dashboard,
|
||||
onPanelConfigChange,
|
||||
onPanelOptionsChanged,
|
||||
onFieldConfigsChange,
|
||||
}) => {
|
||||
const elements: JSX.Element[] = [];
|
||||
const linkVariablesSuggestions = useMemo(() => getPanelLinksVariableSuggestions(), []);
|
||||
|
||||
const variableOptions = getVariableOptions();
|
||||
const directionOptions = [
|
||||
{ label: 'Horizontal', value: 'h' },
|
||||
{ label: 'Vertical', value: 'v' },
|
||||
];
|
||||
|
||||
const maxPerRowOptions = [2, 3, 4, 6, 8, 12].map(value => ({ label: value.toString(), value }));
|
||||
|
||||
// Fist common panel settings Title, description
|
||||
elements.push(
|
||||
<OptionsGroup title="Basic" key="basic settings">
|
||||
<Forms.Field label="Panel title">
|
||||
<Input defaultValue={panel.title} onBlur={e => onPanelConfigChange('title', e.currentTarget.value)} />
|
||||
</Forms.Field>
|
||||
<Forms.Field label="Description" description="Panel description supports markdown and links.">
|
||||
<Forms.TextArea
|
||||
defaultValue={panel.description}
|
||||
onBlur={e => onPanelConfigChange('description', e.currentTarget.value)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
<Forms.Field label="Transparent" description="Display panel without background.">
|
||||
<Forms.Switch
|
||||
value={panel.transparent}
|
||||
onChange={e => onPanelConfigChange('transparent', e.currentTarget.checked)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
</OptionsGroup>
|
||||
);
|
||||
|
||||
// Old legacy react editor
|
||||
if (plugin.editor && panel && !plugin.optionEditors) {
|
||||
elements.push(
|
||||
<OptionsGroup title="Display" key="legacy react editor">
|
||||
<plugin.editor
|
||||
data={data}
|
||||
options={panel.getOptions()}
|
||||
onOptionsChange={onPanelOptionsChanged}
|
||||
fieldConfig={panel.getFieldConfig()}
|
||||
onFieldConfigChange={onFieldConfigsChange}
|
||||
/>
|
||||
</OptionsGroup>
|
||||
);
|
||||
}
|
||||
|
||||
if (plugin.optionEditors && panel) {
|
||||
elements.push(
|
||||
<OptionsGroup title="Display" key="panel plugin options">
|
||||
<PanelOptionsEditor
|
||||
key="panel options"
|
||||
options={panel.getOptions()}
|
||||
onChange={onPanelOptionsChanged}
|
||||
plugin={plugin}
|
||||
/>
|
||||
</OptionsGroup>
|
||||
);
|
||||
}
|
||||
|
||||
if (plugin.angularPanelCtrl) {
|
||||
elements.push(
|
||||
<OptionsGroup title="Display" key="angular plugin editor">
|
||||
<AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />
|
||||
</OptionsGroup>
|
||||
);
|
||||
}
|
||||
|
||||
elements.push(
|
||||
<>
|
||||
<OptionsGroup title="Panel links" key="panel links" defaultToClosed={true}>
|
||||
<DataLinksInlineEditor
|
||||
links={panel.links}
|
||||
onChange={links => onPanelConfigChange('links', links)}
|
||||
suggestions={linkVariablesSuggestions}
|
||||
data={[]}
|
||||
/>
|
||||
</OptionsGroup>
|
||||
<OptionsGroup title="Panel repeats" key="panel repeats" defaultToClosed={true}>
|
||||
<Forms.Field
|
||||
label="Repeat by variable"
|
||||
description="Repeat this panel for each value in the selected variable.
|
||||
This is not visible while in edit mode. You need to go back to dashboard and then update the variable or
|
||||
reload the dashboard."
|
||||
>
|
||||
<Select
|
||||
value={panel.repeat}
|
||||
onChange={value => onPanelConfigChange('repeat', value.value)}
|
||||
options={variableOptions}
|
||||
/>
|
||||
</Forms.Field>
|
||||
{panel.repeat && (
|
||||
<Forms.Field label="Repeat direction">
|
||||
<Forms.RadioButtonGroup
|
||||
options={directionOptions}
|
||||
value={panel.repeatDirection || 'h'}
|
||||
onChange={value => onPanelConfigChange('repeatDirection', value)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
)}
|
||||
|
||||
{panel.repeat && panel.repeatDirection === 'h' && (
|
||||
<Forms.Field label="Max per row">
|
||||
<Select
|
||||
options={maxPerRowOptions}
|
||||
value={panel.maxPerRow}
|
||||
onChange={value => onPanelConfigChange('maxPerRow', value.value)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
)}
|
||||
</OptionsGroup>
|
||||
</>
|
||||
);
|
||||
|
||||
return <>{elements}</>;
|
||||
};
|
||||
|
||||
function getVariableOptions(): Array<SelectableValue<string>> {
|
||||
const options = getVariables().map((item: any) => {
|
||||
return { label: item.name, value: item.name };
|
||||
});
|
||||
|
||||
if (options.length === 0) {
|
||||
options.unshift({
|
||||
label: 'No template variables found',
|
||||
value: null,
|
||||
});
|
||||
}
|
||||
|
||||
options.unshift({
|
||||
label: 'Disable repeating',
|
||||
value: null,
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
Loading…
Reference in New Issue
Block a user