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/state/PanelModel.ts

418 lines
9.9 KiB

// Libraries
import _ from 'lodash';
6 years ago
// Utils
import { Emitter } from 'app/core/utils/emitter';
6 years ago
import { getNextRefIdChar } from 'app/core/utils/query';
6 years ago
// Types
import {
DataQuery,
DataQueryResponseData,
PanelPlugin,
PanelEvents,
DataLink,
DataTransformerConfig,
ScopedVars,
} from '@grafana/data';
import { AngularComponent } from '@grafana/runtime';
import config from 'app/core/config';
import { PanelQueryRunner } from './PanelQueryRunner';
import { eventFactory } from '@grafana/data';
import { take } from 'rxjs/operators';
export const panelAdded = eventFactory<PanelModel | undefined>('panel-added');
export const panelRemoved = eventFactory<PanelModel | undefined>('panel-removed');
export const angularPanelUpdated = eventFactory('panel-angular-panel-updated');
export interface GridPos {
x: number;
y: number;
w: number;
h: number;
static?: boolean;
}
const notPersistedProperties: { [str: string]: boolean } = {
events: true,
fullscreen: true,
isEditing: true,
isInView: true,
isNewEdit: true,
hasRefreshed: true,
cachedPluginOptions: true,
plugin: true,
queryRunner: true,
angularPanel: true,
restoreModel: true,
};
// For angular panels we need to clean up properties when changing type
// To make sure the change happens without strange bugs happening when panels use same
// named property with different type / value expectations
// This is not required for react panels
const mustKeepProps: { [str: string]: boolean } = {
id: true,
gridPos: true,
type: true,
title: true,
scopedVars: true,
repeat: true,
repeatIteration: true,
repeatPanelId: true,
repeatDirection: true,
repeatedByRow: true,
minSpan: true,
collapsed: true,
panels: true,
targets: true,
datasource: true,
timeFrom: true,
timeShift: true,
hideTimeOverride: true,
description: true,
links: true,
fullscreen: true,
isEditing: true,
hasRefreshed: true,
events: true,
cacheTimeout: true,
cachedPluginOptions: true,
transparent: true,
pluginVersion: true,
queryRunner: true,
transformations: true,
};
const defaults: any = {
gridPos: { x: 0, y: 0, h: 3, w: 6 },
targets: [{ refId: 'A' }],
cachedPluginOptions: {},
transparent: false,
};
export class PanelModel {
id: number;
gridPos: GridPos;
type: string;
title: string;
alert?: any;
6 years ago
scopedVars?: ScopedVars;
repeat?: string;
repeatIteration?: number;
repeatPanelId?: number;
repeatDirection?: string;
repeatedByRow?: boolean;
maxPerRow?: number;
8 years ago
collapsed?: boolean;
8 years ago
panels?: any;
soloMode?: boolean;
targets: DataQuery[];
transformations?: DataTransformerConfig[];
datasource: string;
thresholds?: any;
pluginVersion?: string;
snapshotData?: DataQueryResponseData[];
timeFrom?: any;
timeShift?: any;
hideTimeOverride?: any;
6 years ago
options: {
[key: string]: any;
};
maxDataPoints?: number;
interval?: string;
description?: string;
Graph: Add data links feature (click on graph) (#17267) * WIP: initial panel links editor * WIP: Added dashboard migration to new panel drilldown link schema * Make link_srv interpolate new variables * Fix failing tests * Drilldown: Add context menu to graph viz (#17284) * Add simple context menu for adding graph annotations and showing drilldown links * Close graph context menu when user start scrolling * Move context menu component to grafana/ui * Make graph context menu appear on click, use cmd/ctrl click for quick annotations * Move graph context menu controller to separate file * Drilldown: datapoint variables interpolation (#17328) * Add simple context menu for adding graph annotations and showing drilldown links * Close graph context menu when user start scrolling * Move context menu component to grafana/ui * Make graph context menu appear on click, use cmd/ctrl click for quick annotations * Add util for absolute time range transformation * Add series name and datapoint timestamp interpolation * Rename drilldown link variables tot snake case, use const values instead of strings in tests * Bring LinkSrv.getPanelLinkAnchorInfo for compatibility reasons and add deprecation warning * Rename seriesLabel to seriesName * Drilldown: use separate editors for panel and series links (#17355) * Use correct target ini context menu links * Rename PanelLinksEditor to DrilldownLinksEditor and mote it to grafana/ui * Expose DrilldownLinksEditor as an angular directive * Enable visualization specifix drilldown links * Props interfaces rename * Drilldown: Add variables suggestion and syntax highlighting for drilldown link editor (#17391) * Add variables suggestion in drilldown link editor * Enable prism * Fix backspace not working * Move slate value helpers to grafana/ui * Add syntax higlighting for links input * Rename drilldown link components to data links * Add template variabe suggestions * Bugfix * Fix regexp not working in Firefox * Display correct links in panel header corner * bugfix * bugfix * Bugfix * Context menu UI tweaks * Use data link terminology instead of drilldown * DataLinks: changed autocomplete syntax * Use singular form for data link * Use the same syntax higlighting for built-in and template variables in data links editor * UI improvements to context menu * UI review tweaks * Tweak layout of data link editor * Fix vertical spacing * Remove data link header in context menu * Remove pointer cursor from series label in context menu * Fix variable selection on click * DataLinks: migrations for old links * Update docs about data links * Use value time instead of time range when interpolating datapoint timestamp * Remove not used util * Update docs * Moved icon a bit more down * Interpolate value ts only when using __value_time variable * Bring href property back to LinkModel * Add any type annotations * Fix TS error on slate's Value type * minor changes
6 years ago
links?: DataLink[];
transparent: boolean;
// non persisted
fullscreen: boolean;
isEditing: boolean;
isInView: boolean;
isNewEdit: boolean;
hasRefreshed: boolean;
events: Emitter;
cacheTimeout?: any;
cachedPluginOptions?: any;
legend?: { show: boolean };
plugin?: PanelPlugin;
angularPanel?: AngularComponent;
private queryRunner?: PanelQueryRunner;
constructor(model: any) {
this.events = new Emitter();
// should not be part of defaults as defaults are removed in save model and
// this should not be removed in save model as exporter needs to templatize it
this.datasource = null;
this.restoreModel(model);
}
/** Given a persistened PanelModel restores property values */
restoreModel = (model: any) => {
// copy properties from persisted model
for (const property in model) {
(this as any)[property] = model[property];
}
// defaults
_.defaultsDeep(this, _.cloneDeep(defaults));
// queries must have refId
this.ensureQueryIds();
};
ensureQueryIds() {
if (this.targets && _.isArray(this.targets)) {
for (const query of this.targets) {
if (!query.refId) {
6 years ago
query.refId = getNextRefIdChar(this.targets);
}
}
}
}
getOptions() {
return this.options;
}
updateOptions(options: object) {
this.options = options;
this.render();
}
getSaveModel() {
const model: any = {};
for (const property in this) {
if (notPersistedProperties[property] || !this.hasOwnProperty(property)) {
continue;
}
if (_.isEqual(this[property], defaults[property])) {
continue;
}
model[property] = _.cloneDeep(this[property]);
}
return model;
}
setViewMode(fullscreen: boolean, isEditing: boolean) {
this.fullscreen = fullscreen;
this.isEditing = isEditing;
this.events.emit(PanelEvents.viewModeChanged);
}
updateGridPos(newPos: GridPos) {
let sizeChanged = false;
if (this.gridPos.w !== newPos.w || this.gridPos.h !== newPos.h) {
sizeChanged = true;
}
this.gridPos.x = newPos.x;
this.gridPos.y = newPos.y;
this.gridPos.w = newPos.w;
this.gridPos.h = newPos.h;
if (sizeChanged) {
this.events.emit(PanelEvents.panelSizeChanged);
}
}
resizeDone() {
this.events.emit(PanelEvents.panelSizeChanged);
}
refresh() {
this.hasRefreshed = true;
this.events.emit(PanelEvents.refresh);
}
render() {
if (!this.hasRefreshed) {
this.refresh();
} else {
this.events.emit(PanelEvents.render);
}
}
initialized() {
this.events.emit(PanelEvents.panelInitialized);
}
private getOptionsToRemember() {
return Object.keys(this).reduce((acc, property) => {
if (notPersistedProperties[property] || mustKeepProps[property]) {
return acc;
}
return {
...acc,
[property]: (this as any)[property],
};
}, {});
}
private restorePanelOptions(pluginId: string) {
const prevOptions = this.cachedPluginOptions[pluginId] || {};
Object.keys(prevOptions).map(property => {
(this as any)[property] = prevOptions[property];
});
}
private applyPluginOptionDefaults(plugin: PanelPlugin) {
if (plugin.angularConfigCtrl) {
return;
}
this.options = _.mergeWith({}, plugin.defaults, this.options || {}, (objValue: any, srcValue: any): any => {
if (_.isArray(srcValue)) {
return srcValue;
}
});
}
pluginLoaded(plugin: PanelPlugin) {
this.plugin = plugin;
if (plugin.panel && plugin.onPanelMigration) {
const version = getPluginVersion(plugin);
if (version !== this.pluginVersion) {
this.options = plugin.onPanelMigration(this);
this.pluginVersion = version;
}
}
this.applyPluginOptionDefaults(plugin);
}
changePlugin(newPlugin: PanelPlugin) {
const pluginId = newPlugin.meta.id;
const oldOptions: any = this.getOptionsToRemember();
const oldPluginId = this.type;
const wasAngular = !!this.plugin.angularPanelCtrl;
if (this.angularPanel) {
this.setAngularPanel(undefined);
}
// remove panel type specific options
for (const key of _.keys(this)) {
if (mustKeepProps[key]) {
continue;
}
delete (this as any)[key];
}
this.cachedPluginOptions[oldPluginId] = oldOptions;
this.restorePanelOptions(pluginId);
// Let panel plugins inspect options from previous panel and keep any that it can use
if (newPlugin.onPanelTypeChanged) {
let old: any = {};
if (wasAngular) {
old = { angular: oldOptions };
} else if (oldOptions && oldOptions.options) {
old = oldOptions.options;
}
this.options = this.options || {};
Object.assign(this.options, newPlugin.onPanelTypeChanged(this.options, oldPluginId, old));
}
// switch
this.type = pluginId;
this.plugin = newPlugin;
this.applyPluginOptionDefaults(newPlugin);
if (newPlugin.onPanelMigration) {
this.pluginVersion = getPluginVersion(newPlugin);
}
}
addQuery(query?: Partial<DataQuery>) {
query = query || { refId: 'A' };
6 years ago
query.refId = getNextRefIdChar(this.targets);
this.targets.push(query as DataQuery);
}
changeQuery(query: DataQuery, index: number) {
// ensure refId is maintained
query.refId = this.targets[index].refId;
// update query in array
this.targets = this.targets.map((item, itemIndex) => {
if (itemIndex === index) {
return query;
}
return item;
});
}
getEditClone() {
const clone = new PanelModel(this.getSaveModel());
clone.queryRunner = new PanelQueryRunner();
// This will send the last result to the new runner
this.getQueryRunner()
.getData()
.pipe(take(1))
.subscribe(val => clone.queryRunner.pipeDataToSubject(val));
clone.isNewEdit = true;
return clone;
}
getQueryRunner(): PanelQueryRunner {
if (!this.queryRunner) {
QueryProcessing: Observable query interface and RxJS for query & stream processing (#18899) * I needed to learn some rxjs and understand this more, so just playing around * Updated * Removed all the complete calls * Refactoring * StreamHandler -> observable start * progress * simple singal works * Handle update time range * added error handling * wrap old function * minor changes * handle data format in the subscribe function * Use replay subject to return last value to subscribers * Set loading state after no response in 50ms * added missing file * updated comment * Added cancelation of network requests * runRequest: Added unit test scenario framework * Progress on tests * minor refactor of unit tests * updated test * removed some old code * Shared queries work again, and also became so much simplier * unified query and observe methods * implict any fix * Fixed closed subject issue * removed comment * Use last returned data for loading state * WIP: Explore to runRequest makover step1 * Minor progress * Minor progress on explore and runRequest * minor progress * Things are starting to work in explore * Updated prometheus to use new observable query response, greatly simplified code * Revert refId change * Found better solution for key/refId/requestId problem * use observable with loki * tests compile * fix loki query prep * Explore: correct first response handling * Refactorings * Refactoring * Explore: Fixes LoadingState and GraphResults between runs (#18986) * Refactor: Adds state to DataQueryResponse * Fix: Fixes so we do not empty results before new data arrives Fixes: #17409 * Transformations work * observable test data * remove single() from loki promise * Fixed comment * Explore: Fixes failing Loki and Prometheus unit tests (#18995) * Tests: Makes datasource tests work again * Fix: Fixes loki datasource so highligthing works * Chore: Runs Prettier * Fixed query runner tests * Delay loading state indication to 200ms * Fixed test * fixed unit tests * Clear cached calcs * Fixed bug getProcesedDataFrames * Fix the correct test is a better idea * Fix: Fixes so queries in Explore are only run if Graph/Table is shown (#19000) * Fix: Fixes so queries in Explore are only run if Graph/Table is shown Fixes: #18618 * Refactor: Removes unnecessary condition * PanelData: provide legacy data only when needed (#19018) * no legacy * invert logic... now compiles * merge getQueryResponseData and getDataRaw * update comment about query editor * use single getData() function * only send legacy when it is used in explore * pre process rather than post process * pre process rather than post process * Minor refactoring * Add missing tags to test datasource response * MixedDatasource: Adds query observable pattern to MixedDatasource (#19037) * start mixed datasource * Refactor: Refactors into observable parttern * Tests: Fixes tests * Tests: Removes console.log * Refactor: Adds unique requestId
6 years ago
this.queryRunner = new PanelQueryRunner();
this.setTransformations(this.transformations);
}
return this.queryRunner;
}
hasTitle() {
return this.title && this.title.length > 0;
}
QueryProcessing: Observable query interface and RxJS for query & stream processing (#18899) * I needed to learn some rxjs and understand this more, so just playing around * Updated * Removed all the complete calls * Refactoring * StreamHandler -> observable start * progress * simple singal works * Handle update time range * added error handling * wrap old function * minor changes * handle data format in the subscribe function * Use replay subject to return last value to subscribers * Set loading state after no response in 50ms * added missing file * updated comment * Added cancelation of network requests * runRequest: Added unit test scenario framework * Progress on tests * minor refactor of unit tests * updated test * removed some old code * Shared queries work again, and also became so much simplier * unified query and observe methods * implict any fix * Fixed closed subject issue * removed comment * Use last returned data for loading state * WIP: Explore to runRequest makover step1 * Minor progress * Minor progress on explore and runRequest * minor progress * Things are starting to work in explore * Updated prometheus to use new observable query response, greatly simplified code * Revert refId change * Found better solution for key/refId/requestId problem * use observable with loki * tests compile * fix loki query prep * Explore: correct first response handling * Refactorings * Refactoring * Explore: Fixes LoadingState and GraphResults between runs (#18986) * Refactor: Adds state to DataQueryResponse * Fix: Fixes so we do not empty results before new data arrives Fixes: #17409 * Transformations work * observable test data * remove single() from loki promise * Fixed comment * Explore: Fixes failing Loki and Prometheus unit tests (#18995) * Tests: Makes datasource tests work again * Fix: Fixes loki datasource so highligthing works * Chore: Runs Prettier * Fixed query runner tests * Delay loading state indication to 200ms * Fixed test * fixed unit tests * Clear cached calcs * Fixed bug getProcesedDataFrames * Fix the correct test is a better idea * Fix: Fixes so queries in Explore are only run if Graph/Table is shown (#19000) * Fix: Fixes so queries in Explore are only run if Graph/Table is shown Fixes: #18618 * Refactor: Removes unnecessary condition * PanelData: provide legacy data only when needed (#19018) * no legacy * invert logic... now compiles * merge getQueryResponseData and getDataRaw * update comment about query editor * use single getData() function * only send legacy when it is used in explore * pre process rather than post process * pre process rather than post process * Minor refactoring * Add missing tags to test datasource response * MixedDatasource: Adds query observable pattern to MixedDatasource (#19037) * start mixed datasource * Refactor: Refactors into observable parttern * Tests: Fixes tests * Tests: Removes console.log * Refactor: Adds unique requestId
6 years ago
isAngularPlugin(): boolean {
return this.plugin && !!this.plugin.angularPanelCtrl;
}
destroy() {
this.events.removeAllListeners();
if (this.queryRunner) {
this.queryRunner.destroy();
this.queryRunner = null;
}
if (this.angularPanel) {
this.angularPanel.destroy();
}
}
setTransformations(transformations: DataTransformerConfig[]) {
this.transformations = transformations;
QueryProcessing: Observable query interface and RxJS for query & stream processing (#18899) * I needed to learn some rxjs and understand this more, so just playing around * Updated * Removed all the complete calls * Refactoring * StreamHandler -> observable start * progress * simple singal works * Handle update time range * added error handling * wrap old function * minor changes * handle data format in the subscribe function * Use replay subject to return last value to subscribers * Set loading state after no response in 50ms * added missing file * updated comment * Added cancelation of network requests * runRequest: Added unit test scenario framework * Progress on tests * minor refactor of unit tests * updated test * removed some old code * Shared queries work again, and also became so much simplier * unified query and observe methods * implict any fix * Fixed closed subject issue * removed comment * Use last returned data for loading state * WIP: Explore to runRequest makover step1 * Minor progress * Minor progress on explore and runRequest * minor progress * Things are starting to work in explore * Updated prometheus to use new observable query response, greatly simplified code * Revert refId change * Found better solution for key/refId/requestId problem * use observable with loki * tests compile * fix loki query prep * Explore: correct first response handling * Refactorings * Refactoring * Explore: Fixes LoadingState and GraphResults between runs (#18986) * Refactor: Adds state to DataQueryResponse * Fix: Fixes so we do not empty results before new data arrives Fixes: #17409 * Transformations work * observable test data * remove single() from loki promise * Fixed comment * Explore: Fixes failing Loki and Prometheus unit tests (#18995) * Tests: Makes datasource tests work again * Fix: Fixes loki datasource so highligthing works * Chore: Runs Prettier * Fixed query runner tests * Delay loading state indication to 200ms * Fixed test * fixed unit tests * Clear cached calcs * Fixed bug getProcesedDataFrames * Fix the correct test is a better idea * Fix: Fixes so queries in Explore are only run if Graph/Table is shown (#19000) * Fix: Fixes so queries in Explore are only run if Graph/Table is shown Fixes: #18618 * Refactor: Removes unnecessary condition * PanelData: provide legacy data only when needed (#19018) * no legacy * invert logic... now compiles * merge getQueryResponseData and getDataRaw * update comment about query editor * use single getData() function * only send legacy when it is used in explore * pre process rather than post process * pre process rather than post process * Minor refactoring * Add missing tags to test datasource response * MixedDatasource: Adds query observable pattern to MixedDatasource (#19037) * start mixed datasource * Refactor: Refactors into observable parttern * Tests: Fixes tests * Tests: Removes console.log * Refactor: Adds unique requestId
6 years ago
this.getQueryRunner().setTransformations(transformations);
}
setAngularPanel(component: AngularComponent) {
if (this.angularPanel) {
// this will remove all event listeners
this.angularPanel.destroy();
}
this.angularPanel = component;
this.events.emit(angularPanelUpdated);
}
}
function getPluginVersion(plugin: PanelPlugin): string {
return plugin && plugin.meta.info.version ? plugin.meta.info.version : config.buildInfo.version;
}