trying to make it work
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { AnnotationEvent, DataFrame, toDataFrame } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { config, getBackendSrv } from '@grafana/runtime';
|
||||
import { StateHistoryItem } from 'app/types/unified-alerting';
|
||||
|
||||
import { getAPINamespace } from '../../api/utils';
|
||||
import { ScopedResourceClient } from '../apiserver/client';
|
||||
import type { Resource, ResourceForCreate, ListOptions } from '../apiserver/types';
|
||||
|
||||
import { AnnotationTagsResponse } from './types';
|
||||
|
||||
export interface AnnotationServer {
|
||||
@@ -47,11 +51,182 @@ class LegacyAnnotationServer implements AnnotationServer {
|
||||
}
|
||||
}
|
||||
|
||||
// K8s-style annotation spec based on the CUE definition
|
||||
interface K8sAnnotationSpec {
|
||||
text: string;
|
||||
time: number;
|
||||
timeEnd?: number;
|
||||
dashboardUID?: string;
|
||||
panelID?: number;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
interface K8sAnnotationTagsResponse {
|
||||
tags: Array<{ tag: string; count: number }>;
|
||||
}
|
||||
|
||||
const K8S_ANNOTATION_API_CONFIG = {
|
||||
group: 'annotation.grafana.app',
|
||||
version: 'v0alpha1',
|
||||
resource: 'annotations',
|
||||
};
|
||||
|
||||
class K8sAnnotationServer implements AnnotationServer {
|
||||
private client: ScopedResourceClient<K8sAnnotationSpec>;
|
||||
|
||||
constructor() {
|
||||
this.client = new ScopedResourceClient<K8sAnnotationSpec>(K8S_ANNOTATION_API_CONFIG);
|
||||
}
|
||||
|
||||
async query(params: Record<string, unknown>, requestId: string): Promise<DataFrame> {
|
||||
// Convert legacy query params to k8s list options
|
||||
const listOpts: ListOptions = {};
|
||||
|
||||
// Map limit parameter
|
||||
if (params.limit) {
|
||||
listOpts.limit = Number(params.limit);
|
||||
}
|
||||
|
||||
// Build field selectors for dashboard/panel filtering and time ranges
|
||||
const fieldSelectors: string[] = [];
|
||||
|
||||
if (params.dashboardUID) {
|
||||
fieldSelectors.push(`spec.dashboardUID=${params.dashboardUID}`);
|
||||
}
|
||||
|
||||
if (params.panelId) {
|
||||
fieldSelectors.push(`spec.panelID=${params.panelId}`);
|
||||
}
|
||||
|
||||
// Handle time range filters using spec.time field
|
||||
// Note: < and > operators need URL encoding when passed as query parameters
|
||||
// < becomes %3C and > becomes %3E
|
||||
if (params.from) {
|
||||
// Use URL-encoded > operator
|
||||
fieldSelectors.push(`spec.time%3E${params.from}`);
|
||||
}
|
||||
|
||||
if (params.to) {
|
||||
// Use URL-encoded < operator
|
||||
fieldSelectors.push(`spec.time%3C${params.to}`);
|
||||
}
|
||||
|
||||
if (fieldSelectors.length > 0) {
|
||||
listOpts.fieldSelector = fieldSelectors.join(',');
|
||||
}
|
||||
|
||||
// Handle tags using label selectors
|
||||
if (params.tags && Array.isArray(params.tags) && params.tags.length > 0) {
|
||||
// Tags should be converted to label selectors
|
||||
// Format: tag1,tag2 means annotations that have both tags
|
||||
listOpts.labelSelector = params.tags.map((tag) => `tag=${tag}`).join(',');
|
||||
}
|
||||
|
||||
const result = await this.client.list(listOpts);
|
||||
|
||||
// Convert k8s resources to legacy annotation format
|
||||
const annotations = result.items.map((item: Resource<K8sAnnotationSpec>) => ({
|
||||
id: item.metadata.name,
|
||||
...item.spec,
|
||||
panelId: item.spec.panelID, // Map panelID back to panelId
|
||||
}));
|
||||
|
||||
return toDataFrame(annotations);
|
||||
}
|
||||
|
||||
async forAlert(alertUID: string): Promise<StateHistoryItem[]> {
|
||||
const result = await this.client.list({
|
||||
labelSelector: `alertUID=${alertUID}`,
|
||||
});
|
||||
|
||||
// Return as any[] since the k8s annotation format doesn't match StateHistoryItem exactly
|
||||
// This is a limitation of the current type definitions
|
||||
return result.items.map((item: Resource<K8sAnnotationSpec>) => ({
|
||||
id: item.metadata.name,
|
||||
...item.spec,
|
||||
panelId: item.spec.panelID,
|
||||
})) as any;
|
||||
}
|
||||
|
||||
async save(annotation: AnnotationEvent): Promise<AnnotationEvent> {
|
||||
const resource: ResourceForCreate<K8sAnnotationSpec> = {
|
||||
metadata: {
|
||||
name: '', // Will be auto-generated by the server
|
||||
},
|
||||
spec: {
|
||||
text: annotation.text || '',
|
||||
time: annotation.time || Date.now(),
|
||||
timeEnd: annotation.timeEnd,
|
||||
dashboardUID: annotation.dashboardUID ?? undefined,
|
||||
panelID: annotation.panelId,
|
||||
tags: annotation.tags,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await this.client.create(resource);
|
||||
|
||||
return {
|
||||
...annotation,
|
||||
id: result.metadata.name,
|
||||
...result.spec,
|
||||
panelId: result.spec.panelID,
|
||||
};
|
||||
}
|
||||
|
||||
async update(annotation: AnnotationEvent): Promise<unknown> {
|
||||
if (!annotation.id) {
|
||||
throw new Error('Annotation ID is required for update');
|
||||
}
|
||||
|
||||
// Get the existing resource to preserve metadata
|
||||
const existing = await this.client.get(String(annotation.id));
|
||||
|
||||
// Update only the spec fields
|
||||
const updated: Resource<K8sAnnotationSpec> = {
|
||||
...existing,
|
||||
spec: {
|
||||
text: annotation.text || existing.spec.text,
|
||||
time: annotation.time || existing.spec.time,
|
||||
timeEnd: annotation.timeEnd ?? existing.spec.timeEnd,
|
||||
dashboardUID: annotation.dashboardUID ? annotation.dashboardUID : existing.spec.dashboardUID,
|
||||
panelID: annotation.panelId ?? existing.spec.panelID,
|
||||
tags: annotation.tags ?? existing.spec.tags,
|
||||
},
|
||||
};
|
||||
|
||||
return this.client.update(updated);
|
||||
}
|
||||
|
||||
async delete(annotation: AnnotationEvent): Promise<unknown> {
|
||||
if (!annotation.id) {
|
||||
throw new Error('Annotation ID is required for delete');
|
||||
}
|
||||
return this.client.delete(String(annotation.id), false);
|
||||
}
|
||||
|
||||
async tags(): Promise<Array<{ term: string; count: number }>> {
|
||||
// Use the custom /tags route defined in the CUE manifest
|
||||
const namespace = getAPINamespace();
|
||||
const url = `/apis/${K8S_ANNOTATION_API_CONFIG.group}/${K8S_ANNOTATION_API_CONFIG.version}/namespaces/${namespace}/${K8S_ANNOTATION_API_CONFIG.resource}/tags`;
|
||||
|
||||
const response = await getBackendSrv().get<K8sAnnotationTagsResponse>(url, { limit: 1000 });
|
||||
|
||||
return response.tags.map(({ tag, count }) => ({
|
||||
term: tag,
|
||||
count,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let instance: AnnotationServer | null = null;
|
||||
|
||||
export function annotationServer(): AnnotationServer {
|
||||
if (!instance) {
|
||||
instance = new LegacyAnnotationServer();
|
||||
if (config.featureToggles.kubernetesAnnotations) {
|
||||
instance = new K8sAnnotationServer();
|
||||
} else {
|
||||
instance = new LegacyAnnotationServer();
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user