|
|
|
|
@ -4,17 +4,29 @@ import { |
|
|
|
|
DataFrame, |
|
|
|
|
DynamicConfigValue, |
|
|
|
|
FieldConfigOptionsRegistry, |
|
|
|
|
VariableSuggestionsScope, |
|
|
|
|
FieldConfigProperty, |
|
|
|
|
GrafanaTheme, |
|
|
|
|
VariableSuggestionsScope, |
|
|
|
|
} from '@grafana/data'; |
|
|
|
|
import { fieldMatchersUI, stylesFactory, useTheme, ValuePicker, selectThemeVariant } from '@grafana/ui'; |
|
|
|
|
import { |
|
|
|
|
Field, |
|
|
|
|
fieldMatchersUI, |
|
|
|
|
HorizontalGroup, |
|
|
|
|
Icon, |
|
|
|
|
IconButton, |
|
|
|
|
Label, |
|
|
|
|
stylesFactory, |
|
|
|
|
useTheme, |
|
|
|
|
ValuePicker, |
|
|
|
|
} from '@grafana/ui'; |
|
|
|
|
import { DynamicConfigValueEditor } from './DynamicConfigValueEditor'; |
|
|
|
|
|
|
|
|
|
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv'; |
|
|
|
|
import { css } from 'emotion'; |
|
|
|
|
import { FieldConfigItemHeaderTitle } from '@grafana/ui/src/components/FieldConfigs/FieldConfigItemHeaderTitle'; |
|
|
|
|
import { OptionsGroup } from './OptionsGroup'; |
|
|
|
|
|
|
|
|
|
interface OverrideEditorProps { |
|
|
|
|
name: string; |
|
|
|
|
data: DataFrame[]; |
|
|
|
|
override: ConfigOverrideRule; |
|
|
|
|
onChange: (config: ConfigOverrideRule) => void; |
|
|
|
|
@ -22,8 +34,28 @@ interface OverrideEditorProps { |
|
|
|
|
registry: FieldConfigOptionsRegistry; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export const OverrideEditor: React.FC<OverrideEditorProps> = ({ data, override, onChange, onRemove, registry }) => { |
|
|
|
|
const COLLECTION_STANDARD_PROPERTIES = [ |
|
|
|
|
FieldConfigProperty.Thresholds, |
|
|
|
|
FieldConfigProperty.Links, |
|
|
|
|
FieldConfigProperty.Mappings, |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
export const OverrideEditor: React.FC<OverrideEditorProps> = ({ |
|
|
|
|
name, |
|
|
|
|
data, |
|
|
|
|
override, |
|
|
|
|
onChange, |
|
|
|
|
onRemove, |
|
|
|
|
registry, |
|
|
|
|
}) => { |
|
|
|
|
const theme = useTheme(); |
|
|
|
|
const matcherUi = fieldMatchersUI.get(override.matcher.id); |
|
|
|
|
const styles = getStyles(theme); |
|
|
|
|
const matcherLabel = ( |
|
|
|
|
<Label category={['Matcher']} description={matcherUi.description}> |
|
|
|
|
{matcherUi.name} |
|
|
|
|
</Label> |
|
|
|
|
); |
|
|
|
|
const onMatcherConfigChange = useCallback( |
|
|
|
|
(matcherConfig: any) => { |
|
|
|
|
override.matcher.options = matcherConfig; |
|
|
|
|
@ -71,20 +103,34 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({ data, override, |
|
|
|
|
}; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const matcherUi = fieldMatchersUI.get(override.matcher.id); |
|
|
|
|
const styles = getStyles(theme); |
|
|
|
|
const renderOverrideTitle = (isExpanded: boolean) => { |
|
|
|
|
return ( |
|
|
|
|
<div> |
|
|
|
|
<HorizontalGroup justify="space-between"> |
|
|
|
|
<div>{name}</div> |
|
|
|
|
<IconButton name="trash-alt" onClick={onRemove} /> |
|
|
|
|
</HorizontalGroup> |
|
|
|
|
{!isExpanded && ( |
|
|
|
|
<div className={styles.overrideDetails}> |
|
|
|
|
Matcher <Icon name="angle-right" /> {matcherUi.name} <br /> |
|
|
|
|
{override.properties.length === 0 ? 'No' : override.properties.length} properties overriden |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div className={styles.wrapper}> |
|
|
|
|
<FieldConfigItemHeaderTitle onRemove={onRemove} title={matcherUi.name} description={matcherUi.description}> |
|
|
|
|
<div className={styles.matcherUi}> |
|
|
|
|
<matcherUi.component |
|
|
|
|
matcher={matcherUi.matcher} |
|
|
|
|
data={data} |
|
|
|
|
options={override.matcher.options} |
|
|
|
|
onChange={option => onMatcherConfigChange(option)} |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
</FieldConfigItemHeaderTitle> |
|
|
|
|
<OptionsGroup renderTitle={renderOverrideTitle}> |
|
|
|
|
<Field label={matcherLabel} description={matcherUi.description}> |
|
|
|
|
<matcherUi.component |
|
|
|
|
matcher={matcherUi.matcher} |
|
|
|
|
data={data} |
|
|
|
|
options={override.matcher.options} |
|
|
|
|
onChange={option => onMatcherConfigChange(option)} |
|
|
|
|
/> |
|
|
|
|
</Field> |
|
|
|
|
|
|
|
|
|
<div> |
|
|
|
|
{override.properties.map((p, j) => { |
|
|
|
|
const item = registry.getIfExists(p.id); |
|
|
|
|
@ -92,70 +138,56 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({ data, override, |
|
|
|
|
if (!item) { |
|
|
|
|
return <div>Unknown property: {p.id}</div>; |
|
|
|
|
} |
|
|
|
|
const isCollapsible = |
|
|
|
|
Array.isArray(p.value) || COLLECTION_STANDARD_PROPERTIES.includes(p.id as FieldConfigProperty); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div key={`${p.id}/${j}`}> |
|
|
|
|
<DynamicConfigValueEditor |
|
|
|
|
onChange={value => onDynamicConfigValueChange(j, value)} |
|
|
|
|
onRemove={() => onDynamicConfigValueRemove(j)} |
|
|
|
|
property={p} |
|
|
|
|
registry={registry} |
|
|
|
|
context={{ |
|
|
|
|
data, |
|
|
|
|
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope), |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
<DynamicConfigValueEditor |
|
|
|
|
key={`${p.id}/${j}`} |
|
|
|
|
isCollapsible={isCollapsible} |
|
|
|
|
onChange={value => onDynamicConfigValueChange(j, value)} |
|
|
|
|
onRemove={() => onDynamicConfigValueRemove(j)} |
|
|
|
|
property={p} |
|
|
|
|
registry={registry} |
|
|
|
|
context={{ |
|
|
|
|
data, |
|
|
|
|
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope), |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
); |
|
|
|
|
})} |
|
|
|
|
<div className={styles.propertyPickerWrapper}> |
|
|
|
|
<ValuePicker |
|
|
|
|
label="Set config property" |
|
|
|
|
icon="plus" |
|
|
|
|
options={configPropertiesOptions} |
|
|
|
|
variant={'link'} |
|
|
|
|
onChange={o => { |
|
|
|
|
onDynamicConfigValueAdd(o.value); |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
{override.matcher.options && ( |
|
|
|
|
<div className={styles.propertyPickerWrapper}> |
|
|
|
|
<ValuePicker |
|
|
|
|
label="Add override property" |
|
|
|
|
variant="secondary" |
|
|
|
|
size="md" |
|
|
|
|
icon="plus" |
|
|
|
|
options={configPropertiesOptions} |
|
|
|
|
onChange={o => { |
|
|
|
|
onDynamicConfigValueAdd(o.value); |
|
|
|
|
}} |
|
|
|
|
isFullWidth={false} |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</OptionsGroup> |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const getStyles = stylesFactory((theme: GrafanaTheme) => { |
|
|
|
|
const borderColor = selectThemeVariant( |
|
|
|
|
{ |
|
|
|
|
light: theme.palette.gray85, |
|
|
|
|
dark: theme.palette.dark9, |
|
|
|
|
}, |
|
|
|
|
theme.type |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const shadow = selectThemeVariant( |
|
|
|
|
{ |
|
|
|
|
light: theme.palette.gray85, |
|
|
|
|
dark: theme.palette.black, |
|
|
|
|
}, |
|
|
|
|
theme.type |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
wrapper: css` |
|
|
|
|
border: 1px dashed ${borderColor}; |
|
|
|
|
margin-bottom: ${theme.spacing.md}; |
|
|
|
|
transition: box-shadow 0.5s cubic-bezier(0.19, 1, 0.22, 1); |
|
|
|
|
box-shadow: none; |
|
|
|
|
&:hover { |
|
|
|
|
box-shadow: 0 0 10px ${shadow}; |
|
|
|
|
} |
|
|
|
|
`,
|
|
|
|
|
matcherUi: css` |
|
|
|
|
padding: ${theme.spacing.sm}; |
|
|
|
|
`,
|
|
|
|
|
propertyPickerWrapper: css` |
|
|
|
|
border-top: 1px solid ${borderColor}; |
|
|
|
|
margin-top: ${theme.spacing.formSpacingBase * 2}px; |
|
|
|
|
`,
|
|
|
|
|
overrideDetails: css` |
|
|
|
|
font-size: ${theme.typography.size.sm}; |
|
|
|
|
color: ${theme.colors.textWeak}; |
|
|
|
|
font-weight: ${theme.typography.weight.regular}; |
|
|
|
|
`,
|
|
|
|
|
}; |
|
|
|
|
}); |
|
|
|
|
|