Loading...
Loading...
This skill should be used when the user asks to "IntegrationHub", "spoke", "flow action", "integration", "subflow", "connection alias", "credential alias", "REST step", or any ServiceNow IntegrationHub development.
npx skill4agent add groeimetai/snow-flow integration-hubSpoke (sn_ih_spoke)
├── Actions (sn_ih_action)
│ ├── Inputs
│ ├── Steps
│ └── Outputs
├── Connection Alias
└── Credential Alias
Flow Designer
└── Uses Spoke Actions| Table | Purpose |
|---|---|
| Spoke definitions |
| Spoke actions |
| Action step configuration |
| Connection/credential aliases |
// Create connection alias (ES5 ONLY!)
var alias = new GlideRecord('sys_alias');
alias.initialize();
alias.setValue('name', 'Jira Connection');
alias.setValue('id', 'x_myapp_jira_connection');
alias.setValue('type', 'connection');
alias.setValue('connection_type', 'http');
// Connection attributes
alias.setValue('attributes', JSON.stringify({
base_url: 'https://mycompany.atlassian.net/rest/api/3',
timeout: 30000
}));
alias.insert();// Create credential alias (ES5 ONLY!)
var credAlias = new GlideRecord('sys_alias');
credAlias.initialize();
credAlias.setValue('name', 'Jira API Token');
credAlias.setValue('id', 'x_myapp_jira_credential');
credAlias.setValue('type', 'credential');
credAlias.insert();
// Link to actual credential
var credential = new GlideRecord('basic_auth_credentials');
credential.initialize();
credential.setValue('name', 'Jira API Token Credential');
credential.setValue('user_name', 'api-user@company.com');
credential.setValue('password', ''); // Set via secure method
credential.insert();
// Create alias-credential mapping
var mapping = new GlideRecord('sys_alias_credential');
mapping.initialize();
mapping.setValue('alias', credAlias.getUniqueValue());
mapping.setValue('credential', credential.getUniqueValue());
mapping.insert();// Create spoke (ES5 ONLY!)
var spoke = new GlideRecord('sn_ih_spoke');
spoke.initialize();
spoke.setValue('name', 'Custom ITSM Connector');
spoke.setValue('label', 'Custom ITSM Connector');
spoke.setValue('description', 'Integration with external ITSM system');
spoke.setValue('vendor', 'My Company');
spoke.setValue('version', '1.0.0');
// Scope
spoke.setValue('scope', 'x_myapp_itsm');
// Logo
spoke.setValue('logo', 'attachment_sys_id');
spoke.insert();// Create action for spoke (ES5 ONLY!)
var action = new GlideRecord('sn_ih_action');
action.initialize();
action.setValue('name', 'Create External Ticket');
action.setValue('label', 'Create External Ticket');
action.setValue('spoke', spokeSysId);
action.setValue('description', 'Creates a ticket in external ITSM system');
// Category
action.setValue('category', 'record_operations');
// Accessibility
action.setValue('accessible_from', 'flow_designer');
action.insert();
// Add inputs
addActionInput(action.getUniqueValue(), 'summary', 'String', true, 'Ticket summary');
addActionInput(action.getUniqueValue(), 'description', 'String', false, 'Ticket description');
addActionInput(action.getUniqueValue(), 'priority', 'String', false, 'Priority level');
// Add outputs
addActionOutput(action.getUniqueValue(), 'ticket_id', 'String', 'Created ticket ID');
addActionOutput(action.getUniqueValue(), 'ticket_url', 'String', 'URL to ticket');// Add action input (ES5 ONLY!)
function addActionInput(actionSysId, name, type, mandatory, label) {
var input = new GlideRecord('sn_ih_input');
input.initialize();
input.setValue('action', actionSysId);
input.setValue('name', name);
input.setValue('label', label);
input.setValue('type', type);
input.setValue('mandatory', mandatory);
input.setValue('order', getNextOrder(actionSysId, 'input'));
return input.insert();
}
// Add action output (ES5 ONLY!)
function addActionOutput(actionSysId, name, type, label) {
var output = new GlideRecord('sn_ih_output');
output.initialize();
output.setValue('action', actionSysId);
output.setValue('name', name);
output.setValue('label', label);
output.setValue('type', type);
output.setValue('order', getNextOrder(actionSysId, 'output'));
return output.insert();
}// Create REST step for action (ES5 ONLY!)
var step = new GlideRecord('sn_ih_step_config');
step.initialize();
step.setValue('action', actionSysId);
step.setValue('name', 'Call External API');
step.setValue('order', 100);
step.setValue('step_type', 'rest');
// REST configuration
step.setValue('rest_config', JSON.stringify({
connection_alias: 'x_myapp_jira_connection',
credential_alias: 'x_myapp_jira_credential',
http_method: 'POST',
resource_path: '/issue',
request_body: {
fields: {
project: { key: '${inputs.project_key}' },
summary: '${inputs.summary}',
description: '${inputs.description}',
issuetype: { name: 'Task' }
}
},
headers: {
'Content-Type': 'application/json'
}
}));
step.insert();// Create script step (ES5 ONLY!)
var scriptStep = new GlideRecord('sn_ih_step_config');
scriptStep.initialize();
scriptStep.setValue('action', actionSysId);
scriptStep.setValue('name', 'Process Response');
scriptStep.setValue('order', 200);
scriptStep.setValue('step_type', 'script');
// Script (ES5 ONLY!)
scriptStep.setValue('script',
'(function execute(inputs, outputs) {\n' +
' // Get REST response from previous step\n' +
' var response = inputs.rest_response;\n' +
' \n' +
' if (response.status_code === 201) {\n' +
' var body = JSON.parse(response.body);\n' +
' outputs.ticket_id = body.id;\n' +
' outputs.ticket_url = body.self;\n' +
' outputs.success = true;\n' +
' } else {\n' +
' outputs.success = false;\n' +
' outputs.error_message = "Failed: " + response.status_code;\n' +
' }\n' +
'})(inputs, outputs);'
);
scriptStep.insert();// Subflows encapsulate reusable integration logic
// Created via Flow Designer UI, but can be invoked via script
// Invoke subflow from script (ES5 ONLY!)
var inputs = {
ticket_id: 'INC0010001',
action: 'update',
fields: {
status: 'resolved',
resolution: 'Fixed'
}
};
// Start subflow
sn_fd.FlowAPI.startSubflow('x_myapp_update_external_ticket', inputs);// Execute spoke action from script (ES5 ONLY!)
var actionInputs = {
summary: 'New ticket from ServiceNow',
description: 'Created via integration',
priority: 'Medium'
};
try {
var result = sn_fd.FlowAPI.executeAction(
'x_myapp_itsm.create_external_ticket',
actionInputs
);
if (result.outputs.success) {
gs.info('Created ticket: ' + result.outputs.ticket_id);
} else {
gs.error('Failed: ' + result.outputs.error_message);
}
} catch (e) {
gs.error('Action execution failed: ' + e.message);
}// Error handling in action script (ES5 ONLY!)
(function execute(inputs, outputs) {
try {
// Main logic
var response = callExternalAPI(inputs);
if (response.status_code >= 400) {
throw new Error('API error: ' + response.status_code + ' - ' + response.body);
}
outputs.result = JSON.parse(response.body);
outputs.success = true;
} catch (e) {
outputs.success = false;
outputs.error_code = 'INTEGRATION_ERROR';
outputs.error_message = e.message;
// Log for debugging
gs.error('IntegrationHub action failed: ' + e.message);
// Optionally throw to trigger Flow Designer error handling
// throw e;
}
})(inputs, outputs);// Retry wrapper for transient failures (ES5 ONLY!)
function executeWithRetry(fn, maxRetries, delayMs) {
var attempts = 0;
var lastError = null;
while (attempts < maxRetries) {
try {
return fn();
} catch (e) {
lastError = e;
attempts++;
if (attempts < maxRetries) {
gs.info('Retry ' + attempts + ' of ' + maxRetries + ' after error: ' + e.message);
gs.sleep(delayMs * attempts); // Exponential backoff
}
}
}
throw new Error('Failed after ' + maxRetries + ' attempts: ' + lastError.message);
}| Tool | Purpose |
|---|---|
| Query spokes and actions |
| Find integration configs |
| Test connections |
| Test action scripts |
// 1. Find available spokes
await snow_query_table({
table: 'sn_ih_spoke',
query: 'active=true',
fields: 'name,label,vendor,version'
});
// 2. Get spoke actions
await snow_query_table({
table: 'sn_ih_action',
query: 'spoke.name=Jira Spoke',
fields: 'name,label,description,category'
});
// 3. Test connection
await snow_test_rest_connection({
connection_alias: 'x_myapp_jira_connection',
credential_alias: 'x_myapp_jira_credential'
});