Name
sn_uibtk_api.BuilderToolkitAPIBase
Description
No description available
Script
var BuilderToolkitAPIBase = Class.create();
BuilderToolkitAPIBase.prototype = Object.extendsObject(global.BuilderToolkitAPIGlobal, {
/**
* @param table {string} the table we are working on
* @param fields {string} the list of fields we care about on that table
* @param noDomain {boolean} true if we are querying without domains (dom sep situations only)
* @param includeDisplayValue {boolean} true if we are storing value/display value pairs for fields
*/
initialize: function(table, fields, noDomain, includeDisplayValue) {
this.table = table;
this.fields = fields || [];
this.noDomain = noDomain || false;
this.includeDisplayValue = includeDisplayValue || false;
this.arrayUtil = new global.ArrayUtil();
},
// -------------------
// CREATE
// -------------------
/**
* @param glideRecord {GlideRecord | null} the record we want to copy
* @param sysId {string} the ID of the record we want to copy
* @param overrideFieldValues {object} an object with camelCase record keys and values {name, value}
* @param scope {string} sys_id of the scope to use
* @param domain {string} sys_id of the domain to use
*/
copyRecord: function(glideRecord, sysId, overrideFieldValues = {}, scope = null, domain = null) {
const originalRecordGR = glideRecord ? glideRecord : this.getRecordById(sysId, true);
if (originalRecordGR) {
const newRecordGR = new GlideRecord(this.table);
newRecordGR.initialize();
for (const field in originalRecordGR) {
if (!this._isSystemGeneratedField(field)) {
newRecordGR[field] = originalRecordGR[field];
}
}
this.setFieldValues(newRecordGR, overrideFieldValues);
if (overrideFieldValues && (overrideFieldValues.sys_id || overrideFieldValues.sysId)) {
newRecordGR.setNewGuidValue(overrideFieldValues.sys_id ?? overrideFieldValues.sysId);
}
if (scope) {
newRecordGR.setValue('sys_scope', scope);
}
if (domain && newRecordGR.isValidField('sys_domain')) {
newRecordGR.setValue('sys_domain', domain);
}
//newRecordGR.setWorkflow(false);
return newRecordGR.insert();
}
return false;
},
/**
* @param glideRecords {GlideRecord | null} a glide record object with one or many records in a query
* @param query {string} the query string to search by
* @param overrideFieldValues {object} an object with camelCase record keys and values {name, value}
* @param scope {string} sys_id of the scope to use
* @param domain {string} sys_id of the domain to use
*/
copyRecords: function(glideRecords, query, overrideFieldValues = {}, scope = null, domain = null) {
const records = glideRecords ? glideRecords : this.getRecordsByQuery(query, '', true);
if (!records || !records.hasNext()) {
return [];
}
const results = [];
while (records.next()) {
results.push(this.copyRecord(records, null, overrideFieldValues, scope, domain));
}
return results;
},
/**
* @param fields {object} an object with record keys in camel case and values
*/
createRecord: function(fields) {
const recordGR = new GlideRecordSecure(this.table);
if (recordGR.canCreate()) {
recordGR.initialize();
this.setFieldValues(recordGR, fields);
if (fields && (fields.sys_id || fields.sysId)) {
recordGR.setNewGuidValue(fields.sys_id ?? fields.sysId);
}
return recordGR.insert();
}
return {
error: true,
reason: BuilderToolkitConstants.ERRORS.WRITE_ACCESS_ERROR_MESSAGE
};
},
/**
* @param records {object[]} a array of field objects
*/
createRecords: function(records = []) {
return this._iterateRecords(records, this.createRecord.bind(this));
},
// -------------------
// READ
// -------------------
/**
* @param query {string} the query to check with
*/
checkIfRecordsExist: function(query) {
const recordCheckGA = new GlideAggregate(this.TABLE);
recordCheckGA.addAggregate("COUNT");
recordCheckGA.addQuery(query);
recordCheckGA.query();
if (recordCheckGA.next()) {
return parseInt(recordCheckGA.getAggregate("COUNT")) !== 0;
}
return false;
},
/**
* @param recordGR {GlideRecord} the record we want to get values for
*/
getValuesFromGlideRecord: function(recordGR) {
return recordGR.getElements().reduce(this.getFieldValues.bind(this), {});
},
/**
* @param acc {object} the accumulator object
* @param field {GlideElement} the field element we are currently working on
*/
getFieldValues: function(acc, field) {
const fieldName = field.getName();
const fieldsIsEmpty = this.fields.length === 0;
const isAllowedField = (this.fields ?? []).indexOf(fieldName) !== -1;
const isSysGeneratedField = this._isSystemGeneratedField(fieldName);
const isAllowedSysField = BuilderToolkitConstants.ALLOWED_SYS_FIELDS.contains(fieldName);
let fieldObject = null;
if (!field.nil()) {
if ((fieldsIsEmpty && !isSysGeneratedField) || isAllowedField || isAllowedSysField) {
const fieldType = field.getED().getInternalType();
let fieldValue = field.toString();
let fieldDisplayValue = field.getDisplayValue();
switch (fieldType) {
case 'integer':
fieldValue = parseInt(fieldValue);
break;
case 'boolean':
fieldValue = fieldValue === 'true';
break;
case 'json_translations':
case 'string':
case 'json':
if (!this._checkIfValueIsJSON(fieldValue)) {
break;
}
try {
const tempValue = this.parseJSON(field, {
field: fieldName,
sysId: acc.sysId
});
if (tempValue !== null) {
fieldValue = fieldDisplayValue = fieldName === 'props' ?
this._translateJSONField(tempValue) : tempValue;
} else {
fieldValue = fieldDisplayValue = null;
}
} catch (err) {
// Failed to parse a JSON field probably due to bad data which apparently is rampant. setting to straight value
fieldValue = fieldDisplayValue = field;
}
break;
}
if (fieldName === 'sys_scope') {
fieldObject = {
value: fieldValue,
displayValue: fieldDisplayValue,
scopeName: field.getRefRecord().scope
};
} else {
fieldObject = this.includeDisplayValue || (isAllowedSysField && fieldName !== 'sys_id') ? {
value: fieldValue,
displayValue: fieldDisplayValue
} : fieldValue;
}
acc[this._toCamelCase(fieldName)] = fieldObject;
}
}
return acc;
},
/**
* @param sysId {string} the ID of the record we want to get
* @param shouldReturnGR {boolean} true if we are returning the GlideRecord vs. an object with values
*/
getRecordById: function(sysId, shouldReturnGR) {
try {
const recordGR = this.get(this.table, 'sys_id=' + sysId, false, this.noDomain);
if (recordGR.next()) {
return shouldReturnGR ? recordGR : this.getValuesFromGlideRecord(recordGR);
}
} catch (err) {
gs.error(err);
}
return null;
},
/**
* @param encodedQuery {string} the query to use, can be null for no query
* @param orderBy {string} the the field name to order the list by
* @param shouldReturnGR {boolean} true if we are returning the GlideRecord vs. an array of objects with values
*/
getRecordsByQuery: function(encodedQuery, orderBy, shouldReturnGR) {
const records = [];
const recordGR = this.get(this.table, encodedQuery, orderBy, this.noDomain);
if (shouldReturnGR) {
return recordGR.hasNext() ? recordGR : false;
}
while (recordGR.next()) {
records.push(this.getValuesFromGlideRecord(recordGR));
}
return records;
},
// -------------------
// UPDATE
// -------------------
/**
* @param recordGR {GlideRecord} the record we are setting values for
* @param fields {object} a object with keys in camel case and values
*/
setFieldValues: function(recordGR, fields = {}) {
Object.entries(fields).forEach(([key, value]) => {
const fieldName = this._toSnakeCase(key);
if (recordGR.isValidField(fieldName)) {
const element = recordGR.getElement(fieldName);
const elementDef = element.getED();
if (value && (fieldName === 'sys_scope' || fieldName === 'sys_domain')) {
if (typeof value === 'string') {
recordGR.setValue(fieldName, value);
} else if (typeof value === 'object' && value?.value) {
recordGR.setValue(fieldName, value.value);
}
} else if (!this._isSystemGeneratedField(fieldName)) {
if (elementDef.getInternalType() === 'json' || elementDef.getInternalType() === 'json_translations' ||
this._checkIfValueIsJSON(value + '')) {
value = this.stringifyJSON(value);
}
if (Array.isArray(value) && elementDef.getInternalType() === 'string') {
value = value.join(',');
}
recordGR.setValue(fieldName, value);
}
}
});
},
/**
* @param fields {object} a object with keys in camel case and values
*/
updateRecord: function({
sysId,
...fields
}) {
const recordGR = this.getRecordById(sysId, true);
if (recordGR && recordGR.canWrite()) {
this.setFieldValues(recordGR, fields);
//recordGR.setWorkflow(false);
return recordGR.update();
}
return {
error: true,
reason: !recordGR ? BuilderToolkitConstants.ERRORS.NO_RECORD_FOUND : BuilderToolkitConstants.ERRORS.WRITE_ACCESS_ERROR_MESSAGE
};
},
/**
* @param records {object[]} a array of field objects
*/
updateRecords: function(records = []) {
return this._iterateRecords(records, this.updateRecord);
},
// -------------------
// DELETE
// -------------------
/**
* @param fields {object} a object with record keys in camel case and values
*/
deleteRecord: function({
sysId
}) {
const recordGR = this.getRecordById(sysId, true);
if (recordGR && recordGR.canDelete()) {
return recordGR.deleteRecord();
}
return {
error: true,
reason: !recordGR ? BuilderToolkitConstants.ERRORS.NO_RECORD_FOUND : BuilderToolkitConstants.ERRORS.DELETE_ACCESS_ERROR_MESSAGE
};
},
/**
* @param records {object[]} a array of camelCased record objects
*/
deleteRecords: function(records = []) {
return this._iterateRecords(records, this.deleteRecord).bind(this);
},
/**
* @param deleteFunctions {array} an array of functions to execute
*/
executeDeleteFunctions: function(deleteFunctions) {
deleteFunctions.forEach(({
handler,
records
}) => {
handler.deleteRecords(records);
});
},
// -------------------
// SEARCH
// -------------------
/**
* @param table {string} the table to search
* @param searchValue {string} the value to find for the search
* @param options {object} options for customizing the search
*/
searchTableForResults: function(table, searchValue, options) {
// Validate the provided table
const referenceGlideRecord = new GlideRecord(table);
if (!referenceGlideRecord.isValid()) {
throw new Error('Invalid GlideRecord table provided');
}
// Set the field and validate
const field = options.field || referenceGlideRecord.getDisplayName();
if (field && !referenceGlideRecord.isValidField(field)) {
throw new Error('Invalid field provided');
}
// Set the limit, validate, and ensure we don't return the world
const limit = options.limit || 20;
if (isNaN(limit)) {
throw new Error('Invalid limit provided, limit must be a number');
}
if (limit > 100) {
throw new Error('Limit must be less than 100');
}
if (options.referenceFilter) {
referenceGlideRecord.addQuery(options.referenceFilter);
}
// Query GlideRecord
referenceGlideRecord.addQuery(field, 'CONTAINS', searchValue);
referenceGlideRecord.setLimit(limit);
referenceGlideRecord.query();
let records = [];
while (referenceGlideRecord.next()) {
records.push({
id: referenceGlideRecord.getUniqueValue(),
label: referenceGlideRecord.getValue(field)
});
}
return records;
},
// -------------------
// HELPERS
// -------------------
/**
* @param jsonString {string} stringified JSON
*/
parseJSON: function(jsonString, source) {
if (jsonString === null) {
return null;
}
try {
return JSON.parse(jsonString.trim());
} catch (err) {
gs.error(`
Failed parsing JSON: ${jsonString} with error: ${err.message}
Table: ${this.table}
${source && source.sysId ? `Link: /${this.table}.do?sys_id=${source.sysId}` : ''}
${source ? Object.entries(source).map(function([key,value]){return `${key}: ${value}`}).join('\n') : ''}
${err.stack}
`);
return null;
}
},
/**
* @param json {JSON} JSON to be stringified
* @param spacing {number} the numver of spacing to use when stringifying
*/
stringifyJSON: function(json, spacing) {
spacing = spacing || 4;
try {
return JSON.stringify(json, null, spacing);
} catch (err) {
gs.error(`
Failed parsing JSON: ${json} with error: ${err.message}
${err.stack}
`);
return null;
}
},
/**
* @param value {string} the value we want to check if it is stringified JSON or a real string
*/
_checkIfValueIsJSON: function(value = '') {
const trimmedValue = typeof value === 'string' ? value.trim() : (value + '').trim();
return (trimmedValue.startsWith('[') || trimmedValue.startsWith('{')) &&
!trimmedValue.toLowerCase().startsWith('[deprecated]');
},
/**
* @param jsonObject {Object} a json object from a json field that needs translation (e.g. Macroponent properties)
* @param propsToTranslate {string[]} an array of object props that need translation
*/
_getTranslatedTextForJSON: function(jsonObject = {}, propsToTranslate) {
return Object.keys(jsonObject).reduce(function(acc, key) {
if (propsToTranslate.indexOf(key) !== -1) {
if (typeof jsonObject[key] === 'string') {
acc[key] = gs.getMessage(jsonObject[key]);
return acc;
} else if (jsonObject[key]) {
const uxValue = jsonObject[key];
if (uxValue.type === 'TRANSLATION_LITERAL') {
uxValue.value.message = gs.getMessage(uxValue.value.message);
} else {
uxValue.value = gs.getMessage(uxValue.value);
}
acc[key] = uxValue;
return acc;
}
}
acc[key] = jsonObject[key];
return acc;
}, {});
},
/**
* @param fieldName {string} checks if the field is a system generated field
*/
_isSystemGeneratedField: function(fieldName) {
return fieldName.startsWith('sys_');
},
/**
* @param records {object[]} a array of camelCased record objects
*/
_iterateRecords: function(records = [], mapFunction) {
return records.length === 0 ? true : records.map(mapFunction);
},
/**
* @param message {string} the message we want to log
* @param prefix {string} the prefix for the log message to find it easier
*/
_logMessage: function(message, prefix) {
prefix = prefix || this.type;
gs.info('{0}\n\n{1}', prefix, message);
},
/**
* @param messageObj {JSON} the JSON object or array we want to log
* @param prefix {string} the prefix for the log message to find it easier
*/
_logJSON: function(messageObj, prefix) {
this._logMessage(JSON.stringify(messageObj, null, 4), prefix);
},
/**
* @param fieldName {string} a field name in Snake case
*/
_toCamelCase: (fieldName) => {
return fieldName.toLowerCase().replace(/([-_][a-z])/g, namePart => namePart.toUpperCase().replace('-', '').replace('_', ''));
},
/**
* @param fieldName {string} a field name in Camel case
*/
_toSnakeCase: (fieldName) => {
return fieldName.replace(/[A-Z]/g, namePart => '_' + namePart.toLowerCase());
},
/**
* @param jsonObject {Object | Array} a parsed JSON object or array from a field value
*/
_translateJSONField: function(jsonObject) {
if (Array.isArray(jsonObject)) {
for (const index in jsonObject) {
jsonObject[index] = typeof jsonObject[index] === 'string' ? gs.getMessage(jsonObject[index]) :
this._translateObjectProperties(jsonObject[index]);
}
} else if (typeof jsonObject === 'object') {
jsonObject = this._translateObjectProperties(jsonObject);
}
return jsonObject;
},
/**
* @param objectToTranslate {Object | Array} a parsed JSON object or array from a field value
*/
_translateObjectProperties: function(objectToTranslate) {
const that = this;
const translatedObject = that._getTranslatedTextForJSON(objectToTranslate, ['label', 'description']);
if (translatedObject.typeMetadata && translatedObject.typeMetadata.choices && translatedObject.typeMetadata.choices.length > 0) {
translatedObject.typeMetadata.choices = translatedObject.typeMetadata.choices.reduce(function(acc, choice) {
acc.push(that._getTranslatedTextForJSON(choice, ['label']));
return acc;
}, []);
}
return translatedObject;
},
type: 'BuilderToolkitAPIBase'
});
Sys ID
b523d03d85321110f877e10cffeb7b8e