Use cases

Send system messages in channels (Webhooks/Groovy process action)

The description of this use case was removed from the Intrexx online help in September 2024, as the corresponding functionality was discontinued by Microsoft. Information on the deadlines can be found at the following link: https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/

            import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.*;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.*;

def id = g_record["96BA727080433D309562AE579A8AD507B8CDB8A2"].value
def title = g_record["1C07C0BA592B734D2BB2050F9A60C2D2132FDDE5"].value
def descr = g_record["364EB5C7725271398BA44FCBD74C1EF115F81A20"].value

def payload = """
{
    "@type": "MessageCard",
    "@context": "http://schema.org/extensions",
    "themeColor": "0076D7",
    "summary": "Larry Bryant created a new support ticket",
    "sections": [{
        "activityTitle": "${title}",
        "activitySubtitle": "${descr}",
        "activityImage": "https://teamsnodesample.azurewebsites.net/static/img/image5.png",
        "facts": [{
            "name": "Assigned to",
            "value": "Unassigned"
        }, {
            "name": "Status",
            "value": "Not started"
        }],
        "markdown": true
    }],
    "potentialAction": [{
        "@type": "ActionCard",
        "name": "Add a comment",
        "inputs": [{
            "@type": "TextInput",
            "id": "comment",
            "isMultiline": false,
            "title": "Add a comment here for this task"
        }],
        "actions": [{
            "@type": "HttpPOST",
            "name": "Add comment",
            "target": "https://docs.microsoft.com/outlook/actionable-messages"
        }]
    },  {
        "@type": "OpenUri",
        "name": "Open in Intrexx",
        "targets": [{
            "os": "default",
            "uri": "https://localhost:1337/path/app/?rq_AppGuid=5F0E4968468F8C4C36515228523D26B6CA16202F&rq_TargetPageGuid=6C02ABACBED2AFA937C1A6D97C2FA12937017C01&qs_link=946E2A263B44AB6052F8556F08B2EF64F49F7464&qs_STRID=${id}"
        }]
    }, {
        "@type": "ActionCard",
        "name": "Change status",
        "inputs": [{
            "@type": "MultichoiceInput",
            "id": "list",
            "title": "Select a status",
            "isMultiSelect": "false",
            "choices": [{
                "display": "In Progress",
                "value": "1"
            }, {
                "display": "Active",
                "value": "2"
            }, {
                "display": "Closed",
                "value": "3"
            }]
        }],
        "actions": [{
            "@type": "HttpPOST",
            "name": "Assign to me",
            "target": "https://a75dafc0b855.ngrok.io/?qs_path=groovy/de/uplanet/lucy/sample/teams-change-ticket-status.groovy&id=${id}",
            "body": "{\\"status\\":\\"{{list.value}}\\", \\"ticketID\\":\\"${id}\\", \\"ticketTitle\\":\\"${title}\\", \\"ticketDescr\\":\\"${descr}\\"}",
			"bodyContentType": "application/json"
        }]
    }]
}
"""

g_syslog.info(payload)

HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("<Webhook-URL>"))
        .POST(BodyPublishers.ofString(payload))
        .build();

HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
g_syslog.info(response.statusCode());
g_syslog.info(response.body());

        
            import java.io.*
import de.uplanet.io.IOHelper

ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOHelper.copyStream(bos, g_request.getInputStream())
def id = g_request["id"]
def body = new String(bos.toByteArray())
def json = g_json.parse(body)
//def reply = "Hello from Intrexx, ${json.from.name}"
def title = json["ticketTitle"]
def descr = json["ticketDescr"]

def user = ""
if (g_session != null)
    user = g_session.user.name
else
    user = "Supporter"

g_syslog.info("Ticket: " + id)
g_syslog.info(json)

def payload = """
{
    "@type": "MessageCard",
    "@context": "http://schema.org/extensions",
    "themeColor": "0076D7",
    "summary": "Larry Bryant created a new support ticket",
    "sections": [{
        "activityTitle": "${title}",
        "activitySubtitle": "${descr}",
        "activityImage": "https://teamsnodesample.azurewebsites.net/static/img/image5.png",
        "facts": [{
            "name": "Assigned to",
            "value": "${user}"
        }, {
            "name": "Status",
            "value": "Active"
        }],
        "markdown": true
    }],
    "potentialAction": [{
        "@type": "OpenUri",
        "name": "Open in Intrexx",
        "targets": [{
            "os": "default",
            "uri": "https://localhost:1337/path/app/?rq_AppGuid=5F0E4968468F8C4C36515228523D26B6CA16202F&rq_TargetPageGuid=6C02ABACBED2AFA937C1A6D97C2FA12937017C01&qs_link=946E2A263B44AB6052F8556F08B2EF64F49F7464&qs_STRID=${id}"
        }]
    }]
}
"""
response.json()
response.setHeader("CARD-UPDATE-IN-BODY", "true")
write(payload)
        
 

Respond to Teams messages in Intrexx

The description of this use case was removed from the Intrexx online help in September 2024, as the corresponding functionality was discontinued by Microsoft. Information on the deadlines can be found at the following link: https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/

            import java.io.*
import de.uplanet.io.IOHelper
import de.uplanet.tools.HMACVerifier

ByteArrayOutputStream bos = new ByteArrayOutputStream()
IOHelper.copyStream(bos, g_request.getInputStream())
def bytes = bos.toByteArray()

def req = g_request.getHttpServletRequest()
def authHeader = req.getHeader("Authorization")
g_syslog.info(authHeader)

if (authHeader == null || !authHeader.startsWith("HMAC")) {
  g_syslog.error("Unauthorized request")
  response.setStatus(401)
  return
}

def key = "beuBhNzxssEKxljQ90tftG6Xc4Rf0gx/fjZR28V31aw="
def hmac = authHeader.substring(5)
g_syslog.info(hmac)

if (!HMACVerifier.verify(hmac, key, bytes)) {
  g_syslog.error("Unauthorized request")
  response.setStatus(401)
  return
}

def body = new String(bytes)
def json = g_json.parse(body)
def reply = """
   {
    "type": "message",
    "attachments": [
        {
            "contentType": "application/vnd.microsoft.card.adaptive",
            "content": {
                "type": "AdaptiveCard",
                "version": "1.4",
                "body": [
                    {
                        "type": "TextBlock",
                        "text": "**Open tickets for ${json.from.name}**"
                    },
                    {
                        "type": "TextBlock",
                        "text": "[Ticket 1](https://localhost:1337/)"
                    },
                    {
                        "type": "TextBlock",
                        "text": "[Ticket 2](https://localhost:1337/)"
                    }
                ]
            }
        }
    ]
}
"""
g_syslog.info(reply)
response.json()
write(reply)
        

Interact with bots in Chats/Groupchats/Channels

Teams Messaging Extensions Action for Intrexx Share Tasks

Example

In Teams chats, group chats or channels, a task is to be created in Intrexx Share via a new or an existing message. A Teams Messaging Extension action with a bot is used for this purpose. On the Intrexx page, an OData service is offered for the task data group, which is called by the bot to create tasks.

Teams Bot Deployment

Requirements

  • Microsoft Teams is installed and you have an account

  • .NET Core SDK version 3.1

  • ngrok or equivalent tunneling solution

  • Visual Studio or Visal Studio Code with C#/.Net extension

  • Intrexx 21.09 Portal with Intrexx Share and the "Projects" module (this example is based on the Intrexx DemoPortal, but any other portal with Intrexx Share and the "Projects" module can be used)

Deploy and execute bot web app

Please note that this guide is intended for executing the example on your local system. The tunnelling solution is needed as the Microsoft Teams service requires the call to the bot.

Step 1

Clone the repository with

            git clone https://github.com/UnitedPlanet/ms365-integration-samples.git
        

or download it here: teams-messaging-extensions-action-for-ix-tasks.zip

Step 2

If you use Visual Studio:

  • Start Visual Studio

  • Select the "File / Open / Project/Solution" menu

  • Navigate to the "teams-messaging-extensions-action" folder

  • Select the file "TeamsMessagingExtensionsAction.csproj" here

Step 3

Run ngrok point to port 3978 or use a reverse proxy to tunnel external https requests to the Intrexx Portal Server

                ngrok http -host-header=rewrite 3978
            

Step 4

Create a Bot Framework Registration Resource in Azure.

Step 5

Update the appsettings.json configuration for the bot to use Microsoft App ID and App Password from the Bot Framework Registration. Please note that the app password corresponds to the "Client Secret" in the Azure portal and that you can create a new Client Secret at any time. Also, provide the Intrexx user name and password for authentication with the Intrexx OData service.

Step 6

This step is specific to Microsoft Teams.

  • In the "manifest.json" file, which you will find in the "teamsAppManifest" folder, insert the Microsoft app ID that was previously generated when the bot was registered wherever you find the placeholder string "<<YOUR-MICROSOFT-APP-ID>>". Depending on the scenario, the Microsoft app ID may appear several times in the "manifest.json" file.

  • Pack the contents of the teamsAppManifest folder to create a "manifest.zip".

  • Upload the manifest.zip to Teams. To do this, click on "Upload a custom app" in the app view.

Step 7

Run your bot - either using F5 from Visual Studio or using Dotnet run in a suitable folder.

Edit projects and recipient list for the task card

The task card contains two value lists for projects and recipients. The values can be edited in the file "teams-messaging-extensions-action/Resources/taskCard.json".

            {
  "type": "TextBlock",
  "text": "Project",
  "size": "default"
},
{
  "type": "Input.ChoiceSet",
  "id": "taskProject",
  "style": "compact",
  "isMultiSelect": false,
  "value": "",
  "choices": [
    {
      "title": "My Tasks",
      "value": "FEF3C9FB5C9F6F45C25E7EFD6149BB39F44CF04C"
    },
    {
      "title": "Trade fair at the conference center",
      "value": "C2838ACA9DBCB25EF5447D0C847FC9BFD53DEE81"
    },
    {
      "title": "The digital kitchen",
      "value": "2C44139F68ECC98CBB604CD1866FAF866E2449A6"
    }
  ]
},
{
  "type": "TextBlock",
  "text": "Assign to",
  "size": "default"
},
{
  "type": "Input.ChoiceSet",
  "id": "taskResponsibleUserId",
  "style": "compact",
  "isMultiSelect": false,
  "value": "",
  "choices": [
    {
      "title": "Administrator",
      "value": "1"
    },
    {
      "title": "PeterM",
      "value": "6"
    },
    {
      "title": "TinaM",
      "value": "16"
    },
    {
      "title": "FrankN",
      "value": "17"
    }
  ]
}
        

The project list contains the project name and the GUID. These values can be determined in the "TSK_CATEGORIES" database table. The recipient list values are Intrexx user names and their user ID. These values can be determined in the "VBLUSER" view.

Deploy bot in Azure

Under Deploy your bot you will find a complete list of instructions for deployment.

Deploy Intrexx OData service

To make the OData service available in your portal, unpack the following ZIP file into the portal directory "internal/cfg/odata/producer" and restart the portal service. The OData service configuration should then be available in the "Integration" module in the Portal Manager.

Download msteams-msg-extensions-share-task-odata.zip

Test the task creation

In the Teams Channel, click on "New conversation" and then on the app icon. Then select "Create Intrexx task". The task card is displayed if the bot is correctly registered and the bot service for Microsoft Teams is available via the ngrok tunnel.

After the task has been submitted, the bot responds with a summary and a link to the task in Intrexx.

The task can be viewed immediately in Intrexx.

Responsible users receive a notification in Intrexx Share.

The task is also displayed in overviews and in the task table.

Use MS Graph SDK in Groovy process action

Authentication

The following classes are available in Groovy scripts for authentication with the MS Graph API.

            import de.uplanet.lucy.server.odata.v4.consumer.http.MsGraphSdkAuthenticationProviderFactory

def clientId = "msgraphsdk"  // Name of the MS Graph OAuth2 configuration
def authFactory = new MsGraphSdkAuthenticationProviderFactory()
def accessTokenProvider = authFactory.createForCurrentUser(clientId) // 1) Anmeldung mit aktuellem Portaluser (Authorization Code)
def accessTokenProvider = authFactory.createForImpersonatedUser(clientId, "<USER_GUID>") // 2) Anmeldung mit statischem Benutzer aus Konfiguration (Password)
def accessTokenProvider = authFactory.createForServiceUser(clientId, "<USER_GUID>") // 3) Anmeldung mit Service Account aus Konfiguration (Client Credentials) 
        
  1. Login with current portal user (authorization code flow)

    This login redirects the portal user in the browser to the login to Microsoft 365 and then back to Intrexx. Only suitable for interactive actions.

  2. Login with static user from configuration (password flow)

    This login type uses a Microsoft 365 user from the configuration.

  3. Login with service account from configuration (client credentials flow)

    This login type uses a Microsoft 365 service account from the configuration.

Graph SDK examples

Send an email in processes

The following example shows how an e-mail can be sent from processes or page handlers.

            import de.uplanet.lucy.server.odata.v4.consumer.http.MsGraphSdkAuthenticationProviderFactory
import java.util.ArrayList;
import okhttp3.Request;
import com.microsoft.graph.models.BodyType;
import com.microsoft.graph.models.ChatMessage;
import com.microsoft.graph.models.EmailAddress;
import com.microsoft.graph.models.Importance;
import com.microsoft.graph.models.ItemBody;
import com.microsoft.graph.models.Message;
import com.microsoft.graph.models.Recipient;
import com.microsoft.graph.requests.GraphServiceClient;

def clientId = "msgraphsdk"  // Name of the MS Graph OAuth2 configuration
def authFactory = new MsGraphSdkAuthenticationProviderFactory()
def accessTokenProvider = authFactory.createForImpersonatedUser(clientId, "7312F993D0DA4CECCA9AE5A9D865BE142DE413EA") // 2) Anmeldung mit statischem Benutzer aus Konfiguration (Password)

def graphClient =
          GraphServiceClient
            .builder()
            .authenticationProvider(accessTokenProvider)
            .buildClient();

def message = new Message();
message.subject = "Hello";
message.importance = Importance.LOW;
def body = new ItemBody();
body.contentType = BodyType.HTML;
body.content = "Hi from <b>Intrexx</b>!";
message.body = body;
def toRecipientsList = new ArrayList<Recipient>();
def toRecipients = new Recipient();
def emailAddress = new EmailAddress();
emailAddress.address = "john.smith@intrexx365.com";
toRecipients.emailAddress = emailAddress;
toRecipientsList.add(toRecipients);
message.toRecipients = toRecipientsList;

def msgResponse = graphClient.me().messages()
    .buildRequest()
    .post(message);
def msgId = msgResponse.id;

graphClient.me().messages(msgId)
    .send()
    .buildRequest()
    .post();
        

Click here for more information:

https://docs.microsoft.com/en-us/graph/api/user-post-messages

Display and change the presence of the current user

The following example shows how the presence information for the current portal user can be queried and changed. You can download the application here: ms365presence.zip

Groovy Page Rendering Handler (Before) to display the presence on a page (via VTL):

            import de.uplanet.lucy.server.odata.v4.consumer.http.MsGraphSdkAuthenticationProviderFactory
import com.microsoft.graph.requests.GraphServiceClient

def clientId = "teams"  // Name of the MS Graph OAuth2 configuration
def authFactory = new MsGraphSdkAuthenticationProviderFactory()
def accessTokenProvider = authFactory.createForCurrentUser(clientId)

def graphClient = GraphServiceClient
            .builder()
            .authenticationProvider(accessTokenProvider)
            .buildClient()

def presence = graphClient.me().presence()
    .buildRequest()
    .get()

g_log.info(presence.activity)
g_log.info(presence.availability)

g_sharedState["my_activity"] = presence.activity
g_sharedState["my_availability"] = presence.availability
        

You can find more information about this here:

https://docs.microsoft.com/en-us/graph/api/presence-get?view=graph-rest-beta&tabs=java

Groovy page action handler (before) to change the presence via a view page:

            import de.uplanet.lucy.server.odata.v4.consumer.http.MsGraphSdkAuthenticationProviderFactory
import com.microsoft.graph.requests.GraphServiceClient
import com.microsoft.graph.models.PresenceSetPresenceParameterSet;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;

def clientId = "teams"  // Name of the MS Graph OAuth2 configuration
def authFactory = new MsGraphSdkAuthenticationProviderFactory()
def accessTokenProvider = authFactory.createForServiceUser(clientId)

def graphClient = GraphServiceClient
            .builder()
            .authenticationProvider(accessTokenProvider)
            .buildClient()

def upn = g_session.user?.emailBiz  // the current user's upn
def appId = "89094413-170f-440b-a3e7-6f8e45257531" // the azure app id
def myId = graphClient.users(upn).buildRequest().get().id; // get the current user's Azure id
def availability = g_request["dropdowncontrolAA8B5A6C"] // get the status
if (availability == null)
    availability = "Available"
def acitivity = availability == "Busy" ? "InACall" : availability; // get the activity
def expirationDuration = DatatypeFactory.newInstance().newDuration("PT5M"); // define expiration
def params = PresenceSetPresenceParameterSet.newBuilder() // build parameter set
        .withActivity(acitivity)
        .withSessionId(appId)
        .withAvailability(availability)
        .withExpirationDuration(expirationDuration)
        .build();

graphClient.users(myId).presence().setPresence(params).buildRequest().post();

g_log.info("Set presence for user ${upn}");
        

You can find more information about this here:

https://docs.microsoft.com/en-us/graph/api/presence-setpresence?view=graph-rest-beta&tabs=java

More information

Microsoft 365 Collaboration

Process action - Create team/channel

Process action - Teams message action

Process action - Create team meeting

Process action - Create MS365 group