Create custom Insight Panel result actions

When building a custom Insight Panel, you can enhance user workflows by implementing custom result actions. This article covers two distinct and compatible approaches:

Use the Quick Action API

This section shows how to implement custom result actions that integrate with Salesforce’s Quick Action API. It provides examples of posting results to the Chatter feed and sending results as emails.

In either case, you need a custom Lightning Web Component (LWC) that uses the QuanticResultAction component, which serves as a base for rendering your custom actions in the target result template. This custom LWC dispatches a custom event when the user interacts with it.

Also, until Salesforce makes the Salesforce Quick Action API available for Lightning Web Components, you need to create an Aura wrapper component that listens to the custom event dispatched by your LWC and invokes the appropriate Quick Action from the Aura Quick Action API.

You can navigate to the Coveo Quantic repository to find all the code samples explained in this section:

Aura wrapper component

  1. Create a new Aura component named ExampleInsightPanelWrapper.

  2. Add the following code to the ExampleInsightPanelWrapper.cmp:

    <aura:component implements="force:hasRecordId,force:hasSObjectName,flexipage:availableForRecordHome" access="global"> 1
    
      <lightning:quickActionAPI aura:id="quickActionAPI" /> 2
    
      <article>
        <c:exampleInsightPanel 3
          oninsightpanel_auraposttofeed="{!c.handleQuickAction}" 4
          oninsightpanel_aurasendasemail="{!c.handleQuickAction}">
        </c:exampleInsightPanel>
      </article>
    </aura:component>
    1 These interfaces let the component access the record ID, the SObject name, and make the component available on the Record home page.
    2 The lightning:quickActionAPI component lets your component call the Quick Action API.
    3 Wrap your custom Insight Panel component.
    4 Listen to the insightpanel_auraposttofeed and insightpanel_aurasendasemail events, which will be emitted by the LWCs. Handle these events in the handleQuickAction controller method defined in the Aura component’s JavaScript controller.
  3. Add the following code to ExampleInsightPanelWrapperController.js to handle the custom events dispatched by the embedded Lightning Web Component:

    ({
      handleQuickAction: function (component, event, helper) {
        helper.executeQuickActionFromEvent(component, event); 1
      },
    });
    1 This method delegates the event handling logic to the executeQuickActionFromEvent helper function, keeping the controller clean and maintainable.
  4. Add the following code to ExampleInsightPanelWrapperHelper.js to handle the custom event sent from the LWC and call the Quick Action API:

    ({
      /**
       * Executes a Quick Action using the QuickActionAPI with parameters coming from
       * the event details.
       * @param {Aura.Component} component
       * @param {Event} event Custom Event.
       */
      executeQuickActionFromEvent: function (component, event) {
        const quickActionsApi = component.find('quickActionAPI'); 1
        const targetFields = event.getParam('targetFields'); 2
        const actionName = event.getParam('actionName'); 3
        const resultPromiseResolve = event.getParam('resultPromiseResolve'); 4
        const resultPromiseReject = event.getParam('resultPromiseReject'); 5
        if (quickActionsApi && targetFields && actionName) {
          quickActionsApi
            .setActionFieldValues({ 6
              actionName,
              targetFields,
            })
            .then((data) => {
              if (resultPromiseResolve) { 7
                resultPromiseResolve(data);
              }
            })
            .catch((error) => { 8
              if (resultPromiseReject) {
                resultPromiseReject(error);
              }
            });
        }
      },
    });
    1 The lightning:quickActionAPI component lets your Aura component call the Quick Action API.
    2 Extract the name of the Quick Action to call from the event payload sent by the LWC.
    3 Extract the fields and values to pass to the Quick Action from the event payload sent by the LWC.
    4 Extract the promise resolve function from the event payload sent by the LWC.
    5 Extract the promise reject function from the event payload sent by the LWC.
    6 The setActionFieldValues method of the Quick Action API lets you set the parameters of the Quick Action.
    7 If the Quick Action API call is successful, the promise resolves with the data returned by the API.
    8 If the Quick Action API call fails, the promise rejects with the error information.
  5. Optionally, configure the CSS styles in ExampleInsightPanelWrapper.css for your custom action components.

    .THIS { 1
      border-width: var(
        --slds-c-card-sizing-border, 2
        var(--sds-c-card-sizing-border, var(--lwc-borderWidthThin, 1px))
      );
      border-style: solid;
      border-color: var(
        --slds-c-card-color-border,
        var(
          --sds-c-card-color-border,
          var(
            --slds-g-color-border-base-1,
            var(--lwc-cardColorBorder, rgb(201, 201, 201))
          )
        )
      );
      border-radius: var(
        --slds-c-card-radius-border,
        var(--sds-c-card-radius-border, var(--lwc-borderRadiusMedium, 0.25rem))
      );
    }
    1 The .THIS selector targets the root element of the ExampleInsightPanelWrapper Aura component.
    2 Use the Salesforce Lightning Design System (SLDS) CSS custom properties (with fallbacks) for consistent theming and Lightning Experience compatibility.

Post to feed LWC

This section guides you through the implementation of a custom LWC that lets users post a Coveo result to the Salesforce Chatter feed.

  1. Create a new LWC named resultPostToFeed.

  2. Add the following code to resultPostToFeed.html:

    <template>
      <c-quantic-result-action event-name={eventName} result={result} icon-name={iconName} label={label} loading={_isLoading}> 1
      </c-quantic-result-action>
    </template>
    1 Set the QuanticResultAction component properties. In particular, the event specified in eventName is dispatched when the user clicks the action button. You’ll create an event listener for this event in the JavaScript file.
  3. In resultPostToFeed.js, emit the Quick Action custom event when the user clicks the custom action button as in the following code sample, which relies on promises to establish a communication channel between the LWC and the Aura wrapper component.

    This code sample has been abbreviated to focus on the relevant parts of the implementation. For the full sample, see resultPostToFeed.js.

    // ...
    
    export default class ResultPostToFeed extends LightningElement {
      actionName = 'FeedItem.TextPost'; 1
    
      // ...
    
      @api textTemplate = 2
        '<a href="${clickUri}">${title}</a><br/><br/><quote>${excerpt}</quote>';
    
      // ...
    
      eventName = 'insightpanel__posttofeed'; 3
    
      connectedCallback() {
        registerComponentForInit(this, this.engineId);
        this.addEventListener(this.eventName, this.handlePostToFeedClick); 4
      }
    
      // ...
    
      postResultToFeed() {
        this._isLoading = true;
        this._actionHandled = false;
    
        let resultPromiseResolve;
        let resultPromiseReject;
        const resultPromise = new Promise((resolve, reject) => { 5
          resultPromiseResolve = resolve;
          resultPromiseReject = reject;
        });
        resultPromise.catch(this.handleResultPromiseFailure).finally(() => { 6
          clearTimeout(this.timeout);
          this._actionHandled = true;
          this._isLoading = false;
        });
    
        this.timeout = setTimeout(() => { 7
          if (!this._actionHandled) {
            resultPromiseReject({auraWrapperMissing: true});
          }
        }, 2000);
    
        const postToFeedEvent = new CustomEvent('insightpanel_auraposttofeed', 8
        {
          detail: {
            targetFields: {
              Body: {
                value: buildTemplateTextFromResult(this.textTemplate, this.result),
                insertType: this.insertType,
              },
            },
            actionName: this.actionName,
            resultPromiseResolve,
            resultPromiseReject,
          },
          bubbles: true,
          composed: true,
        });
    
        this.dispatchEvent(postToFeedEvent);
      }
    
      handlePostToFeedClick = (event) => {
        event.stopPropagation();
        this.engine.dispatch(this.actions.logFeedItemTextPost(this.result)); 9
        this.postResultToFeed();
      };
    
      handleResultPromiseFailure = (error) => { 10
        const {auraWrapperMissing} = error;
        const message = auraWrapperMissing
          ? this.labels.actionIsUnavailable
          : `[${this.actionName}] ${error?.errors?.[0] ?? this.labels.errorWithQuickAction}`;
        console.error(message);
      };
    }
    1 Target the FeedItem object’s TextPost action.
    2 The textTemplate is a template used to generate the text to insert in the body of the chatter post. In this case, it defaults to a template that includes the result’s clickUri, title, and excerpt, but users can customize it.
    3 Define the name of the custom event that the c-quantic-result-action component dispatches when the user clicks the action button. This is the event that your ResultPostToFeed LWC listens to in the connectedCallback method.
    4 Listen to the custom event dispatched by the c-quantic-result-action component, and call the handlePostToFeedClick method when the event is triggered. Mainly, handlePostToFeedClick will call postResultToFeed, defined next.
    5 The postResultToFeed method creates a promise that resolves or rejects based on the success or failure of the Quick Action API call.
    6 If there’s an error, call the handleResultPromiseFailure to handle it. And regardless of success or failure, the finally block clears the timeout and sets the _actionHandled and _isLoading flags.
    7 The timeout is set to reject the promise after 2 seconds if no Aura component handles the event.
    8 Dispatch the CustomEvent with the name insightpanel_auraposttofeed, which the Aura wrapper listens to.
    9 The logFeedItemTextPost action logs the analytics event for posting a result to the feed.
    10 The handleResultPromiseFailure method handles the error from the Quick Action API call and logs an appropriate message.
  4. Include the resultPostToFeed LWC in the target result templates.

    <!--- main/lwc/exampleInsightPanel/resultTemplates/defaultResultTemplate.html --->
    
    <template>
      <div
        class="result-action_container slds-is-relative slds-var-p-horizontal_medium"
      >
        <c-quantic-result-template
          is-any-preview-open={isAnyPreviewOpen}
          result-preview-should-not-be-accessible={resultPreviewShouldNotBeAccessible}
        >
          <!-- ... --->
          <div slot="actions">
            <template if:true={isHovered}>
              <c-quantic-result-action-bar>
                <!-- ... --->
                <c-result-post-to-feed result={result} engine-id={engineId}></c-result-post-to-feed> 1
              </c-quantic-result-action-bar>
            </template>
          </div>
          <!-- ... --->
        </c-quantic-result-template>
      </div>
    </template>
    1 The resultPostToFeed LWC.

Send as email LWC

Another custom result action you can implement would be to send a Coveo result as an email. The logic is similar to the Post to feed action. For the full code sample, see resultSendAsEmail LWC.

Use Apex classes

This section guides you through the implementation of a custom result action that lets users attach or detach a Coveo result to/from a Salesforce Case.

At a high level, you need to:

Tip

The code samples explained in this section are available in the Coveo Quantic repository:

Create a resultAttachToCase LWC

  1. Create a new LWC named resultAttachToCase.

  2. In resultAttachToCase.html, copy the code from the resultAttachToCase.html file in the Quantic repository.

  3. In resultAttachToCase.js, copy the code from the resultAttachToCase.js file in the Quantic repository.

    This code relies on utilities and services that you’ll create in the next steps.

Create an attachToCaseUtils LWC

  1. Create a new LWC named attachToCaseUtils.

  2. In attachToCaseUtils.js, copy the code from the attachToCaseUtils.js file in the Quantic repository.

Create an attachToCaseService LWC to connect to the Apex classes

While you could call the Apex classes directly from the resultAttachToCase and exampleInsightPanel LWCs, we recommend creating a dedicated LWC named attachToCaseService to centralize the communication with the Apex classes.

  1. Create a new LWC named attachToCaseService.

  2. Add the following code to attachToCaseService.js to wrap the AttachToCaseController methods AttachToCase, DetachFromCase, and getAttachedResults.

    // @ts-ignore
    import AttachToCase from '@salesforce/apex/CoveoV2.AttachToCaseController.AuraAttachToCase';
    // @ts-ignore
    import DetachFromCase from '@salesforce/apex/CoveoV2.AttachToCaseController.AuraDetachFromCase';
    // @ts-ignore
    import getAttachedResults from '@salesforce/apex/CoveoV2.AttachToCaseController.getAttachedResults';
    
    export function attachToCase(resultToAttach) {
      return AttachToCase(resultToAttach);
    }
    
    export function detachFromCase(resultToDetach) {
      return DetachFromCase(resultToDetach);
    }
    
    export function getAllAttachedResults(caseId) {
      return getAttachedResults({
        caseId,
      });
    }

Initialize attached results

For the resultAttachToCase LWC to display the correct state of the result (attached or not), you need to initialize it with the current state of the result.

  1. Open the exampleInsightPanel.js file, where you define the custom Insight Panel. Add the following code (you can find the complete file in the Coveo Quantic repository: exampleInsightPanel.js).

    import {
      getHeadlessBundle,
      registerComponentForInit,
      initializeWithHeadless,
    } from 'c/quanticHeadlessLoader';
    import {buildAttachedResultsPayloadHeadless} from 'c/attachToCaseUtils';
    import {getAllAttachedResults} from 'c/attachToCaseService';
    // ...
    
    export default class ExampleInsightPanel extends LightningElement {
      // ...
    
      @api caseId; 1
    
      isInitAttachedResults = false; 2
    
      loadAttachedResults() {
        getAllAttachedResults(this.caseId) 3
          .then((data) => {
            const dataParsed = JSON.parse(data);
            if (
              dataParsed?.succeeded &&
              Array.isArray(dataParsed.attachedResults)
            ) {
              this._attachedResults = dataParsed.attachedResults; 4
              this.initAttachedResults();
            } else {
              console.warn(dataParsed?.message);
            }
          })
          .catch((error) => {
            console.warn(error?.body?.message);
          });
      }
    
      connectedCallback() {
        // ...
        this.loadAttachedResults(); 5
        registerComponentForInit(this, this.engineId);
      }
    
      renderedCallback() {
        initializeWithHeadless(this, this.engineId, this.initialize); 6
      }
    
      initialize = (engine) => {
        this.headless = getHeadlessBundle(this.engineId);
        this.engine = engine;
    
        this.insightInterface = this.headless.buildInsightInterface(engine);
        this.searchStatus = this.headless.buildSearchStatus(engine);
    
        this.actions = { 7
          ...this.headless.loadCaseContextActions(engine),
          ...this.headless.loadAttachedResultsActions(engine),
        };
    
        this.engine.dispatch(this.actions.setCaseId(this.caseId)); 8
        this.initAttachedResults();
      };
    
      initAttachedResults = () => {
        if (!this.isInitAttachedResults) {
          if (this.engine && Array.isArray(this._attachedResults)) {
            this.isInitAttachedResults = true;
            if (this.actions.setAttachedResults) {
              this.engine.dispatch( 9
                this.actions.setAttachedResults({
                  results: buildAttachedResultsPayloadHeadless(
                    this._attachedResults
                  ),
                  loading: false,
                })
              );
            }
          }
        }
      };
    
      // ...
    
    }
    1 The case ID property that identifies which Salesforce Case to load attached results for. It’s passed from the parent component, such as ExampleInsightPanelWrapper.
    2 Flag to ensure attached results are only initialized once during the component lifecycle.
    3 Call the service to retrieve all results currently attached to this case.
    4 Store the attached results data locally for later use in initializing the Headless state.
    5 Load attached results when the component connects to the DOM.
    6 Initialize the Headless engine when the component renders.
    7 Load the necessary Headless action creators for managing case context and attached results.
    8 Set the case ID in the Headless engine’s state.
    9 Dispatch the attached results to the Headless engine, converting them to the expected format.
  2. Pass the caseId to your Insight Panel. When you use the exampleInsightPanel LWC, pass the caseId property to it. In the ExampleInsightPanelWrapper.cmp, edit the caseId property as follows:

    <c:exampleInsightPanel
      // ... other properties ... //
      caseId="{!v.recordId}">
    </c:exampleInsightPanel>

Leverage the resultAttachToCase LWC in your result templates

We recommend including your result-attach-to-case component as a result action in the quantic-result-action-bar of your result templates.

<c-quantic-result-action-bar>
  <!-- ... --->
  <c-result-attach-to-case result={result} engine-id={engineId}></c-result-attach-to-case> 2
</c-quantic-result-action-bar>

Also, you can include result-attach-to-case component as a read-only icon indicating whether the result is already attached. We recommend inserting it near the title and result link.

<c-quantic-result-template>
  <div slot="title" class="slds-truncate">
    <c-quantic-result-link
      result={result}
      engine-id={engineId}
    ></c-quantic-result-link>
    <c-result-attach-to-case read-only engine-id={engineId} result={result}></c-result-attach-to-case> 1
  </div>
  <!-- ... --->
</c-quantic-result-template>

For a full example, see defaultResultTemplate.html in the Quantic repository.