Skip to content

Commit

Permalink
APP-3031 Activity API Documentation (#202)
Browse files Browse the repository at this point in the history
* APP-3031 Activity API Documentation

* APP-3031 Finalized Activity API Documentation

* APP-3031 Fixed @el-youness comments

* APP-3031 Snyk issue: Fixed Jackson version in legacy BDK

* APP-3031 Trigger Snyk
  • Loading branch information
thibauult authored Sep 9, 2020
1 parent ceee4e6 commit 791cd5a
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 27 deletions.
154 changes: 138 additions & 16 deletions docs/activity.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,173 @@
# Activity API

The Activity API is an abstraction built on top of the Datafeed's [_Real Time Events_](https://developers.symphony.com/restapi/docs/real-time-events).
An Activity is basically a user interaction triggered from the chat.

At the moment, two different kinds of activities are supported by the BDK:
The Activity API is an abstraction built on top of the Datafeed's [_Real Time Events_](https://developers.symphony.com/restapi/docs/real-time-events). An Activity is basically a user interaction triggered from the chat.
Two different kinds of activities are supported by the BDK:
- **Command Activity**: triggered when a message is sent in an `IM`, `MIM` or `Chatroom`
- **Form Activity**: triggered when a user replies to an [_Elements_](https://developers.symphony.com/symphony-developer/docs/overview-of-symphony-elements) form message

Using the Activity API will help you to make your bot interactions easier and faster to implement.
## Activity Registry
The central component for activities is the [`ActivityRegistry`](../symphony-bdk-core/src/main/java/com/symphony/bdk/core/activity/ActivityRegistry.java).
This component is used to either add or retrieve activities. It is accessible from the `SymphonyBdk` object:

```java
public class Example {

public static void main(String[] args) throws Exception {
// Create BDK entry point
final SymphonyBdk bdk = new SymphonyBdk(loadFromSymphonyDir("config.yaml"));
// Access to the registry for activities
final ActivityRegistry registry = bdk.activities();
}
}
```

## Command Activity
A command activity is triggered when a message is sent in an `IM`, `MIM` or `Chatroom`.
A command activity is triggered when a message is sent in an `IM`, `MIM` or `Chatroom`. This is the most basic interaction
between an end-user and the bot. Here are some command activity examples:

```
$ @BotMention /buy # (1)
$ /buy 1000 goog # (2)
$ I want to say hello to the world # (3)
```
1. the bot is mentioned followed by a [_slash_](#slash-command) command
2. a command with parameters, the bot is not mentioned
3. any message that contains 'hello' can be a command

### How to create a Command Activity

```java
public class CommandActivityExample {
public class Example {

public static void main(String[] args) {
// TODO
public static void main(String[] args) throws Exception {
// setup SymphonyBdk facade object
final SymphonyBdk bdk = new SymphonyBdk(loadFromSymphonyDir("config.yaml"));
// register Hello Command within the registry
bdk.activities().register(new HelloCommandActivity());
// finally, start the datafeed loop
bdk.datafeed().start();
}
}

class HelloCommandActivity extends CommandActivity<CommandContext> {

@Override
protected ActivityMatcher<CommandContext> matcher() {
return c -> c.getTextContent().contains("hello"); // (1)
}

@Override
protected void onActivity(CommandContext context) {
log.info("Hello command triggered by user {}", context.getInitiator().getUser().getDisplayName()); // (2)
}

@Override
protected ActivityInfo info() {
final ActivityInfo info = ActivityInfo.of(ActivityType.COMMAND); // (3)
info.setName("Hello Command");
return info;
}
}
```
1. the [`ActivityMatcher`](../symphony-bdk-core/src/main/java/com/symphony/bdk/core/activity/ActivityMatcher.java)
allows detecting if the activity logic has to be executed or not. In this case, it will execute `onActivity(CommandContext)`
each time a message that contains "hello" is sent in a stream where the bot is also a member
2. this is where the command logic has to be implemented
3. define activity information

### Slash Command
A _Slash_ command can be used to directly define a very simple bot command such as:
```
$ @BotMention /command
$ /command
```

#### How to create a Slash Command
> :information_source: a Slash cannot have parameters
```java
public class SlashCommandExample {
public class Example {

public static void main(String[] args) throws Exception {

// setup SymphonyBdk facade object
final SymphonyBdk bdk = new SymphonyBdk(loadFromSymphonyDir("config.yaml"));

public static void main(String[] args) {
// TODO
bdk.activities().register(new SlashCommand("/hello" /*(1)*/, true /*(2)*/, context /*(3)*/ -> {
log.info("Hello slash command triggered by user {}", context.getInitiator().getUser().getDisplayName()); // (2)
}));

// finally, start the datafeed loop
bdk.datafeed().start();
}
}
```
1. `/hello` is the command name
2. `true` means that the bot has to be mentioned
3. the command callback provides the `CommandContext` that allows to retrieve some information about the source of the
event, or the event initiator (i.e. user that triggered the command)

## Form Activity
A form activity is triggered when an end-user reply or submit to an _Elements_ form.

### How to create a Form Activity
For this example, we will assume that the following Elements form has been posted into a room:
```xml
<messageML>
<h2>Hello Form</h2>
<form id="hello-form"> <!-- (1) -->

<text-field name="name" placeholder="Enter a name here..."/> <!-- (2) -->

<button name="submit" type="action">Submit</button> <!-- (3) -->
<button type="reset">Reset Data</button>

</form>
</messageML>
```
1. the formId is "**hello-form**"
2. the form has one unique `<text-field>` called "**name**"
3. the has one action button called "**submit**"

```java
public class FormActivityExample {
public class Example {

public static void main(String[] args) throws Exception {
// setup SymphonyBdk facade object
final SymphonyBdk bdk = new SymphonyBdk(loadFromSymphonyDir("config.yaml"));
// register Hello FormReply Activity within the registry
bdk.activities().register(new HelloFormReplyActivity(bdk.messages()));
// finally, start the datafeed loop
bdk.datafeed().start();
}
}

class HelloFormReplyActivity extends FormReplyActivity<FormReplyContext> {

private final MessageService messageService;

public HelloFormReplyActivity(MessageService messageService) {
this.messageService = messageService;
}

@Override
protected ActivityMatcher<FormReplyContext> matcher() {
return c -> "hello-form".equals(c.getFormId()) && "submit".equals(c.getFormValue("action")); // (1)
}

@Override
protected void onActivity(FormReplyContext context) {
final String message = "Hello, " + context.getFormValue("name") + "!"; // (2)
this.messageService.send(context.getSourceEvent().getStream(), "<messageML>" + message + "</messageML>");
}

public static void main(String[] args) {
// TODO
@Override
protected ActivityInfo info() {
final ActivityInfo info = ActivityInfo.of(ActivityType.FORM);
info.setName("Hello Form Reply Activity");
return info;
}
}
```
1. The `ActivityMatcher` ensures that activity logic is triggered only when the form with `id` "**hello-form**" has been
submitted from the action button "**submit**"
2. The activity context allows to directly retrieve form values. Here the "**name**" `<text-field>` value
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void onActivity(CommandContext context) {

@Override
protected ActivityInfo info() {
final ActivityInfo info = ActivityInfo.of(ActivityType.command);
final ActivityInfo info = ActivityInfo.of(ActivityType.COMMAND);
info.setName("Slash command '" + this.slashCommandName + "'");
info.setDescription("Usage: " + (this.requiresBotMention ? "@BotMention " : "") + this.slashCommandName);
return info;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ public enum ActivityType {
/**
* Message sent in the chat.
*/
command,
COMMAND,
/**
* Form submitted.
*/
form
FORM
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class TestCommandActivity extends CommandActivity<CommandContext> {

@Override
protected ActivityInfo info() {
return ActivityInfo.of(ActivityType.command);
return ActivityInfo.of(ActivityType.COMMAND);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ void testSlashCommandNotTriggered() {
void testVerifyBotInfo() {
final SlashCommand cmd = new SlashCommand("/test", c -> {});
final ActivityInfo info = cmd.getInfo();
assertEquals(ActivityType.command, info.getType());
assertEquals(ActivityType.COMMAND, info.getType());
assertEquals("Slash command '/test'", info.getName());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class TestFormReplyActivity extends FormReplyActivity<FormReplyContext> {

@Override
protected ActivityInfo info() {
return ActivityInfo.of(ActivityType.form);
return ActivityInfo.of(ActivityType.FORM);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
import org.junit.jupiter.api.io.TempDir;

import java.io.FileOutputStream;
Expand Down Expand Up @@ -118,8 +119,8 @@ void loadClientGlobalConfig() throws BdkConfigException {
assertEquals(config.getSessionAuth().getContext(), "context");
}

//@Test
// CircleCI does not allow to create file in the home directory
@Test
@DisabledIfEnvironmentVariable(named = "CIRCLECI", matches = "true") // cf. https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables
void testLoadConfigFromSymphonyDirectory() throws Exception {

final String tmpConfigFileName = UUID.randomUUID().toString() + "-config.yaml";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.symphony.bdk.examples.activity;

import static com.symphony.bdk.core.config.BdkConfigLoader.loadFromSymphonyDir;

import com.symphony.bdk.core.SymphonyBdk;
import com.symphony.bdk.core.activity.ActivityMatcher;
import com.symphony.bdk.core.activity.form.FormReplyActivity;
import com.symphony.bdk.core.activity.form.FormReplyContext;
import com.symphony.bdk.core.activity.model.ActivityInfo;
import com.symphony.bdk.core.activity.model.ActivityType;
import com.symphony.bdk.core.service.MessageService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Example {

public static void main(String[] args) throws Exception {

// setup SymphonyBdk facade object
final SymphonyBdk bdk = new SymphonyBdk(loadFromSymphonyDir("config.yaml"));
// register Hello FormReply Activity within the registry
bdk.activities().register(new HelloFormReplyActivity(bdk.messages()));
// finally, start the datafeed loop
bdk.datafeed().start();
}
}

@Slf4j
class HelloFormReplyActivity extends FormReplyActivity<FormReplyContext> {

private final MessageService messageService;

public HelloFormReplyActivity(MessageService messageService) {
this.messageService = messageService;
}

@Override
protected ActivityMatcher<FormReplyContext> matcher() {
return c -> "hello-form".equals(c.getFormId()) && "submit".equals(c.getFormValue("action"));
}

@Override
protected void onActivity(FormReplyContext context) {
final String message = "Hello, " + context.getFormValue("name") + "!";
this.messageService.send(context.getSourceEvent().getStream(), "<messageML>" + message + "</messageML>");
}

@Override
protected ActivityInfo info() {
final ActivityInfo info = ActivityInfo.of(ActivityType.FORM);
info.setName("Hello Form Reply Activity");
return info;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void onActivity(final GifCommandContext context) {

@Override
protected ActivityInfo info() {
final ActivityInfo info = ActivityInfo.of(ActivityType.command);
final ActivityInfo info = ActivityInfo.of(ActivityType.COMMAND);
info.setName("Gif Random by Category command");
info.setDescription("Usage: @BotMention /gif {category}");
return info;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public void onActivity(GifFormReplyContext context) {

@Override
protected ActivityInfo info() {
final ActivityInfo info = ActivityInfo.of(ActivityType.form);
final ActivityInfo info = ActivityInfo.of(ActivityType.FORM);
info.setName("Gif Display category form command");
info.setDescription("Form handler for the Gif Category form");
return info;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<messageML>
<h2>Hello Form</h2>
<form id="hello-form">

<text-field name="name" placeholder="Enter a name here..."/>

<button name="submit" type="action">Submit</button>
<button type="reset">Reset Data</button>

</form>
</messageML>
14 changes: 13 additions & 1 deletion symphony-bdk-legacy/symphony-bdk-bot-sdk-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<description>Legacy Symphony Bot Developer Kit - Bot SDK</description>

<properties>
<handlebars.version>4.1.2</handlebars.version>
<handlebars.version>4.2.0</handlebars.version>
<resilience4j.version>1.4.0</resilience4j.version>
<guava.version>28.2-jre</guava.version>
<esapi.version>2.2.0.0</esapi.version>
Expand Down Expand Up @@ -62,6 +62,18 @@
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-jackson2</artifactId>
<version>${handlebars.version}</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

<!-- Lombok -->
Expand Down

0 comments on commit 791cd5a

Please sign in to comment.