integration-hub

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

IntegrationHub 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 Actions
Spoke (sn_ih_spoke)
    ├── Actions (sn_ih_action)
    │   ├── Inputs
    │   ├── Steps
    │   └── Outputs
    ├── Connection Alias
    └── Credential Alias

Flow Designer
    └── Uses Spoke Actions

Key Tables

核心表

TablePurpose
sn_ih_spoke
Spoke definitions
sn_ih_action
Spoke actions
sn_ih_step_config
Action step configuration
sys_alias
Connection/credential aliases
表名用途
sn_ih_spoke
Spoke 定义
sn_ih_action
Spoke 动作
sn_ih_step_config
动作步骤配置
sys_alias
连接/凭证别名

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

可用工具

ToolPurpose
snow_query_table
Query spokes and actions
snow_find_artifact
Find integration configs
snow_test_rest_connection
Test connections
snow_execute_script_with_output
Test action scripts
工具用途
snow_query_table
查询 Spoke 和动作
snow_find_artifact
查找集成配置
snow_test_rest_connection
测试连接
snow_execute_script_with_output
测试动作脚本

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

最佳实践

  1. Connection Aliases - Abstract connection details
  2. Credential Security - Never hardcode credentials
  3. Error Handling - Graceful failure handling
  4. Retry Logic - Handle transient failures
  5. Logging - Comprehensive debug logging
  6. Testing - Test with mock data first
  7. Versioning - Track spoke versions
  8. ES5 Only - No modern JavaScript syntax
  1. 连接别名 - 抽象连接详情
  2. 凭证安全 - 切勿硬编码凭证
  3. 错误处理 - 优雅处理故障
  4. 重试逻辑 - 处理临时故障
  5. 日志记录 - 全面的调试日志
  6. 测试 - 先使用模拟数据测试
  7. 版本控制 - 跟踪 Spoke 版本
  8. 仅支持 ES5 - 不使用现代 JavaScript 语法