SAP-samples / btp-bridge-framework

Create a Microsoft Teams extension app to SAP S/4HANA with JSON configuration.
Apache License 2.0
5 stars 5 forks source link

Notification to Teams app from S/4HANA #20

Closed ABVN-Thien closed 1 year ago

ABVN-Thien commented 1 year ago

Hi Alex,

If we remember correctly, the user can trigger a notification from S/4HANA with Event-Mesh in the scenario. In the new version, we cannot see it mentioned.

Could you please advise how we can achieve that?

Thank you, Thien

AFK-Python commented 1 year ago

Hi Thien,

This is correct.

You would need to configure this json file, and then re-deploy your config application to cloud foundry, via the cf push command. If you have more question on configuration or re-deployment, please let us know!

Best, Alex

ABVN-Thien commented 1 year ago

Hi Alex,

We can push the event from S/4HANA OP to Teams. Thanks a lot for your strong support.

There is a behavior I would like to share when we tested the app. we experienced some crashes during send the event to BTP via abap code. The scenario we run is:

Once again, thank you so much.

BR Thien

AFK-Python commented 1 year ago

Hi Thien,

Thanks for reporting the crash! We'll go ahead and look into this behavior.

Best, Alex

AFK-Python commented 1 year ago

Hi Thien,

We have created a fix for this issue. It will be pushed with the new version of the framework. However, until then, please update your /backend/router/NotificationRouter.js file with the code below:

const Router = require("express");

const { MsGraphClient } = require("../services/MsGraphClient");
const AuthManager = require("../services/authManager");
const { MessageFactory, CardFactory } = require("botbuilder");

const adaptivecard = require("../bots/adaptiveCardsHelper.js");
const botActivityHandler = require("../bots/botActivityHandler");
const botAdapter = require("../bots/botAdapter");
const ConfigInterface = require("../helpers/configInterface");

const notificationRouter = Router();

// Push notification endpoint
notificationRouter.post("/notifyUser", async (req, res) => {
  console.log("\nData received for notification:\n", req.body);

  let eventData = { ...req.body };

  // If the Receiver emails are sent as a comma-separated string
  if (typeof eventData["USER_EMAIL"] === "string") {
    eventData["USER_EMAIL"] = eventData["USER_EMAIL"]
      .split(",")
      .map((substring) => substring.trim());
  }

  try {
    // Obtaining notification config from the config server
    const notificationConfig = await ConfigInterface.getInterfaceMappingConfig(
      "/notification/notificationConfig.json",
      req.logger
    );

    // Obtaining ObjectScreenConfig/detail screen config

    const frontendPageConfig = await ConfigInterface.getInterfaceMappingConfig(
      notificationConfig.DetailScreenConfig,
      req.logger
    );

    try {
      // Obtaining app level token to fetch user details
      const token = await AuthManager.getAccessTokenForApplication();
      const client = new MsGraphClient(token);

      // Delivering notification to all recepients in the notification config

      for (email of eventData[notificationConfig.fields.ReceiverEmailList]) {
        const receiver = await client.getUserProfile(email);

        if (!receiver) {
          console.error(`Email ${email} not found in Azure AD!`);
          continue;
        }

        // Get conversation reference for the user
        const conversationReference =
          await botActivityHandler.getConversationReference(receiver.teamsId);

        if (conversationReference) {
          let cardData = { Others: {} };
          cardData["BusinessObjectName"] =
            notificationConfig.BusinessObjectName;

          for (let key in notificationConfig.fields) {
            if (key === "Others") {
              for (let oth in notificationConfig.fields["Others"]) {
                if (
                  req.body.hasOwnProperty(
                    notificationConfig.fields["Others"][oth]
                  )
                ) {
                  cardData.Others[oth] =
                    req.body[notificationConfig.fields["Others"][oth]];
                }
              }
            } else {
              cardData[key] = req.body[notificationConfig.fields[key]];
            }
          }

          cardData["Forward"] = req.body.FORWARD;
          cardData["origSysManagementAppURL"] =
            notificationConfig.origSysManagementAppURL;

          const detailPageUrl =
            process.env.frontendUrl +
            `/${frontendPageConfig.system}/${frontendPageConfig.interface}/${frontendPageConfig.businessObject}Object?${frontendPageConfig.businessObject}=${cardData.ObjectId}`;

          await botAdapter.continueConversation(
            conversationReference,
            async (turnContext) => {
              const notificationCard = CardFactory.adaptiveCard(
                adaptivecard.NotificationCard(cardData, detailPageUrl, "0000")
              );
              try {
                await turnContext.sendActivity(
                  MessageFactory.attachment(notificationCard)
                );
              } catch (error) {
                console.log("\nTurn context error: ", error);
              }
            }
          );
        } else {
          console.log(
            "\nNo ConversationReference found for user " + receiver.fullName
          );
          continue;
        }
      }
      res.status(200).send("Notification sent!");
    } catch (e) {
      console.log("Error while sending notification\n", e);
      res
        .status(500)
        .send(
          "Internal server error: Error while sending notification to the user(s)."
        );
    }
  } catch (e) {
    console.log("Error while retrieving Bridge Config files\n", e);
    res
      .status(500)
      .send("Internal server error: Could not retrieve Bridge Config files");
  }
});

module.exports = notificationRouter;

Don't forget to push your backend code after pasting this in to replace the existing code!

Best, Alex