integration-hub
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIntegrationHub for ServiceNow
ServiceNow IntegrationHub
IntegrationHub provides reusable integration components for Flow Designer and workflows.
IntegrationHub 为 Flow Designer 和工作流提供可复用的集成组件。
IntegrationHub Architecture
IntegrationHub 架构
Spoke (sn_ih_spoke)
├── Actions (sn_ih_action)
│ ├── Inputs
│ ├── Steps
│ └── Outputs
├── Connection Alias
└── Credential Alias
Flow Designer
└── Uses Spoke ActionsSpoke (sn_ih_spoke)
├── Actions (sn_ih_action)
│ ├── Inputs
│ ├── Steps
│ └── Outputs
├── Connection Alias
└── Credential Alias
Flow Designer
└── Uses Spoke ActionsKey Tables
核心表
| Table | Purpose |
|---|---|
| Spoke definitions |
| Spoke actions |
| Action step configuration |
| Connection/credential aliases |
| 表名 | 用途 |
|---|---|
| Spoke 定义 |
| Spoke 动作 |
| 动作步骤配置 |
| 连接/凭证别名 |
Connection & Credential Aliases (ES5)
连接与凭证别名(仅支持 ES5)
Create Connection Alias
创建连接别名
javascript
// 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();javascript
// 创建连接别名(仅支持 ES5!)
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');
// 连接属性
alias.setValue('attributes', JSON.stringify({
base_url: 'https://mycompany.atlassian.net/rest/api/3',
timeout: 30000
}));
alias.insert();Create Credential Alias
创建凭证别名
javascript
// 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();javascript
// 创建凭证别名(仅支持 ES5!)
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();
// 关联实际凭证
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', ''); // 通过安全方式设置
credential.insert();
// 创建别名-凭证映射
var mapping = new GlideRecord('sys_alias_credential');
mapping.initialize();
mapping.setValue('alias', credAlias.getUniqueValue());
mapping.setValue('credential', credential.getUniqueValue());
mapping.insert();Spoke Development (ES5)
Spoke 开发(仅支持 ES5)
Create Custom Spoke
创建自定义 Spoke
javascript
// 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();javascript
// 创建 Spoke(仅支持 ES5!)
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');
// 作用域
spoke.setValue('scope', 'x_myapp_itsm');
// 图标
spoke.setValue('logo', 'attachment_sys_id');
spoke.insert();Create Spoke Action
创建 Spoke 动作
javascript
// 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');javascript
// 为 Spoke 创建动作(仅支持 ES5!)
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');
// 分类
action.setValue('category', 'record_operations');
// 可访问性
action.setValue('accessible_from', 'flow_designer');
action.insert();
// 添加输入参数
addActionInput(action.getUniqueValue(), 'summary', 'String', true, 'Ticket summary');
addActionInput(action.getUniqueValue(), 'description', 'String', false, 'Ticket description');
addActionInput(action.getUniqueValue(), 'priority', 'String', false, 'Priority level');
// 添加输出参数
addActionOutput(action.getUniqueValue(), 'ticket_id', 'String', 'Created ticket ID');
addActionOutput(action.getUniqueValue(), 'ticket_url', 'String', 'URL to ticket');Action Input/Output Helpers
动作输入/输出辅助函数
javascript
// 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();
}javascript
// 添加动作输入参数(仅支持 ES5!)
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();
}
// 添加动作输出参数(仅支持 ES5!)
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();
}Action Steps (ES5)
动作步骤(仅支持 ES5)
REST Step Configuration
REST 步骤配置
javascript
// 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();javascript
// 为动作创建 REST 步骤(仅支持 ES5!)
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 配置
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();Script Step
脚本步骤
javascript
// 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();javascript
// 创建脚本步骤(仅支持 ES5!)
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');
// 脚本(仅支持 ES5!)
scriptStep.setValue('script',
'(function execute(inputs, outputs) {\n' +
' // 获取上一步的 REST 响应\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 for Reuse (ES5)
可复用子流程(仅支持 ES5)
Create Integration Subflow
创建集成子流程
javascript
// 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);javascript
// 子流程封装可复用的集成逻辑
// 通过 Flow Designer UI 创建,但可通过脚本调用
// 从脚本调用子流程(仅支持 ES5!)
var inputs = {
ticket_id: 'INC0010001',
action: 'update',
fields: {
status: 'resolved',
resolution: 'Fixed'
}
};
// 启动子流程
sn_fd.FlowAPI.startSubflow('x_myapp_update_external_ticket', inputs);Execute Action from Script
从脚本执行动作
javascript
// 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);
}javascript
// 从脚本执行 Spoke 动作(仅支持 ES5!)
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 (ES5)
错误处理(仅支持 ES5)
Action Error Handling
动作错误处理
javascript
// 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);javascript
// 动作脚本中的错误处理(仅支持 ES5!)
(function execute(inputs, outputs) {
try {
// 主逻辑
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;
// 记录日志用于调试
gs.error('IntegrationHub action failed: ' + e.message);
// 可选:抛出异常以触发 Flow Designer 错误处理
// throw e;
}
})(inputs, outputs);Retry Logic
重试逻辑
javascript
// 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);
}javascript
// 针对临时故障的重试包装器(仅支持 ES5!)
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); // 指数退避
}
}
}
throw new Error('Failed after ' + maxRetries + ' attempts: ' + lastError.message);
}MCP Tool Integration
MCP 工具集成
Available Tools
可用工具
| Tool | Purpose |
|---|---|
| Query spokes and actions |
| Find integration configs |
| Test connections |
| Test action scripts |
| 工具 | 用途 |
|---|---|
| 查询 Spoke 和动作 |
| 查找集成配置 |
| 测试连接 |
| 测试动作脚本 |
Example Workflow
示例工作流
javascript
// 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'
});javascript
// 1. 查找可用的 Spoke
await snow_query_table({
table: 'sn_ih_spoke',
query: 'active=true',
fields: 'name,label,vendor,version'
});
// 2. 获取 Spoke 动作
await snow_query_table({
table: 'sn_ih_action',
query: 'spoke.name=Jira Spoke',
fields: 'name,label,description,category'
});
// 3. 测试连接
await snow_test_rest_connection({
connection_alias: 'x_myapp_jira_connection',
credential_alias: 'x_myapp_jira_credential'
});Best Practices
最佳实践
- Connection Aliases - Abstract connection details
- Credential Security - Never hardcode credentials
- Error Handling - Graceful failure handling
- Retry Logic - Handle transient failures
- Logging - Comprehensive debug logging
- Testing - Test with mock data first
- Versioning - Track spoke versions
- ES5 Only - No modern JavaScript syntax
- 连接别名 - 抽象连接详情
- 凭证安全 - 切勿硬编码凭证
- 错误处理 - 优雅处理故障
- 重试逻辑 - 处理临时故障
- 日志记录 - 全面的调试日志
- 测试 - 先使用模拟数据测试
- 版本控制 - 跟踪 Spoke 版本
- 仅支持 ES5 - 不使用现代 JavaScript 语法