diff --git a/src/datasources/tag/TagDataSource.test.ts b/src/datasources/tag/TagDataSource.test.ts index 44522cb..71f0d75 100644 --- a/src/datasources/tag/TagDataSource.test.ts +++ b/src/datasources/tag/TagDataSource.test.ts @@ -275,6 +275,90 @@ describe('queries', () => { expect(result.data).toMatchSnapshot(); }); + test('history of tags from different workspaces', async () => { + const queryRequest = buildQuery({ type: TagQueryType.History, path: 'my.tag.*' }); + + backendSrv.fetch + .calledWith(requestMatching({ url: '/nitag/v2/query-tags-with-values', data: { filter: 'path = "my.tag.*"' } })) + .mockReturnValue(createQueryTagsResponse([ + { tag: { path: 'my.tag.1', workspace: '1' } }, + { tag: { path: 'my.tag.2', workspace: '1' } }, + { tag: { path: 'my.tag.3', workspace: '2' } }, + { tag: { path: 'my.tag.4', workspace: '2' } } + ])); + + backendSrv.fetch + .calledWith( + requestMatching({ + url: '/nitaghistorian/v2/tags/query-decimated-history', + data: { + paths: ['my.tag.1', 'my.tag.2'], + workspace: '1', + startTime: queryRequest.range.from.toISOString(), + endTime: queryRequest.range.to.toISOString(), + decimation: 300, + }, + }) + ) + .mockReturnValue( + createTagHistoryResponse([ + { + path: 'my.tag.1', + type: TagDataType.DOUBLE, + values: [ + { timestamp: '2023-01-01T00:00:00Z', value: '1' }, + { timestamp: '2023-01-01T00:01:00Z', value: '2' }, + ], + }, + { + path: 'my.tag.2', + type: TagDataType.DOUBLE, + values: [ + { timestamp: '2023-01-01T00:00:00Z', value: '2' }, + { timestamp: '2023-01-01T00:01:00Z', value: '3' }, + ] + } + ]) + ) + + backendSrv.fetch.calledWith( + requestMatching({ + url: '/nitaghistorian/v2/tags/query-decimated-history', + data: { + paths: ['my.tag.3', 'my.tag.4'], + workspace: '2', + startTime: queryRequest.range.from.toISOString(), + endTime: queryRequest.range.to.toISOString(), + decimation: 300, + }, + }) + ) + .mockReturnValue( + createTagHistoryResponse([ + { + path: 'my.tag.3', + type: TagDataType.DOUBLE, + values: [ + { timestamp: '2023-01-01T00:00:00Z', value: '3' }, + { timestamp: '2023-01-01T00:01:00Z', value: '4' }, + ], + }, + { + path: 'my.tag.4', + type: TagDataType.DOUBLE, + values: [ + { timestamp: '2023-01-01T00:00:00Z', value: '4' }, + { timestamp: '2023-01-01T00:01:00Z', value: '5' }, + ] + } + ]) + ) + + const result = await ds.query(queryRequest); + + expect(result.data).toMatchSnapshot(); + }); + test('decimation parameter does not go above 1000', async () => { const queryRequest = buildQuery({ type: TagQueryType.History, path: 'my.tag' }); queryRequest.maxDataPoints = 1500; diff --git a/src/datasources/tag/TagDataSource.ts b/src/datasources/tag/TagDataSource.ts index c04c3e0..1301cb1 100644 --- a/src/datasources/tag/TagDataSource.ts +++ b/src/datasources/tag/TagDataSource.ts @@ -18,7 +18,7 @@ import { TimeAndTagTypeValues, TypeAndValues, } from './types'; -import { Throw } from 'core/utils'; +import { Throw, getWorkspaceName } from 'core/utils'; export class TagDataSource extends DataSourceBase { constructor( @@ -44,7 +44,7 @@ export class TagDataSource extends DataSourceBase { this.templateSrv.replace(query.path, scopedVars), this.templateSrv.replace(query.workspace, scopedVars) ); - + const workspaces = await this.getWorkspaces(); const result: DataFrameDTO = { refId: query.refId, fields: [] }; if (query.type === TagQueryType.Current) { @@ -78,18 +78,36 @@ export class TagDataSource extends DataSourceBase { return result } else { + const workspaceTagMap: Record = {}; const tagPropertiesMap: Record | null> = {}; - tagsWithValues.forEach((tag: TagWithValue) => { - tagPropertiesMap[tag.tag.path] = tag.tag.properties + + tagsWithValues.forEach(tagWithValue => { + let workspace = tagWithValue.tag.workspace ?? tagWithValue.tag.workspace_id + if (!workspaceTagMap[workspace]) { + workspaceTagMap[workspace] = []; + } + workspaceTagMap[workspace].push(tagWithValue); + tagPropertiesMap[`${getWorkspaceName(workspaces, workspace)}.${tagWithValue.tag.path}`] = tagWithValue.tag.properties }); - const workspaceFromFirstTag = tagsWithValues[0].tag.workspace ?? tagsWithValues[0].tag.workspace_id; - const tagHistoryResponse = await this.getTagHistoryWithChunks( - Object.keys(tagPropertiesMap), - workspaceFromFirstTag, - range, - maxDataPoints - ) - const mergedTagValuesWithType = this.mergeTagsHistoryValues(tagHistoryResponse.results); + let tagsDecimatedHistory: { [key: string]: TypeAndValues } = {} + for (const workspace in workspaceTagMap) { + const tagHistoryResponse = await this.getTagHistoryWithChunks( + workspaceTagMap[workspace], + workspace, + range, + maxDataPoints + ) + for (const path in tagHistoryResponse.results) { + // If tags are mixed from different workspaces, add the workspace name as a prefix + if (Object.keys(workspaceTagMap).length === 1) { + tagsDecimatedHistory[path] = tagHistoryResponse.results[path] + } else { + tagsDecimatedHistory[`${getWorkspaceName(await this.getWorkspaces(), workspace)}.${path}`] = tagHistoryResponse.results[path] + } + } + } + + const mergedTagValuesWithType = this.mergeTagsHistoryValues(tagsDecimatedHistory); result.fields.push({ name: 'time', 'values': mergedTagValuesWithType.timestamps, type: FieldType.time }); @@ -133,15 +151,15 @@ export class TagDataSource extends DataSourceBase { return response.tagsWithValues.length ? response.tagsWithValues : Throw(`No tags matched the path '${path}'`) } - private async getTagHistoryWithChunks(paths: string[], workspace: string, range: TimeRange, intervals?: number): Promise { - const pathChunks: string[][] = []; + private async getTagHistoryWithChunks(paths: TagWithValue[], workspace: string, range: TimeRange, intervals?: number): Promise { + const pathChunks: TagWithValue[][] = []; for (let i = 0; i < paths.length; i += 10) { pathChunks.push(paths.slice(i, i + 10)); } // Fetch and aggregate the data from each chunk const aggregatedResults: TagHistoryResponse = { results: {} }; for (const chunk of pathChunks) { - const chunkResult = await this.getTagHistoryValues(chunk, workspace, range, intervals); + const chunkResult = await this.getTagHistoryValues(chunk.map(tag => tag.tag.path), workspace, range, intervals); // Merge the results from the current chunk with the aggregated results for (const [path, data] of Object.entries(chunkResult.results)) { if (!aggregatedResults.results[path]) { diff --git a/src/datasources/tag/__snapshots__/TagDataSource.test.ts.snap b/src/datasources/tag/__snapshots__/TagDataSource.test.ts.snap index 56696c4..da21acd 100644 --- a/src/datasources/tag/__snapshots__/TagDataSource.test.ts.snap +++ b/src/datasources/tag/__snapshots__/TagDataSource.test.ts.snap @@ -649,3 +649,53 @@ exports[`queries uses displayName property 1`] = ` }, ] `; + +exports[`queries history of tags from different workspaces 1`] = ` +[ + { + "fields": [ + { + "name": "time", + "type": "time", + "values": [ + "2023-01-01T00:00:00Z", + "2023-01-01T00:01:00Z", + ], + }, + { + "config": {}, + "name": "Default workspace.my.tag.1", + "values": [ + 1, + 2, + ], + }, + { + "config": {}, + "name": "Default workspace.my.tag.2", + "values": [ + 2, + 3, + ], + }, + { + "config": {}, + "name": "Other workspace.my.tag.3", + "values": [ + 3, + 4, + ], + }, + { + "config": {}, + "name": "Other workspace.my.tag.4", + "values": [ + 4, + 5, + ], + }, + ], + "refId": "A", + }, +] +`;