Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@newrelic/apollo-server-plugin

Package Overview
Dependencies
Maintainers
2
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@newrelic/apollo-server-plugin - npm Package Compare versions

Comparing version 0.3.0 to 1.0.0

154

lib/create-plugin.js

@@ -30,5 +30,9 @@ /*

const OPERATION_NAME_ATTR = 'graphql.operation.name'
const OPERATION_PATH_ATTR = 'graphql.operation.deepestPath'
const OPERATION_QUERY_ATTR = 'graphql.operation.query'
const INTROSPECTION_TYPES = ['__schema', '__type']
const IGNORED_PATH_FIELDS = ['id', '__typename']
const SERVICE_DEFINITION_QUERY_NAME = '__ApolloGetServiceDefinition__'
const HEALTH_CHECK_QUERY_NAME = '__ApolloServiceHealthCheck__'
const DESTINATIONS = {

@@ -65,2 +69,4 @@ NONE: 0x00

config.captureIntrospectionQueries = config.captureIntrospectionQueries || false
config.captureServiceDefinitionQueries = config.captureServiceDefinitionQueries || false
config.captureHealthCheckQueries = config.captureHealthCheckQueries || false

@@ -99,12 +105,3 @@ logger.debug('Plugin configuration: ', config)

didResolveOperation(resolveContext) {
if (
isIntrospectionQuery(
resolveContext.operation,
config.captureIntrospectionQueries
)
) {
logger.trace('Request is an introspection query and ' +
'`config.captureIntrospectionQueries` is set to `false`. ' +
'Force ignoring the transaction.')
if (shouldIgnoreTransaction(resolveContext.operation, config, logger)) {
const transaction = instrumentationApi.tracer.getTransaction()

@@ -220,3 +217,3 @@ if (transaction) {

if (operationDetails) {
const {operationName, operationType, deepestPath} = operationDetails
const {operationName, operationType, deepestUniquePath} = operationDetails

@@ -233,6 +230,4 @@ operationSegment.addAttribute(OPERATION_TYPE_ATTR, operationType)

// Certain requests, such as introspection, won't hit any resolvers
if (deepestPath) {
operationSegment.addAttribute(OPERATION_PATH_ATTR, deepestPath)
formattedOperation += `/${deepestPath}`
if (deepestUniquePath) {
formattedOperation += `/${deepestUniquePath}`
}

@@ -297,3 +292,4 @@

const deepestSelectionPath = getDeepestSelectionPath(definition)
const deepestUniquePath = getDeepestUniqueSelection(definition)
const definitionName = definition.name && definition.name.value

@@ -304,3 +300,3 @@

operationName: definitionName,
deepestPath: deepestSelectionPath.join('.')
deepestUniquePath: deepestUniquePath.join('.')
}

@@ -350,35 +346,56 @@ }

/**
* Returns the deepest path as an array of parts
* from an apollo-server document definition.
* Checks if selection is an InlineFragment that is a
* NamedType
* see: https://graphql.org/learn/queries/#inline-fragments
*
* @param {Object} selection node in grapql document AST
*/
function getDeepestSelectionPath(definition) {
let deepestPath = null
function isNamedType(selection) {
return selection.kind === 'InlineFragment' &&
selection.typeCondition &&
selection.typeCondition.kind === 'NamedType' &&
selection.typeCondition.name
}
definition.selectionSet.selections.forEach((selection) => {
searchSelection(selection)
})
/**
* Returns the deepest path in the document definition selectionSet
* where only one field was selected.
*
* 'id' and '__typename' fields are filtered out of consideration to improve
* naming in sub graph scenarioes.
*/
function getDeepestUniqueSelection(definition) {
const deepestPath = []
let selection = definition
while (selection.selectionSet) {
const filtered = selection.selectionSet.selections.filter((currentSelection) => {
// Inline fragments describe the prior element (_entities or unions) but contain
// selections for further naming.
if (currentSelection.kind === 'InlineFragment') {
return true
}
return deepestPath
return IGNORED_PATH_FIELDS.indexOf(currentSelection.name.value) < 0
})
/**
* Search each selection path until no-more sub-selections
* exist. If the curent path is deeper than deepestPath,
* deepestPath is replaced.
*/
function searchSelection(selection, currentParts) {
const parts = currentParts ? [...currentParts] : []
if (filtered.length === 0 || filtered.length > 1) {
// selections not unique OR
// only one IGNORED_PATH_FIELDS item in selections
break
}
// we have found that when queries contain InlineFragments
// they lack `name` property, check for property before adding to parts
// see https://github.com/newrelic/newrelic-node-apollo-server-plugin/issues/84
selection.name && parts.push(selection.name.value)
selection = filtered[0]
if (selection.selectionSet) {
selection.selectionSet.selections.forEach((innerSelection) => {
searchSelection(innerSelection, parts)
})
} else if (!deepestPath || parts.length > deepestPath.length) {
deepestPath = parts
if (isNamedType(selection)) {
const lastItemIdx = deepestPath.length - 1
// add type to the last item in deepestPath array
// (i.e - `_entities<Human>`)
deepestPath[lastItemIdx] =
`${deepestPath[lastItemIdx]}<${selection.typeCondition.name.value}>`
} else {
selection.name && deepestPath.push(selection.name.value)
}
}
return deepestPath
}

@@ -427,7 +444,3 @@

function isIntrospectionQuery(operation, captureIntrospectionQueries) {
if (captureIntrospectionQueries) {
return false
}
function isIntrospectionQuery(operation) {
return operation.selectionSet.selections.every((selection) => {

@@ -439,2 +452,47 @@ const fieldName = selection.name.value

function isServiceDefinitionQuery(operation) {
return operation.name && operation.name.value === SERVICE_DEFINITION_QUERY_NAME
}
function isHealthCheckQuery(operation) {
return operation.name && operation.name.value === HEALTH_CHECK_QUERY_NAME
}
function shouldIgnoreTransaction(operation, config, logger) {
if (
!config.captureIntrospectionQueries &&
isIntrospectionQuery(operation)
) {
logger.trace('Request is an introspection query and ' +
'`config.captureIntrospectionQueries` is set to `false`. ' +
'Force ignoring the transaction.')
return true
}
if (
!config.captureServiceDefinitionQueries &&
isServiceDefinitionQuery(operation)
) {
logger.trace('Request is an Apollo Federated Gateway service definition query and ' +
'`config.captureServiceDefinitionQueries` is set to `false`. ' +
'Force ignoring the transaction.')
return true
}
if (
!config.captureHealthCheckQueries &&
isHealthCheckQuery(operation)
) {
logger.trace('Request is an Apollo Federated Gateway health check query and ' +
'`config.captureHealthCheckQueries` is set to `false`. ' +
'Force ignoring the transaction.')
return true
}
return false
}
module.exports = createPlugin
{
"name": "@newrelic/apollo-server-plugin",
"version": "0.3.0",
"version": "1.0.0",
"description": "Apollo Server plugin that adds New Relic Node.js agent instrumentation.",

@@ -14,3 +14,3 @@ "main": "./index.js",

"versioned:folder": "versioned-tests --minor -i 2",
"versioned:npm6": "versioned-tests --minor -i 2 'tests/versioned/*'",
"versioned:npm6": "versioned-tests --minor -i 2 'tests/versioned/**/!(apollo-server-koa)' && versioned-tests --minor --all -i 2 'tests/versioned/apollo-server-koa'",
"versioned:npm7": "versioned-tests --minor --all -i 2 'tests/versioned/*'"

@@ -33,3 +33,3 @@ },

"engines": {
"node": ">=10.0.0"
"node": ">=12.0.0"
},

@@ -36,0 +36,0 @@ "devDependencies": {

@@ -46,20 +46,2 @@ [![Community Plus header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Community_Plus.png)](https://opensource.newrelic.com/oss-category/#community-plus)

To override configuration, invoke the `createPlugin` function prior to passing to Apollo Server:
```js
// index.js
const createPlugin = require('@newrelic/apollo-server-plugin')
const plugin = createPlugin({
captureScalars: false,
captureIntrospectionQueries: false
})
// imported from supported module
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [plugin]
})
```
## Usage

@@ -96,3 +78,3 @@

Configuration may be passed into the `createPlugin` function to override specific values. The configuration object and all properties are optional.
Configuration may be passed into the `createPlugin` function to override specific values. To override configuration, invoke the `createPlugin` function prior to passing to Apollo Server. The configuration object and all properties are optional.

@@ -102,3 +84,5 @@ ```js

captureScalars: true,
captureIntrospectionQueries: true
captureIntrospectionQueries: true,
captureServiceDefinitionQueries: true,
captureHealthCheckQueries: true
})

@@ -116,2 +100,6 @@ ```

* `[captureServiceDefinitionQueries = false]` Enable capture of timings for a [Service Definition query](https://www.apollographql.com/docs/federation/federation-spec/#fetch-service-capabilities) received from an Apollo Federated Gateway Server.
* `[captureHealthCheckQueries = false]` Enable capture of timings for a [Health Check query](https://www.apollographql.com/docs/federation/api/apollo-gateway/#servicehealthcheck) received from an Apollo Federated Gateway Server.
### Apollo Federation Support

@@ -178,3 +166,3 @@

`post /query/<anonymous>/libraries.books.author.name`
`post /query/<anonymous>/libraries.books`

@@ -191,5 +179,5 @@ For more information on how transactions are named, including how query errors may impact naming, please see the [transaction documentation](./docs/transactions.md).

`/GraphQL/operation/ApolloServer/[operation-type]/[operation-name]/[deepest-path]`
`/GraphQL/operation/ApolloServer/[operation-type]/[operation-name]/[deepest-unique-path]`
Operation metrics are very similar to how transaction names are constructed including the operation type, operation name and deepest-path. These metrics represent the durations of the individual queries or mutations and can be used to compare outside of the context of individual transactions which may have multiple queries.
Operation metrics are very similar to how transaction names are constructed including the operation type, operation name and deepest unique path. These metrics represent the durations of the individual queries or mutations and can be used to compare outside of the context of individual transactions which may have multiple queries.

@@ -225,7 +213,7 @@ If you would like to have a list of the top 10 slowest operations, the following query can be used to pull the data on demand or as a part of a dashboard. The 'Bar' chart type is a recommended visualization for this query.

`/GraphQL/operation/ApolloServer/[operation-type]/[operation-name]/[deepest-path]`
`/GraphQL/operation/ApolloServer/[operation-type]/[operation-name]/[deepest-unique-path]`
Operation segments/spans include the operation type, operation name and deepest-path. These represent the individual duration and attributes of a specific invocation within a transaction or trace.
Operation segments/spans include the operation type, operation name and deepest unique path. These represent the individual duration and attributes of a specific invocation within a transaction or trace.
The operation type, operation name and deepest-path are captured as attributes on a segment or span as well as the query with obfuscated arguments.
The operation type and operation name are captured as attributes on a segment or span as well as the query with obfuscated arguments.

@@ -232,0 +220,0 @@ For more information on collected attributes, see the [segments and spans documentation](./docs/segments-and-spans.md)

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc