Skip to content

Commit e4a978c

Browse files
authored
feat: added support for graphql-subscriptions v3 (#1446)
1 parent 94337f8 commit e4a978c

File tree

8 files changed

+119
-46
lines changed

8 files changed

+119
-46
lines changed

package-lock.json

+13-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@
160160
"got": "^14.4.4",
161161
"got-v11": "npm:got@^11.8.6",
162162
"graphql": "^16.9.0",
163-
"graphql-subscriptions": "^2.0.0",
163+
"graphql-subscriptions-v2": "npm:graphql-subscriptions@^2.0.0",
164+
"graphql-subscriptions": "^3.0.0",
164165
"graphql-tag": "^2.12.6",
165166
"graphql-ws": "^5.16.0",
166167
"husky": "^7.0.4",

packages/collector/test/tracing/control_flow/pubsub_async_iterator/app.js

+15-10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ process.on('SIGTERM', () => {
1111
process.exit(0);
1212
});
1313

14+
require('./mockVersion');
1415
require('../../../..')();
1516

1617
const cls = require('../../../../../core/src/tracing/cls');
@@ -22,10 +23,14 @@ const port = require('../../../test_util/app-port')();
2223

2324
const pubsub = new graphqlSubscriptions.PubSub();
2425
const eventName = 'event-name';
25-
const asyncIterator = pubsub.asyncIterator(eventName);
26+
27+
const version = process.env.GRAPHQL_SUBSCRIPTIONS_VERSION;
28+
const isV2 = version === 'v2';
29+
30+
const iterator = isV2 ? pubsub.asyncIterator(eventName) : pubsub.asyncIterableIterator(eventName);
2631

2732
const app = express();
28-
const logPrefix = `PubSub AsyncIterator pull-before-push app (${process.pid}):\t`;
33+
const logPrefix = `PubSub iterator pull-before-push app (${process.pid}):\t`;
2934

3035
if (process.env.WITH_STDOUT) {
3136
app.use(morgan(`${logPrefix}:method :url :status`));
@@ -37,17 +42,17 @@ app.get('/', (req, res) => {
3742

3843
const valuesReadFromCls = [];
3944

40-
// Calling asyncIterator.pullValue is what happens _before_ the pubsub.publish happens (possibly during
45+
// Calling iterator.pullValue is what happens _before_ the pubsub.publish happens (possibly during
4146
// pubsub.subscribe). The promises returned by pullValue will only resolve after we have pushed values.
42-
asyncIterator.pullValue().then(event1 => {
47+
iterator.pullValue().then(event1 => {
4348
// Chronologically, everything inside the then-handler this happens after cls.ns.set (see below). Due to custom
4449
// queueing in pubsub_async_iterator, the cls context would get lost though (unless we fix it).
4550
log(event1);
4651
valuesReadFromCls.push(cls.ns.get('key', true));
47-
asyncIterator.pullValue().then(event2 => {
52+
iterator.pullValue().then(event2 => {
4853
log(event2);
4954
valuesReadFromCls.push(cls.ns.get('key', true));
50-
asyncIterator.pullValue().then(event3 => {
55+
iterator.pullValue().then(event3 => {
5156
log(event3);
5257
valuesReadFromCls.push(cls.ns.get('key', true));
5358
});
@@ -57,11 +62,11 @@ asyncIterator.pullValue().then(event1 => {
5762
app.get('/pull-before-push', (req, res) => {
5863
// This order of events (pulling values before pushing values) does not work without some tracing blood magic.
5964

60-
// Calling asyncIterator.pushValue is what happens during pubsub.publish('event-name', { ... })
65+
// Calling iterator.pushValue is what happens during pubsub.publish('event-name', { ... })
6166
cls.ns.set('key', 'test-value');
62-
asyncIterator.pushValue({ name: 'event-01' });
63-
asyncIterator.pushValue({ name: 'event-02' });
64-
asyncIterator.pushValue({ name: 'event-03' });
67+
iterator.pushValue({ name: 'event-01' });
68+
iterator.pushValue({ name: 'event-02' });
69+
iterator.pushValue({ name: 'event-03' });
6570
setTimeout(() => {
6671
res.send(valuesReadFromCls);
6772
}, 200);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* (c) Copyright IBM Corp. 2024
3+
*/
4+
5+
'use strict';
6+
7+
const mock = require('mock-require');
8+
const hook = require('../../../../../core/src/util/hook');
9+
10+
const GRAPHQL_SUBSCRIPTIONS_VERSION = process.env.GRAPHQL_SUBSCRIPTIONS_VERSION;
11+
const GRAPHQL_SUBSCRIPTIONS_REQUIRE =
12+
process.env.GRAPHQL_SUBSCRIPTIONS_VERSION === 'latest'
13+
? 'graphql-subscriptions'
14+
: `graphql-subscriptions-${GRAPHQL_SUBSCRIPTIONS_VERSION}`;
15+
16+
if (GRAPHQL_SUBSCRIPTIONS_REQUIRE !== 'graphql-subscriptions') {
17+
mock('graphql-subscriptions', GRAPHQL_SUBSCRIPTIONS_REQUIRE);
18+
}
19+
20+
const originalOnFileLoad = hook.onFileLoad;
21+
hook.onFileLoad = function onFileLoad() {
22+
if (arguments[0].source === '\\/graphql-subscriptions\\/dist\\/pubsub-async-iterator\\.js') {
23+
const str = arguments[0].source.replace('graphql-subscriptions', GRAPHQL_SUBSCRIPTIONS_REQUIRE);
24+
const reg = new RegExp(str, '');
25+
arguments[0] = reg;
26+
return originalOnFileLoad.apply(this, arguments);
27+
}
28+
29+
return originalOnFileLoad.apply(this, arguments);
30+
};

packages/collector/test/tracing/control_flow/pubsub_async_iterator/test.js

+38-33
Original file line numberDiff line numberDiff line change
@@ -14,43 +14,48 @@ const globalAgent = require('../../../globalAgent');
1414

1515
const mochaSuiteFn = supportedVersion(process.versions.node) ? describe : describe.skip;
1616

17-
mochaSuiteFn('tracing/graphql-subscriptions - PubSub/async iterator (pull before push)', function () {
18-
this.timeout(config.getTestTimeout());
19-
20-
globalAgent.setUpCleanUpHooks();
21-
let controls;
22-
23-
before(async () => {
24-
controls = new ProcessControls({
25-
dirname: __dirname,
26-
useGlobalAgent: true
17+
['latest', 'v2'].forEach(version => {
18+
mochaSuiteFn(`tracing/graphql-subscriptions@${version} - PubSub/async iterator (pull before push)`, function () {
19+
this.timeout(config.getTestTimeout());
20+
21+
globalAgent.setUpCleanUpHooks();
22+
let controls;
23+
24+
before(async () => {
25+
controls = new ProcessControls({
26+
dirname: __dirname,
27+
env: {
28+
GRAPHQL_SUBSCRIPTIONS_VERSION: version
29+
},
30+
useGlobalAgent: true
31+
});
32+
33+
await controls.startAndWaitForAgentConnection();
2734
});
2835

29-
await controls.startAndWaitForAgentConnection();
30-
});
36+
beforeEach(async () => {
37+
await globalAgent.instance.clearReceivedTraceData();
38+
});
3139

32-
beforeEach(async () => {
33-
await globalAgent.instance.clearReceivedTraceData();
34-
});
40+
after(async () => {
41+
await controls.stop();
42+
});
3543

36-
after(async () => {
37-
await controls.stop();
38-
});
44+
afterEach(async () => {
45+
await controls.clearIpcMessages();
46+
});
3947

40-
afterEach(async () => {
41-
await controls.clearIpcMessages();
48+
it('should keep cls context when pulling before pushing', () =>
49+
controls
50+
.sendRequest({
51+
method: 'GET',
52+
path: '/pull-before-push'
53+
})
54+
.then(valuesReadFromCls => {
55+
expect(valuesReadFromCls).to.have.lengthOf(3);
56+
expect(valuesReadFromCls[0]).to.equal('test-value');
57+
expect(valuesReadFromCls[1]).to.equal('test-value');
58+
expect(valuesReadFromCls[2]).to.equal('test-value');
59+
}));
4260
});
43-
44-
it('should keep cls context when pulling before pushing', () =>
45-
controls
46-
.sendRequest({
47-
method: 'GET',
48-
path: '/pull-before-push'
49-
})
50-
.then(valuesReadFromCls => {
51-
expect(valuesReadFromCls).to.have.lengthOf(3);
52-
expect(valuesReadFromCls[0]).to.equal('test-value');
53-
expect(valuesReadFromCls[1]).to.equal('test-value');
54-
expect(valuesReadFromCls[2]).to.equal('test-value');
55-
}));
5661
});

packages/collector/test/tracing/protocols/graphql/schema.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,14 @@ module.exports = function exportSchema() {
202202
},
203203
subscribe: (__, { id }) => {
204204
pinoLogger.warn(`subscribe: ${id}`);
205-
return pubsub.asyncIterator('characterUpdated');
205+
206+
// for graphql-subscriptions, asyncIterator is replaced with asyncIterableIterator in v3
207+
if (pubsub?.asyncIterableIterator) {
208+
return pubsub.asyncIterableIterator('characterUpdated');
209+
} else {
210+
// v2 or lesser
211+
return pubsub.asyncIterator('characterUpdated');
212+
}
206213
}
207214
}
208215
}

packages/collector/test/tracing/protocols/graphql/test_definition.js

+3
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,9 @@ function verifySpansForSubscriptionUpdates(spans, triggerUpdateVia) {
814814
}
815815

816816
const subscribeHttpEntryInClientApp = verifyHttpEntry(null, /\/subscription/, spans);
817+
818+
expect(spans.length).to.be.eql(8);
819+
817820
// verify that the subscription entry has no children
818821
spans.forEach(span => {
819822
expect(span.p).to.not.equal(subscribeHttpEntryInClientApp.s);

packages/core/src/tracing/instrumentation/control_flow/graphqlSubscriptions.js

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ const CLS_CONTEXT_SYMBOL = Symbol('_instana_cls_context');
1616

1717
exports.init = () => {
1818
hook.onModuleLoad('graphql-subscriptions', instrumentModule);
19+
// In v3, pubsub-async-iterator is replaced with pubsub-async-iterable-iterator
20+
hook.onFileLoad(/\/graphql-subscriptions\/dist\/pubsub-async-iterable-iterator\.js/, instrumentAsyncIterableIterator);
21+
22+
// In v2, pubsub-async-iterator is available
1923
hook.onFileLoad(/\/graphql-subscriptions\/dist\/pubsub-async-iterator\.js/, instrumentAsyncIterator);
2024
};
2125

@@ -40,11 +44,17 @@ function instrumentAsyncIterator(pubSubAsyncIterator) {
4044
shimmer.wrap(pubSubAsyncIterator.PubSubAsyncIterator.prototype, 'pullValue', shimPullValue);
4145
}
4246

47+
function instrumentAsyncIterableIterator(pubSubAsyncIterator) {
48+
shimmer.wrap(pubSubAsyncIterator.PubSubAsyncIterableIterator.prototype, 'pushValue', shimPushValue);
49+
shimmer.wrap(pubSubAsyncIterator.PubSubAsyncIterableIterator.prototype, 'pullValue', shimPullValue);
50+
}
51+
4352
function shimPushValue(originalFunction) {
4453
return function (event) {
4554
if (isActive && event && typeof event === 'object' && cls.ns.active) {
4655
event[CLS_CONTEXT_SYMBOL] = cls.ns.active;
4756
}
57+
4858
return originalFunction.apply(this, arguments);
4959
};
5060
}

0 commit comments

Comments
 (0)