在本教程中,我们将遵循开发插件  来开发我们的Slack Notification插件的  指导原则。 有关更多详细信息步骤,请参阅第一个教程  如何开发一个Bean Shell哈希变量插件

1.什么问题?

我们希望发送消息给  Slack  ,以便在Joget Workflow中为他们创建任务时通知用户。

2.如何解决问题?

我们将开发一个  审计追踪插件  发送消息给  Slack。 

3.你的插件需要什么输入?

为了开发一个Slack Notification插件,我们将考虑提供类似于User Notification插件的属性选项  。

4.你的插件的输出和预期结果是什么?

当为用户创建分配时,具有分配链接的消息将根据配置发送到他/她的Slack帐户。

5.是否有任何资源/ API可以重复使用?

我们可以使用  slack-webhook  库来与Slack集成。我们还可以扩展  org.joget.apps.app.lib.UserNotificationAuditTrail来节省我们重新实现类似方法的时间。

6.准备你的开发环境

我们需要始终准备好我们的Joget工作流程源代码,并按照  这个指导方针建立。 

下面的教程是用Macbook Pro编写的,Joget源代码是5.0.1版本。 其他平台命令请参阅  如何开发插件文章。

假设我们的文件夹目录如下所示。 

- Home
  - joget
    - plugins
    - jw-community
      -5.0.1

“插件”目录是我们将创建和存储我们所有插件的文件夹,“jw-community”目录是Joget Workflow源代码的存储位置。

运行以下命令在“plugins”目录下创建一个maven项目。

cd joget/plugins/
~/joget/jw-community/5.0.1/wflow-plugin-archetype/create-plugin.sh org.joget slack_notification 5.0.1

然后,shell脚本会要求我们输入插件的版本号,并在生成maven项目之前要求我们确认。

Define value for property 'version':  1.0-SNAPSHOT: : 5.0.0
[INFO] Using property: package = org.joget
Confirm properties configuration:
groupId: org.joget
artifactId: slack_notification
version: 5.0.0
package: org.joget
Y: : y

我们应该在终端上显示“BUILD SUCCESS”消息,在“plugins”文件夹中创建一个“slack_notification”文件夹。

用你最喜欢的IDE打开maven项目。我将使用  NetBeans。  

7. 开始编码吧!

a. 扩展插件类型的抽象类

在“org.joget”包下创建一个“SlackNotification”类。然后,使用  org.joget.apps.app.lib.UserNotificationAuditTrail  类扩展org.joget.plugin.base.DefaultAuditTrailPlugin  抽象类。请参考  审计追踪插件。我们还需要实现  org.joget.plugin.base.PluginWebSupport  接口类,并在插件属性页面提供一个发送测试消息按钮。请参考  Web Service 插件.

b. 实现所有的抽象方法

像往常一样,我们必须执行所有的抽象方法。我们将使用AppPluginUtil.getMessage方法来支持i18n,并使用常量变量MESSAGE_PATH作为消息资源包目录。

Implementation of all basic abstract methods
package org.joget;
 
import org.joget.apps.app.lib.UserNotificationAuditTrail;
import org.joget.apps.app.service.AppPluginUtil;
import org.joget.apps.app.service.AppUtil;
 
public class SlackNotification extends UserNotificationAuditTrail {
    private final static String MESSAGE_PATH = "message/SlackNotification";
    
    @Override
    public String getName() {
        return "Slack Notification";
    }
    @Override
    public String getVersion() {
        return "5.0.0";
    }
    
    @Override
    public String getClassName() {
        return getClass().getName();
    }
    
    @Override
    public String getLabel() {
        //support i18n
        return AppPluginUtil.getMessage("org.joget.SlackNotification.pluginLabel", getClassName(), MESSAGE_PATH);
    }
    @Override
    public String getDescription() {
        //support i18n
        return AppPluginUtil.getMessage("org.joget.SlackNotification.pluginDesc", getClassName(), MESSAGE_PATH);
    }
    
    @Override
    public String getPropertyOptions() {
        return AppUtil.readPluginResource(getClass().getName(), "/properties/slackNotification.json", null, true, MESSAGE_PATH);
    }
}

现在,我们必须为管理员用户创建一个UI,为我们的插件提供输入。在getPropertyOptions方法中,我们已经指定了我们的  插件属性选项和配置  定义文件位于“/properties/slackNotification.json”。让我们在“slack_notification / src / main”目录下创建一个目录“resources / properties”。创建目录后,在“properties”文件夹中创建一个名为“slackNotification.json”的文件。

在属性定义选项文件中,我们需要提供如下的选项。请注意,我们可以在我们的属性选项中使用“@@ message.key @@”语法来支持i18n。在这里,我们实际上可以复制用户通知插件的属性选项,并从那里修改。请参阅  userNotificationAuditTrail.json

[{
    title : '@@SlackNotification.config@@',
    properties : [
    {
        name : 'apiurl',
        label : '@@SlackNotification.url@@',
        type : 'textfield',
        required : 'true'
    },
    {
        label : '@@SlackNotification.from@@',
        type : 'header'
    },
    {
        name : 'username',
        label : '@@SlackNotification.fromUsername@@',
        type : 'textfield',
        value : '@@SlackNotification.fromUsername.value@@'
    },
    {
        name : 'customIcon',
        label : '@@SlackNotification.customIcon@@',
        type : 'selectbox',
        value : 'joget',
        options : [{
            value : '',
            label : '@@SlackNotification.customIcon.none@@'
        },
        {
            value : 'joget',
            label : '@@SlackNotification.customIcon.joget@@'
        },
        {
            value : 'url',
            label : '@@SlackNotification.customIcon.url@@'
        },
        {
            value : 'emoji',
            label : '@@SlackNotification.customIcon.emoji@@'
        }]
    },
    {
        name : 'iconUrl',
        label : '@@SlackNotification.customIcon.url@@',
        type : 'textfield',
        required : 'true',
        control_field: 'customIcon',
        control_value: 'url',
        control_use_regex: 'false'
    },
    {
        name : 'iconEmoji',
        label : '@@SlackNotification.customIcon.emoji@@',
        type : 'textfield',
        required : 'true',
        control_field: 'customIcon',
        control_value: 'emoji',
        control_use_regex: 'false'
    },
    {
        label : '@@SlackNotification.to@@',
        type : 'header'
    },
    {
        name : 'usernameTransform',
        label : '@@SlackNotification.usernameTransform@@',
        description : '@@SlackNotification.usernameTransform.desc@@',
        type : 'textfield',
        value : '@@SlackNotification.usernameTransform.value@@',
        required : 'True'
    },
    {
        label : '@@SlackNotification.message@@',
        type : 'header'
    },
    {
        name : 'text',
        label : '@@SlackNotification.text@@',
        description : '@@SlackNotification.text.desc@@',
        type : 'codeeditor',
        required : 'True'
    },
    {
        name : 'unfurl_links',
        label : '@@SlackNotification.unfurl_links@@',
        description : '@@SlackNotification.unfurl_links.desc@@',
        type : 'checkbox',
        value : 'true',
        options : [{
            value : 'true',
            label : ''
        }]
    },
    {
        name : 'unfurl_media',
        label : '@@SlackNotification.unfurl_media@@',
        description : '@@SlackNotification.unfurl_media.desc@@',
        type : 'checkbox',
        value : 'true',
        options : [{
            value : 'true',
            label : ''
        }]
    }],
    buttons : [{
        name : 'sendTestMessage',    
        label : '@@SlackNotification.sendTestMessage@@',
        ajax_url : '[CONTEXT_PATH]/web/json/app[APP_PATH]/plugin/org.joget.SlackNotification/service?action=sendTestMessage',
        fields : ['url'],
        addition_fields : [
            {
                name : 'testChannel',
                label : '@@SlackNotification.sendTestMessage.testChannel@@',
                type : 'textfield'
            }
        ]
    }]
},
{
    title : '@@app.usernotificationaudittrail.notificationLink@@',
    properties : [
    {
        name : 'base',
        label : '@@app.usernotificationaudittrail.baseUrl@@',
        type : 'textfield',
        description : '@@app.usernotificationaudittrail.baseUrl.desc@@',
        required : 'True'
    },
    {
        name : 'url',
        label : '@@app.usernotificationaudittrail.url@@',
        type : 'textfield'
    },
    {
        name : 'urlName',
        label : '@@app.usernotificationaudittrail.urlName@@',
        type : 'textfield'
    },
    {
        name : 'parameterName',
        label : '@@app.usernotificationaudittrail.parameterName@@',
        description : '@@app.usernotificationaudittrail.parameterName.desc@@',
        type : 'textfield',
        value : 'activityId'
    },
    {
        name : 'passoverMethod',
        label : '@@app.usernotificationaudittrail.passoverMethod@@',
        type : 'selectbox',
        value : 'param',
        options : [{
            value : 'none',
            label : '@@app.usernotificationaudittrail.passoverMethod.none@@'
        },
        {
            value : 'append',
            label : '@@app.usernotificationaudittrail.passoverMethod.append@@'
        },
        {
            value : 'param',
            label : '@@app.usernotificationaudittrail.passoverMethod.param@@'
        }]
    }]
},
{
    title : '@@app.usernotificationaudittrail.advanced@@',
    properties : [{
        name : 'exclusion',
        label : '@@app.usernotificationaudittrail.activityExclusion@@',
        type : 'multiselect',
        size : '10',
        options_ajax : '[CONTEXT_PATH]/web/json/app[APP_PATH]/plugin/org.joget.apps.app.lib.UserNotificationAuditTrail/service?action=getActivities'
    }]
}]

在完成属性选项以收集输入之后,我们可以处理作为执行方法的插件的主要方法。但是,由于我们扩展了UserNotificationAuditTrail类,因此我们只需要重写用于通过UserNotificationAuditTrail类发送电子邮件的sendEmail方法。

    private SlackApi api = null;
 
    @Override
    protected void sendEmail (final Map props, final AuditTrail auditTrail, final WorkflowManager workflowManager, final List<String> users, final WorkflowActivity wfActivity) {
        new PluginThread(new Runnable() {
            public void run() {
                WorkflowUserManager workflowUserManager = (WorkflowUserManager) AppUtil.getApplicationContext().getBean("workflowUserManager");
                String base = (String) props.get("base");
                String url = (String) props.get("url");
                String urlName = (String) props.get("urlName");
                String parameterName = (String) props.get("parameterName");
                String passoverMethod = (String) props.get("passoverMethod");
                String text = (String) props.get("text");
                String linkLabel = AppPluginUtil.getMessage("SlackNotification.viewAssignment", getClassName(), MESSAGE_PATH);
                String activityInstanceId = wfActivity.getId();
                String link = getLink(base, url, passoverMethod, parameterName, activityInstanceId);
                if (!link.startsWith("http")) {
                    if (!link.startsWith("/")) {
                        link = "/" + link;
                    }
                    link = base + link;
                }
                
                SlackMessage message = createMessage();
                try {
                    for (String username : users) {
                        workflowUserManager.setCurrentThreadUser(username);
                        WorkflowAssignment wfAssignment = null;
                        int count = 0;
                        do {
                            wfAssignment = workflowManager.getAssignment(activityInstanceId);
                            if (wfAssignment == null) {
                                Thread.sleep(4000); //wait for assignment creation
                            }
                            count++;
                        } while (wfAssignment == null && count < 5); // try max 5 times
                        if (wfAssignment != null) {
                            String channel = getSlackUsername(username, wfAssignment);
                            if (channel != null && !channel.isEmpty()) {
                                message.setText(AppUtil.processHashVariable(text, wfAssignment, null, null));
                                message.setAttachments(new ArrayList<SlackAttachment>());
                                SlackAttachment attachment = new SlackAttachment();
                                attachment.setFallback(link);
                                if (urlName != null && !urlName.isEmpty()) {
                                    attachment.setTitle(AppUtil.processHashVariable(urlName, wfAssignment, null, null));
                                } else {
                                    attachment.setTitle(linkLabel);
                                }
                                attachment.setTitleLink(link);
                                message.addAttachments(attachment);
                                try {
                                    LogUtil.info(SlackNotification.class.getName(), "Sending slack message to " + username);
                                    sendMessage(channel, message);
                                    LogUtil.info(SlackNotification.class.getName(), "Sending slack message completed to " + username);
                                } catch (Exception ex) {
                                    LogUtil.error(UserNotificationAuditTrail.class.getName(), ex, "Error sending slack message");
                                }
                            }
                        } else {
                            LogUtil.debug(UserNotificationAuditTrail.class.getName(), "Fail to retrieve assignment for " + username);
                        }
                    }
                } catch (Exception e) {
                    LogUtil.error(UserNotificationAuditTrail.class.getName(), e, "Error executing plugin");
                }
            }
        }).start();
    }
    
    protected SlackApi getApi() {
        if (api == null) {
            api = new SlackApi(getPropertyString("apiurl"));
        }
        return api;
    }
    
    protected String getSlackUsername(String username, WorkflowAssignment assignment) {
        String syntax = getPropertyString("usernameTransform");
        syntax = syntax.replaceAll(StringUtil.escapeRegex("{username}"), StringUtil.escapeRegex(username));
        return AppUtil.processHashVariable(syntax, assignment, null, null);
    }
    
    protected void sendMessage(String channel, SlackMessage message) {
        if (message == null) {
            message = createMessage();
        }
        if (channel != null && !channel.isEmpty()) {
            message.setChannel(channel);
        }
        
        getApi().call(message);
    }
    
    protected SlackMessage createMessage() {
        SlackMessage message = new SlackMessage();
        
        String username = getPropertyString("username");
        if (!username.isEmpty()) {
            message.setUsername(username);
        }
        
        String customIcon = getPropertyString("customIcon");
        if (!customIcon.isEmpty()) {
            if ("joget".equals(customIcon)) {
                HttpServletRequest request = WorkflowUtil.getHttpServletRequest();
                if (request != null) {
                    String url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/images/v3/logo.png";
                    message.setIcon(url);
                }
            } else if ("url".equals(customIcon)) {
                message.setIcon(getPropertyString("iconUrl"));
            } else {
                message.setIcon(getPropertyString("iconEmoji"));
            }
        }
        
        message.setUnfurlLinks("true".equalsIgnoreCase(getPropertyString("unfurl_links")));
        message.setUnfurlMedia("true".equalsIgnoreCase(getPropertyString("unfurl_media")));
        
        return message;
    }

在我们的插件属性中,我们有一个按钮来发送测试消息。让我们实现webService方法来提供一个API来发送测试消息。

    public void webService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        boolean isAdmin = WorkflowUtil.isCurrentUserInRole(WorkflowUserManager.ROLE_ADMIN);
        if (!isAdmin) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
        
        String action = request.getParameter("action");
        if ("sendTestMessage".equals(action)) {
            String message = "";
            try {
                AppDefinition appDef = AppUtil.getCurrentAppDefinition();
                
                String url = AppUtil.processHashVariable(request.getParameter("url"), null, null, null, appDef);
                String testChannel = AppUtil.processHashVariable(request.getParameter("testChannel"), null, null, null, appDef);
                
                setProperty("apiurl", url);
                setProperty("text", AppPluginUtil.getMessage("SlackWebhookTool.testMessage", getClassName(), MESSAGE_PATH));
                
                if (testChannel != null && !testChannel.isEmpty()) {
                    sendMessage(testChannel, null);
                } else {
                    sendMessage(null, null);
                }
                
                message = AppPluginUtil.getMessage("SlackWebhookTool.sendTestMessage.success", getClassName(), MESSAGE_PATH);
            } catch (Exception e) {
                LogUtil.error(this.getClassName(), e, "Fail to send Test Message to Slack");
                message = AppPluginUtil.getMessage("SlackWebhookTool.sendTestMessage.fail", getClassName(), MESSAGE_PATH) + "\n" + StringEscapeUtils.escapeJavaScript(e.getMessage());
            }
            try {
                JSONObject jsonObject = new JSONObject();
                jsonObject.accumulate("message", message);
                jsonObject.write(response.getWriter());
            } catch (Exception e) {
                //ignore
            }
        } else {
            response.setStatus(HttpServletResponse.SC_NO_CONTENT);
        }
    }

c. 管理你的插件的依赖库

我们需要在我们的POM文件中包含“jsp-api”和“slack-webhook”库。

        <!-- Change plugin specific dependencies here -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>net.gpedro.integrations.slack</groupId>
            <artifactId>slack-webhook</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!-- End change plugin specific dependencies here -->

d. 让你的插件国际化(国际化)

我们在getLabel和getDescription方法中使用i18n消息密钥。我们将在我们的属性选项定义中使用i18n消息密钥。然后,我们将需要为我们的插件创建一个消息资源包属性文件。

在“slack_webhook / src / main”目录下创建一个目录“resources / message”。然后,在该文件夹中创建一个“SlackWebhookTool.properties”文件。在属性文件中,添加所有消息密钥及其标签,如下所示。

org.joget.SlackNotification.pluginLabel=Slack Notification
org.joget.SlackNotification.pluginDesc=Send notification message to Slack user when an assignment is available.
SlackNotification.config=Configure Slack Notification
SlackNotification.url=Webhook URL
SlackNotification.from=From
SlackNotification.fromUsername=Username
SlackNotification.fromUsername.value=Joget Workflow
SlackNotification.customIcon=Custom Icon
SlackNotification.customIcon.none=None
SlackNotification.customIcon.joget=Joget Workflow Logo
SlackNotification.customIcon.url=Image URL
SlackNotification.customIcon.emoji=Emoji Code
SlackNotification.to=To
SlackNotification.usernameTransform=Transform username to Slack username
SlackNotification.usernameTransform.desc=Hash Variable can be used to transform username to Slack username. Eg. @#form.slack.username[{username}]#
SlackNotification.usernameTransform.value=@{username}
SlackNotification.message=Message
SlackNotification.text=Text
SlackNotification.text.desc=Refer to <a href="https://api.slack.com/docs/formatting" target="_blank">Slack Message Formatting</a>.
SlackNotification.unfurl_links=Unfurling Links
SlackNotification.unfurl_links.desc=Automatically find URLs in a message and create attachments based on the content of those URLs
SlackNotification.unfurl_media=Unfurling Media
SlackNotification.unfurl_media.desc=Automatically find Media URLs in a message and create attachments based on the media of those URLs
SlackNotification.sendTestMessage=Send Test Message
SlackNotification.sendTestMessage.testChannel=Test Channel
SlackNotification.sendTestMessage.success=Test message sent.
SlackNotification.sendTestMessage.fail=Fail to sent test message. Error:
SlackNotification.testMessage=Test Message
SlackNotification.viewAssignment=View Assignment

e. 注册你的插件到Felix框架

接下来,我们将需要在Activator类(在同一个类包中自动生成)中注册我们的插件类,以告诉Felix框架这是一个插件。

    public void start(BundleContext context) {
        registrationList = new ArrayList<ServiceRegistration>();
        //Register plugin here
        registrationList.add(context.registerService(SlackNotification.class.getName(), new SlackNotification(), null));
    }

f. 构建它并测试

让我们建立我们的插件。构建过程完成后,我们将在“slack_notification / target”目录下找到一个“slack_notification-5.0.0.jar”文件。

然后,让我们上传插件jar到  管理插件。上传jar文件后,再次检查插件是否正确上传并激活。

检查 插件默认属性. 是否提供了Slack Notification插件。

现在,让我们在Slack平台上配置Incoming Webhooks。

  1. 转到  your_team.slack.com/services/new
  2. 搜索  传入WebHook  并点击 Add
  3. 选择要发布的频道并按 Add Incoming WebHooks Integration
  4. 进入  设置说明,你有一个WebHook网址。这是稍后将用于“ Webhook URL ” 的参数。然后,复制它。

配置Slack Notification插件。我们可以看到属性选项与用户通知插件非常相似。

在测试运行一个进程时,一旦创建了一个新的任务,就会在Slack中接收到这个消息。

8. 再进一步,分享或出售

您可以从 slack_notification_src.zip. 下载源代码  

要下载现成的插件jar,请在  http://marketplace.joget.org/. (Coming Soon)

 

  • No labels