diff --git a/app/code/Magento/GraphQlNewRelic/Helper/NewRelicReportData.php b/app/code/Magento/GraphQlNewRelic/Helper/NewRelicReportData.php new file mode 100644 index 0000000000000..d27e1537f2a9a --- /dev/null +++ b/app/code/Magento/GraphQlNewRelic/Helper/NewRelicReportData.php @@ -0,0 +1,221 @@ +getGqlFieldsInfo($schema); + if (!$gqlFieldsInfo) { + return []; + } + + $gqlInfo = $this->extractGqlInfo($gqlFieldsInfo); + $operationName = $this->getOperationNameFromQuery($querySource); + + $primaryFieldName = $this->getPrimaryFieldNameFromQuery($querySource); + $finalGqlCallName = $operationName !== '' + ? $operationName + : ($gqlInfo['field_count'] > 1 + ? self::MULTIPLE_QUERIES_FLAG + : ($primaryFieldName !== '' ? $primaryFieldName : $gqlInfo['first_field_name'])); + + return [ + 'transactionName' => $this->buildTransactionName($finalGqlCallName), + 'fieldCount' => $gqlInfo['field_count'], + 'fieldNames' => $gqlInfo['all_field_names'], + ]; + } + + /** + * @param ObjectType $gqlFieldsInfo + * @return array + */ + private function extractGqlInfo(ObjectType $gqlFieldsInfo): array + { + $gqlFields = $gqlFieldsInfo->getFields(); + + return [ + 'field_count' => count($gqlFields), + 'first_field_name' => array_key_first($gqlFields) ?? '', + 'all_field_names' => array_keys($gqlFields), + ]; + } + + /** + * @param Schema|null $schema + * @return ObjectType|null + */ + private function getGqlFieldsInfo(?Schema $schema): ?ObjectType + { + if (!$schema) { + return null; + } + + $schemaConfig = $schema->getConfig(); + if (!$schemaConfig) { + return null; + } + + $mutation = $schemaConfig->getMutation(); + $hasMutationFields = $mutation && count($mutation->getFields()) > 0; + + return $hasMutationFields ? $mutation : $schemaConfig->getQuery(); + } + + /** + * Build a transaction name based on operation name. + * + * @param string $operationName + * @return string + */ + private function buildTransactionName(string $operationName): string + { + return self::TRANSACTION_PREFIX . $operationName; + } + + /** + * Get operation name from query. + * + * @param DocumentNode|string $querySource + * @return string + */ + public function getOperationNameFromQuery(DocumentNode|string $querySource): string + { + $query = $this->getQueryString($querySource); + if ($query === '') { + return ''; + } + + $bracePosition = stripos($query, '{'); + if ($bracePosition === false) { + return ''; + } + + $operationBeginningSegment = substr($query, 0, $bracePosition); + if ($operationBeginningSegment === '') { + return ''; + } + + $operationName = ''; + if (preg_match('/(query|mutation)/', $operationBeginningSegment, $matches, PREG_OFFSET_CAPTURE)) { + $strQueryOrMutation = $matches[0][0]; + $operationName = trim($this->getSubString($strQueryOrMutation, '(', $operationBeginningSegment)); + } + + return $operationName; + } + + /** + * Prefer the top-level field alias when available. + * + * @param DocumentNode|string $querySource + * @return string + */ + private function getPrimaryFieldNameFromQuery(DocumentNode|string $querySource): string + { + $document = $this->getDocumentNode($querySource); + if (!$document) { + return ''; + } + + foreach ($document->definitions as $definition) { + if (!$definition instanceof OperationDefinitionNode || !$definition->selectionSet) { + continue; + } + + foreach ($definition->selectionSet->selections as $selection) { + if (!$selection instanceof FieldNode) { + continue; + } + + if ($selection->alias) { + return $selection->alias->value; + } + + return $selection->name->value ?? ''; + } + } + + return ''; + } + + /** + * @param DocumentNode|string $querySource + * @return DocumentNode|null + */ + private function getDocumentNode(DocumentNode|string $querySource): ?DocumentNode + { + if ($querySource instanceof DocumentNode) { + return $querySource; + } + + if ($querySource === '') { + return null; + } + + try { + return Parser::parse($querySource); + } catch (\Exception) { + return null; + } + } + + /** + * @param DocumentNode|string $querySource + * @return string + */ + private function getQueryString(DocumentNode|string $querySource): string + { + if ($querySource instanceof DocumentNode) { + return Printer::doPrint($querySource); + } + + return $querySource; + } + + /** + * Get string in between two strings. + * + * @param string $startingStr + * @param string $endingStr + * @param string $str + * @return string + */ + private function getSubString(string $startingStr, string $endingStr, string $str): string + { + $subStrStart = strpos($str, $startingStr); + $subStrStart += strlen($startingStr); + + $hasEndingStr = (strpos($str, $endingStr, $subStrStart)) !== false; + $lengthOfSubstr = $hasEndingStr + ? (strpos($str, $endingStr, $subStrStart) - $subStrStart) + : (strlen($str) - $subStrStart); + + return substr($str, $subStrStart, $lengthOfSubstr); + } +} diff --git a/app/code/Magento/GraphQlNewRelic/Model/Query/Logger/NewRelic.php b/app/code/Magento/GraphQlNewRelic/Model/Query/Logger/NewRelic.php index 582caf1084507..d73ef97989e8e 100644 --- a/app/code/Magento/GraphQlNewRelic/Model/Query/Logger/NewRelic.php +++ b/app/code/Magento/GraphQlNewRelic/Model/Query/Logger/NewRelic.php @@ -30,8 +30,6 @@ public function __construct( */ public function execute(array $queryDetails) { - $transactionName = $queryDetails[LoggerInterface::TOP_LEVEL_OPERATION_NAME] ?? ''; - $this->newRelicWrapper->setTransactionName('GraphQL-' . $transactionName); if (!$this->config->isNewRelicEnabled()) { return; } diff --git a/app/code/Magento/GraphQlNewRelic/Plugin/TransactionNaming.php b/app/code/Magento/GraphQlNewRelic/Plugin/TransactionNaming.php new file mode 100644 index 0000000000000..a4d661ff40e0d --- /dev/null +++ b/app/code/Magento/GraphQlNewRelic/Plugin/TransactionNaming.php @@ -0,0 +1,61 @@ +reportData->getTransactionData($schema, $source); + if (empty($transactionData)) { + return; + } + + $this->newRelicWrapper->setTransactionName($transactionData['transactionName']); + $this->newRelicWrapper->addCustomParameter('GraphqlNumberOfFields', $transactionData['fieldCount']); + $this->newRelicWrapper->addCustomParameter('FieldNames', implode('|', $transactionData['fieldNames'])); + } +} diff --git a/app/code/Magento/GraphQlNewRelic/etc/di.xml b/app/code/Magento/GraphQlNewRelic/etc/di.xml index 71383f1689be9..ba9e10a923c10 100644 --- a/app/code/Magento/GraphQlNewRelic/etc/di.xml +++ b/app/code/Magento/GraphQlNewRelic/etc/di.xml @@ -6,6 +6,9 @@ */ --> + + +