PanelInspect: Interpolates variables in CSV file name (#31936)

* PanelInspect: Variables in CSV file name are interpolated

* Chore: fixes failing test
This commit is contained in:
Hugo Häggmark 2021-03-15 08:44:13 +01:00 committed by GitHub
parent 3e66328deb
commit 66177e9463
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 51 additions and 39 deletions

View File

@ -3,6 +3,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { import {
applyFieldOverrides, applyFieldOverrides,
applyRawFieldOverrides, applyRawFieldOverrides,
CSVConfig,
DataFrame, DataFrame,
DataTransformerID, DataTransformerID,
dateTimeFormat, dateTimeFormat,
@ -10,9 +11,8 @@ import {
SelectableValue, SelectableValue,
toCSV, toCSV,
transformDataFrame, transformDataFrame,
CSVConfig,
} from '@grafana/data'; } from '@grafana/data';
import { Button, Container, Field, HorizontalGroup, Spinner, Select, Switch, Table, VerticalGroup } from '@grafana/ui'; import { Button, Container, Field, HorizontalGroup, Select, Spinner, Switch, Table, VerticalGroup } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { getPanelInspectorStyles } from './styles'; import { getPanelInspectorStyles } from './styles';
@ -94,7 +94,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
type: 'text/csv;charset=utf-8', type: 'text/csv;charset=utf-8',
}); });
const transformation = transformId !== DataTransformerID.noop ? '-as-' + transformId.toLocaleLowerCase() : ''; const transformation = transformId !== DataTransformerID.noop ? '-as-' + transformId.toLocaleLowerCase() : '';
const fileName = `${panel.title}-data${transformation}-${dateTimeFormat(new Date())}.csv`; const fileName = `${panel.getDisplayTitle()}-data${transformation}-${dateTimeFormat(new Date())}.csv`;
saveAs(blob, fileName); saveAs(blob, fileName);
}; };

View File

@ -37,6 +37,7 @@ function setupTestContext(options: Partial<Props>) {
events: { subscribe: jest.fn() }, events: { subscribe: jest.fn() },
getQueryRunner: () => panelQueryRunner, getQueryRunner: () => panelQueryRunner,
getOptions: jest.fn(), getOptions: jest.fn(),
getDisplayTitle: jest.fn(),
} as unknown) as PanelModel, } as unknown) as PanelModel,
dashboard: ({ dashboard: ({
panelInitialized: jest.fn(), panelInitialized: jest.fn(),

View File

@ -28,7 +28,7 @@ export interface Props {
export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, data, alertState, dashboard }) => { export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, data, alertState, dashboard }) => {
const onCancelQuery = () => panel.getQueryRunner().cancelQuery(); const onCancelQuery = () => panel.getQueryRunner().cancelQuery();
const title = panel.replaceVariables(panel.title, {}, 'text'); const title = panel.getDisplayTitle();
const className = cx('panel-header', !(isViewing || isEditing) ? 'grid-drag-handle' : ''); const className = cx('panel-header', !(isViewing || isEditing) ? 'grid-drag-handle' : '');
return ( return (

View File

@ -1,13 +1,12 @@
import { PanelModel } from './PanelModel'; import { PanelModel } from './PanelModel';
import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks';
import { import {
DataLinkBuiltInVars,
FieldConfigProperty, FieldConfigProperty,
PanelData,
PanelProps, PanelProps,
standardEditorsRegistry, standardEditorsRegistry,
standardFieldConfigEditorRegistry, standardFieldConfigEditorRegistry,
PanelData,
DataLinkBuiltInVars,
VariableModel,
} from '@grafana/data'; } from '@grafana/data';
import { ComponentClass } from 'react'; import { ComponentClass } from 'react';
import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
@ -17,6 +16,7 @@ import { setTemplateSrv } from '@grafana/runtime';
import { variableAdapters } from '../../variables/adapters'; import { variableAdapters } from '../../variables/adapters';
import { createQueryVariableAdapter } from '../../variables/query/adapter'; import { createQueryVariableAdapter } from '../../variables/query/adapter';
import { mockStandardFieldConfigOptions } from '../../../../test/helpers/fieldConfig'; import { mockStandardFieldConfigOptions } from '../../../../test/helpers/fieldConfig';
import { queryBuilder } from 'app/features/variables/shared/testing/builders';
standardFieldConfigEditorRegistry.setInit(() => mockStandardFieldConfigOptions()); standardFieldConfigEditorRegistry.setInit(() => mockStandardFieldConfigOptions());
standardEditorsRegistry.setInit(() => mockStandardFieldConfigOptions()); standardEditorsRegistry.setInit(() => mockStandardFieldConfigOptions());
@ -28,16 +28,15 @@ setTimeSrv({
}), }),
} as any); } as any);
const getVariables = () => variablesMock;
const getVariableWithName = (name: string) => variablesMock.filter((v) => v.name === name)[0];
const getFilteredVariables = jest.fn();
setTemplateSrv( setTemplateSrv(
new TemplateSrv({ new TemplateSrv({
// @ts-ignore getVariables,
getVariables: () => { getVariableWithName,
return variablesMock; getFilteredVariables,
},
// @ts-ignore
getVariableWithName: (name: string) => {
return variablesMock.filter((v) => v.name === name)[0];
},
}) })
); );
@ -223,7 +222,7 @@ describe('PanelModel', () => {
it('should interpolate $__all_variables variable', () => { it('should interpolate $__all_variables variable', () => {
const out = model.replaceVariables(`/d/1?$${DataLinkBuiltInVars.includeVars}`); const out = model.replaceVariables(`/d/1?$${DataLinkBuiltInVars.includeVars}`);
expect(out).toBe('/d/1?var-test1=val1&var-test2=val2'); expect(out).toBe('/d/1?var-test1=val1&var-test2=val2&var-test3=Value%203&var-test4=A&var-test4=B');
}); });
it('should prefer the local variable value', () => { it('should prefer the local variable value', () => {
@ -328,7 +327,7 @@ describe('PanelModel', () => {
expect(model.showColumns).toBe(true); expect(model.showColumns).toBe(true);
}); });
it('should restore custom field config to what it was and preseve standard options', () => { it('should restore custom field config to what it was and preserve standard options', () => {
model.changePlugin(tablePlugin); model.changePlugin(tablePlugin);
expect(model.fieldConfig.defaults.custom.customProp).toBe(true); expect(model.fieldConfig.defaults.custom.customProp).toBe(true);
}); });
@ -438,30 +437,32 @@ describe('PanelModel', () => {
expect(model.getQueryRunner().getLastResult()).toBeDefined(); expect(model.getQueryRunner().getLastResult()).toBeDefined();
}); });
}); });
describe('getDisplayTitle', () => {
it('when called then it should interpolate singe value variables in title', () => {
const model = new PanelModel({
title: 'Single value variable [[test3]] ${test3} ${test3:percentencode}',
});
const title = model.getDisplayTitle();
expect(title).toEqual('Single value variable Value 3 Value 3 Value%203');
});
it('when called then it should interpolate multi value variables in title', () => {
const model = new PanelModel({
title: 'Multi value variable [[test4]] ${test4} ${test4:percentencode}',
});
const title = model.getDisplayTitle();
expect(title).toEqual('Multi value variable A + B A + B %7BA%2CB%7D');
});
});
}); });
}); });
const variablesMock = [ const variablesMock = [
{ queryBuilder().withId('test1').withName('test1').withCurrent('val1').build(),
type: 'query', queryBuilder().withId('test2').withName('test2').withCurrent('val2').build(),
name: 'test1', queryBuilder().withId('test3').withName('test3').withCurrent('Value 3').build(),
label: 'Test1', queryBuilder().withId('test4').withName('test4').withCurrent(['A', 'B']).build(),
hide: false,
current: { value: 'val1' },
skipUrlSync: false,
getValueForUrl: function () {
return 'val1';
},
} as VariableModel,
{
type: 'query',
name: 'test2',
label: 'Test2',
hide: false,
current: { value: 'val2' },
skipUrlSync: false,
getValueForUrl: function () {
return 'val2';
},
} as VariableModel,
]; ];

View File

@ -59,6 +59,7 @@ const notPersistedProperties: { [str: string]: boolean } = {
replaceVariables: true, replaceVariables: true,
editSourceId: true, editSourceId: true,
hasChanged: true, hasChanged: true,
getDisplayTitle: true,
}; };
// For angular panels we need to clean up properties when changing type // For angular panels we need to clean up properties when changing type
@ -102,6 +103,7 @@ const mustKeepProps: { [str: string]: boolean } = {
interval: true, interval: true,
replaceVariables: true, replaceVariables: true,
libraryPanel: true, libraryPanel: true,
getDisplayTitle: true,
}; };
const defaults: any = { const defaults: any = {
@ -551,6 +553,14 @@ export class PanelModel implements DataConfigSource {
getSavedId(): number { getSavedId(): number {
return this.editSourceId ?? this.id; return this.editSourceId ?? this.id;
} }
/*
* This is the title used when displaying the title in the UI so it will include any interpolated variables.
* If you need the raw title without interpolation use title property instead.
* */
getDisplayTitle(): string {
return this.replaceVariables(this.title, {}, 'text');
}
} }
function getPluginVersion(plugin: PanelPlugin): string { function getPluginVersion(plugin: PanelPlugin): string {