Customize Adapter for Microsoft Dynamics 365

Enter a Case number before making a call

As an example of customizing the Adapter for Microsoft Dynamics 365 we are going to add a custom input field to New Call screen and check entered Case number before making a manual call. We are going to take just a few steps to accomplish this:

  1. Create an HTML page that contains a customization
  2. Load the HTML page into the Adapter

0. Create Microsoft D365 solution

Before starting to work on the customization let's create a D365 solution where we will store all our assets. Please follow this instruction to create a new solution. See more about solutions here.

1. Customization HTML page

It will do the following:

  1. Add an input field to New Call screen using CustomComponentsApi#registerCustomComponents API
  2. Register HookApiCallbacks#beforeMakeCall hook
  3. Check that the entered Case number exists

Create HTML Web Resource

The HTML page will be a Web Resource in our solution. Follow this instruction to create a new Webpage (HTML) Web Resource.

We need to include two scripts to our page:

  • To get an access to Microsoft native APIs
    <script type="text/javascript" src="ClientGlobalContext.js.aspx"></script>
    
  • To get an access to Five9 CRM SDK
    <script type="text/javascript" src="https://app.five9.com/dev/sdk/crm/latest/five9.crm.sdk.js"></script>
    

Add an input field

The below code will add an input field to New Call screen and save entered value to caseNumber variable.

let caseNumber = null;

const customComponentsApi = Five9.CrmSdk.customComponentsApi();
customComponentsApi.registerCustomComponents({
  template: `
    <adt-components>
      <adt-component location="3rdPartyComp-newcall-middle" style="flex-direction: column">
        <adt-input value="" id="caseNumber" name="caseNumberInput" label="Case Number" placeholder=""
                   onchange="onCaseNumberChanged">
        </adt-input>
      </adt-component>
    </adt-components>`,
  callbacks: {
    onCaseNumberChanged({ value }) {
      caseNumber = value;
    }
  }
});

Register beforeMakeCall hook

beforeMakeCall hook will validate entered case number and returns Proceed status code when a case number is valid. Otherwise returns Error (see HookApiStatusCode)

const hookApi = Five9.CrmSdk.hookApi();
hookApi.registerApi({
  beforeMakeCall() {
    return isValidCaseNumber(caseNumber)
      .then(valid => {
        if (valid) {
          return { status: { statusCode: Five9.CrmSdk.HookStatusCode.Proceed } };
        } else {
          return {
            status: {
              statusCode: Five9.CrmSdk.HookStatusCode.Error,
              message: 'Please enter a valid case number!'
            }
          };
        }
      });
  }
});

Check Case exists

We are going to implement a helper method that takes a case number and search cases that end with this number. If there are no and more then one case found the method returns false. Otherwise it returns true.

const isValidCaseNumber = number => {
  return fetch(
    Xrm.Page.context.getClientUrl() +
      `/api/data/v9.1/incidents?$select=incidentid&$filter=endswith(ticketnumber,'${number}')`,
    {
      headers: {
        'OData-MaxVersion': '4.0',
        'OData-Version': '4.0',
        'Accept': 'application/json',
        'Content-Type': 'application/json; charset=utf-8',
        'Prefer': 'odata.include-annotations="*",odata.maxpagesize=2'
      }
    }
  )
    .then(response => {
      if (!response.ok) {
        throw new Error('Network response was not OK');
      }
      return response.json();
    })
    .then(result => result.value.length === 1);
};

Put it all together

Here is the complete HTML page code:

<html><head>
    <meta charset="UTF-8">
    <title>Five9 CRM SDK Demo</title>
    <script type="text/javascript" src="ClientGlobalContext.js.aspx"></script>
    <script type="text/javascript" src="https://app.five9.com/dev/sdk/crm/latest/five9.crm.sdk.js"></script>
    <script type="text/javascript">
      let caseNumber = null;

      const isValidCaseNumber = number => {
        return fetch(
          Xrm.Page.context.getClientUrl() +
            `/api/data/v9.1/incidents?$select=incidentid&$filter=endswith(ticketnumber,'${number}')`,
          {
            headers: {
              'OData-MaxVersion': '4.0',
              'OData-Version': '4.0',
              'Accept': 'application/json',
              'Content-Type': 'application/json; charset=utf-8',
              'Prefer': 'odata.include-annotations="*",odata.maxpagesize=2'
            }
          }
        )
          .then(response => {
            if (!response.ok) {
              throw new Error('Network response was not OK');
            }
            return response.json();
          })
          .then(result => result.value.length === 1);
      };

      const hookApi = Five9.CrmSdk.hookApi();
      hookApi.registerApi({
        beforeMakeCall() {
          return isValidCaseNumber(caseNumber)
            .then(valid => {
              if (valid) {
                return { status: { statusCode: Five9.CrmSdk.HookStatusCode.Proceed } };
              } else {
                return {
                  status: {
                    statusCode: Five9.CrmSdk.HookStatusCode.Error,
                    message: 'Please enter a valid case number!'
                  }
                };
              }
            })
            .catch(error => {
              return {
                status: {
                  statusCode: Five9.CrmSdk.HookStatusCode.Error,
                  message: `Could not check case number. Please contact your administrator (${error})`
                }
              };
            });
        }
      });

      const customComponentsApi = Five9.CrmSdk.customComponentsApi();
      customComponentsApi.registerCustomComponents({
        template: `
          <adt-components>
            <adt-component location="3rdPartyComp-newcall-middle" style="flex-direction: column">
              <adt-input value="" id="caseNumber" name="caseNumberInput" label="Case Number" placeholder=""
                         onchange="onCaseNumberChanged">
              </adt-input>
             </adt-component>
           </adt-components>`,
        callbacks: {
          onCaseNumberChanged({ value }) {
            caseNumber = value;
          }
        }
      });
    </script>
  </head>
  <body>
  </body>
</html>

2. Load HTML page

There are several steps we need to take here:

  1. Create Script (JScript) Web Resource
  2. Update VCC Configuration to run the loader on application start (NOTE: This will require to enable External JS/CSS functionality on you domain. Please contact your Five9 representetive to enable it.)

Loader JavaScript file

Below is a code snipped of a loader. You need to change a path_to_html to your HTML Web Resource (it's shown on a Web Resource edit page).

define('3rdparty.bundle', [], function () {
  function appendIFrame(name, url) {
    const iframe = document.createElement('iframe');
    iframe.id = name;
    iframe.name = name;
    iframe.src = url;
    iframe.height = '0px';
    iframe.width = '200px';
    iframe.style.display = 'none';
    window.document.body.appendChild(iframe);
  }

  appendIFrame('customization', 'https://<path_to_html>');
});

VCC configuration

Modify VCC Configuration to run the loader on the adapter start:

  1. Go to Actions
  2. Go to Configure
  3. Open Desktop Toolkit tab
  4. Select Microsoft Dynamics entry
  5. Press Edit
  6. Past path to JScript Web Resource into JavaScript URL field and check Enabled
  7. Press OK
  8. Press Save

VCC Configuration