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
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:
@@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user