FieldDisplay: Smarter naming of stat values when visualising row values (all values) in stat panels (#31704)

* StatPanels: Improving naming

* Finally making progress

* Removed default templaet

* Removed unused function
This commit is contained in:
Torkel Ödegaard
2021-03-05 10:42:37 +01:00
committed by GitHub
parent fdc6f2cc6f
commit dd1486eef6
2 changed files with 245 additions and 127 deletions

View File

@@ -203,6 +203,100 @@ describe('FieldDisplay', () => {
expect(result[3].display.text).toEqual(mappedValue);
});
});
describe('auto option', () => {
it('No string fields, single value', () => {
const options = createDisplayOptions({
reduceOptions: {
values: true,
calcs: [],
},
data: [
toDataFrame({
name: 'Series Name',
fields: [{ name: 'A', values: [10] }],
}),
],
});
const result = getFieldDisplayValues(options);
expect(result[0].display.title).toEqual('A');
expect(result[0].display.text).toEqual('10');
});
it('Single other string field', () => {
const options = createDisplayOptions({
reduceOptions: {
values: true,
calcs: [],
},
data: [
toDataFrame({
fields: [
{ name: 'Name', values: ['A', 'B'] },
{ name: 'Value', values: [10, 20] },
],
}),
],
});
const result = getFieldDisplayValues(options);
expect(result[0].display.title).toEqual('A');
expect(result[0].display.text).toEqual('10');
expect(result[1].display.title).toEqual('B');
expect(result[1].display.text).toEqual('20');
});
it('Single string field multiple value fields', () => {
const options = createDisplayOptions({
reduceOptions: {
values: true,
calcs: [],
},
data: [
toDataFrame({
fields: [
{ name: 'Name', values: ['A', 'B'] },
{ name: 'SensorA', values: [10, 20] },
{ name: 'SensorB', values: [10, 20] },
],
}),
],
});
const result = getFieldDisplayValues(options);
expect(result[0].display.title).toEqual('A SensorA');
expect(result[0].display.text).toEqual('10');
expect(result[1].display.title).toEqual('B SensorA');
expect(result[1].display.text).toEqual('20');
expect(result[2].display.title).toEqual('A SensorB');
expect(result[3].display.title).toEqual('B SensorB');
});
it('Multiple other string fields', () => {
const options = createDisplayOptions({
reduceOptions: {
values: true,
calcs: [],
},
data: [
toDataFrame({
fields: [
{ name: 'Country', values: ['Sweden', 'Norway'] },
{ name: 'City', values: ['Stockholm', 'Oslo'] },
{ name: 'Value', values: [10, 20] },
],
}),
],
});
const result = getFieldDisplayValues(options);
expect(result[0].display.title).toEqual('Sweden Stockholm');
expect(result[0].display.text).toEqual('10');
expect(result[1].display.title).toEqual('Norway Oslo');
expect(result[1].display.text).toEqual('20');
});
});
});
function createEmptyDisplayOptions(extend = {}): GetFieldDisplayValuesOptions {

View File

@@ -22,6 +22,7 @@ import { ScopedVars } from '../types/ScopedVars';
import { getTimeField } from '../dataframe/processDataFrame';
import { getFieldMatcher } from '../transformations';
import { FieldMatcherID } from '../transformations/matchers/ids';
import { getFieldDisplayName } from './fieldState';
/**
* Options for how to turn DataFrames into an array of display values
@@ -44,17 +45,6 @@ export const VAR_FIELD_LABELS = '__field.labels';
export const VAR_CALC = '__calc';
export const VAR_CELL_PREFIX = '__cell_'; // consistent with existing table templates
function getTitleTemplate(stats: string[]): string {
const parts: string[] = [];
if (stats.length > 1) {
parts.push('${' + VAR_CALC + '}');
}
parts.push('${' + VAR_FIELD_NAME + '}');
return parts.join(' ');
}
export interface FieldSparkline {
y: Field; // Y values
x?: Field; // if this does not exist, use the index
@@ -104,136 +94,144 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
}
);
if (options.data) {
// Field overrides are applied already
const data = options.data;
let hitLimit = false;
const limit = reduceOptions.limit ? reduceOptions.limit : DEFAULT_FIELD_DISPLAY_VALUES_LIMIT;
const scopedVars: ScopedVars = {};
const defaultDisplayName = getTitleTemplate(calcs);
const data = options.data ?? [];
const limit = reduceOptions.limit ? reduceOptions.limit : DEFAULT_FIELD_DISPLAY_VALUES_LIMIT;
const scopedVars: ScopedVars = {};
for (let s = 0; s < data.length && !hitLimit; s++) {
const series = data[s]; // Name is already set
let hitLimit = false;
const { timeField } = getTimeField(series);
const view = new DataFrameView(series);
for (let s = 0; s < data.length && !hitLimit; s++) {
const dataFrame = data[s]; // Name is already set
for (let i = 0; i < series.fields.length && !hitLimit; i++) {
const field = series.fields[i];
const fieldLinksSupplier = field.getLinks;
const { timeField } = getTimeField(dataFrame);
const view = new DataFrameView(dataFrame);
// To filter out time field, need an option for this
if (!fieldMatcher(field, series, data)) {
continue;
}
for (let i = 0; i < dataFrame.fields.length && !hitLimit; i++) {
const field = dataFrame.fields[i];
const fieldLinksSupplier = field.getLinks;
let config = field.config; // already set by the prepare task
// To filter out time field, need an option for this
if (!fieldMatcher(field, dataFrame, data)) {
continue;
}
if (field.state?.range) {
// Us the global min/max values
config = {
...config,
...field.state?.range,
};
}
let config = field.config; // already set by the prepare task
const displayName = field.config.displayName ?? defaultDisplayName;
if (field.state?.range) {
// Us the global min/max values
config = {
...config,
...field.state?.range,
};
}
const display =
field.display ??
getDisplayProcessor({
field,
theme: options.theme,
timeZone,
});
// const displayName = getFieldDisplayName(field, dataFrame, data);
const displayName = field.config.displayName ?? '';
// Show all rows
if (reduceOptions.values) {
const usesCellValues = displayName.indexOf(VAR_CELL_PREFIX) >= 0;
const display =
field.display ??
getDisplayProcessor({
field,
theme: options.theme,
timeZone,
});
for (let j = 0; j < field.values.length; j++) {
// Add all the row variables
if (usesCellValues) {
for (let k = 0; k < series.fields.length; k++) {
const f = series.fields[k];
const v = f.values.get(j);
scopedVars[VAR_CELL_PREFIX + k] = {
value: v,
text: toString(v),
};
}
}
// Show all rows
if (reduceOptions.values) {
const usesCellValues = displayName.indexOf(VAR_CELL_PREFIX) >= 0;
const displayValue = display(field.values.get(j));
displayValue.title = replaceVariables(displayName, {
...field.state?.scopedVars, // series and field scoped vars
...scopedVars,
});
values.push({
name: '',
field: config,
display: displayValue,
view,
colIndex: i,
rowIndex: j,
getLinks: fieldLinksSupplier
? () =>
fieldLinksSupplier({
valueRowIndex: j,
})
: () => [],
hasLinks: hasLinks(field),
});
if (values.length >= limit) {
hitLimit = true;
break;
}
}
} else {
const results = reduceField({
field,
reducers: calcs, // The stats to calculate
});
for (const calc of calcs) {
scopedVars[VAR_CALC] = { value: calc, text: calc };
const displayValue = display(results[calc]);
displayValue.title = replaceVariables(displayName, {
...field.state?.scopedVars, // series and field scoped vars
...scopedVars,
});
let sparkline: FieldSparkline | undefined = undefined;
if (options.sparkline) {
sparkline = {
y: series.fields[i],
x: timeField,
for (let j = 0; j < field.values.length; j++) {
// Add all the row variables
if (usesCellValues) {
for (let k = 0; k < dataFrame.fields.length; k++) {
const f = dataFrame.fields[k];
const v = f.values.get(j);
scopedVars[VAR_CELL_PREFIX + k] = {
value: v,
text: toString(v),
};
if (calc === ReducerID.last) {
sparkline.highlightIndex = sparkline.y.values.length - 1;
} else if (calc === ReducerID.first) {
sparkline.highlightIndex = 0;
}
}
values.push({
name: calc,
field: config,
display: displayValue,
sparkline,
view,
colIndex: i,
getLinks: fieldLinksSupplier
? () =>
fieldLinksSupplier({
calculatedValue: displayValue,
})
: () => [],
hasLinks: hasLinks(field),
});
}
const displayValue = display(field.values.get(j));
if (displayName !== '') {
displayValue.title = replaceVariables(displayName, {
...field.state?.scopedVars, // series and field scoped vars
...scopedVars,
});
} else {
displayValue.title = getSmartDisplayNameForRow(dataFrame, field, j);
}
values.push({
name: '',
field: config,
display: displayValue,
view,
colIndex: i,
rowIndex: j,
getLinks: fieldLinksSupplier
? () =>
fieldLinksSupplier({
valueRowIndex: j,
})
: () => [],
hasLinks: hasLinks(field),
});
if (values.length >= limit) {
hitLimit = true;
break;
}
}
} else {
const results = reduceField({
field,
reducers: calcs, // The stats to calculate
});
for (const calc of calcs) {
scopedVars[VAR_CALC] = { value: calc, text: calc };
const displayValue = display(results[calc]);
if (displayName !== '') {
displayValue.title = replaceVariables(displayName, {
...field.state?.scopedVars, // series and field scoped vars
...scopedVars,
});
} else {
displayValue.title = getFieldDisplayName(field, dataFrame, data);
}
let sparkline: FieldSparkline | undefined = undefined;
if (options.sparkline) {
sparkline = {
y: dataFrame.fields[i],
x: timeField,
};
if (calc === ReducerID.last) {
sparkline.highlightIndex = sparkline.y.values.length - 1;
} else if (calc === ReducerID.first) {
sparkline.highlightIndex = 0;
}
}
values.push({
name: calc,
field: config,
display: displayValue,
sparkline,
view,
colIndex: i,
getLinks: fieldLinksSupplier
? () =>
fieldLinksSupplier({
calculatedValue: displayValue,
})
: () => [],
hasLinks: hasLinks(field),
});
}
}
}
@@ -246,6 +244,32 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
return values;
};
function getSmartDisplayNameForRow(frame: DataFrame, field: Field, rowIndex: number): string {
let parts: string[] = [];
let otherNumericFields = 0;
for (const otherField of frame.fields) {
if (otherField === field) {
continue;
}
if (otherField.type === FieldType.string) {
const value = otherField.values.get(rowIndex) ?? '';
if (value.length > 0) {
parts.push(value);
}
} else if (otherField.type === FieldType.number) {
otherNumericFields++;
}
}
if (otherNumericFields || parts.length === 0) {
parts.push(getFieldDisplayName(field));
}
return parts.join(' ');
}
export function hasLinks(field: Field): boolean {
return field.config?.links?.length ? field.config.links.length > 0 : false;
}