Skip to content

Commit 19a36ab

Browse files
chore: Fix change tracking (#1750)
1 parent e59c60a commit 19a36ab

6 files changed

Lines changed: 219 additions & 136 deletions

File tree

.github/actions/change-tracking/action.yml

Lines changed: 7 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,12 @@ inputs:
6464
runs:
6565
using: 'composite'
6666
steps:
67-
- name: Create change tracking event via NerdGraph
67+
- name: Install dependencies
68+
run: npm install --silent --no-progress --prefix $GITHUB_ACTION_PATH/..
6869
shell: bash
70+
71+
- name: Run action script
72+
id: action-script
6973
env:
7074
ENTITY_GUID: ${{ inputs.entityGuid }}
7175
API_KEY: ${{ inputs.apiKey }}
@@ -81,137 +85,5 @@ runs:
8185
USER: ${{ inputs.user }}
8286
GROUP_ID: ${{ inputs.groupId }}
8387
SHORT_DESCRIPTION: ${{ inputs.shortDescription }}
84-
run: |
85-
# All NerdGraph API calls go to staging
86-
API_ENDPOINT="https://staging-api.newrelic.com/graphql"
87-
88-
echo "Creating change tracking event via NerdGraph..."
89-
echo " Category: $CATEGORY"
90-
echo " Type: $TYPE"
91-
echo " Entity GUID: $ENTITY_GUID"
92-
echo " Application: $APPLICATION"
93-
echo " API Endpoint: $API_ENDPOINT"
94-
95-
# Convert category to uppercase with underscores (e.g., "Feature Flag" -> "FEATURE_FLAG")
96-
CATEGORY_GQL=$(echo "$CATEGORY" | tr '[:lower:]' '[:upper:]' | sed 's/ /_/g')
97-
98-
# Convert type to PascalCase without spaces (e.g., "Blue Green" -> "BlueGreen")
99-
TYPE_GQL=$(echo "$TYPE" | sed -e 's/\b\(.\)/\u\1/g' -e 's/ //g')
100-
101-
# Build categoryFields based on category
102-
CATEGORY_FIELDS=""
103-
if [ "$CATEGORY" = "Deployment" ]; then
104-
echo " Version: $VERSION"
105-
106-
# Escape quotes in values
107-
VERSION_ESC=$(echo "$VERSION" | sed 's/"/\\"/g')
108-
CATEGORY_FIELDS="deployment: { version: \\\"$VERSION_ESC\\\""
109-
110-
if [ -n "$CHANGELOG" ]; then
111-
CHANGELOG_ESC=$(echo "$CHANGELOG" | sed 's/"/\\"/g')
112-
CATEGORY_FIELDS="$CATEGORY_FIELDS, changelog: \\\"$CHANGELOG_ESC\\\""
113-
fi
114-
if [ -n "$COMMIT" ]; then
115-
COMMIT_ESC=$(echo "$COMMIT" | sed 's/"/\\"/g')
116-
CATEGORY_FIELDS="$CATEGORY_FIELDS, commit: \\\"$COMMIT_ESC\\\""
117-
fi
118-
if [ -n "$DEEP_LINK" ]; then
119-
DEEP_LINK_ESC=$(echo "$DEEP_LINK" | sed 's/"/\\"/g')
120-
CATEGORY_FIELDS="$CATEGORY_FIELDS, deepLink: \\\"$DEEP_LINK_ESC\\\""
121-
fi
122-
CATEGORY_FIELDS="$CATEGORY_FIELDS }"
123-
124-
elif [ "$CATEGORY" = "Feature Flag" ]; then
125-
echo " Feature Flag ID: $FEATURE_FLAG_ID"
126-
FEATURE_FLAG_ID_ESC=$(echo "$FEATURE_FLAG_ID" | sed 's/"/\\"/g')
127-
CATEGORY_FIELDS="featureFlag: { featureFlagId: \\\"$FEATURE_FLAG_ID_ESC\\\" }"
128-
fi
129-
130-
# Build optional fields with proper escaping
131-
OPTIONAL_FIELDS=""
132-
if [ -n "$USER" ]; then
133-
USER_ESC=$(echo "$USER" | sed 's/"/\\"/g')
134-
OPTIONAL_FIELDS="$OPTIONAL_FIELDS, user: \\\"$USER_ESC\\\""
135-
fi
136-
if [ -n "$DESCRIPTION" ]; then
137-
DESCRIPTION_ESC=$(echo "$DESCRIPTION" | sed 's/"/\\"/g' | sed 's/$//')
138-
OPTIONAL_FIELDS="$OPTIONAL_FIELDS, description: \\\"$DESCRIPTION_ESC\\\""
139-
fi
140-
if [ -n "$SHORT_DESCRIPTION" ]; then
141-
SHORT_DESCRIPTION_ESC=$(echo "$SHORT_DESCRIPTION" | sed 's/"/\\"/g')
142-
OPTIONAL_FIELDS="$OPTIONAL_FIELDS, shortDescription: \\\"$SHORT_DESCRIPTION_ESC\\\""
143-
fi
144-
if [ -n "$GROUP_ID" ]; then
145-
GROUP_ID_ESC=$(echo "$GROUP_ID" | sed 's/"/\\"/g')
146-
OPTIONAL_FIELDS="$OPTIONAL_FIELDS, groupId: \\\"$GROUP_ID_ESC\\\""
147-
fi
148-
149-
# Build the complete GraphQL mutation
150-
MUTATION_QUERY="mutation {
151-
changeTrackingCreateEvent(
152-
changeTrackingEvent: {
153-
categoryAndTypeData: {
154-
${CATEGORY_FIELDS:+categoryFields: { $CATEGORY_FIELDS },}
155-
kind: { category: \\\"$CATEGORY_GQL\\\", type: \\\"$TYPE_GQL\\\" }
156-
},
157-
entitySearch: { query: \\\"id = '$ENTITY_GUID'\\\" }
158-
$OPTIONAL_FIELDS
159-
}
160-
) {
161-
changeTrackingEvent {
162-
changeTrackingId
163-
timestamp
164-
category
165-
type
166-
user
167-
shortDescription
168-
description
169-
groupId
170-
}
171-
}
172-
}"
173-
174-
# Create JSON payload
175-
PAYLOAD=$(jq -n --arg query "$MUTATION_QUERY" '{"query": $query}')
176-
177-
# Execute the NerdGraph mutation
178-
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$API_ENDPOINT" \
179-
-H "Content-Type: application/json" \
180-
-H "API-Key: $API_KEY" \
181-
-d "$PAYLOAD")
182-
183-
# Split response body and status code
184-
HTTP_BODY=$(echo "$RESPONSE" | sed '$d')
185-
HTTP_STATUS=$(echo "$RESPONSE" | tail -n1)
186-
187-
echo ""
188-
echo "HTTP Status: $HTTP_STATUS"
189-
echo "Response:"
190-
echo "$HTTP_BODY" | jq '.' 2>/dev/null || echo "$HTTP_BODY"
191-
192-
# Check HTTP status
193-
if [ "$HTTP_STATUS" != "200" ]; then
194-
echo ""
195-
echo "Error: HTTP request failed with status $HTTP_STATUS"
196-
exit 1
197-
fi
198-
199-
# Check for GraphQL errors
200-
if echo "$HTTP_BODY" | jq -e '.errors' > /dev/null 2>&1; then
201-
echo ""
202-
echo "Error: Change tracking event creation failed!"
203-
echo "$HTTP_BODY" | jq '.errors'
204-
exit 1
205-
fi
206-
207-
# Extract and display tracking ID
208-
TRACKING_ID=$(echo "$HTTP_BODY" | jq -r '.data.changeTrackingCreateEvent.changeTrackingEvent.changeTrackingId // empty')
209-
if [ -n "$TRACKING_ID" ]; then
210-
echo ""
211-
echo "✓ Change tracking event created successfully!"
212-
echo " Tracking ID: $TRACKING_ID"
213-
else
214-
echo ""
215-
echo "Error: Tracking ID not found in response"
216-
exit 1
217-
fi
88+
run: node $GITHUB_ACTION_PATH/index.js
89+
shell: bash
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import { gql, GraphQLClient } from 'graphql-request'
2+
3+
/**
4+
* Get inputs from environment variables
5+
*/
6+
function getInputs() {
7+
return {
8+
entityGuid: process.env.ENTITY_GUID,
9+
apiKey: process.env.API_KEY,
10+
application: process.env.APPLICATION,
11+
version: process.env.VERSION,
12+
category: process.env.CATEGORY || 'Deployment',
13+
type: process.env.TYPE || 'Basic',
14+
featureFlagId: process.env.FEATURE_FLAG_ID,
15+
description: process.env.DESCRIPTION,
16+
changelog: process.env.CHANGELOG,
17+
commit: process.env.COMMIT,
18+
deepLink: process.env.DEEP_LINK,
19+
user: process.env.USER,
20+
groupId: process.env.GROUP_ID,
21+
shortDescription: process.env.SHORT_DESCRIPTION,
22+
}
23+
}
24+
25+
/**
26+
* Safely escape and quote a string for GraphQL
27+
*/
28+
function gqlString(str) {
29+
if (!str) return null
30+
// Let GraphQL handle the escaping by using JSON.stringify
31+
return JSON.stringify(str)
32+
}
33+
34+
/**
35+
* Build the categoryFields GraphQL string based on category
36+
*/
37+
function buildCategoryFieldsGQL(category, inputs) {
38+
if (category === 'Deployment') {
39+
const fields = [`version: ${gqlString(inputs.version)}`]
40+
41+
if (inputs.changelog) {
42+
fields.push(`changelog: ${gqlString(inputs.changelog)}`)
43+
}
44+
if (inputs.commit) {
45+
fields.push(`commit: ${gqlString(inputs.commit)}`)
46+
}
47+
if (inputs.deepLink) {
48+
fields.push(`deepLink: ${gqlString(inputs.deepLink)}`)
49+
}
50+
51+
return `categoryFields: { deployment: { ${fields.join(', ')} } }`
52+
} else if (category === 'Feature Flag') {
53+
return `categoryFields: { featureFlag: { featureFlagId: ${gqlString(inputs.featureFlagId)} } }`
54+
}
55+
56+
return ''
57+
}
58+
59+
/**
60+
* Build optional fields GraphQL string
61+
*/
62+
function buildOptionalFieldsGQL(inputs) {
63+
const fields = []
64+
65+
if (inputs.user) {
66+
fields.push(`user: ${gqlString(inputs.user)}`)
67+
}
68+
if (inputs.description) {
69+
fields.push(`description: ${gqlString(inputs.description)}`)
70+
}
71+
if (inputs.shortDescription) {
72+
fields.push(`shortDescription: ${gqlString(inputs.shortDescription)}`)
73+
}
74+
if (inputs.groupId) {
75+
fields.push(`groupId: ${gqlString(inputs.groupId)}`)
76+
}
77+
78+
return fields.join('\n ')
79+
}
80+
81+
/**
82+
* Create change tracking event
83+
*/
84+
async function createChangeTrackingEvent() {
85+
try {
86+
// All NerdGraph API calls go to staging
87+
const apiEndpoint = 'https://staging-api.newrelic.com/graphql'
88+
89+
const inputs = getInputs()
90+
91+
console.log('Creating change tracking event via NerdGraph...')
92+
console.log(` Category: ${inputs.category}`)
93+
console.log(` Type: ${inputs.type}`)
94+
console.log(` Entity GUID: ${inputs.entityGuid}`)
95+
console.log(` Application: ${inputs.application}`)
96+
console.log(` API Endpoint: ${apiEndpoint}`)
97+
98+
if (inputs.category === 'Deployment') {
99+
console.log(` Version: ${inputs.version}`)
100+
} else if (inputs.category === 'Feature Flag') {
101+
console.log(` Feature Flag ID: ${inputs.featureFlagId}`)
102+
}
103+
104+
// Build category fields
105+
const categoryFieldsGQL = buildCategoryFieldsGQL(inputs.category, inputs)
106+
107+
// Build optional fields
108+
const optionalFieldsGQL = buildOptionalFieldsGQL(inputs)
109+
110+
// Build the GraphQL mutation inline (matching the documented examples)
111+
// Note: We use inline values instead of variables to match New Relic's API expectations
112+
const mutation = gql`
113+
mutation {
114+
changeTrackingCreateEvent(
115+
changeTrackingEvent: {
116+
categoryAndTypeData: {
117+
${categoryFieldsGQL}
118+
kind: { category: ${gqlString(inputs.category)}, type: ${gqlString(inputs.type)} }
119+
}
120+
entitySearch: { query: "id = '${inputs.entityGuid}'" }
121+
${optionalFieldsGQL}
122+
}
123+
) {
124+
changeTrackingEvent {
125+
changeTrackingId
126+
timestamp
127+
category
128+
type
129+
user
130+
shortDescription
131+
description
132+
groupId
133+
}
134+
messages
135+
}
136+
}
137+
`
138+
139+
// Create GraphQL client
140+
const client = new GraphQLClient(apiEndpoint, {
141+
headers: {
142+
'API-Key': inputs.apiKey,
143+
},
144+
})
145+
146+
// Execute the mutation
147+
const response = await client.request(mutation)
148+
149+
console.log('')
150+
console.log('Response:')
151+
console.log(JSON.stringify(response, null, 2))
152+
153+
const trackingId = response.changeTrackingCreateEvent.changeTrackingEvent.changeTrackingId
154+
155+
if (trackingId) {
156+
console.log('')
157+
console.log('✓ Change tracking event created successfully!')
158+
console.log(` Tracking ID: ${trackingId}`)
159+
160+
// Display any messages from the API
161+
const messages = response.changeTrackingCreateEvent.messages
162+
if (messages && messages.length > 0) {
163+
console.log(' Messages:', messages)
164+
}
165+
} else {
166+
throw new Error('Tracking ID not found in response')
167+
}
168+
169+
} catch (error) {
170+
console.error('')
171+
console.error('Error: Change tracking event creation failed!')
172+
173+
if (error.response) {
174+
console.error('GraphQL Errors:')
175+
console.error(JSON.stringify(error.response.errors, null, 2))
176+
console.error('')
177+
if (error.response.status) {
178+
console.error('HTTP Status:', error.response.status)
179+
}
180+
} else {
181+
console.error(error.message)
182+
if (error.stack) {
183+
console.error(error.stack)
184+
}
185+
}
186+
187+
process.exit(1)
188+
}
189+
}
190+
191+
// Run the function
192+
createChangeTrackingEvent()
193+

.github/actions/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"console-table-printer": "^2.11.2",
1515
"fastly": "^7.0.0",
1616
"filesize": "^10.1.0",
17+
"graphql": "^16.8.1",
18+
"graphql-request": "^6.1.0",
1719
"handlebars": "^4.7.8",
1820
"mime-types": "^2.1.35",
1921
"node-fetch": "^3.3.2",

.github/workflows/publish-dev.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@ jobs:
341341
type: 'Basic'
342342
description: 'Staging deployment when PR merged to main'
343343
shortDescription: 'Staging: main branch deployment'
344+
changelog: 'This deployment updates the second agent (pointed at Browser 550352) on New Relic pages to use the latest build from the main branch. This is a release-candidate build synchronized with the HEAD of main, ahead of the officially released agent used in New Relic One production accounts.'
345+
deepLink: 'https://github.com/${{ github.repository }}/tree/main'
344346
commit: ${{ github.sha }}
345347
user: ${{ github.actor }}
346348
groupId: '${{ github.run_id }}-${{ github.sha }}'
@@ -358,6 +360,7 @@ jobs:
358360
featureFlagId: 'hardcoded feature flag'
359361
description: 'Feature flag configuration updated in released.js'
360362
shortDescription: 'Staging: Feature flags updated (released.js)'
363+
deepLink: 'https://github.com/${{ github.repository }}/tree/main'
361364
commit: ${{ github.sha }}
362365
user: ${{ github.actor }}
363366
groupId: '${{ github.run_id }}-${{ github.sha }}'
@@ -375,6 +378,7 @@ jobs:
375378
featureFlagId: 'latest.js feature flag'
376379
description: 'Feature flag configuration updated in latest.js'
377380
shortDescription: 'Staging: Feature flags updated (latest.js)'
381+
deepLink: 'https://github.com/${{ github.repository }}/tree/main'
378382
commit: ${{ github.sha }}
379383
user: ${{ github.actor }}
380384
groupId: '${{ github.run_id }}-${{ github.sha }}'

0 commit comments

Comments
 (0)