Skip to content

Commit c30c67d

Browse files
authored
Merge pull request #10 from CDPTechnologies/EventTagTests
Added tests and fake data related to Event Tags
2 parents 02116b1 + 189a984 commit c30c67d

9 files changed

Lines changed: 332 additions & 193 deletions

File tree

.github/workflows/test.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Run Jest Tests
2+
3+
on:
4+
push:
5+
branches: [ '**' ] # Run on all branches
6+
pull_request:
7+
branches: [ '**' ] # Run on PRs targeting any branch
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v3
15+
16+
- name: Set up Node.js
17+
uses: actions/setup-node@v3
18+
with:
19+
node-version: '18'
20+
cache: 'npm'
21+
22+
- name: Install dependencies
23+
run: npm ci
24+
25+
- name: Run tests
26+
run: npm test
27+
28+
- name: Cleanup
29+
if: always()
30+
run: |
31+
echo "Cleaning up..."
32+
rm -rf node_modules
33+
rm -rf .npm

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,16 @@ node test/testTimeSync.js
2929

3030
## Usage
3131

32-
The index.js file contains a simple logger built for the CDP Studio example case.
32+
The value.js file contains a simple logger built for the CDP Studio example case.
3333

3434
1. Set up and run the Logger in CDP Studio.
3535
(Refer to Help → Framework - Data Logging → How to Setup Logging in Automation System)
3636
https://cdpstudio.com/manual/cdp/cdplogger/cdplogger-configuration-example.html
3737

38-
2. Run the index.js file from the command line:
38+
2. Run the value.js file from the command line:
3939

4040
```bash
41-
node examples/index.js
41+
node examples/value.js
4242
```
4343

4444
For usage related to events run:

client.js

Lines changed: 85 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ const WebSocket = require('ws');
22
const root = require('./generated/containerPb.js');
33
const Container = root.DBMessaging.Protobuf.Container;
44
const CDPValueType = root.ICD.Protobuf.CDPValueType;
5+
const EventQuery = root.DBMessaging.Protobuf.EventQuery;
6+
57

68
/**
79
* A client for interacting with a CDP Logger or LogServer via WebSocket.
@@ -14,7 +16,7 @@ const CDPValueType = root.ICD.Protobuf.CDPValueType;
1416
class Client {
1517
// Defined property names to use instead of ambiguous numbers.
1618
static EventQueryFlags = Object.freeze({
17-
None: 0, // Client.EventQueryFlags.None === 0
19+
None: 0, // cdplogger.Client.EventQueryFlags.None === 0
1820
NewestFirst: 1,
1921
TimeRangeBeginExclusive: 2,
2022
TimeRangeEndExclusive: 4,
@@ -38,35 +40,35 @@ class Client {
3840
if (!/^wss?:\/\//.test(url)) {
3941
url = `ws://${url}`;
4042
}
41-
43+
4244
this.reqId = -1;
4345
this.autoReconnect = autoReconnect;
4446
this.enableTimeSync = true; // Time synchronization is enabled by default.
45-
47+
4648
this.isOpen = false;
4749
this.queuedRequests = {};
4850
this.storedPromises = {};
4951
this.nameToId = {};
5052
this.idToName = {};
51-
53+
5254
// Mapping for signal types (in case we need to interpret values).
5355
this.nameToType = {};
54-
56+
5557
// Time-diff related
5658
this.timeDiff = 0;
5759
this.timeReceived = null;
5860
this.lastTimeRequest = Date.now() / 1000;
5961
this.haveSentQueuedReq = false;
6062
this.roundTripTimes = {};
61-
63+
6264
// Initialize the cache for sender tags and pending tag requests.
6365
this.senderTags = {}; // Cache for event sender tags (keyed by sender)
6466
this.pendingSenderTags = {}; // Holds pending promises for sender tags
65-
67+
6668
// Create the WebSocket connection
6769
this.ws = this._connect(url);
6870
}
69-
71+
7072

7173
/**
7274
* Enable or disable time synchronization with the server.
@@ -83,7 +85,7 @@ class Client {
8385
setEnableTimeSync(enable) {
8486
this.enableTimeSync = enable;
8587
if (!enable) {
86-
// Cancel any pending time sync requests so they wont update timeDiff later.
88+
// Cancel any pending time sync requests so they won't update timeDiff later.
8789
for (const key in this.storedPromises) {
8890
this.storedPromises[key].reject(new Error("Time sync disabled"));
8991
}
@@ -127,7 +129,7 @@ class Client {
127129
* larger data sets should be downloaded in patches.
128130
* - 4.0 (2024-01, CDP 4.12):
129131
* - Added NodeTag support to save custom tags for logged values (e.g. Unit or Description),
130-
* accessible via the clients API.
132+
* accessible via the client's API.
131133
* - Reduced network usage by having data responses only include changes instead of repeating unchanged values.
132134
* - Added support for string values and events.
133135
*
@@ -288,52 +290,52 @@ class Client {
288290
* dataConditions: {
289291
* Text: ["Invalid or missing feature license detected."],
290292
* // Multiple data conditions can be specified:
291-
* Level: { value: "ERROR", matchType: Client.MatchType.Exact }
293+
* Level: { value: "ERROR", matchType: cdplogger.Client.MatchType.Exact }
292294
* },
293295
* limit: 100,
294296
* offset: 0,
295-
* flags: Client.EventQueryFlags.NewestFirst
297+
* flags: cdplogger.Client.EventQueryFlags.NewestFirst
296298
* });
297299
*
298300
* @param {Object} query - A simple plain object representing the EventQuery.
299301
* @returns {Promise<Array>} Resolves with an array of event objects.
300302
*/
301-
// Modified requestEvents() to wait for missing sender tag info.
302-
requestEvents(query) {
303-
this._timeRequest();
304-
const requestId = this._getRequestId();
305-
const eventQuery = this._buildEventQuery(query);
306-
if (!this.isOpen) {
307-
this.queuedRequests[requestId] = { type: "events", query: eventQuery };
308-
} else {
309-
this._sendEventsRequest(requestId, eventQuery);
310-
}
311-
return new Promise((resolve, reject) => {
312-
this.storedPromises[requestId] = { resolve, reject };
313-
})
314-
.then(events => {
315-
// Collect the unique sender names from events that lack cached tags.
316-
const missingSenders = Array.from(new Set(
317-
events
318-
.filter(evt => !this.senderTags[evt.sender])
319-
.map(evt => evt.sender)
320-
));
321-
322-
if (missingSenders.length === 0) {
323-
return events;
303+
// Modified requestEvents() to wait for missing sender tag info.
304+
requestEvents(query) {
305+
this._timeRequest();
306+
const requestId = this._getRequestId();
307+
const eventQuery = this._buildEventQuery(query);
308+
if (!this.isOpen) {
309+
this.queuedRequests[requestId] = { type: "events", query: eventQuery };
310+
} else {
311+
this._sendEventsRequest(requestId, eventQuery);
324312
}
325-
// Request tag info for all missing senders.
326-
return Promise.all(
327-
missingSenders.map(sender => this.getSenderTags(sender))
328-
).then(() => {
329-
// Attach tags to events after tag info is available.
330-
events.forEach(evt => {
331-
evt.tags = this.senderTags[evt.sender];
313+
return new Promise((resolve, reject) => {
314+
this.storedPromises[requestId] = { resolve, reject };
315+
})
316+
.then(events => {
317+
// Collect the unique sender names from events that lack cached tags.
318+
const missingSenders = Array.from(new Set(
319+
events
320+
.filter(evt => !this.senderTags[evt.sender])
321+
.map(evt => evt.sender)
322+
));
323+
324+
if (missingSenders.length === 0) {
325+
return events;
326+
}
327+
// Request tag info for all missing senders.
328+
return Promise.all(
329+
missingSenders.map(sender => this.getSenderTags(sender))
330+
).then(() => {
331+
// Attach tags to events after tag info is available.
332+
events.forEach(evt => {
333+
evt.tags = this.senderTags[evt.sender];
334+
});
335+
return events;
336+
});
332337
});
333-
return events;
334-
});
335-
});
336-
}
338+
}
337339

338340
/**
339341
* Request a count of events that match the given query.
@@ -437,18 +439,17 @@ requestEvents(query) {
437439
return s;
438440
}
439441

440-
/**
441-
* Retrieves the tags associated with a given sender.
442-
*
443-
* This method checks if the tags for the specified sender are already cached. If so, it returns a
444-
* resolved promise with the cached tags. Otherwise, it initializes a pending promise for the sender,
445-
* sends a request for the sender's tags using `_sendEventSenderTagsRequest`, and returns a promise that
446-
* resolves when the tags are received. If no response is received within 5000 ms, it falls back to resolving
447-
* with an empty object.
448-
*
449-
* @param {string} sender - The identifier of the event sender.
450-
* @returns {Promise<Object>} A promise that resolves with an object representing the tags for the sender.
451-
*/
442+
/**
443+
* Retrieves the tags associated with a given sender.
444+
*
445+
* This method checks if the tags for the specified sender are already cached. If so, it returns a
446+
* resolved promise with the cached tags. Otherwise, it initializes a pending promise for the sender,
447+
* sends a request for the sender's tags using `_sendEventSenderTagsRequest`, and returns a promise that
448+
* resolves when the tags are received.
449+
*
450+
* @param {string} sender - The identifier of the event sender.
451+
* @returns {Promise<Object>} A promise that resolves with an object representing the tags for the sender.
452+
*/
452453
getSenderTags(sender) {
453454
if (this.senderTags && this.senderTags[sender]) {
454455
return Promise.resolve(this.senderTags[sender]);
@@ -458,16 +459,8 @@ requestEvents(query) {
458459
this.pendingSenderTags[sender] = [];
459460
this._sendEventSenderTagsRequest(sender);
460461
}
461-
return new Promise(resolve => {
462-
this.pendingSenderTags[sender].push(resolve);
463-
// Increase timeout to 5000 ms to wait longer for tag info.
464-
setTimeout(() => {
465-
if (this.pendingSenderTags[sender]) {
466-
this.senderTags[sender] = {}; // Fallback to empty object.
467-
this.pendingSenderTags[sender].forEach(fn => fn({}));
468-
delete this.pendingSenderTags[sender];
469-
}
470-
}, 5000);
462+
return new Promise((resolve, reject) => {
463+
this.pendingSenderTags[sender].push({ resolve, reject });
471464
});
472465
}
473466

@@ -497,14 +490,20 @@ requestEvents(query) {
497490
if (!error) {
498491
error = new Error("Something went wrong");
499492
}
500-
if (!this.autoReconnect) {
501-
for (const key in this.storedPromises) {
502-
this.storedPromises[key].reject(error);
503-
}
504-
this.storedPromises = {};
505-
this.queuedRequests = {};
493+
// Reject all stored promises.
494+
for (const key in this.storedPromises) {
495+
this.storedPromises[key].reject(error);
496+
}
497+
this.storedPromises = {};
498+
this.queuedRequests = {};
499+
500+
// Reject any pending sender tag promises.
501+
for (const sender in this.pendingSenderTags) {
502+
this.pendingSenderTags[sender].forEach(promiseObj => promiseObj.reject(error));
503+
delete this.pendingSenderTags[sender];
506504
}
507505
}
506+
508507

509508
_onClose(ws) {
510509
this.isOpen = false;
@@ -663,7 +662,7 @@ requestEvents(query) {
663662
}
664663
break;
665664
}
666-
665+
667666

668667
case Container.Type.eCountEventsResponse: {
669668
if (this.storedPromises[data.countEventsResponse.requestId]) {
@@ -673,7 +672,7 @@ requestEvents(query) {
673672
}
674673
break;
675674
}
676-
675+
677676
case Container.Type.eEventSenderTagsResponse: {
678677
// Get the mapping of sender names to TagMap objects.
679678
const tagsMapping = data.eventSenderTagsResponse.senderTags;
@@ -683,13 +682,14 @@ requestEvents(query) {
683682
this.senderTags[sender] = tags;
684683
// Resolve any pending promises waiting for tags for this sender.
685684
if (this.pendingSenderTags[sender]) {
686-
this.pendingSenderTags[sender].forEach(resolveFn => resolveFn(tags));
685+
this.pendingSenderTags[sender].forEach(promiseObj => promiseObj.resolve(tags));
687686
delete this.pendingSenderTags[sender];
688687
}
689688
}
690689
break;
691690
}
692-
691+
692+
693693
default:
694694
console.error("Unknown message type", data.messageType);
695695
}
@@ -866,7 +866,7 @@ requestEvents(query) {
866866

867867
_reqDataPoints(nodeNames, startS, endS, noOfDataPoints, limit, requestId) {
868868
const _getDataPoints = (nodeIds) => {
869-
this._sendDataPointsRequest(nodeIds, startS, endS, requestId, limit, noOfDataPoints);
869+
this._sendDataPointsRequest(nodeIds, startS, endS, requestId, noOfDataPoints, limit);
870870
};
871871

872872
const rejectRequest = (error) => {
@@ -908,7 +908,7 @@ requestEvents(query) {
908908
});
909909
}
910910

911-
_sendDataPointsRequest(nodeIds, startS, endS, requestId, limit, noOfDataPoints) {
911+
_sendDataPointsRequest(nodeIds, startS, endS, requestId, noOfDataPoints, limit) {
912912
const container = Container.create();
913913
container.messageType = Container.Type.eSignalDataRequest;
914914
container.signalDataRequest = {
@@ -945,7 +945,7 @@ requestEvents(query) {
945945
container.countEventsRequest = { requestId, query };
946946
const buffer = Container.encode(container).finish();
947947
this.ws.send(buffer);
948-
}
948+
}
949949

950950
_sendEventSenderTagsRequest(sender) {
951951
const container = Container.create();
@@ -1017,9 +1017,6 @@ requestEvents(query) {
10171017
// Validate the query object before building the EventQuery.
10181018
this._validateEventQuery(query);
10191019

1020-
const root = require('./generated/containerPb.js');
1021-
const { EventQuery } = root.DBMessaging.Protobuf;
1022-
10231020
// Conditionally include these fields only if the user has set them
10241021
const optionalFields = [
10251022
"timeRangeBegin",
@@ -1120,4 +1117,7 @@ requestEvents(query) {
11201117
}
11211118
}
11221119

1123-
module.exports = Client;
1120+
const cdplogger = {};
1121+
cdplogger.Client = Client;
1122+
1123+
module.exports = cdplogger;

0 commit comments

Comments
 (0)