import { AnnotationChangeEvent, AnnotationEventUIModel, CoreApp, DataFrame } from '@grafana/data'; import { config, getDataSourceSrv } from '@grafana/runtime'; import { AdHocFiltersVariable, dataLayers, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes'; import { DataSourceRef } from '@grafana/schema'; import { AdHocFilterItem, PanelContext } from '@grafana/ui'; import { deleteAnnotation, saveAnnotation, updateAnnotation } from 'app/features/annotations/api'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils'; import { DashboardScene } from './DashboardScene'; export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelContext) { context.app = CoreApp.Dashboard; context.canAddAnnotations = () => { const dashboard = getDashboardSceneFor(vizPanel); const builtInLayer = getBuiltInAnnotationsLayer(dashboard); // When there is no builtin annotations query we disable the ability to add annotations if (!builtInLayer) { return false; } // If feature flag is enabled we pass the info of whether annotation can be added through the dashboard permissions if (!config.featureToggles.annotationPermissionUpdate && !dashboard.canEditDashboard()) { return false; } // If RBAC is enabled there are additional conditions to check. return Boolean(dashboard.state.meta.annotationsPermissions?.dashboard.canAdd); }; context.canEditAnnotations = (dashboardUID?: string) => { const dashboard = getDashboardSceneFor(vizPanel); // If feature flag is enabled we pass the info of whether annotation can be edited through the dashboard permissions if (!config.featureToggles.annotationPermissionUpdate && !dashboard.canEditDashboard()) { return false; } if (dashboardUID) { return Boolean(dashboard.state.meta.annotationsPermissions?.dashboard.canEdit); } return Boolean(dashboard.state.meta.annotationsPermissions?.organization.canEdit); }; context.canDeleteAnnotations = (dashboardUID?: string) => { const dashboard = getDashboardSceneFor(vizPanel); // If feature flag is enabled we pass the info of whether annotation can be deleted through the dashboard permissions if (!config.featureToggles.annotationPermissionUpdate && !dashboard.canEditDashboard()) { return false; } if (dashboardUID) { return Boolean(dashboard.state.meta.annotationsPermissions?.dashboard.canDelete); } return Boolean(dashboard.state.meta.annotationsPermissions?.organization.canDelete); }; context.onAnnotationCreate = async (event: AnnotationEventUIModel) => { const dashboard = getDashboardSceneFor(vizPanel); const isRegion = event.from !== event.to; const anno = { dashboardUID: dashboard.state.uid, panelId: getPanelIdForVizPanel(vizPanel), isRegion, time: event.from, timeEnd: isRegion ? event.to : 0, tags: event.tags, text: event.description, }; await saveAnnotation(anno); reRunBuiltInAnnotationsLayer(dashboard); context.eventBus.publish(new AnnotationChangeEvent(anno)); }; context.onAnnotationUpdate = async (event: AnnotationEventUIModel) => { const dashboard = getDashboardSceneFor(vizPanel); const isRegion = event.from !== event.to; const anno = { id: event.id, dashboardUID: dashboard.state.uid, panelId: getPanelIdForVizPanel(vizPanel), isRegion, time: event.from, timeEnd: isRegion ? event.to : 0, tags: event.tags, text: event.description, }; await updateAnnotation(anno); reRunBuiltInAnnotationsLayer(dashboard); context.eventBus.publish(new AnnotationChangeEvent(anno)); }; context.onAnnotationDelete = async (id: string) => { await deleteAnnotation({ id }); reRunBuiltInAnnotationsLayer(getDashboardSceneFor(vizPanel)); context.eventBus.publish(new AnnotationChangeEvent({ id })); }; context.onAddAdHocFilter = (newFilter: AdHocFilterItem) => { const dashboard = getDashboardSceneFor(vizPanel); const queryRunner = getQueryRunnerFor(vizPanel); if (!queryRunner) { return; } const filterVar = getAdHocFilterVariableFor(dashboard, queryRunner.state.datasource); updateAdHocFilterVariable(filterVar, newFilter); }; context.onUpdateData = (frames: DataFrame[]): Promise => { // TODO //return onUpdatePanelSnapshotData(this.props.panel, frames); return Promise.resolve(true); }; } function getBuiltInAnnotationsLayer(scene: DashboardScene): dataLayers.AnnotationsDataLayer | undefined { const set = dashboardSceneGraph.getDataLayers(scene); // When there is no builtin annotations query we disable the ability to add annotations for (const layer of set.state.annotationLayers) { if (layer instanceof dataLayers.AnnotationsDataLayer) { if (layer.state.isEnabled && layer.state.query.builtIn) { return layer; } } } return undefined; } function reRunBuiltInAnnotationsLayer(scene: DashboardScene) { const layer = getBuiltInAnnotationsLayer(scene); if (layer) { layer.runLayer(); } } export function getAdHocFilterVariableFor(scene: DashboardScene, ds: DataSourceRef | null | undefined) { const variables = sceneGraph.getVariables(scene); for (const variable of variables.state.variables) { if (sceneUtils.isAdHocVariable(variable)) { const filtersDs = variable.state.datasource; if (filtersDs === ds || filtersDs?.uid === ds?.uid) { return variable; } } } const newVariable = new AdHocFiltersVariable({ name: 'Filters', datasource: ds, supportsMultiValueOperators: Boolean(getDataSourceSrv().getInstanceSettings(ds)?.meta.multiValueFilterOperators), useQueriesAsFilterForOptions: true, layout: config.featureToggles.newFiltersUI ? 'combobox' : undefined, }); // Add it to the scene variables.setState({ variables: [...variables.state.variables, newVariable], }); return newVariable; } function updateAdHocFilterVariable(filterVar: AdHocFiltersVariable, newFilter: AdHocFilterItem) { // This function handles 'Filter for value' and 'Filter out value' from table cell // We are allowing to add filters with the same key because elastic search ds supports that // Update is only required when we change operator and keep key and value the same // key1 = value1 -> key1 != value1 const filterToReplaceIndex = filterVar.state.filters.findIndex( (filter) => filter.key === newFilter.key && filter.value === newFilter.value && filter.operator !== newFilter.operator ); if (filterToReplaceIndex >= 0) { const updatedFilters = filterVar.state.filters.slice(); updatedFilters.splice(filterToReplaceIndex, 1, newFilter); filterVar.updateFilters(updatedFilters); return; } // Add new filter filterVar.updateFilters([...filterVar.state.filters, newFilter]); }