chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,384 @@
import type {
NewRelicChangeTrackingEvent,
NewRelicCreateDeploymentEventParams,
NewRelicCreateDeploymentEventResponse,
NewRelicDeploymentType,
} from '@/tools/new_relic/types'
import {
cleanOptionalString,
getNerdGraphEndpoint,
gqlString,
newRelicHeaders,
parseNerdGraphResponse,
} from '@/tools/new_relic/utils'
import type { ToolConfig } from '@/tools/types'
interface CreateDeploymentEventData {
changeTrackingCreateEvent?: {
changeTrackingEvent?: NewRelicChangeTrackingEvent | null
messages?: string[]
} | null
}
const DEPLOYMENT_TYPES: NewRelicDeploymentType[] = [
'basic',
'blue green',
'canary',
'rolling',
'shadow',
]
const CUSTOM_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/
const RESTRICTED_CUSTOM_ATTRIBUTE_NAMES = new Set([
'accountId',
'ago',
'and',
'appID',
'as',
'auto',
'begin',
'begintime',
'category',
'categoryType',
'changeTrackingId',
'compare',
'customAttributes',
'customType',
'day',
'days',
'description',
'end',
'endtime',
'explain',
'entityGuid',
'entityName',
'eventType',
'facet',
'from',
'groupId',
'hostname',
'hour',
'hours',
'in',
'is',
'like',
'limit',
'log',
'minute',
'minutes',
'month',
'months',
'not',
'null',
'offset',
'or',
'raw',
'second',
'seconds',
'select',
'since',
'timeseries',
'timestamp',
'type',
'until',
'user',
'week',
'weeks',
'where',
'with',
])
const getDeploymentType = (value?: string): NewRelicDeploymentType => {
const normalized = value?.toLowerCase().replace(/[_-]/g, ' ')
return DEPLOYMENT_TYPES.includes(normalized as NewRelicDeploymentType)
? (normalized as NewRelicDeploymentType)
: 'basic'
}
const graphqlLiteral = (value: string | number | boolean): string => {
if (typeof value === 'string') return gqlString(value)
return String(value)
}
const buildCustomAttributes = (
customAttributes?: NewRelicCreateDeploymentEventParams['customAttributes']
): string | undefined => {
if (!customAttributes) return undefined
const entries = Object.entries(customAttributes)
if (!entries.length) return undefined
for (const [key, value] of entries) {
if (!CUSTOM_ATTRIBUTE_NAME_PATTERN.test(key)) {
throw new Error(
`Invalid New Relic custom attribute name "${key}". Use letters, numbers, and underscores, and do not start with a number.`
)
}
if (RESTRICTED_CUSTOM_ATTRIBUTE_NAMES.has(key) || key.includes('.')) {
throw new Error(`New Relic custom attribute name "${key}" is restricted`)
}
if (!['string', 'number', 'boolean'].includes(typeof value)) {
throw new Error(
`Invalid value for New Relic custom attribute "${key}". Use a string, number, or boolean.`
)
}
if (typeof value === 'number' && !Number.isFinite(value)) {
throw new Error(`Invalid numeric value for New Relic custom attribute "${key}"`)
}
}
const fields = entries.map(([key, value]) => `${key}: ${graphqlLiteral(value)}`).join(', ')
return `customAttributes: { ${fields} }`
}
const buildDeploymentMutation = (params: NewRelicCreateDeploymentEventParams): string => {
const deploymentType = getDeploymentType(params.deploymentType)
const entityGuid = params.entityGuid.trim()
if (!entityGuid || entityGuid.includes("'")) {
throw new Error('Invalid entity GUID: value must not be empty or contain single quotes')
}
const version = params.version.trim()
const shortDescription = cleanOptionalString(params.shortDescription)
const description = cleanOptionalString(params.description)
const changelog = cleanOptionalString(params.changelog)
const commit = cleanOptionalString(params.commit)
const deepLink = cleanOptionalString(params.deepLink)
const user = cleanOptionalString(params.user)
const groupId = cleanOptionalString(params.groupId)
const customAttributes = buildCustomAttributes(params.customAttributes)
const deploymentFields = [
`version: ${gqlString(version)}`,
changelog ? `changelog: ${gqlString(changelog)}` : undefined,
commit ? `commit: ${gqlString(commit)}` : undefined,
deepLink ? `deepLink: ${gqlString(deepLink)}` : undefined,
]
.filter((field): field is string => Boolean(field))
.join(', ')
const optionalFields = [
shortDescription ? `shortDescription: ${gqlString(shortDescription)}` : undefined,
description ? `description: ${gqlString(description)}` : undefined,
user ? `user: ${gqlString(user)}` : undefined,
groupId ? `groupId: ${gqlString(groupId)}` : undefined,
customAttributes,
params.timestamp ? `timestamp: ${Math.trunc(Number(params.timestamp))}` : undefined,
]
.filter((field): field is string => Boolean(field))
.join('\n ')
return `mutation {
changeTrackingCreateEvent(
changeTrackingEvent: {
categoryAndTypeData: {
categoryFields: { deployment: { ${deploymentFields} } }
kind: { category: "deployment", type: ${gqlString(deploymentType)} }
}
entitySearch: { query: ${gqlString(`id = '${entityGuid}'`)} }
${optionalFields}
}
) {
changeTrackingEvent {
category
categoryAndType
changeTrackingId
customAttributes
description
entity {
guid
name
}
groupId
shortDescription
timestamp
type
user
}
messages
}
}`
}
export const newRelicCreateDeploymentEventTool: ToolConfig<
NewRelicCreateDeploymentEventParams,
NewRelicCreateDeploymentEventResponse
> = {
id: 'new_relic_create_deployment_event',
name: 'New Relic Create Deployment Event',
description: 'Record a deployment change event in New Relic change tracking.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'New Relic user API key for NerdGraph',
},
region: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'New Relic data center region: us or eu',
},
entityGuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'GUID of the entity associated with the deployment',
},
version: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Deployment version, release name, or commit SHA',
},
shortDescription: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Short description of the deployment',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Longer deployment description',
},
changelog: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Deployment changelog text or URL',
},
commit: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Commit SHA or identifier associated with the deployment',
},
deepLink: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'URL to the deployment, build, or release details',
},
user: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User or automation that performed the deployment',
},
groupId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional group ID to correlate related changes',
},
customAttributes: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description:
'Custom change event metadata as key-value pairs with string, number, or boolean values',
},
deploymentType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Deployment type: basic, blue green, canary, rolling, or shadow',
},
timestamp: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Event timestamp in milliseconds since Unix epoch',
},
},
request: {
url: (params) => getNerdGraphEndpoint(params.region),
method: 'POST',
headers: (params) => newRelicHeaders(params.apiKey),
body: (params) => ({
query: buildDeploymentMutation(params),
}),
},
transformResponse: async (response) => {
const payload = await parseNerdGraphResponse<CreateDeploymentEventData>(response)
const result = payload.data?.changeTrackingCreateEvent
if (!result) {
throw new Error('New Relic did not return a deployment change tracking result')
}
if (!result.changeTrackingEvent) {
const message = result.messages?.length
? result.messages.join('; ')
: 'New Relic did not create a deployment change tracking event'
throw new Error(message)
}
return {
success: true,
output: {
event: result.changeTrackingEvent,
messages: result?.messages ?? [],
},
}
},
outputs: {
event: {
type: 'object',
description: 'Created New Relic change tracking event',
properties: {
changeTrackingId: {
type: 'string',
description: 'New Relic change tracking ID',
nullable: true,
},
customAttributes: {
type: 'json',
description: 'Custom attributes on the change tracking event',
optional: true,
nullable: true,
},
category: { type: 'string', description: 'Change category', nullable: true },
categoryAndType: {
type: 'string',
description: 'Combined category and type',
nullable: true,
},
type: { type: 'string', description: 'Change type', nullable: true },
shortDescription: {
type: 'string',
description: 'Short change description',
nullable: true,
},
description: { type: 'string', description: 'Change description', nullable: true },
timestamp: {
type: 'number',
description: 'Change timestamp in milliseconds',
nullable: true,
},
user: { type: 'string', description: 'User associated with the change', nullable: true },
groupId: { type: 'string', description: 'Change group ID', nullable: true },
entity: {
type: 'object',
description: 'Entity associated with the change',
nullable: true,
properties: {
guid: { type: 'string', description: 'Entity GUID', nullable: true },
name: { type: 'string', description: 'Entity name', nullable: true },
},
},
},
},
messages: {
type: 'array',
description: 'Messages returned by New Relic for the created change event',
items: {
type: 'string',
description: 'New Relic message',
},
},
},
}
+119
View File
@@ -0,0 +1,119 @@
import type { NewRelicGetEntityParams, NewRelicGetEntityResponse } from '@/tools/new_relic/types'
import {
getNerdGraphEndpoint,
gqlString,
type NewRelicRawEntity,
newRelicHeaders,
normalizeNewRelicEntity,
parseNerdGraphResponse,
} from '@/tools/new_relic/utils'
import type { ToolConfig } from '@/tools/types'
interface GetEntityData {
actor?: {
entity?: NewRelicRawEntity | null
} | null
}
export const newRelicGetEntityTool: ToolConfig<NewRelicGetEntityParams, NewRelicGetEntityResponse> =
{
id: 'new_relic_get_entity',
name: 'New Relic Get Entity',
description: 'Fetch a New Relic entity by GUID.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'New Relic user API key for NerdGraph',
},
region: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'New Relic data center region: us or eu',
},
guid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Entity GUID',
},
},
request: {
url: (params) => getNerdGraphEndpoint(params.region),
method: 'POST',
headers: (params) => newRelicHeaders(params.apiKey),
body: (params) => ({
query: `{
actor {
entity(guid: ${gqlString(params.guid.trim())}) {
name
entityType
domain
reporting
... on AlertableEntity {
alertSeverity
}
tags {
key
values
}
}
}
}`,
}),
},
transformResponse: async (response, params) => {
const payload = await parseNerdGraphResponse<GetEntityData>(response)
const entity = payload.data?.actor?.entity
return {
success: true,
output: {
entity: entity
? { ...normalizeNewRelicEntity(entity), guid: params?.guid ?? null }
: null,
},
}
},
outputs: {
entity: {
type: 'object',
description: 'New Relic entity details',
optional: true,
properties: {
guid: { type: 'string', description: 'Entity GUID', nullable: true },
name: { type: 'string', description: 'Entity name', nullable: true },
entityType: { type: 'string', description: 'Entity type', nullable: true },
domain: { type: 'string', description: 'Entity domain, e.g. APM, INFRA', nullable: true },
reporting: {
type: 'boolean',
description: 'Whether the entity is currently reporting data',
nullable: true,
},
alertSeverity: {
type: 'string',
description: 'Current alert severity for the entity',
nullable: true,
},
tags: {
type: 'array',
description: 'Entity tags',
items: {
type: 'object',
properties: {
key: { type: 'string', description: 'Tag key', nullable: true },
values: { type: 'array', description: 'Tag values', items: { type: 'string' } },
},
},
},
},
},
},
}
+5
View File
@@ -0,0 +1,5 @@
export { newRelicCreateDeploymentEventTool } from '@/tools/new_relic/create_deployment_event'
export { newRelicGetEntityTool } from '@/tools/new_relic/get_entity'
export { newRelicNrqlQueryTool } from '@/tools/new_relic/nrql_query'
export { newRelicSearchEntitiesTool } from '@/tools/new_relic/search_entities'
export type * from '@/tools/new_relic/types'
+111
View File
@@ -0,0 +1,111 @@
import type { NewRelicNrqlQueryParams, NewRelicNrqlQueryResponse } from '@/tools/new_relic/types'
import {
getNerdGraphEndpoint,
gqlString,
newRelicHeaders,
parseNerdGraphResponse,
} from '@/tools/new_relic/utils'
import type { ToolConfig } from '@/tools/types'
interface NrqlQueryData {
actor?: {
account?: {
nrql?: {
results?: Record<string, unknown>[]
} | null
} | null
} | null
}
export const newRelicNrqlQueryTool: ToolConfig<NewRelicNrqlQueryParams, NewRelicNrqlQueryResponse> =
{
id: 'new_relic_nrql_query',
name: 'New Relic NRQL Query',
description: 'Run a NRQL query against a New Relic account using NerdGraph.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'New Relic user API key for NerdGraph',
},
region: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'New Relic data center region: us or eu',
},
accountId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'New Relic account ID to query',
},
nrql: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'NRQL query to execute',
},
timeout: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Optional query timeout in seconds',
},
},
request: {
url: (params) => getNerdGraphEndpoint(params.region),
method: 'POST',
headers: (params) => newRelicHeaders(params.apiKey),
body: (params) => {
const timeout = params.timeout ? `, timeout: ${Math.trunc(Number(params.timeout))}` : ''
return {
query: `{
actor {
account(id: ${Math.trunc(Number(params.accountId))}) {
nrql(query: ${gqlString(params.nrql)}${timeout}) {
results
}
}
}
}`,
}
},
},
transformResponse: async (response) => {
const payload = await parseNerdGraphResponse<NrqlQueryData>(response)
const nrql = payload.data?.actor?.account?.nrql
if (!nrql) {
throw new Error('New Relic did not return NRQL data for the requested account')
}
const results = nrql.results ?? []
return {
success: true,
output: {
results,
resultCount: results.length,
},
}
},
outputs: {
results: {
type: 'array',
description: 'NRQL result rows. Row fields depend on the query projection.',
items: {
type: 'object',
description: 'A NRQL result row',
},
},
resultCount: {
type: 'number',
description: 'Number of NRQL result rows returned',
},
},
}
+163
View File
@@ -0,0 +1,163 @@
import type {
NewRelicSearchEntitiesParams,
NewRelicSearchEntitiesResponse,
} from '@/tools/new_relic/types'
import {
getNerdGraphEndpoint,
type NewRelicRawEntity,
newRelicHeaders,
normalizeNewRelicEntity,
parseNerdGraphResponse,
} from '@/tools/new_relic/utils'
import type { ToolConfig } from '@/tools/types'
interface SearchEntitiesData {
actor?: {
entitySearch?: {
count?: number
query?: string
results?: {
nextCursor?: string | null
entities?: NewRelicRawEntity[]
} | null
} | null
} | null
}
export const newRelicSearchEntitiesTool: ToolConfig<
NewRelicSearchEntitiesParams,
NewRelicSearchEntitiesResponse
> = {
id: 'new_relic_search_entities',
name: 'New Relic Search Entities',
description: 'Search New Relic entities by name, GUID, domain type, tags, or reporting state.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'New Relic user API key for NerdGraph',
},
region: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'New Relic data center region: us or eu',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Entity search query, for example: name like "api" or domainType = "APM-APPLICATION"',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from a previous entity search',
},
},
request: {
url: (params) => getNerdGraphEndpoint(params.region),
method: 'POST',
headers: (params) => newRelicHeaders(params.apiKey),
body: (params) => ({
query: `query($query: String!, $cursor: String) {
actor {
entitySearch(query: $query) {
count
query
results(cursor: $cursor) {
nextCursor
entities {
guid
name
entityType
domain
reporting
... on AlertableEntityOutline {
alertSeverity
}
tags {
key
values
}
}
}
}
}
}`,
variables: {
query: params.query,
cursor: params.cursor || null,
},
}),
},
transformResponse: async (response) => {
const payload = await parseNerdGraphResponse<SearchEntitiesData>(response)
const entitySearch = payload.data?.actor?.entitySearch
if (!entitySearch) {
throw new Error('New Relic did not return entity search data')
}
const entities = (entitySearch?.results?.entities ?? []).map(normalizeNewRelicEntity)
return {
success: true,
output: {
count: entitySearch?.count ?? entities.length,
query: entitySearch?.query ?? '',
entities,
nextCursor: entitySearch?.results?.nextCursor ?? null,
},
}
},
outputs: {
count: { type: 'number', description: 'Total number of entities matching the query' },
query: { type: 'string', description: 'Entity search query New Relic executed' },
entities: {
type: 'array',
description: 'Matching New Relic entities',
items: {
type: 'object',
properties: {
guid: { type: 'string', description: 'Entity GUID', nullable: true },
name: { type: 'string', description: 'Entity name', nullable: true },
entityType: { type: 'string', description: 'Entity type', nullable: true },
domain: { type: 'string', description: 'Entity domain, e.g. APM, INFRA', nullable: true },
reporting: {
type: 'boolean',
description: 'Whether the entity is currently reporting data',
nullable: true,
},
alertSeverity: {
type: 'string',
description: 'Current alert severity for the entity',
nullable: true,
},
tags: {
type: 'array',
description: 'Entity tags',
items: {
type: 'object',
properties: {
key: { type: 'string', description: 'Tag key', nullable: true },
values: { type: 'array', description: 'Tag values', items: { type: 'string' } },
},
},
},
},
},
},
nextCursor: {
type: 'string',
description: 'Cursor for the next page of results',
optional: true,
},
},
}
+109
View File
@@ -0,0 +1,109 @@
import type { ToolResponse } from '@/tools/types'
export type NewRelicRegion = 'us' | 'eu'
export interface NewRelicBaseParams {
apiKey: string
region?: NewRelicRegion
}
export interface NewRelicNrqlQueryParams extends NewRelicBaseParams {
accountId: number
nrql: string
timeout?: number
}
export interface NewRelicNrqlQueryResponse extends ToolResponse {
output: {
results: Record<string, unknown>[]
resultCount: number
}
}
export interface NewRelicSearchEntitiesParams extends NewRelicBaseParams {
query: string
cursor?: string
}
export interface NewRelicEntityTag {
key: string | null
values: string[]
}
export interface NewRelicEntity {
guid: string | null
name: string | null
entityType: string | null
domain: string | null
reporting: boolean | null
alertSeverity: string | null
tags: NewRelicEntityTag[]
}
export interface NewRelicSearchEntitiesResponse extends ToolResponse {
output: {
count: number
query: string
entities: NewRelicEntity[]
nextCursor: string | null
}
}
export interface NewRelicGetEntityParams extends NewRelicBaseParams {
guid: string
}
export interface NewRelicGetEntityResponse extends ToolResponse {
output: {
entity: NewRelicEntity | null
}
}
export type NewRelicDeploymentType = 'basic' | 'blue green' | 'canary' | 'rolling' | 'shadow'
export type NewRelicCustomAttributes = Record<string, string | number | boolean>
export interface NewRelicCreateDeploymentEventParams extends NewRelicBaseParams {
entityGuid: string
version: string
shortDescription?: string
description?: string
changelog?: string
commit?: string
deepLink?: string
user?: string
groupId?: string
customAttributes?: NewRelicCustomAttributes
deploymentType?: NewRelicDeploymentType
timestamp?: number
}
export interface NewRelicChangeTrackingEvent {
category: string | null
categoryAndType: string | null
changeTrackingId: string | null
customAttributes?: Record<string, unknown> | null
description: string | null
groupId: string | null
shortDescription: string | null
timestamp: number | null
type: string | null
user: string | null
entity: {
guid: string | null
name: string | null
} | null
}
export interface NewRelicCreateDeploymentEventResponse extends ToolResponse {
output: {
event: NewRelicChangeTrackingEvent | null
messages: string[]
}
}
export type NewRelicResponse =
| NewRelicNrqlQueryResponse
| NewRelicSearchEntitiesResponse
| NewRelicGetEntityResponse
| NewRelicCreateDeploymentEventResponse
+66
View File
@@ -0,0 +1,66 @@
import type { NewRelicEntity, NewRelicRegion } from '@/tools/new_relic/types'
interface GraphQLError {
message?: string
}
export interface NewRelicRawEntity {
guid?: string | null
name?: string | null
entityType?: string | null
domain?: string | null
reporting?: boolean | null
alertSeverity?: string | null
tags?: ({ key?: string | null; values?: string[] | null } | null)[] | null
}
interface GraphQLResponse<TData> {
data?: TData
errors?: GraphQLError[]
}
export const getNerdGraphEndpoint = (region?: NewRelicRegion): string =>
region === 'eu' ? 'https://api.eu.newrelic.com/graphql' : 'https://api.newrelic.com/graphql'
export const newRelicHeaders = (apiKey: string): Record<string, string> => ({
'API-Key': apiKey,
'Content-Type': 'application/json',
})
export const gqlString = (value: string): string => JSON.stringify(value)
export async function parseNerdGraphResponse<TData>(
response: Response
): Promise<GraphQLResponse<TData>> {
const payload = (await response.json().catch(() => ({}))) as GraphQLResponse<TData>
if (!response.ok || payload.errors?.length) {
const message =
payload.errors
?.map((error) => error.message)
.filter((errorMessage): errorMessage is string => Boolean(errorMessage))
.join('; ') || `HTTP ${response.status}: ${response.statusText}`
throw new Error(message)
}
return payload
}
export const cleanOptionalString = (value?: string): string | undefined => {
const trimmed = value?.trim()
return trimmed ? trimmed : undefined
}
export const normalizeNewRelicEntity = (entity: NewRelicRawEntity): NewRelicEntity => ({
guid: entity.guid ?? null,
name: entity.name ?? null,
entityType: entity.entityType ?? null,
domain: entity.domain ?? null,
reporting: entity.reporting ?? null,
alertSeverity: entity.alertSeverity ?? null,
tags:
entity.tags?.map((tag) => ({
key: tag?.key ?? null,
values: tag?.values ?? [],
})) ?? [],
})