diff --git a/packages/beta/src/index.ts b/packages/beta/src/index.ts index 8fe02b29f6..0007ae4725 100644 --- a/packages/beta/src/index.ts +++ b/packages/beta/src/index.ts @@ -14,6 +14,7 @@ export { DatapointsMultiQueryBase, DatapointsQuery, DatapointsQueryExternalId, + DatapointsQueryInstanceId, DatapointsQueryId, DatapointsQueryProperties, DoubleDatapoint, diff --git a/packages/beta/src/types.ts b/packages/beta/src/types.ts index 3c7c2cbb2e..1fb32ec84f 100644 --- a/packages/beta/src/types.ts +++ b/packages/beta/src/types.ts @@ -25,6 +25,7 @@ import type { CogniteInternalId, ExternalId, FilterQuery, + InstanceId, InternalId, } from '@cognite/sdk-core'; @@ -373,7 +374,10 @@ export interface DatapointsMultiQueryBase aggregates?: Aggregate[]; } -export type DatapointsQuery = DatapointsQueryId | DatapointsQueryExternalId; +export type DatapointsQuery = + | DatapointsQueryId + | DatapointsQueryExternalId + | DatapointsQueryInstanceId; export interface DatapointsQueryExternalId extends DatapointsQueryProperties, @@ -383,6 +387,10 @@ export interface DatapointsQueryId extends DatapointsQueryProperties, InternalId {} +export interface DatapointsQueryInstanceId + extends DatapointsQueryProperties, + InstanceId {} + export type Aggregate = | AggregateStable | 'countGood' diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b6b7113b04..46e7b715ce 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -38,6 +38,33 @@ export interface ExternalId { externalId: CogniteExternalId; } +export type IdEitherWithInstance = InternalId | ExternalId | InstanceId; + +export interface InstanceId { + instanceId: CogniteInstanceId; +} + +/** + * Unique identifier of an instance in Cognite Data Modeling + */ +export interface CogniteInstanceId { + externalId: InstanceExternalId; + space: InstanceSpace; +} + +/** + * External id of an instance in Cognite Data Modeling + * + * @pattern ^[a-zA-Z]([a-zA-Z0-9_]{0,253}[a-zA-Z0-9])?$ + */ +export type InstanceExternalId = string; +/** + * Instance space of an instance in Cognite Data Modeling + * + * @pattern ^[a-zA-Z][a-zA-Z0-9_-]{0,41}[a-zA-Z0-9]?$ + */ +export type InstanceSpace = string; + /** * External Id provided by client. Should be unique within the project. */ diff --git a/packages/stable/src/__tests__/api/datapoints.int.spec.ts b/packages/stable/src/__tests__/api/datapoints.int.spec.ts index 7512a72c24..e31d276974 100644 --- a/packages/stable/src/__tests__/api/datapoints.int.spec.ts +++ b/packages/stable/src/__tests__/api/datapoints.int.spec.ts @@ -2,13 +2,43 @@ import { afterAll, beforeAll, describe, expect, test } from 'vitest'; import type CogniteClient from '../../cogniteClient'; -import type { DatapointAggregate, Timeseries } from '../../types'; -import { setupLoggedInClient } from '../testUtils'; +import type { DatapointAggregate, NodeWrite, Timeseries } from '../../types'; +import { randomInt, setupLoggedInClient } from '../testUtils'; describe('Datapoints integration test', () => { let client: CogniteClient; let timeserie: Timeseries; + const testSpace = { + space: 'test_data_space', + name: 'test_data_space', + description: 'Instance space used for integration tests.', + }; + + const timeseriesCdmInstance: NodeWrite = { + externalId: `external_${randomInt()}`, + space: testSpace.space, + instanceType: 'node', + sources: [ + { + source: { + externalId: 'CogniteTimeSeries', + space: 'cdf_cdm', + type: 'view', + version: 'v1', + }, + properties: { + type: 'numeric', + }, + }, + ], + }; + + const timeseriesCdmInstanceId = { + externalId: timeseriesCdmInstance.externalId, + space: timeseriesCdmInstance.space, + }; + const timestampFrom = Date.now() - 60 * 60 * 48 * 1000; const timestampTill = Date.now() - 60 * 60 * 24 * 1000; const datapoints = [ @@ -25,9 +55,20 @@ describe('Datapoints integration test', () => { beforeAll(async () => { client = setupLoggedInClient(); [timeserie] = await client.timeseries.create([{ name: 'tmp' }]); + await client.spaces.upsert([testSpace]); + await client.instances.upsert({ + items: [timeseriesCdmInstance], + }); }); afterAll(async () => { await client.timeseries.delete([{ id: timeserie.id }]); + await client.instances.delete([ + { + instanceType: 'node', + externalId: timeseriesCdmInstance.externalId, + space: timeseriesCdmInstance.space, + }, + ]); }); test('insert', async () => { @@ -39,6 +80,16 @@ describe('Datapoints integration test', () => { ]); }); + test('insert by instance id', async () => { + const res = await client.datapoints.insert([ + { + instanceId: timeseriesCdmInstanceId, + datapoints, + }, + ]); + expect(res).toEqual({}); + }); + test('retrieve', async () => { const response = await client.datapoints.retrieve({ items: [{ id: timeserie.id }], @@ -50,6 +101,17 @@ describe('Datapoints integration test', () => { expect(response[0].isString).toBe(false); }); + test('retrieve by instance id', async () => { + const response = await client.datapoints.retrieve({ + items: [{ instanceId: timeseriesCdmInstanceId }], + start: '2d-ago', + end: new Date(), + }); + expect(response[0].datapoints.length).toBeGreaterThan(0); + expect(response[0].datapoints[0].timestamp).toBeInstanceOf(Date); + expect(response[0].isString).toBe(false); + }); + test('retrieve latest', async () => { const response = await client.datapoints.retrieveLatest([ { @@ -61,6 +123,17 @@ describe('Datapoints integration test', () => { expect(response[0].datapoints[0].timestamp).toBeInstanceOf(Date); }); + test('retrieve latest by instance id', async () => { + const response = await client.datapoints.retrieveLatest([ + { + before: '1d-ago', + instanceId: timeseriesCdmInstanceId, + }, + ]); + expect(response[0].datapoints.length).toBeGreaterThan(0); + expect(response[0].datapoints[0].timestamp).toBeInstanceOf(Date); + }); + test('retrieve with cursor', async () => { const queryTimeRange = { start: '3d-ago', @@ -94,6 +167,16 @@ describe('Datapoints integration test', () => { expect(result.length).toBe(1); expect(result[0].datapoints[0].timestamp).toBeInstanceOf(Date); }); + + test('delete by instance id', async () => { + const res = await client.datapoints.delete([ + { + instanceId: timeseriesCdmInstanceId, + inclusiveBegin: 0, + }, + ]); + expect(res).toEqual({}); + }); }); describe.skip('Datapoints integration test for monthly granularity', () => { diff --git a/packages/stable/src/__tests__/api/timeseries.int.spec.ts b/packages/stable/src/__tests__/api/timeseries.int.spec.ts index 3dee5124a8..f1ffa84992 100644 --- a/packages/stable/src/__tests__/api/timeseries.int.spec.ts +++ b/packages/stable/src/__tests__/api/timeseries.int.spec.ts @@ -2,7 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from 'vitest'; import type CogniteClient from '../../cogniteClient'; -import type { Asset, Timeseries } from '../../types'; +import type { Asset, NodeWrite, Timeseries } from '../../types'; import { randomInt, runTestWithRetryWhenFailing, @@ -20,10 +20,21 @@ describe('Timeseries integration test', () => { externalId: `external_${randomInt()}`, }, ]); + await client.spaces.upsert([testSpace]); + await client.instances.upsert({ + items: [timeseriesCdmInstance], + }); }); afterAll(async () => { await client.assets.delete([{ id: asset.id }]); + await client.instances.delete([ + { + instanceType: 'node', + externalId: timeseriesCdmInstance.externalId, + space: timeseriesCdmInstance.space, + }, + ]); }); const timeseries = [ @@ -91,6 +102,36 @@ describe('Timeseries integration test', () => { }, ]; + const testSpace = { + space: 'test_data_space', + name: 'test_data_space', + description: 'Instance space used for integration tests.', + }; + + const timeseriesCdmInstance: NodeWrite = { + externalId: `external_${randomInt()}`, + space: testSpace.space, + instanceType: 'node', + sources: [ + { + source: { + externalId: 'CogniteTimeSeries', + space: 'cdf_cdm', + type: 'view', + version: 'v1', + }, + properties: { + type: 'numeric', + }, + }, + ], + }; + + const timeseriesCdmInstanceId = { + externalId: timeseriesCdmInstance.externalId, + space: timeseriesCdmInstance.space, + }; + let createdTimeseries: Timeseries[]; test('create', async () => { @@ -107,6 +148,15 @@ describe('Timeseries integration test', () => { expect(single.name).toBe(timeseries[0].name); }); + test('retrieve by instance id', async () => { + const [single] = await client.timeseries.retrieve([ + { instanceId: timeseriesCdmInstanceId }, + ]); + expect(single.instanceId?.externalId).toBe( + timeseriesCdmInstance.externalId + ); + }); + test('retrieve with non-existent external id', async () => { const res = await client.timeseries.retrieve([{ externalId: '_n/a_' }], { ignoreUnknownIds: true, @@ -128,6 +178,22 @@ describe('Timeseries integration test', () => { expect(updateResult[0].name).toBe(newName); }); + test('update by instance id', async () => { + const testMetadata = { testKey: 'testValue' }; + const updateResult = await client.timeseries.update([ + { + instanceId: timeseriesCdmInstanceId, + update: { + metadata: { + set: testMetadata, + }, + }, + }, + ]); + expect(updateResult[0].instanceId).toEqual(timeseriesCdmInstanceId); + expect(updateResult[0].metadata).toEqual(testMetadata); + }); + test('connect to an asset', async () => { await client.timeseries.update([ { diff --git a/packages/stable/src/api/timeSeries/timeSeriesApi.ts b/packages/stable/src/api/timeSeries/timeSeriesApi.ts index 6d193ecec1..17a623155b 100644 --- a/packages/stable/src/api/timeSeries/timeSeriesApi.ts +++ b/packages/stable/src/api/timeSeries/timeSeriesApi.ts @@ -4,6 +4,7 @@ import { BaseResourceAPI, type CDFHttpClient, type CursorAndAsyncIterator, + type IdEitherWithInstance, type MetadataMap, } from '@cognite/sdk-core'; import type { @@ -92,7 +93,7 @@ export class TimeSeriesAPI extends BaseResourceAPI { * ``` */ public retrieve = ( - ids: IdEither[], + ids: IdEitherWithInstance[], params: TimeseriesRetrieveParams = {} ) => { return super.retrieveEndpoint(ids, params); diff --git a/packages/stable/src/types.ts b/packages/stable/src/types.ts index e7dfcd5aeb..a1ff18a44b 100644 --- a/packages/stable/src/types.ts +++ b/packages/stable/src/types.ts @@ -2,11 +2,13 @@ import type { CogniteExternalId, + CogniteInstanceId, CogniteInternalId, Cursor, ExternalId, FilterQuery, IdEither, + InstanceId, InternalId, Limit, } from '@cognite/sdk-core'; @@ -662,7 +664,8 @@ export interface DatapointsDeleteRange { export type DatapointsDeleteRequest = | (InternalId & DatapointsDeleteRange) - | (ExternalId & DatapointsDeleteRange); + | (ExternalId & DatapointsDeleteRange) + | (InstanceId & DatapointsDeleteRange); export interface NextCursor { nextCursor?: string; @@ -716,6 +719,12 @@ export interface DatapointsMetadata extends InternalId { * External id of the timeseries the datapoints belong to. */ externalId?: CogniteExternalId; + + /** + * Instance id of the timeseries the datapoints belong to in Cognite Data Modeling. + */ + instanceId?: CogniteInstanceId; + /** * Whether or not the datapoints are string values. */ @@ -765,15 +774,23 @@ Note: Time zones with minute offsets (e.g. UTC+05:30 or Asia/Kolkata) may take l export type ExternalDatapointsQuery = | ExternalDatapointId - | ExternalDatapointExternalId; + | ExternalDatapointExternalId + | ExternalDatapointInstanceId; export interface ExternalDatapointExternalId extends ExternalDatapoints, ExternalId {} +export interface ExternalDatapointInstanceId + extends ExternalDatapoints, + InstanceId {} + export interface ExternalDatapointId extends ExternalDatapoints, InternalId {} -export type DatapointsQuery = DatapointsQueryId | DatapointsQueryExternalId; +export type DatapointsQuery = + | DatapointsQueryId + | DatapointsQueryExternalId + | DatapointsQueryInstanceId; export interface DatapointsQueryExternalId extends DatapointsQueryProperties, @@ -783,6 +800,10 @@ export interface DatapointsQueryId extends DatapointsQueryProperties, InternalId {} +export interface DatapointsQueryInstanceId + extends DatapointsQueryProperties, + InstanceId {} + export interface DatapointsQueryProperties extends Limit, Cursor { /** * Get datapoints after this time. Format is N[timeunit]-ago where timeunit is w,d,h,m,s. Example: '2d-ago' will get everything that is up to 2 days old. Can also send in Date object. Note that when using aggregates, the start time will be rounded down to a whole granularity unit (in UTC timezone). For granularity 2d it will be rounded to 0:00 AM on the same day, for 3h it will be rounded to the start of the hour, etc. @@ -1155,6 +1176,10 @@ export interface Timeseries extends InternalId, CreatedAndLastUpdatedTime { * Externally supplied id of the time series */ externalId?: CogniteExternalId; + /** + * The ID of an instance in Cognite Data Models. + */ + instanceId?: CogniteInstanceId; name?: TimeseriesName; isString: TimeseriesIsString; /** @@ -1404,7 +1429,8 @@ export type LIST = 'LIST'; export type LatestDataBeforeRequest = | (InternalId & LatestDataPropertyFilter) - | (ExternalId & LatestDataPropertyFilter); + | (ExternalId & LatestDataPropertyFilter) + | (InstanceId & LatestDataPropertyFilter); export interface LatestDataPropertyFilter { /** @@ -2293,6 +2319,15 @@ export interface TimeSeriesPatch { }; } +export interface TimeSeriesPatchByInstanceId { + update: { + externalId?: NullableSinglePatchString; + metadata?: MetadataPatch; + assetId?: NullableSinglePatchLong; + dataSetId?: NullableSinglePatchLong; + }; +} + export interface TimeseriesSearch { /** * Prefix and fuzzy search on name. @@ -2315,7 +2350,8 @@ export interface TimeseriesSearchFilter extends Limit { export type TimeSeriesUpdate = | TimeSeriesUpdateById - | TimeSeriesUpdateByExternalId; + | TimeSeriesUpdateByExternalId + | TimeSeriesUpdateByInstanceId; export interface TimeSeriesUpdateByExternalId extends TimeSeriesPatch, @@ -2323,6 +2359,10 @@ export interface TimeSeriesUpdateByExternalId export interface TimeSeriesUpdateById extends TimeSeriesPatch, InternalId {} +export interface TimeSeriesUpdateByInstanceId + extends TimeSeriesPatchByInstanceId, + InstanceId {} + export interface TimeseriesFilter extends CreatedAndLastUpdatedTimeFilter { name?: TimeseriesName; unit?: TimeseriesUnit;