//native
function authHeader(auth: RT.Servicenow) {
return auth.token
? `Bearer ${auth.token}`
: `Basic ${btoa(`${auth.username}:${auth.password}`)}`
}
// Change Management API fields come back as { value, display_value }.
function value(field: any) {
return field != null && typeof field === "object" ? field.value : field
}
function label(field: any) {
return field != null && typeof field === "object"
? field.display_value
: field
}
/**
* Update Change Request
* Partially update a change request by sys_id through the Change Management API (PATCH /api/sn_chg_rest/change/{sys_id}, documented in the Yokohama and Brazil API references; needs the itil, change_manager or admin role). Optionally move it to a target state: use a state value from Get change next states (e.g. -1 for Implement). Throws with ServiceNow's message when the update is rejected, and also when ServiceNow answers 200 but leaves the state unchanged (transition not allowed, or the field was ignored).
*/
export async function main(
auth: RT.Servicenow,
sys_id: string,
state: string | undefined,
work_notes: string | undefined,
fields: { [key: string]: any } | undefined
) {
const url = new URL(
`${auth.instance_url}/api/sn_chg_rest/change/${encodeURIComponent(sys_id)}`
)
const headers = {
Authorization: authHeader(auth),
Accept: "application/json",
}
// Read the state first, so a 200 that silently leaves it unchanged is caught.
let previousState: any
let alreadyInState = false
if (state) {
const current = await fetch(url, { headers })
if (!current.ok) {
throw new Error(`${current.status} ${await current.text()}`)
}
const { result } = (await current.json()) as { result: any }
previousState = value(result.state)
alreadyInState =
String(state) === String(previousState) ||
String(state).toLowerCase() === String(label(result.state)).toLowerCase()
}
const body: { [key: string]: any } = { ...(fields ?? {}) }
if (work_notes) body.work_notes = work_notes
if (state) body.state = state
const response = await fetch(url, {
method: "PATCH",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify(body),
})
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`)
}
const { result } = (await response.json()) as { result: any }
const ignoredFields: string[] = result.__meta?.ignoredFields ?? []
if (state) {
const newState = value(result.state)
if (
ignoredFields.includes("state") ||
(String(newState) === String(previousState) && !alreadyInState)
) {
throw new Error(
`ServiceNow did not move change ${value(result.number)} to state ${state}: it is still in ${label(result.state)} (${newState}). Check the allowed transitions with Get change next states.`
)
}
}
return {
sys_id: value(result.sys_id),
number: value(result.number),
state: value(result.state),
state_label: label(result.state),
ignored_fields: ignoredFields,
record: result,
}
}
Submitted by hugo989 7 hours ago