The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx

288 lines
8.3 KiB

import { css } from '@emotion/css';
import { cloneDeep } from 'lodash';
import * as React from 'react';
import {
FieldConfigOptionsRegistry,
SelectableValue,
isSystemOverride as isSystemOverrideGuard,
VariableSuggestionsScope,
DynamicConfigValue,
ConfigOverrideRule,
GrafanaTheme2,
fieldMatchers,
FieldConfigSource,
DataFrame,
} from '@grafana/data';
import { fieldMatchersUI, useStyles2, ValuePicker } from '@grafana/ui';
import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
import { DynamicConfigValueEditor } from './DynamicConfigValueEditor';
import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor';
import { OverrideCategoryTitle } from './OverrideCategoryTitle';
export function getFieldOverrideCategories(
fieldConfig: FieldConfigSource,
registry: FieldConfigOptionsRegistry,
data: DataFrame[],
searchQuery: string,
onFieldConfigsChange: (config: FieldConfigSource) => void
): OptionsPaneCategoryDescriptor[] {
const categories: OptionsPaneCategoryDescriptor[] = [];
const currentFieldConfig = fieldConfig;
if (!registry || registry.isEmpty()) {
return [];
}
const onOverrideChange = (index: number, override: ConfigOverrideRule) => {
let overrides = cloneDeep(currentFieldConfig.overrides);
overrides[index] = override;
onFieldConfigsChange({ ...currentFieldConfig, overrides });
};
const onOverrideRemove = (overrideIndex: number) => {
let overrides = cloneDeep(currentFieldConfig.overrides);
overrides.splice(overrideIndex, 1);
onFieldConfigsChange({ ...currentFieldConfig, overrides });
};
const onOverrideAdd = (value: SelectableValue<string>) => {
const info = fieldMatchers.get(value.value!);
if (!info) {
return;
}
onFieldConfigsChange({
...currentFieldConfig,
overrides: [
...currentFieldConfig.overrides,
{
matcher: {
id: info.id,
options: info.defaultOptions,
},
properties: [],
},
],
});
};
const context = {
data,
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
isOverride: true,
};
/**
* Main loop through all override rules
*/
for (let idx = 0; idx < currentFieldConfig.overrides.length; idx++) {
const override = currentFieldConfig.overrides[idx];
const overrideName = `Override ${idx + 1}`;
const matcherUi = fieldMatchersUI.get(override.matcher.id);
const configPropertiesOptions = getOverrideProperties(registry);
const isSystemOverride = isSystemOverrideGuard(override);
// A way to force open new override categories
const forceOpen = override.properties.length === 0 ? 1 : 0;
const category = new OptionsPaneCategoryDescriptor({
title: overrideName,
id: overrideName,
forceOpen,
renderTitle: function renderOverrideTitle(isExpanded: boolean) {
return (
<OverrideCategoryTitle
override={override}
isExpanded={isExpanded}
registry={registry}
overrideName={overrideName}
matcherUi={matcherUi}
onOverrideRemove={() => onOverrideRemove(idx)}
/>
);
},
});
const onMatcherConfigChange = (options: unknown) => {
override.matcher.options = options;
onOverrideChange(idx, override);
};
const onDynamicConfigValueAdd = (override: ConfigOverrideRule, value: SelectableValue<string>) => {
const registryItem = registry.get(value.value!);
const propertyConfig: DynamicConfigValue = {
id: registryItem.id,
value: registryItem.defaultValue,
};
const properties = override.properties ?? [];
properties.push(propertyConfig);
onOverrideChange(idx, { ...override, properties });
};
/**
* Add override matcher UI element
*/
category.addItem(
new OptionsPaneItemDescriptor({
title: matcherUi.name,
render: function renderMatcherUI() {
return (
<matcherUi.component
id={`${matcherUi.matcher.id}-${idx}`}
matcher={matcherUi.matcher}
data={data ?? []}
options={override.matcher.options}
onChange={onMatcherConfigChange}
/>
);
},
})
);
/**
* Loop through all override properties
*/
for (let propIdx = 0; propIdx < override.properties.length; propIdx++) {
const property = override.properties[propIdx];
const registryItemForProperty = registry.getIfExists(property.id);
if (!registryItemForProperty) {
continue;
}
const onPropertyChange = (value: DynamicConfigValue) => {
onOverrideChange(idx, {
...override,
properties: override.properties.map((prop, i) => {
if (i === propIdx) {
return { ...prop, value: value };
}
return prop;
}),
});
};
const onPropertyRemove = () => {
onOverrideChange(idx, {
...override,
properties: override.properties.filter((_, i) => i !== propIdx),
});
};
/**
* Add override property item
*/
category.addItem(
new OptionsPaneItemDescriptor({
title: registryItemForProperty.name,
skipField: true,
render: function renderPropertyEditor() {
return (
<DynamicConfigValueEditor
key={`${property.id}/${propIdx}`}
isSystemOverride={isSystemOverride}
onChange={onPropertyChange}
onRemove={onPropertyRemove}
property={property}
registry={registry}
context={context}
searchQuery={searchQuery}
/>
);
},
})
);
}
/**
* Add button that adds new overrides
*/
if (!isSystemOverride && override.matcher.options) {
category.addItem(
new OptionsPaneItemDescriptor({
title: '----------',
skipField: true,
render: function renderAddPropertyButton() {
return (
<ValuePicker
key="Add override property"
label="Add override property"
variant="secondary"
isFullWidth={true}
icon="plus"
menuPlacement="auto"
options={configPropertiesOptions}
onChange={(v) => onDynamicConfigValueAdd(override, v)}
/>
);
},
})
);
}
categories.push(category);
}
categories.push(
new OptionsPaneCategoryDescriptor({
title: 'add button',
id: 'add button',
customRender: function renderAddButton() {
return (
<AddOverrideButtonContainer key="Add override">
<ValuePicker
icon="plus"
label="Add field override"
variant="secondary"
menuPlacement="auto"
isFullWidth={true}
size="md"
options={fieldMatchersUI
.list()
.filter((o) => !o.excludeFromPicker)
.map<SelectableValue<string>>((i) => ({ label: i.name, value: i.id, description: i.description }))}
onChange={(value) => onOverrideAdd(value)}
/>
</AddOverrideButtonContainer>
);
},
})
);
return categories;
}
function getOverrideProperties(registry: FieldConfigOptionsRegistry) {
return registry
.list()
.filter((o) => !o.hideFromOverrides)
.map((item) => {
let label = item.name;
if (item.category) {
label = [...item.category, item.name].join(' > ');
}
return {
label,
value: item.id,
description: item.description,
};
});
}
function AddOverrideButtonContainer({ children }: { children: React.ReactNode }) {
const styles = useStyles2(getBorderTopStyles);
return <div className={styles}>{children}</div>;
}
function getBorderTopStyles(theme: GrafanaTheme2) {
return css({
borderTop: `1px solid ${theme.colors.border.weak}`,
padding: `${theme.spacing(2)}`,
display: 'flex',
});
}