Repository: newrelic/node-newrelic (js)

Issue:
Transaction auto-monitoring on MongoDB not working with promises
## Description

When using callbacks, transaction times are correctly reported on MongoDB operations, but these are not shown when using the promise implementation.

Either of these is working:
```js
function withMongoose() {
    return mongoose.model(x).find(query).lean();
}

function withCallbacks() {
    return new Promise((resolve, result) => {
        mongo.db.collection('x').find(query).toArray((err, res) => {
            if (err) {
                reject(err);
                return;
            }
            resolve(res);
        });
    });
}
```

However this is not working:
```js
function withPromises() {
    return mongo.db.collection('x').find(query).toArray();
}
```

Here's what it looks like when we switched from mongoose (withMongoose) to native driver promises (withPromises):
![image](https://user-images.githubusercontent.com/8066261/123647911-ca582d80-d828-11eb-944f-a00232ce3310.png)

## Expected Behavior

I expect the same result with the three methods above.

## Steps to Reproduce

See code snippets.

## Your Environment

NewRelic: 7.5.1
MongoDB (driver): 3.6.4
Mongoose: 5.11.17
Node: 14.16.0


Pull request:
added option to bind promises for cursor methods in mongodb
<!--
Thank you for submitting a Pull Request.

This code is leveraged to monitor critical services. Please consider the following:
* Tests are required.
* Performance matters.
* Features that are specific to just your app are unlikely to make it in.

Please fill out the relevant sections as follows:
* Proposed Release Notes: Bulleted list of recommended release notes for the change(s).
* Links: Any relevant links for the change.
* Details: In-depth description of changes, other technical notes, etc.
-->

## Proposed Release Notes
 * Binds cursor methods when they are promises to properly measure the duration of execution

## Links
Closes #788 

## Details
This was a simple addition of `promise: true` to the spec of all cursor methods within mongodb.  The 2 integration tests added verify that the exclusive duration of the cursor method is greater than its parent which will properly assert the fix.  See #788 comments for more info around the issues and screenshots confirming the fixes within the comments by me.


Patch:
diff --git a/lib/instrumentation/mongodb.js b/lib/instrumentation/mongodb.js
index f84a2dff0..79348358d 100644
--- a/lib/instrumentation/mongodb.js
+++ b/lib/instrumentation/mongodb.js
@@ -288,7 +288,7 @@ function makeQueryDescFunc(shim, methodName) {
     // segment name does not actually use query string
     // method name is set as query so the query parser has access to the op name
     const parameters = getInstanceAttributeParameters(shim, this)
-    return {query: methodName, parameters, callback: shim.LAST}
+    return {query: methodName, parameters, promise: true, callback: shim.LAST}
   }
 }
 


Test patch:
diff --git a/test/versioned/mongodb/cursor-duration.tap.js b/test/versioned/mongodb/cursor-duration.tap.js
new file mode 100644
index 000000000..9f7921958
--- /dev/null
+++ b/test/versioned/mongodb/cursor-duration.tap.js
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2020 New Relic Corporation. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+'use strict'
+
+const common = require('./collection-common')
+const helper = require('../../lib/agent_helper')
+const tap = require('tap')
+const collections = ['testCollection', 'testCollection2']
+
+
+tap.test('cursor duration tests', function(t) {
+  let agent = null
+  let client = null
+  let db = null
+  let collection = null
+  t.autoend()
+
+  t.beforeEach(function(done) {
+    agent = helper.instrumentMockedAgent()
+    const mongodb = require('mongodb')
+    common.dropTestCollections(mongodb, collections, function(err) {
+      if (err) {
+        return done(err)
+      }
+
+      common.connect(mongodb, null, function(err, res) {
+        if (err) {
+          return done(err)
+        }
+
+        client = res.client
+        db = res.db
+        collection = db.collection('testCollection')
+        common.populate(db, collection, done)
+      })
+    })
+  })
+
+  t.afterEach(function(done) {
+    common.close(client, db, function(err) {
+      helper.unloadAgent(agent)
+      agent = null
+      done(err)
+    })
+  })
+
+  t.test('toArray callback duration should be greater than its parent wrapper', function(t) {
+    helper.runInTransaction(agent, function() {
+      collection.find({}).toArray(function onToArray(err, data) {
+        const segment = agent.tracer.getSegment()
+        // current segment is this callback, must get its parent and parent's parent
+        const mongoTime = segment.parent.getExclusiveDurationInMillis()
+        const parentTime = segment.parent.parent.getExclusiveDurationInMillis()
+        const percentDuration = mongoTime / parentTime * 100
+        t.ok(percentDuration >= 35, 'toArray duration should be at least 35% of its parent')
+        t.notOk(err)
+        t.equal(data[0].i, 0)
+        t.end()
+      })
+    })
+  })
+
+  t.test('toArray promise duration should be greater than its parent wrapper', function(t) {
+    helper.runInTransaction(agent, async function() {
+      const data = await collection.find({}).toArray()
+      const segment = agent.tracer.getSegment()
+      // see https://github.com/newrelic/node-newrelic/issues/788
+      const parentTime = segment.getExclusiveDurationInMillis()
+      const mongoTime = segment.children[0].getExclusiveDurationInMillis()
+      const percentDuration = mongoTime / parentTime * 100
+      t.ok(percentDuration >= 35, 'toArray duration should be at least 35% of its parent')
+
+      t.equal(data[0].i, 0)
+      t.end()
+    })
+  })
+})
+
+
diff --git a/test/versioned/mongodb/package.json b/test/versioned/mongodb/package.json
index 5590c179d..a54293c99 100644
--- a/test/versioned/mongodb/package.json
+++ b/test/versioned/mongodb/package.json
@@ -16,6 +16,7 @@
         "collection-misc.tap.js",
         "collection-update.tap.js",
         "cursor.tap.js",
+        "cursor-duration.tap.js",
         "db.tap.js"
       ]
     },
@@ -31,9 +32,25 @@
         "collection-index.tap.js",
         "collection-misc.tap.js",
         "collection-update.tap.js",
+        "cursor-duration.tap.js",
         "cursor.tap.js",
         "db.tap.js"
       ]
+    },
+    {
+      "engines": {
+        "node": ">=10"
+      },
+      "dependencies": {
+        "mongodb": ">=3.3.0"
+      },
+      "files": [
+        "collection-find.tap.js",
+        "collection-index.tap.js",
+        "collection-update.tap.js",
+        "cursor-duration.tap.js",
+        "cursor.tap.js"
+      ]
     }
   ],
   "dependencies": {