1import { from, merge, Observable, of } from 'rxjs'; 2import { delay } from 'rxjs/operators'; 3 4import { 5 AnnotationEvent, 6 ArrayDataFrame, 7 DataFrame, 8 DataQueryRequest, 9 DataQueryResponse, 10 DataSourceInstanceSettings, 11 DataTopic, 12 LiveChannelScope, 13 LoadingState, 14 TimeRange, 15 ScopedVars, 16} from '@grafana/data'; 17import { Scenario, TestDataQuery } from './types'; 18import { DataSourceWithBackend, getBackendSrv, getGrafanaLiveSrv, getTemplateSrv, TemplateSrv } from '@grafana/runtime'; 19import { queryMetricTree } from './metricTree'; 20import { runStream } from './runStreams'; 21import { getSearchFilterScopedVar } from 'app/features/variables/utils'; 22import { TestDataVariableSupport } from './variables'; 23import { generateRandomNodes, savedNodesResponse } from './nodeGraphUtils'; 24 25export class TestDataDataSource extends DataSourceWithBackend<TestDataQuery> { 26 scenariosCache?: Promise<Scenario[]>; 27 28 constructor( 29 instanceSettings: DataSourceInstanceSettings, 30 private readonly templateSrv: TemplateSrv = getTemplateSrv() 31 ) { 32 super(instanceSettings); 33 this.variables = new TestDataVariableSupport(); 34 } 35 36 query(options: DataQueryRequest<TestDataQuery>): Observable<DataQueryResponse> { 37 const backendQueries: TestDataQuery[] = []; 38 const streams: Array<Observable<DataQueryResponse>> = []; 39 40 // Start streams and prepare queries 41 for (const target of options.targets) { 42 if (target.hide) { 43 continue; 44 } 45 46 this.resolveTemplateVariables(target, options.scopedVars); 47 48 switch (target.scenarioId) { 49 case 'live': 50 streams.push(runGrafanaLiveQuery(target, options)); 51 break; 52 case 'streaming_client': 53 streams.push(runStream(target, options)); 54 break; 55 case 'grafana_api': 56 streams.push(runGrafanaAPI(target, options)); 57 break; 58 case 'annotations': 59 streams.push(this.annotationDataTopicTest(target, options)); 60 break; 61 case 'variables-query': 62 streams.push(this.variablesQuery(target, options)); 63 break; 64 case 'node_graph': 65 streams.push(this.nodesQuery(target, options)); 66 break; 67 68 // Unusable since 7, removed in 8 69 case 'manual_entry': { 70 let csvContent = 'Time,Value\n'; 71 if ((target as any).points) { 72 for (const point of (target as any).points) { 73 csvContent += `${point[1]},${point[0]}\n`; 74 } 75 } 76 target.scenarioId = 'csv_content'; 77 target.csvContent = csvContent; 78 } 79 80 default: 81 if (target.alias) { 82 target.alias = this.templateSrv.replace(target.alias, options.scopedVars); 83 } 84 85 backendQueries.push(target); 86 } 87 } 88 89 if (backendQueries.length) { 90 const backendOpts = { 91 ...options, 92 targets: backendQueries, 93 }; 94 streams.push(super.query(backendOpts)); 95 } 96 97 if (streams.length === 0) { 98 return of({ data: [] }); 99 } 100 101 return merge(...streams); 102 } 103 104 resolveTemplateVariables(query: TestDataQuery, scopedVars: ScopedVars) { 105 query.labels = this.templateSrv.replace(query.labels!, scopedVars); 106 } 107 108 annotationDataTopicTest(target: TestDataQuery, req: DataQueryRequest<TestDataQuery>): Observable<DataQueryResponse> { 109 const events = this.buildFakeAnnotationEvents(req.range, 50); 110 const dataFrame = new ArrayDataFrame(events); 111 dataFrame.meta = { dataTopic: DataTopic.Annotations }; 112 113 return of({ key: target.refId, data: [dataFrame] }).pipe(delay(100)); 114 } 115 116 buildFakeAnnotationEvents(range: TimeRange, count: number): AnnotationEvent[] { 117 let timeWalker = range.from.valueOf(); 118 const to = range.to.valueOf(); 119 const events = []; 120 const step = (to - timeWalker) / count; 121 122 for (let i = 0; i < count; i++) { 123 events.push({ 124 time: timeWalker, 125 text: 'This is the text, <a href="https://grafana.com">Grafana.com</a>', 126 tags: ['text', 'server'], 127 }); 128 timeWalker += step; 129 } 130 131 return events; 132 } 133 134 annotationQuery(options: any) { 135 return Promise.resolve(this.buildFakeAnnotationEvents(options.range, 10)); 136 } 137 138 getQueryDisplayText(query: TestDataQuery) { 139 if (query.alias) { 140 return query.scenarioId + ' as ' + query.alias; 141 } 142 return query.scenarioId; 143 } 144 145 testDatasource() { 146 return Promise.resolve({ 147 status: 'success', 148 message: 'Data source is working', 149 }); 150 } 151 152 getScenarios(): Promise<Scenario[]> { 153 if (!this.scenariosCache) { 154 this.scenariosCache = this.getResource('scenarios'); 155 } 156 157 return this.scenariosCache; 158 } 159 160 variablesQuery(target: TestDataQuery, options: DataQueryRequest<TestDataQuery>): Observable<DataQueryResponse> { 161 const query = target.stringInput ?? ''; 162 const interpolatedQuery = this.templateSrv.replace( 163 query, 164 getSearchFilterScopedVar({ query, wildcardChar: '*', options: options.scopedVars }) 165 ); 166 const children = queryMetricTree(interpolatedQuery); 167 const items = children.map((item) => ({ value: item.name, text: item.name })); 168 const dataFrame = new ArrayDataFrame(items); 169 170 return of({ data: [dataFrame] }).pipe(delay(100)); 171 } 172 173 nodesQuery(target: TestDataQuery, options: DataQueryRequest<TestDataQuery>): Observable<DataQueryResponse> { 174 const type = target.nodes?.type || 'random'; 175 let frames: DataFrame[]; 176 switch (type) { 177 case 'random': 178 frames = generateRandomNodes(target.nodes?.count); 179 break; 180 case 'response': 181 frames = savedNodesResponse(); 182 break; 183 default: 184 throw new Error(`Unknown node_graph sub type ${type}`); 185 } 186 187 return of({ data: frames }).pipe(delay(100)); 188 } 189} 190 191function runGrafanaAPI(target: TestDataQuery, req: DataQueryRequest<TestDataQuery>): Observable<DataQueryResponse> { 192 const url = `/api/${target.stringInput}`; 193 return from( 194 getBackendSrv() 195 .get(url) 196 .then((res) => { 197 const frame = new ArrayDataFrame(res); 198 return { 199 state: LoadingState.Done, 200 data: [frame], 201 }; 202 }) 203 ); 204} 205 206let liveQueryCounter = 1000; 207 208function runGrafanaLiveQuery( 209 target: TestDataQuery, 210 req: DataQueryRequest<TestDataQuery> 211): Observable<DataQueryResponse> { 212 if (!target.channel) { 213 throw new Error(`Missing channel config`); 214 } 215 return getGrafanaLiveSrv().getDataStream({ 216 addr: { 217 scope: LiveChannelScope.Plugin, 218 namespace: 'testdata', 219 path: target.channel, 220 }, 221 key: `testStream.${liveQueryCounter++}`, 222 }); 223} 224