A zero-dependency Browser or Node client for the BMC Remedy ARS REST service as described here: BMC Documentation
Node compatibility is achieved through use of the Fetch API. As of node.js v17.5
the Fetch API is available natively as part of Node Core, however it is behind the --experimental-fetch
commandline argument. It is expected this feature will have full support in upcoming Node releases, but in the mean time be aware you'll need that flag to use this library in Node.
The previous version of this library was based on the XHR API, which required an external node module to emulate the XMLHttpRequest
object. Though mature, the XHR API was quite complicated to use. By moving to Fetch API, it was possible to remove the external dependency in the Node environment, though in doing so the progressCallback
option on getTicket()
was deprecated. So if you need to draw a progress indicator on particularly large fetches from the api, you might need to use the older version, which is available in this package under the ./legacy
directory.
- Amy Hicox |
amy@hicox.com
|amy.hicox@nasa.gov
- Version 2.53 | 5/9/2022
- 1.0
6/18/18
- initial release using xhr api - 2.51
3/17/22
- rewrite to use fetch api - 2.52
4/19/22
- added support for webhooks - 2.53
5/9/22
- added support for 'headers' arg oncreateWebHook()
andmodifyWebHook()
// if in node, you'll need to import the module
const RemedyRestAPI = require('./lib/remedy-rest-api.js');
// create object
let Remedy = new RemedyRestAPI({
protocol: 'https',
server: 'remedy.hicox.com',
port: 8443,
user: 'someUser',
password: 'd4fs3krT'
});
/*
if in browser, you're going to need a reverse proxy
since the browser would disallow any connection not
on the same hostname and port, which means you'll
likely need a reverse proxy from the webserver that
serves code referencing this library, to the jetty
server on your arserver. You will need to give said
reverse proxy a path. This is what the `proxyPath`
argument is for.
so in the browser, you'll very likely need to specify
proxyPath
*/
// create object (legit)
let Remedy = new RemedyRestAPI({
protocol: window.location.protocol.replace(':', ''),
server: window.location.hostname,
proxyPath: '/REST',
user: 'someUser',
password: 'd4fs3krT'
});
/*
async authenticate()
establishes a session to an arserver as the specified user
returns a promise resolving to self, so that it can be
chained thusly
*/
let Remedy = await new RemedyRestAPI({
protocol: window.location.protocol.replace(':', ''),
server: window.location.hostname,
proxyPath: '/REST',
user: 'someUser',
password: 'd4fs3krT'
}).authenticate();
// query for some tickets
let tickets = await Remedy.query({
schema: 'demo:recipe',
fields: ['Entry ID', 'Name', 'Procedure', 'picPreview'],
QBE: `'Name' LIKE "%Cashew%"`,
getAssociations: ['demo:recipe to ingredient'],
expandAssociations: true,
fetchAttachments: true
}).catch(function(error){
console.log(`query failed: ${error}`);
});
// create a ticket
let newTicketEntryID = await Remedy.createTicket({
schema: 'demo:recipe:ingredient',
fields: {
'Name': 'Healthy Boy Brand Mushroom Soy Sauce',
'Measurement': '1.5 TBSP',
'recipe Entry ID': '000000000000001'
}
}).catch(function(error){
console.log(`create failed: ${error}`);
});
// modify a ticket
await Remedy.modifyTicket({
schema: 'demo:recipe:ingredient',
ticket: newTicketEntryID,
fields: {
'Measurement': '.5 TBSP',
'Notes': 'less is more'
}
}).catch(function(error){
console.log(`modify failed: ${error}`);
});
// delete a ticket
await Remedy.deleteTicket({
schema: 'demo:recipe:ingredient',
ticket: newTicketEntryID
}).catch(function(error){
console.log(`delete failed: ${error}`);
});
above was an overview, here's the details of how it all works.
Creates a new object. Data on the input object is copied into the object as attribute default values:
let Remedy = new RemedyRestAPI({<arbitrary>})
Establishes a new API session to the specified server as the specified user. Returns a promise resolving to the object reference, such that it can be chained with the constructor. {args}
can be specified alternately on the object constructor, or on the call to the authenticate()
function
-
protocol
enum('http', 'https')
- protocol to use connecting to REST service -
server
string
- hostname to use connecting to REST service -
port
integer
- port number to use connecting to REST service (protocol
determines default ports 80 & 443, only need to specify if non-standard port is used) -
user
string
- Remedy 'Login ID' to use connecting to REST service -
password
string
- password for Remedy 'Login ID' identified byuser
-
proxyPath
string
- perepend this string to the URL prior to the/api
path string. This allows you to place the ARS REST service behind a reverse proxy on the webserver serving code containing this api library see: Apache Documentation
// from already created object (where 'Remedy' is the previously created object)
let abort = false;
Remedy.authenticate({
protocol: window.location.protocol.replace(':', ''),
server: window.location.hostname,
proxyPath: '/REST',
user: 'someUser',
password: 'd4fs3krT'
}).catch(function(error){
abort = true;
console.log(`authenticate failed: ${error}`);
}).then(function(api){
if (! abort){
// call functions against 'api' here
}
});
// inline with constructor in async function
let Remedy = await new RemedyRestAPI({
protocol: window.location.protocol.replace(':', ''),
server: window.location.hostname,
proxyPath: '/REST',
user: 'someUser',
password: 'd4fs3krT'
}).authenticate().catch(function(error){
console.log(`authenticate failed: ${error}`);
});
// call functions against 'Remedy' here
Destroy existing API session on ARServer for specified user (takes no args)
await Remedy.logout().catch(function(error){
console.log(`logout failed: ${error}`)
})
retrieves attachment from the specified Attachment field on the specified form and record (data is returned as a raw binary array buffer)
-
schema
string
- the name of the Form on the server containing the record with the attachment field you want to retrieve -
ticket
string
- the'Entry ID' (field 1)
value identifying the record on the form identified byschema
with the attachment field you wish to retrieve -
fieldName
string
- the label of the attachment field in the default view form identified byschema
(this is unforunately how the ARS REST interface identifies fields -- what happens when two fields have the same label? ask BMC.Field ID
should be supported to end around this ambiguity but isn't, again -- this is BMC's design, not mine π
let data = await Remedy.getAttachment({
schema: 'demo:recipe',
ticket: '000000000000003',
fieldName: 'picPreview'
}).catch(function(error){
console.log(`getAttachment failed: ${error}`);
});
Executes specified query against the specified form, returning values for the specified fields against matching rows.
-
schema
string
- name of form on ARServer to query -
fields
array
- array of strings representing the label of each field in the Default View of the form identified byschema
, that you wish to retrieve values for across rows matching the specified query -
QBE
string
- this is aQBE String
(more or less an SQL 'where' clause with alternate syntax where field names are wrapped in 'single ticks' and field values are wrapped in "double quotes"). see bmc documentation -
offset
integer
- return rows beginning with the integer after this number. For instanceoffset: 10
would return rows11-to-?
where?
is defined as the last matching row orlimit
-
limit
integer
- the maximum number of rows to return -
sort
string
- this is a string specifying sort order. In general it'll befieldName.asc
orfieldName.desc
, and you can specify multiple fields likefieldName1.asc,fieldName2.desc
. Documentation is hilariously sparse as usual but here ya go BMC Sort Order Documentation -
fetchAttachments
bool
- if true, fetch attachment content for Attachment fields specified in thefields
array -
getAssociations
bool | array
- if set toboolean true
, this will return a list of all associations defined on the form for the specific record. alternately you can specify an array of strings matching thename
of associations you want data for (so 'return just these name associations', or 'return all') -
expandAssociations
bool
- ifgetAssociations
is boolean true, AND this flag is set true, then field data for associated records (for the listed associations ingetAssociaitons
array) are included in results.
let result = await Remedy.query({
schema: 'demo:recipe',
QBE: `'Name' LIKE "%Cashew%"`,
fields: ['Entry ID', 'Name', 'Procedure', 'picPreview'],
sort: 'Name.asc',
fetchAttachments: true,
getAssociations: ['demo:recipe to ingredient'],
expandAssociations: true
}).catch(function(error){
console.log(`query failed: ${error}`)
});
get field values (and optionally Attachments and Associations) for a single row on a single form identified by the Entry ID (field 1)
. This is essentially query()
but without sort, paging options and with a hardcoded QBE of '1' = "${ticket}"
-
schema
string
- the name of the form containing the record identified byticket
-
ticket
string
- the value offield 1
(often namedRequest ID
,Entry ID
, orTicket Number
) uniquely identifing the row on the form identified byschema
you wish to retrieve -
fields
array
- same as query, an array of strings matching field labels in the default view of the form identified byschema
-
fetchAttachments
bool
- same as query, if true and if an attachment field is specified in thefields
array, fetch the content of that attachment and return with results -
getAssociations
bool | array
- same as query, get all or get only the named associations -
expandAssociations
bool
- same as query, if true get field values for specified associations -
progressCallback
function
- same as query, if specified, call this function asynchronously on theprogress
event on the XHR dispatching the REST request
let result = await Remedy.getTicket({
schema: 'demo:recipe',
ticket: '000000000000003',
fields: ['Entry ID', 'Name', 'Procedure', 'picPreview'],
fetchAttachments: true,
getAssociations: ['demo:recipe to ingredient'],
expandAssociations: true
})
create a new record on the specified form with the specified field values. the function returns a string indicating the Entry ID (field 1)
value from the newly create record (that is, it gives you the ticket number back after creating it)
-
schema
string
- the name of the form you wish to create a new record on -
fields
object
- an object of the form{ fieldName: value, ... }
-
attachments
object
- an object of the form{name: fieldName, content: fileContent}
where thefieldName
corresponds to an Attachment field on the form identified byschema
. This field must ALSO be present in thefields
object (where the corresponding value will be the fileName). Thecontent
should be the file content. If you're sending ASCII (for instance a CSV file), you don't need to setencoding
. However if you're sending binary data you will need to Base64 encode the data and set it on thecontent
, and you will need to setencoding: 'BASE64'
NOTE: on Binary Attachments in browser environments. If you're reading from a file input, you'll need to use the FileReader
API read about it here. Specifically you're going to need to lop some BS off the front of the output of FileReader.readAsDataURL()
. Here's a handy snippet:
let fileBase64Content = reader.result.replace(/(.+)base64,/,'');
NOTE ALSO: presently this library can only send one attachment per-REST request. In principle, there's no reason this shouldn't work for setting an arbitrary number of attachment fields in one go, however the ARS REST service throws an error when attempting to that. This is an open issue. A work around is to use createTicket()
then to use modifyTicket()
to add additional attachments to the record.
the function returns a datastructure containing the entryId (field 1
) of the newly created record and the REST resource URL for accessing it
{
url: <resource url>,
entryId: <field 1 value>
}
let newTicket = await Remedy.createTicket({
schema: 'demo:recipe',
fields: {
Status: 'Published',
Title: 'Peanutbutter Sandwich',
picPreview: 'pb_and_j.png',
Procedure: `
combine two parts Bread
and one part Peanutbutter,
spread thinly upon the bread
surface. Consume.
`
},
attachments: {
picPreview: {
name: 'pb_and_j.png',
content: fileBase64Content,
encoding: 'BASE64'
}
}
}).catch(function(error){
throw(`createTicket failed: ${error}`);
});
console.log(`created: ${newTicket.entryId}`)
sets given values on specified fields of a specified record on a form.
-
schema
string
- the name of the form you wish to create a new record on -
ticket
string
- the value offield 1
(often namedRequest ID
,Entry ID
, orTicket Number
) uniquely identifing the row on the form identified byschema
you wish to modify -
fields
object
- an object of the form{ fieldName: value, ... }
-
attachments
object
- an object of the form{name: fieldName, content: fileContent}
where thefieldName
corresponds to an Attachment field on the form identified byschema
. See notes oncreateTicket()
await Remedy.modifyTicket({
schema: 'demo:recipe',
ticket: '000000000000003',
fields: {
Title: `PB and J`
}
}).catch(function(error){
throw(`modifyTicket failed: ${error}`);
})
deletes the row identified by '1' = "${ticket}"
on the form identified by schema
await Remedy.deleteTicket({
schema: 'demo:recipe',
ticket: '000000000000003'
}).catch(function(error){
throw(`deleteTicket failed: ${error}`);
});
let ticketIdentifier = await api.mergeData({
schema: formName,
fields: {fieldOne:valueOne, fieldTwo:valueTwo ...},
QBE: qualification, // (optional)
handleDuplicateEntryId: enum, // error | create | overwrite | merge | alwaysCreate (default error)
ignorePatterns: bool, // (default false)
ignoreRequired: bool, // (default false)
workflowEnabled: bool, // (default true)
associationsEnabled: bool, // (default true)
multimatchOption: enum // error | useFirstMatching (default error)
}).catch(function(e){
throw(`merge failed! ${e}`);
});
console.log(`I merged data into: ${ticketIdentifier.entryId}`);
OK, what does mergeData() do? Are you familiar with the BMC Data Import tool? This API call is basically the back-end to that. This function allows you to take a set of field values and a form and say "go update it or make it".
As usual, the BMC Documentation is ... lacking ... so most of what I've got going on here, I had to reverse engineer by trial and error.
Let's start with "how does it know whether to make a new one or update an existing one?"
.
As far as I can tell, it knows this by one of two methods:
-
if you included the fieldName for the field with fieldId 1 (i.e. the "ticket number", "request id", "entry id"), in the fields argument, and the value for that field matches an existing record, then it will try to update that record (depending on handleDuplicateEntryId more on that in a minute).
-
if you specified a QBE qualification that matched one or more rows (depending on multimatchOption). If that's the case, AND you've got handleDuplicateEntryId set to something other than error THEN it will ignore 'entryId' in fields (if you have one there) and it will update the single record identified by QBE. If the record identified by QBE has a different 'entryId', it's just gonna silently dump it from fields
ok so that's how it figures out the existing record to update, and if all that fails, it just makes a new one with a couple exceptions:
- if handleDuplicateEntryId is set to "error", it's just gonna throw an error
- if handleDuplicateEntryId is set to "alwaysCreate", it's just gonna always create one
-
fields
object
- an object containing field names and valuessee createTicket()
-
QBE
string
- Remedy Qualification Stringsee query()
. Find records matching this QBE qualification and update one of them or error. -
multimatchOption
enum(error, useFirstMatching)
- if not specified, defaults to "error". In the case whereQBE
is specified, this indicates how to handle things if more than one record is matched. Obviously a value of"error"
will throw, and a value of"useFirstMatching"
means just treat the first result like it was the only result. -
handleDuplicateEntryId
enum(error, create, overwrite, merge, alwaysCreate)
-
error
throw an error ifQBE
or an 'entryId' onfields
matches an existing record -
create
ifQBE
is specified and either matches an existing record or no records, OR iffields
contains an 'entryId' value that DOES match an existing record create a new record with the given field values. If 'entryId' IS specified BUT does not match any existing value, create a new record on the specified schema with the given field values AND use that value for 'entryId' -
overwrite
ifQBE
is specified and either matches an existing record OR iffields
contains an 'entryId' value that DOES match an existing record, delete the existing record from the database and replace it wholesale with the given field values. This one is insidious, in that it is quite easy to blow away create date / modify date, etc unintentionally. Be careful with this one mmmm'kay? -
merge
ifQBE
is specified and either matches an existing record OR iffields
contains an 'entryId' value that DOES match an existing record, update the existing record with the given field values, leaving all other fields in place. EXCEPT NOT FOR REQUIRED FIELDS. You must supply a value for ALL required fields on this. If you leave 'em null, you're gonna get the "can't reset required field to null" error. For non-required fields it works pretty much likemodifyTicket()
. -
alwaysCreate
just forget everything and make a new entryId for it. Yes, even if you haveQBE
set and it matches something, or if you have an 'entryId' infields
.
-
the function returns a promise resolving to an object of this form:
{
url: <resource url>,
entryId: <field 1 value>
}
see notes on createTicket()
let ticketId = await Remedy.mergeData({
schema: 'demo:recipe',
handleDuplicateEntryId: "error",
fields: {
'Entry ID': 'BOGUS-000000001',
'Title': 'cowabunga dudes!'
}
}).catch(function(error){
throw(`mergeData failed: ${error}`);
});
console.log(`merged data onto ${ticketId.entryId}`);
I do not understand what this is, but it appears in the API so I made a wrapper for it in the library
let result = await Remedy.getFormOptions({
schema: 'demo:recipe'
}).catch(function(error){
throw(`getFormOptions failed: ${error}`);
});
returns meta-data about the ARS Menu object identified by name
. To get the actual menu content,
see getMenuValues()
. See also strangely complete and helpful BMC Documentation
- name
string
- the name of the ARS menu object for which you'd like to retrieve meta-data from the server
the function returns a promise resolving to a data structure of this form:
{
menu_type: 'Search',
refresh_code: 1,
qualification_string: `'Status' = "published"`,
menu_information: {
qualification_current_fields: [ fieldID, ...],
qualification_keywords: [keyWord, ...]
}
}
contains the verbatim QBE in the menu definition so I guess you could parse it if ya wanted I guess
contains an array of the field_id's you can replace in the qualification this array will be null if you have a menu with no qualification replacement inputs
seems to be the same thing for keywords
a string corresponding to the menu type in Dev Studio
- Sql
- Search
- File
- DataDictionary
- List - (this is a 'Character Menu' in Dev Studio)
an integer corresponding to these values from Dev Studio
- On Connect
- On Open
- On 15 Minute Intervales
let menu = await Remedy.getMenu({
name: 'demo:ingredient:Name'
}).catch(function(error){
throw(`getMenu failed: ${error}`);
})
retrieve values for the ARS Menu object item identified by name
-
name
string
- the name of the ARS menu object for which you'd like to retrieve values from the server -
qualification_substitute_info
object
- this object specifies value substitutions for the QBE driving the specified menu. However, there's not a whole lot of detail in the BMC Documentation. This is the example object form given in the documentation:
qualification_substitute_info: {
form_name: "TestForm_dfb88",
field_values: {
"536870915": 100
},
keyword_values: {
"USER": "Demo"
}
}
form_name
needs to be the form owning the field values that you wish to replace in the qualification. For instance if you've got a menu with a qualification like this from the recipe demo:
- [primary ui form] demo:recipe
- [supporting table form] demo:recipe:ingredient
now say on your primary form you have a field: 536870919
, and on a menu you have a qualification like this: 'recipe Entry ID' = $536870919$
where 'recipe Entry ID' is the foreign key on your supporting table that links the rows to the parent
NOW ... say you want to retrieve the ingredient list for the demo:recipe row where '1' = "000000000000003"
this will work:
qualification_substitute_info: {
form_name: 'demo:recipe',
field_values: {
'536870919': "000000000000003"
}
}
-
you can't use the system field '1' [Entry ID] in the menu qualification
it'll work inside ARS, but the API will return an empty string. that's why I created a BS field: 536870919. System fields need not apply, but I suspect the bug is more sinister ... any field-id replicated between your supposed "calling" form (even though the menu would have no concept of that), and your data target form gets total confusion server side. I'll guarantee it like the men's warehouse.
-
in the example above, the menu points at
demo:recipe:ingredient
, but you have to specify the form from which you might call the menu, which is the form with the field536870919
, on it that isdemo:recipe
.
the function returns a promise resolving to an object that has varied forms
/*
here one with two 'Label Fields' specified
*/
{
items: [
{
type: <SubMenu|?>
label: <string menu entry value>,
content: [
{
type: <Value|?>
label: <string menu entry value>,
value: <associated value>
}
]
},
...
]
}
/*
basically type gets "SubMenu" or "Value". If there's just one field in the 'Label Fields' section
it looks like this:
*/
{
items: [
{
type: 'Value',
label: <string>
value: <string>
}
]
}
let menuData = await Remedy.getMenuValues({
name: 'demo:ingredient:Name'
}).catch(function(error){
throw(`getMenuValues failed: ${error}`);
})
get field definitions for the form identified by schema
. See BMC Documentation
-
schema
string
- name of the form you wish to retrieve field definitions for -
fetchMenus
bool
- if set true fetch any menus related to fields on the form
this function returns a promise resolving to an object of this form:
{
idIndex: { fieldID:{field_definition} },
nameIndex: { fieldName:{field_definition} },
menus: { menuName:{menu_definition} }
}
- idIndex
object
- indexes fields by field ID - nameIndex
object
- indexes fields by fieldName - menus
object
- indexes referenced menus by menuName
let formFields = await Remedy.getFormFields({
schema: 'demo:recipe',
fetchMenus: true
}).catch(function(error){
throw(`getFormFields failed: ${error}`);
});
this retrieves formFields for the form identified by schema
, and also recurses to find forms related to tables, and menus related to fields
- schema
string
- recursively get fields, forms and menus starting with this form
the function returns a promise resolving to an object of this form:
{
forms: {
schemaName: {
idIndex: { fieldID:{field_definition} },
nameIndex: { fieldName:{field_definition} },
}, ...
},
menus: {
menuName: {arsMenuDef},
...
}
}
creates a web-hook callback on the specified schema
(aka "form") for the specified operations
matching the optional QBE
, with the specified fields
to the specified REST endpoint
. Returns the entry_id of the newly created web-hook record (see AR System Webhook
form on the ARServer)
NOTE: this function will throw errors unless the authenticated user has Administrator privilege
-
schema
string, required
- the name of the form for which you want to create the webhook -
operations
array, required
- specifies which operations the the form identified byschema
should fire the new webhook. An array consisting of any or all of the following values:create
update
delete
-
QBE
string(QBE)
- if specified, this QBE qualification is evaluated when the actions specified byoperations
are executed on the form identified byschema
. If the qualification evaluatestrue
, the webhook will fire, otherwise it will not. If not specified, the webhook always fires. -
fields
array
- an array of field labels (see note onfieldName
argument ofgetAttachment()
above). This identifies the fields you wish to capture values for in the webhook. These field values will be included in theentry_details
block of the webhook callback http request to the URL identified byendpoint
-
endpoint
string(URL), required
- post data to this URL when the webhook fires. -
headers
object
- Each root-level key/value pair of this object will be echoed as statuc values into the request header when posting data to the URL identified byendpoint
. For instance:headers: { "Bearer": "someVeryComplicatedAPIToken", "Prefer-SomeOption": "true" }
would generate a header of the form
Bearer:someVeryComplicatedAPIToken;Prefer-SomeOption:true
-
basic_auth_user
string
- if the URL identified byendpoint
is behind basic authentication, set the user value on this argument -
basic_auth_password
string
- if the URL identified byendpoint
is behind basic authentication, set the password value on this argument -
description
string, required
- the name of the webhook you are creating. This should be unique.
this is the same format as returned by createTicket()
, the function returns a datastructure containing the entryId (field 1
) of the newly created record and the REST resource URL for accessing it
{
url: <resource url>,
entryId: <field 1 value>
}
let testAbort = false;
api.createWebHook({
schema: 'ahicox_test_form',
operations: ['create', 'update'],
endpoint: `http://apps.hicox.com:3000`,
description:'test 1',
}).catch(function(error){
testAbort = true;
console.log(`createWebHook abort: ${error}`);
}).then(function(res){
if (! testAbort){
console.log(`new webhook id: ${res.entryId}`);
}
});
Do you want a quick and dirty endpoint to receive calls from the webhook you just made? install node.js then:
[user@machine] mkdir quickTestServer; cd ./quickTestServer;
[user@machine] npm install koa koa-router koa-body;
then create a file in that directory what you name it isn't super important but let's just call it testServer.js
. Paste this code into that file:
const Koa = require('koa');
const app = new Koa();
const router = require('koa-router')();
const koaBody = require('koa-body')({multipart:true});
router.post('/', koaBody,
(ctx) => {
console.log("body: ", ctx.request.body);
console.log("files: ", ctx.request.files);
ctx.body = 'You Rang?!';
}
);
app.use(router.routes());
app.listen(3000);
then run the server:
[user@machine] node ./testServer.js
now use createWebHook()
as described above to create a webhook pointing to the machine running your test server, then create, update or delete a record on the form you created the webhook on, and you should see the request logged by your test server π π
fetches a datastructure from the AR Server, describing the webhook identified by webHookID
. Documentation on the returned datatype is scant, however useful.
- webHookID
string, required
- this is theEntry ID
of theAR System Webhook
record corresponding to the webhook you want to get data about. This is also the value ofresoponse.entryId
returned bycreateWebHook()
(see above)
let getAbort = false;
api.getWebHook({
webHookID: 'WBH000000000004'
}).catch(function(error){
getAbort = true;
console.log(`getWebHook abort: ${error}`);
}).then(function(res){
if (! getAbort){
console.log(res);
}
});
modify the properties of an existing webhook identified by webhookID
. Basically takes all of the same arguments as createWebHook
with the addition of the enabled
boolean flag
-
webHookID
string, required
- this is theEntry ID
of theAR System Webhook
record corresponding to the webhook you want to modify. This is also the value ofresoponse.entryId
returned bycreateWebHook()
(see above) -
enabled
bool
- set it to a value offalse
to disable the webhook andtrue
to enable it -
schema
string
- the name of the form you want the webhook identified bywebHookID
to be attached to -
operations
array
- if you specify an array containing one or more of the following values, the webhook identified bywebHookID
will execute on the specified operationscreate
update
delete
-
QBE
string(QBE)
- if specified, this QBE qualification is evaluated when the actions specified byoperations
are executed on the form identified byschema
. If the qualification evaluatestrue
, the webhook will fire, otherwise it will not. If not specified, the webhook always fires. -
fields
array
- an array of field labels (see note onfieldName
argument ofgetAttachment()
above). This identifies the fields you wish to capture values for in the webhook. These field values will be included in theentry_details
block of the webhook callback http request to the URL identified byendpoint
-
endpoint
string(URL)
- post data to this URL when the webhook fires. NOTE: if you want to changebasic_auth_user
orbasic_auth_password
you must also specifyendpoint
-
headers
object
- See notes oncreateWebHook()
above. This works exactly the same except that if you want to update the headers, you also must sendendopoint
again (and if you have basic auth user & pass, send those again as well) -- this is because of the json input structure on the BMC webservice call -- which is to say,endpoint
,headers
,basic_auth_user
&basic_auth_password
are all specified inside the same input struct, so if you want to modify any one of these you have to send a value for all of them on the modify call. -
basic_auth_user
string
- if the URL identified byendpoint
is behind basic authentication, set the user value on this argument -
basic_auth_password
string
- if the URL identified byendpoint
is behind basic authentication, set the password value on this argument -
description
string, required
- change the name of the webhook identified bywebHookID
let changeAbort = false;
api.modifyWebHook({
webHookID: 'WBH000000000004',
description: 'yomamma'
}).catch(function(error){
changeAbort = true;
console.log(`changeWebHook abort: ${error}`);
}).then(function(response){
console.log('changeWebHook success');
});
deletes the webhook identified by webHookID
- webHookID
string, required
- this is theEntry ID
of theAR System Webhook
record corresponding to the webhook you want to delete. This is also the value ofresoponse.entryId
returned bycreateWebHook()
(see above)
let delAbort = false;
api.deleteWebHook({
webHookID: 'WBH000000000004'
}).catch(function(error){
delAbort = true;
console.log(`deleteWebHook abort: ${error}`);
}).then(function(res){
if (! delAbort){
console.log(res);
}
});
details on all of the object attributes
-
isAuthenticated
bool
- true, if the object is currently authenticated to the server -
token
string
- the API token (ifisAuthenticated
== true)
all errors are returned as RemedyRestAPIException
objects. Errors originating from the arserver are not scalar. That is to say for each operation the arserver may return an array
containing an arbitrary number of errors. In practice, you generally only care about the first error though, so the class will abstract several attributes to reference the first error in an array of errors from the server.
RemedyRestAPIException
have these attributes:
-
messageType
<enum (non-ars | ) - if the error is not generated from the arserver, but from local code, the value will be non-ars, otherwise this is themessageType
attribute of the first error object returned from the server. -
message
- this will either be a locally generated error message string (formessageType: 'non-ars'
), or will be themessageText
attribute of the first error object returned from the server -
messageText
- this is a hard-coded passthrough to themessageText
attribute of the first error object returned from the server. If no errors returned from server, this value isnull
-
messageAppendedText
-null
, or themessageAppendedText
attribute of the first error object returned from the server -
messageNumber
-null
, or themessageNumber
attribute of the *first* error object returned from the server -
arsErrorList
- this is an array of error objects returned from the server. Error objects are of the form:
{
messageType: <str>
messageText: <str>
messageAppendedText: <str>
messageNumber: <int>
}
-
thrownByFunction
- name of the RemedyRestAPI class function that threw the error -
thrownByFunctionArgs
- arguments passed to the RemedyRestAPI class function that threw the errorThe
./test.js
node script and the/apiTest.html
browser test require two forms to be installed on the target arserver. These are included in the ./test_data directory in theRemedyRestAPI-demo-forms-and-menu.def
file. This file contains three objects:- [regular form] RemedyRestAPI:test form
- [regular form] RemedyRestAPI:test form2
- [char menu] RemedyRestAPI:test menu
Alternately you could edit
./test.js
and/apiTest.html
to point to forms and menus already installed on your arserver.