diff --git a/utils/__tests__/nr_graphql_helpers.test.js b/utils/__tests__/nr_graphql_helpers.test.js index 3f97287624..78de3420b5 100644 --- a/utils/__tests__/nr_graphql_helpers.test.js +++ b/utils/__tests__/nr_graphql_helpers.test.js @@ -37,7 +37,7 @@ describe('getCategoryTermsFromKeywords', () => { const mockKeywords = undefined; const categoriesFromKeywords = - await nrGraphQlHelpers.getCategoryTermsFromKeywords(mockKeywords); + await nrGraphQlHelpers.getValidCategoryTerms(mockKeywords); expect(nrGraphQlHelpers.fetchNRGraphqlResults).toHaveBeenCalledTimes(1); expect(categoriesFromKeywords).toEqual(undefined); @@ -46,7 +46,7 @@ describe('getCategoryTermsFromKeywords', () => { test('getCategoryTermsFromKeywords returns undefined if no keywords match a category', async () => { const mockKeywords = ['python', 'apm', 'http']; const categoriesFromKeywords = - await nrGraphQlHelpers.getCategoryTermsFromKeywords(mockKeywords); + await nrGraphQlHelpers.getValidCategoryTerms(mockKeywords); expect(nrGraphQlHelpers.fetchNRGraphqlResults).toHaveBeenCalledTimes(1); expect(categoriesFromKeywords).toEqual(undefined); @@ -55,7 +55,7 @@ describe('getCategoryTermsFromKeywords', () => { test('getCategoryTermsFromKeywords returns 1 categoryTerm given a set of keywords where a keyword belong to 1 category', async () => { const mockKeywords = ['python', 'azure']; const categoriesFromKeywords = - await nrGraphQlHelpers.getCategoryTermsFromKeywords(mockKeywords); + await nrGraphQlHelpers.getValidCategoryTerms(mockKeywords); expect(nrGraphQlHelpers.fetchNRGraphqlResults).toHaveBeenCalledTimes(1); expect(categoriesFromKeywords).toEqual(['azure']); @@ -64,7 +64,7 @@ describe('getCategoryTermsFromKeywords', () => { test('getCategoryTermsFromKeywords returns 2 categoryTerms given a set of keywords where keywords belong to 2 categories', async () => { const mockKeywords = ['python', 'os', 'containers']; const categoriesFromKeywords = - await nrGraphQlHelpers.getCategoryTermsFromKeywords(mockKeywords); + await nrGraphQlHelpers.getValidCategoryTerms(mockKeywords); expect(nrGraphQlHelpers.fetchNRGraphqlResults).toHaveBeenCalledTimes(1); expect(categoriesFromKeywords).toEqual(['os', 'containers']); diff --git a/utils/build-validate-quickstart-artifact.ts b/utils/build-validate-quickstart-artifact.ts index 46b88070a2..8445b14acd 100644 --- a/utils/build-validate-quickstart-artifact.ts +++ b/utils/build-validate-quickstart-artifact.ts @@ -10,6 +10,8 @@ import Ajv, { type ErrorObject } from 'ajv'; import { QuickstartConfig, QuickstartConfigAlert } from './types/QuickstartConfig'; import { DataSourceConfig } from './types/DataSourceConfig'; import { passedProcessArguments } from './lib/helpers'; +import { QuickstartMutationVariable } from './types/QuickstartMutationVariable'; +import { getAllCategoryTerms } from './lib/nr-graphql-helpers'; type ArtifactSchema = Record; @@ -28,6 +30,7 @@ type ArtifactComponents = { type Artifact = ArtifactComponents | { dataSourceIds: string[] + quickstarts: QuickstartMutationVariable[] } const getSchema = (filepath: string): ArtifactSchema => { @@ -37,7 +40,8 @@ const getSchema = (filepath: string): ArtifactSchema => { }; // NOTE: we could run these in parallel to speed up the script -export const getArtifactComponents = (): ArtifactComponents => { +export const getArtifactComponents = async (): Promise => { + const quickstarts = Quickstart.getAll().map((quickstart) => quickstart.config); console.log(`[*] Found ${quickstarts.length} quickstarts`); @@ -75,9 +79,9 @@ export const validateArtifact = (schema: ArtifactSchema, artifact: Artifact): Er return ajv.errors ?? []; } -const main = (shouldOutputArtifact: boolean = false) => { +const main = async (shouldOutputArtifact: boolean = false) => { const schema = getSchema('./schema/artifact.json'); - const components = getArtifactComponents(); + const components = await getArtifactComponents(); const dataSourceIds = getDataSourceIds('./schema/core-datasource-ids.json', components.dataSources); const artifact = { ...components, dataSourceIds }; const errors = validateArtifact(schema, artifact); @@ -91,7 +95,13 @@ const main = (shouldOutputArtifact: boolean = false) => { console.log('[*] Validation succeeded'); if (shouldOutputArtifact) { - outputArtifact(artifact); + const categoryTerms = await getAllCategoryTerms(); + const transformedQuickstarts = await Promise.all(Quickstart.getAll().map(async (qs) => { + return qs.getMutationVariables(false, categoryTerms); + } + )); + + outputArtifact({ ...artifact, quickstarts: transformedQuickstarts }); } } diff --git a/utils/lib/Quickstart.ts b/utils/lib/Quickstart.ts index c8e04f8239..cd232b3b6b 100644 --- a/utils/lib/Quickstart.ts +++ b/utils/lib/Quickstart.ts @@ -15,7 +15,8 @@ import { } from '../constants'; import { fetchNRGraphqlResults, - getCategoryTermsFromKeywords, + getAllCategoryTerms, + getValidCategoryTerms, } from './nr-graphql-helpers'; import type { QuickstartMutationVariable, @@ -134,10 +135,15 @@ class Quickstart { /** * Get mutation variables from quickstart config + * @param dryRun if true, will generate a mock UUID for the quickstart + * @param validTerms if provided, will only accept these keywords and filter + * out user-supplied keywords. If not provided, will fetch them from catalog + * service. * @returns - Promised mutation variables for quickstart */ async getMutationVariables( - dryRun: boolean + dryRun: boolean, + validTerms?: string[] ): Promise { if (!this.isValid) { console.error( @@ -158,9 +164,13 @@ class Quickstart { level, } = this.config; + if (!validTerms) { + validTerms = await getAllCategoryTerms(); + } + const metadata = { authors: authors && authors.map((author) => ({ name: author })), - categoryTerms: await getCategoryTermsFromKeywords(keywords), + categoryTerms: getValidCategoryTerms(keywords, validTerms), description: description && description.trim(), displayName: title && title.trim(), slug: slug && slug.trim(), diff --git a/utils/lib/__tests__/Quickstart.test.js b/utils/lib/__tests__/Quickstart.test.js index b28572a87c..589fcdab53 100644 --- a/utils/lib/__tests__/Quickstart.test.js +++ b/utils/lib/__tests__/Quickstart.test.js @@ -3,7 +3,7 @@ import * as nrGraphQlHelpers from '../nr-graphql-helpers'; import { GITHUB_RAW_BASE_URL, GITHUB_REPO_BASE_URL } from '../../constants'; import Quickstart from '../Quickstart'; -nrGraphQlHelpers.getCategoryTermsFromKeywords = jest.fn(); +nrGraphQlHelpers.getValidCategoryTerms = jest.fn(); const MOCK_FILES_BASEPATH = path.resolve(__dirname, '../../mock_files'); @@ -23,7 +23,7 @@ describe('Quickstart', () => { }); test('Creates invalid quickstart', () => { - jest.spyOn(global.console, 'error').mockImplementation(() => {}); + jest.spyOn(global.console, 'error').mockImplementation(() => { }); const qs = new Quickstart('', MOCK_FILES_BASEPATH); @@ -33,7 +33,7 @@ describe('Quickstart', () => { describe('getConfigContent', () => { test('Handles invalid quickstart', () => { - jest.spyOn(global.console, 'error').mockImplementation(() => {}); + jest.spyOn(global.console, 'error').mockImplementation(() => { }); const qs = new Quickstart('', MOCK_FILES_BASEPATH); @@ -80,7 +80,7 @@ describe('Quickstart', () => { }); test('Ensure quickstart is invalid from invalid components', () => { - jest.spyOn(global.console, 'error').mockImplementation(() => {}); + jest.spyOn(global.console, 'error').mockImplementation(() => { }); const qs = new Quickstart( 'quickstarts/mock-quickstart-8/config.yml', MOCK_FILES_BASEPATH @@ -102,8 +102,8 @@ describe('Quickstart', () => { }); test('Ensure quickstart is invalid from an invalid Alert', () => { - jest.spyOn(global.console, 'error').mockImplementation(() => {}); - jest.spyOn(global.console, 'log').mockImplementation(() => {}); + jest.spyOn(global.console, 'error').mockImplementation(() => { }); + jest.spyOn(global.console, 'log').mockImplementation(() => { }); const qs = new Quickstart( 'quickstarts/mock-quickstart-6/config.yml', MOCK_FILES_BASEPATH @@ -135,7 +135,7 @@ describe('Quickstart', () => { MOCK_FILES_BASEPATH ); - nrGraphQlHelpers.getCategoryTermsFromKeywords.mockResolvedValueOnce( + nrGraphQlHelpers.getValidCategoryTerms.mockResolvedValueOnce( undefined ); const variables = await qs.getMutationVariables(true); diff --git a/utils/lib/nr-graphql-helpers.ts b/utils/lib/nr-graphql-helpers.ts index 7f5fc9fd86..5a54ab2324 100644 --- a/utils/lib/nr-graphql-helpers.ts +++ b/utils/lib/nr-graphql-helpers.ts @@ -45,6 +45,7 @@ export const fetchNRGraphqlResults = async ( ): Promise> => { const NR_API_URL = process.env.NR_API_URL || ''; const NR_API_TOKEN = process.env.NR_API_TOKEN || ''; + console.log(NR_API_TOKEN); let results; let graphqlErrors: ErrorOrNerdGraphError[] = []; @@ -176,8 +177,9 @@ type CategoryTermsNRGraphqlResults = { /** * Method which filters out user supplied keywords to only keywords which are valid categoryTerms. - * @param {String[] | undefined} configKeywords - An array of keywords specified in a quickstart config.yml - * @returns {Promise} An array of quickstart categoryTerms + * @param configKeywords - An array of keywords specified in a quickstart config.yml + * @param validTerms - An array of valid categoryTerms (use getAllCategoryTerms) + * @returns An array of quickstart categoryTerms * * @example * // input @@ -186,9 +188,20 @@ type CategoryTermsNRGraphqlResults = { * // return * ['azure', 'infrastructure'] */ -export const getCategoryTermsFromKeywords = async ( - configKeywords: string[] | undefined = [] -): Promise => { +export const getValidCategoryTerms = ( + configKeywords: string[] | undefined = [], validTerms: string[] +): string[] | undefined => { + const categoryKeywords = configKeywords.reduce((acc, keyword) => { + if (validTerms && validTerms.includes(keyword)) { + acc.push(keyword); + } + return acc; + }, []); + + return categoryKeywords.length > 0 ? categoryKeywords : undefined; +}; + +export const getAllCategoryTerms = async () => { logger.debug(`Fetching categories...`); // TODO: handles errors!! @@ -203,16 +216,7 @@ export const getCategoryTermsFromKeywords = async ( const { categories } = data.actor.nr1Catalog; - const allCategoryKeywords = categories.flatMap((category) => category.terms); - - const categoryKeywords = configKeywords.reduce((acc, keyword) => { - if (allCategoryKeywords && allCategoryKeywords.includes(keyword)) { - acc.push(keyword); - } - return acc; - }, []); - - return categoryKeywords.length > 0 ? categoryKeywords : undefined; + return categories?.flatMap((category) => category.terms); }; type CoreDataSourceSearchResults = { diff --git a/utils/schema/artifact.json b/utils/schema/artifact.json index 0114436fe4..728c5b4fe8 100644 --- a/utils/schema/artifact.json +++ b/utils/schema/artifact.json @@ -1,27 +1,36 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "type": "object", "properties": { "quickstarts": { "type": "array", - "items": { "$ref": "#/definitions/quickstart" } + "items": { + "$ref": "#/definitions/quickstart" + } }, "dataSources": { "type": "array", - "items": { "$ref": "#/definitions/dataSource" } + "items": { + "$ref": "#/definitions/dataSource" + } }, "alerts": { "type": "array", - "items": { "$ref": "#/definitions/alert" } + "items": { + "$ref": "#/definitions/alert" + } }, "dashboards": { "type": "array", - "items": { "$ref": "#/definitions/dashboard" } + "items": { + "$ref": "#/definitions/dashboard" + } }, "dataSourceIds": { "type": "array", - "items": { "$ref": "#/definitions/dataSourceIds" } + "items": { + "$ref": "#/definitions/dataSourceIds" + } } }, "required": [ @@ -32,50 +41,87 @@ "dataSourceIds" ], "additionalProperties": false, - "definitions": { "quickstart": { "type": "object", "properties": { - "id": { "type": "string" }, - "description": { "type": "string" }, - "summary": { "type": "string" }, - "title": { "type": "string" }, + "id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "title": { + "type": "string" + }, "authors": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } }, "documentation": { "type": "array", "items": { "type": "object", "properties": { - "name": { "type": "string" }, - "description": { "type": "string" }, - "url": { "type": "string" } + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } }, - "required": ["name", "description", "url"], + "required": [ + "name", + "description", + "url" + ], "additionalProperties": false } }, - "level": { "enum": ["New Relic", "Community", "Verified"] }, - "icon": { "type": "string" }, + "level": { + "enum": [ + "New Relic", + "Community", + "Verified" + ] + }, + "icon": { + "type": "string" + }, "keywords": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } + }, + "slug": { + "type": "string" }, - "slug": { "type": "string" }, "alertPolicies": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } }, "dashboards": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } }, "dataSourceIds": { "type": "array", - "items": { "$ref": "#/definitions/dataSourceIds" } + "items": { + "$ref": "#/definitions/dataSourceIds" + } } }, "required": [ @@ -87,34 +133,56 @@ ], "additionalProperties": false }, - "dataSource": { "type": "object", "properties": { - "id": { "type": "string" }, - "displayName": { "type": "string" }, - "description": { "type": "string" }, + "id": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, "install": { "type": "object", "properties": { - "primary": { "$ref": "#/definitions/installDirective" }, - "fallback": { "$ref": "#/definitions/installDirective" } + "primary": { + "$ref": "#/definitions/installDirective" + }, + "fallback": { + "$ref": "#/definitions/installDirective" + } }, - "required": ["primary"], + "required": [ + "primary" + ], "additionalProperties": false }, "keywords": { - "type": "array", "items": { "type": "string" } + "type": "array", + "items": { + "type": "string" + } }, "categoryTerms": { - "type": "array", "items": { "type": "string" } + "type": "array", + "items": { + "type": "string" + } }, - "icon": { "type": "string" } + "icon": { + "type": "string" + } }, - "required": ["id", "displayName", "install"], + "required": [ + "id", + "displayName", + "install" + ], "additionalProperties": false }, - "installDirective": { "type": "object", "oneOf": [ @@ -124,13 +192,19 @@ "link": { "type": "object", "properties": { - "url": { "type": "string" } + "url": { + "type": "string" + } }, - "required": ["url"], + "required": [ + "url" + ], "additionalProperties": false } }, - "required": ["link"], + "required": [ + "link" + ], "additionalProperties": false }, { @@ -139,71 +213,486 @@ "nerdlet": { "type": "object", "properties": { - "nerdletId": { "type": "string" }, - "nerdletState": { "type": "object" }, - "requiresAccount": { "type": "boolean" } + "nerdletId": { + "type": "string" + }, + "nerdletState": { + "type": "object" + }, + "requiresAccount": { + "type": "boolean" + } }, - "required": ["nerdletId", "requiresAccount"], + "required": [ + "nerdletId", + "requiresAccount" + ], "additionalProperties": false } }, - "required": ["nerdlet"], + "required": [ + "nerdlet" + ], "additionalProperties": false } ] }, - "alert": { "type": "array", "items": { "type": "object", "properties": { - "name": { "type": "string" }, - "description": { "type": "string", "nullable": true }, - "type": { "enum": ["BASELINE", "STATIC"], "type": "string", "nullable": true }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "type": { + "enum": [ + "BASELINE", + "STATIC" + ], + "type": "string", + "nullable": true + }, "nrql": { "type": "object", "properties": { - "query": { "type": "string" } + "query": { + "type": "string" + } }, - "required": ["query"], + "required": [ + "query" + ], "additionalProperties": true }, - "runbookUrl": { "type": "string", "nullable": true }, - "violationTimeLimitSeconds": { "type": "number" }, - "enabled": { "type": "boolean" }, + "runbookUrl": { + "type": "string", + "nullable": true + }, + "violationTimeLimitSeconds": { + "type": "number" + }, + "enabled": { + "type": "boolean" + }, "terms": { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { - "duration": { "type": "number", "minimum": 5, "maximum": 120 }, - "priority": { "enum": ["CRITICAL", "WARNING"] }, - "operator": { "enum": ["ABOVE", "BELOW", "EQUALS"] }, - "threshold": { "type": "number", "minimum": 0 }, - "thresholdDuration": { "type": "number", "minimum": 0 }, - "thresholdOccurances": { "enum": ["ALL", "AT_LEAST_ONCE"] } + "duration": { + "type": "number", + "minimum": 5, + "maximum": 120 + }, + "priority": { + "enum": [ + "CRITICAL", + "WARNING" + ] + }, + "operator": { + "enum": [ + "ABOVE", + "BELOW", + "EQUALS" + ] + }, + "threshold": { + "type": "number", + "minimum": 0 + }, + "thresholdDuration": { + "type": "number", + "minimum": 0 + }, + "thresholdOccurances": { + "enum": [ + "ALL", + "AT_LEAST_ONCE" + ] + } }, "additionalProperties": true } } }, - "required": ["name", "description", "type"], + "required": [ + "name", + "description", + "type" + ], "additionalProperties": true } }, - "dashboard": { "type": "object", "properties": { - "name": { "type": "string" }, - "pages": { "type": "array", "minItems": 1 }, - "description": { "type": ["string", "null"] }, - "variables": { "type": "array" } + "name": { + "type": "string" + }, + "pages": { + "type": "array", + "minItems": 1 + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "variables": { + "type": "array" + } }, - "required": ["name", "pages"], + "required": [ + "name", + "pages" + ], "additionalProperties": false + }, + "dataSourceIds": { + "enum": [ + "active-directory", + "ads-web-gpt", + "ads-web-prebid", + "aerospike", + "akamai", + "alert-quality-management", + "amazon-cloudwatch-metric-streams", + "amazon-eks-on-aws-fargate", + "ansible-automation-controller", + "apache-druid", + "apache-flink", + "apache-hadoop", + "apache-mesos", + "apache-traffic-server", + "apache-zookeeper", + "apdex-optimizer", + "apigee-api", + "apm-signals", + "argocd", + "atlassian-jira", + "audit-events-analysis", + "aws-cloudfront-logs", + "azure-monitor", + "azure-virtual-machines", + "battlesnake", + "bitbucket", + "third-party-biztalk360", + "blameless", + "blazemeter", + "blazor-webassembly", + "boomi", + "browser-segment-investigation", + "browser-signals", + "cacti", + "camel", + "catchpoint-quickstart", + "circleci", + "cloud-spanner", + "cloudflare", + "confluent-cloud", + "contentsquare", + "conviva", + "cribl", + "customer-experience-bottom-funnel-analysis", + "customer-experience-quality-foundation", + "dapr", + "databricks", + "datazoom", + "dbmarlin", + "dbt-cloud", + "deeper-network", + "delphix", + "docker-otel", + "dojo", + "elasticsearch-query", + "elixir", + "email-notifications", + "envoy", + "event-api", + "fastly", + "fivetran", + "flutter-android", + "flutter-ios", + "flutter-web", + "full-story", + "gatsby-build-newrelic", + "gcp-pubsub", + "gh-gates", + "gigamon-appinsights", + "github-repo", + "gitlab-integration", + "glassbox", + "grafana-dashboard-migration", + "grafana-prometheus-integration", + "gridgain-data-source", + "hardware-sentry", + "hcp-consul-otel", + "hcp-consul", + "hcp-envoy", + "hcp-vault-logs", + "hcp-vault-metrics", + "hivemq-integration-docs", + "ibmmq-integration-docs", + "infrastructure-signals", + "jdbc-executebatch", + "otel-jenkins-integration", + "jfrog-platform-cloud", + "jfrog-platform", + "jira-errors-inbox", + "k6-prometheus", + "k6", + "kentik", + "lacework", + "lambdatest", + "langchain-vectordb", + "langchain", + "launchdarkly", + "lighthouse", + "lighttpd", + "linkerd", + "logs-api", + "logs-default", + "metric-api", + "mobile-getting-started", + "mobile-network-performance-install", + "mobile-signals", + "mongodb-prometheus-integration-docs", + "mule-esb", + "mux-video", + "netlify-builds", + "netlify-logs", + "netscaler", + "network-data-ingest-and-cardinality", + "network-routers-and-switches", + "network-syslog", + "newrelic-cli", + "nextcloud", + "nr-reports", + "nuxtjs", + "nvidia-dcgm", + "nvidia-gpu", + "nvidia-jetson", + "nvidia-triton", + "nvml", + "ocsf", + "okhttp", + "onepane", + "opencensus", + "openstack-controller", + "opslevel", + "pagerduty", + "perfmon", + "pihole", + "port-monitoring", + "postfix", + "postman", + "pulumi", + "quantum-metric", + "rafay", + "ray", + "redis-enterprise", + "redis-otel", + "redmine", + "redpanda", + "releaseiq", + "newrelic-java-rmi", + "roku-http-analytics", + "roku", + "salesforce-eventlog-for-logs", + "sendgrid-integration", + "sendmail", + "servicenow-notifications", + "shopify-hydrogen", + "shopify", + "signl4", + "singlestore", + "skykit", + "slack-notifications", + "snowflake", + "solutions-hub-dashboards", + "sonarqube", + "speedscale", + "split", + "squid-prometheus-integration-docs", + "stripe", + "sybase-integration", + "synthetics-private-locations", + "telemetry-data-platform", + "temporal-cloud", + "temporal", + "terraform", + "tidbcloud", + "trace-and-span-api", + "traceloop", + "trendmicro-cloudone-conformity", + "tvos-mobile", + "unix", + "vercel", + "vertica", + "newrelic-java-vertx", + "newrelic-java-vertx-extensions", + "video-android", + "video-chromecast", + "video-ios-tvos", + "video-web-akamai", + "video-web-html5", + "video-web-jwplayer", + "video-web-theplatform", + "video-web-videojs", + "webhook-notifications", + "windows-certs", + "winservices-integration-docs", + "xmatters", + "zebrium", + "zenduty", + "zipkin", + "activemq", + "amazon-cloudwatch", + "amazon-kinesis", + "amazon-linux", + "amazon-linux-logs", + "amazon-s3", + "amazon-sagemaker", + "amazon-web-services", + "android", + "angular", + "angular-js", + "ansible-install", + "apache", + "apm-logs", + "aporia", + "aws-firelens", + "aws-lambda", + "aws-migrate-metricStream", + "aws-security-hub", + "aws-vpc-flow-logs", + "azure-openai", + "backbone", + "bring-your-own-data", + "capacitor", + "cassandra", + "centos", + "centos-logs", + "change-tracking", + "chef-install", + "comet", + "consul", + "cordova", + "couchbase", + "dagshub", + "debian", + "debian-logs", + "dependabot", + "distributed-tracing", + "docker", + "dropwizard", + "elasticsearch", + "ember", + "f5", + "fluent-bit", + "fluentd", + "flutter", + "gatsby", + "gcp-vpc-flow-logs", + "golang", + "google-cloud-platform", + "google-cloud-platform-logs", + "govcloud", + "guided-install", + "haproxy", + "headerless-log-api", + "heroku", + "heroku-http", + "ios", + "java", + "java-logs", + "jmx", + "jquery", + "kafka", + "kamon", + "kubernetes", + "kubernetes-logs", + "linux", + "linux-infra", + "linux-logs", + "llm-application", + "logstash", + "macos", + "maui", + "memcached", + "micrometer", + "microsoft-azure", + "microsoft-azure-blob-storage", + "microsoft-azure-event-hub", + "microsoft-sql-server", + "microsoftnet", + "microsoftnet-logs", + "mona", + "mongodb", + "mysql", + "nagios", + "network-devices", + "network-flows", + "network-synthetics", + "new-relic-browser", + "new-relic-infrastructure-agent", + "new-relic-synthetics", + "next-js", + "nginx", + "node-js", + "nr-ai-monitoring", + "nvidia-nim", + "opentelemetry", + "oracle-database", + "php", + "pixie", + "postgresql", + "powerdns", + "prometheus", + "prometheus-agent-mode", + "puppet-install", + "python", + "rabbitmq", + "react", + "react-native", + "redis", + "rhel", + "rhel-logs", + "ruby", + "ruby-logs", + "security-api", + "sles", + "sles-logs", + "snmp", + "snyk", + "statsd", + "super-agent", + "syslog", + "trivy", + "truera", + "ubuntu", + "ubuntu-logs", + "unity", + "unreal", + "varnish", + "vmware-tanzu", + "vmware-vsphere", + "vue", + "windows", + "windows-infra", + "windows-logs", + "xamarin", + "xcframework", + "zepto" + ] } } -} +} \ No newline at end of file