Edits history of script submission #24035 for ' Update change request (servicenow)'

  • bunnative
    One script reply has been approved by the moderators
    Ap­pro­ved
    //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