Introduction to Lightning Web Components (LWC)
Lightning Web Components (LWC) is a modern framework for building single-page applications on the Salesforce platform. It leverages web standards and provides a powerful way to create reusable components across different Salesforce applications.
Files Overview
JavaScript File
Purpose: Contains the logic and functionality of the component.
Usage: Handles data fetching, event handling, and interaction with the Salesforce backend.
In this LWC:
Fetches AI results using an Apex method.
Initializes charts and handles user interactions.
Manages state variables and computed properties.
Key Variables:
recordId, aiResults, chart, isFlipped, recordType, chartJsInitialized, currentFeedback, isHoveringOverSegment, hoveredSegmentIndex, hoveredRatingName, hoveredRatingScore, isSegmentLocked, lockedSegmentIndex, lockedRatingName, lockedRatingScore, lockedFeedback, shouldInitializeChart, callResolution, overallCallRating, callSummary, nextSteps, customerExperienceRating, empathyRating, listeningCapabilityRating, objectionHandlingRating, politenessRating, upsellCapabilityRating, summaryImageUrl, nextStepsImageUrl, nbheaderUrl, displayRatingName, displayRatingScore, displayFeedback, isDisplayingSegment, toggleButtonLabel, toggleCard, initializeChart, determineRecordType, wiredAIResults, connectedCallback, renderedCallback
Dependencies:
Apex Controller: getNatterboxAIResults method.
Static Resources: ChartJS, summaryImage, nextStepsImage, nbheader.
import { LightningElement, api, wire, track } from 'lwc';
import getNatterboxAIResults from '@salesforce/apex/NatterboxAIResultsController.getNatterboxAIResults';
import { loadScript } from 'lightning/platformResourceLoader';
import ChartJS from '@salesforce/resourceUrl/ChartJS';
import summaryImage from '@salesforce/resourceUrl/summaryImage';
import nextStepsImage from '@salesforce/resourceUrl/nextStepsImage';
import nbheader from '@salesforce/resourceUrl/nbheader';
export default class AiScorecard extends LightningElement {
@api recordId; // VoiceCall or Task record Id
@track aiResults = []; // Array to hold AI results
chart; // Chart instance
isFlipped = false; // Track the state of the card (flipped or not)
recordType = 'Unknown'; // Default record type
chartJsInitialized = false; // Flag to check if ChartJS is initialized
@track currentFeedback = ''; // Holds the feedback to display
@track isHoveringOverSegment = false; // Whether the user is hovering over a chart segment
@track hoveredSegmentIndex = null; // Index of the hovered segment
@track hoveredRatingName = ''; // Name of the hovered rating
@track hoveredRatingScore = null; // Score of the hovered rating
@track isSegmentLocked = false; // Whether a segment is locked
@track lockedSegmentIndex = null; // Index of the locked segment
@track lockedRatingName = ''; // Name of the locked rating
@track lockedRatingScore = null; // Score of the locked rating
@track lockedFeedback = ''; // Feedback of the locked rating
@track shouldInitializeChart = false; // Flag to indicate chart initialization after render
// Lifecycle hook that runs when the component is inserted into the DOM
connectedCallback() {
this.determineRecordType(); // Determine the record type based on the recordId
}
// Method to determine the record type based on the recordId prefix
determineRecordType() {
const recordPrefix = this.recordId.substring(0, 3);
if (recordPrefix === '0LQ') {
this.recordType = 'VoiceCall';
} else if (recordPrefix === '00T') {
this.recordType = 'Task';
} else {
this.recordType = 'Unknown';
}
console.log('Record Type:', this.recordType); // Debug log
}
// Wire service to fetch AI results based on the recordId
@wire(getNatterboxAIResults, { recordIds: '$recordId' })
wiredAIResults({ error, data }) {
if (data) {
console.log('AI Results:', data); // Debug log
this.aiResults = data;
if (this.chartJsInitialized && !this.isFlipped && this.aiResults.length > 0) {
this.initializeChart(); // Initialize the chart if conditions are met
}
} else if (error) {
console.error('Error fetching AI Results:', error); // Log any errors
}
}
// Lifecycle hook that runs after every render of the component
renderedCallback() {
if (this.chartJsInitialized) {
if (this.shouldInitializeChart && !this.isFlipped && this.aiResults.length > 0 && !this.chart) {
this.initializeChart(); // Initialize the chart if conditions are met
this.shouldInitializeChart = false; // Reset the flag
}
return;
}
this.chartJsInitialized = true;
// Load ChartJS library
Promise.all([
loadScript(this, ChartJS)
])
.then(() => {
if (!this.isFlipped && this.aiResults.length > 0) {
this.initializeChart(); // Initialize the chart if conditions are met
}
})
.catch(error => {
console.error('Error loading ChartJS', error); // Log any errors
});
}
// Method to initialize the chart
initializeChart() {
if (this.chart) {
this.chart.destroy(); // Destroy the old chart instance before creating a new one
this.chart = null;
}
if (this.aiResults.length === 0) {
console.warn('No AI Results to display.'); // Warn if there are no AI results
return;
}
const canvas = this.template.querySelector('.polar-area-chart');
if (!canvas) {
console.error('Canvas element not found'); // Error if canvas element is not found
return;
}
// sets max height and width for chart
canvas.style.maxWidth = '550px';
canvas.style.maxHeight = '550px';
const ctx = canvas.getContext('2d');
// Extract ratings and feedback from AI results
const ratings = [
this.aiResults[0].Customer_Experience_Rating__c || 0,
this.aiResults[0].Empathy_Rating__c || 0,
this.aiResults[0].Listening_Capability_Rating__c || 0,
this.aiResults[0].Objection_Handling_Rating__c || 0,
this.aiResults[0].Politeness_Rating__c || 0,
this.aiResults[0].Upsell_Capability_Rating__c || 0
];
const feedbackArray = [
this.aiResults[0].Customer_Experience_Feedback__c || '',
this.aiResults[0].Empathy_Feedback__c || '',
this.aiResults[0].Listening_Feedback__c || '',
this.aiResults[0].Objection_Handling_Feedback__c || '',
this.aiResults[0].Politeness_Feedback__c || '',
this.aiResults[0].Upsell_Capability_Feedback__c || ''
];
// Define background colors for the chart segments
const backgroundColors = [
'rgb(255, 99, 132)', // Customer Experience
'rgb(75, 192, 192)', // Empathy
'rgb(255, 205, 86)', // Listening Capability
'rgb(201, 203, 207)', // Objection Handling
'rgb(54, 162, 235)', // Politeness
'rgb(153, 102, 255)' // Upsell Capability
];
// Data for the chart
const data = {
labels: [
'Customer Experience',
'Empathy',
'Listening Capability',
'Objection Handling',
'Politeness',
'Upsell Capability'
],
datasets: [{
label: 'Ratings',
data: ratings,
backgroundColor: backgroundColors
}]
};
const self = this;
// Chart configuration
const config = {
type: 'polarArea',
data: data,
options: {
legend: {
display: false // Hide the legend
},
tooltips: {
enabled: false // Disable default tooltips
},
hover: {
onHover: function(event, chartElements) {
if (!self.isSegmentLocked) {
if (chartElements && chartElements.length) {
const index = chartElements[0]._index;
self.isHoveringOverSegment = true;
self.hoveredSegmentIndex = index;
self.hoveredRatingName = data.labels[index];
self.hoveredRatingScore = ratings[index];
self.currentFeedback = feedbackArray[index];
} else {
self.isHoveringOverSegment = false;
self.hoveredSegmentIndex = null;
self.hoveredRatingName = '';
self.hoveredRatingScore = null;
self.currentFeedback = '';
}
}
}
},
onClick: function(event, chartElements) {
if (chartElements && chartElements.length) {
const index = chartElements[0]._index;
if (self.isSegmentLocked && self.lockedSegmentIndex === index) {
// Unlock if clicking on the same segment
self.isSegmentLocked = false;
self.lockedSegmentIndex = null;
self.lockedRatingName = '';
self.lockedRatingScore = null;
self.lockedFeedback = '';
self.isHoveringOverSegment = false;
} else {
// Lock the segment
self.isSegmentLocked = true;
self.lockedSegmentIndex = index;
self.lockedRatingName = data.labels[index];
self.lockedRatingScore = ratings[index];
self.lockedFeedback = feedbackArray[index];
// Update the hover variables as well
self.isHoveringOverSegment = true;
self.hoveredSegmentIndex = index;
self.hoveredRatingName = data.labels[index];
self.hoveredRatingScore = ratings[index];
self.currentFeedback = feedbackArray[index];
}
} else {
// Clicked outside segments, unlock
self.isSegmentLocked = false;
self.lockedSegmentIndex = null;
self.lockedRatingName = '';
self.lockedRatingScore = null;
self.lockedFeedback = '';
self.isHoveringOverSegment = false;
self.hoveredSegmentIndex = null;
self.hoveredRatingName = '';
self.hoveredRatingScore = null;
self.currentFeedback = '';
}
},
scale: {
ticks: {
display: true,
stepSize: 2,
maxTicksLimit: 10,
backdropColor: 'rgba(255, 255, 255, 1)' // Set the backdrop color to fully opaque white
}
}
},
plugins: [{
afterDatasetsDraw: function(chart) {
if (chart.scale) {
chart.scale.draw(chart.ctx); // Ensure the scale is drawn after datasets
}
}
}]
};
// Create a new Chart instance
this.chart = new Chart(ctx, config);
}
// Getter methods to retrieve specific AI result data
get callResolution() {
return this.aiResults.length > 0 ? this.aiResults[0].Call_Resolution__c : '';
}
get overallCallRating() {
return this.aiResults.length > 0 ? this.aiResults[0].Overall_Call_Rating__c : '';
}
get callSummary() {
return this.aiResults.length > 0 ? this.aiResults[0].Call_Summary__c : '';
}
get nextSteps() {
return this.aiResults.length > 0 ? this.aiResults[0].Next_Steps__c : '';
}
get customerExperienceRating() {
return this.aiResults.length > 0 ? this.aiResults[0].Customer_Experience_Rating__c : '';
}
get empathyRating() {
return this.aiResults.length > 0 ? this.aiResults[0].Empathy_Rating__c : '';
}
get listeningCapabilityRating() {
return this.aiResults.length > 0 ? this.aiResults[0].Listening_Capability_Rating__c : '';
}
get objectionHandlingRating() {
return this.aiResults.length > 0 ? this.aiResults[0].Objection_Handling_Rating__c : '';
}
get politenessRating() {
return this.aiResults.length > 0 ? this.aiResults[0].Politeness_Rating__c : '';
}
get upsellCapabilityRating() {
return this.aiResults.length > 0 ? this.aiResults[0].Upsell_Capability_Rating__c : '';
}
// Getter methods to retrieve resource URLs
get summaryImageUrl() {
return summaryImage;
}
get nextStepsImageUrl() {
return nextStepsImage;
}
get nbheaderUrl() {
return nbheader;
}
// Getters for displaying data based on hover or lock state
get displayRatingName() {
return this.isSegmentLocked ? this.lockedRatingName : this.hoveredRatingName;
}
get displayRatingScore() {
return this.isSegmentLocked ? this.lockedRatingScore : this.hoveredRatingScore;
}
get displayFeedback() {
return this.isSegmentLocked ? this.lockedFeedback : this.currentFeedback;
}
get isDisplayingSegment() {
return this.isSegmentLocked || this.isHoveringOverSegment;
}
// Method to toggle the card state (flipped or not)
toggleCard() {
this.isFlipped = !this.isFlipped;
if (this.isFlipped) {
// Flipping to the summary view; destroy the chart
if (this.chart) {
this.chart.destroy();
this.chart = null;
}
} else {
// Flipping back to the chart view; set a flag to initialize the chart in renderedCallback
this.shouldInitializeChart = true;
}
}
// Getter for the button label based on the card state
get toggleButtonLabel() {
return this.isFlipped ? 'View Scorecard' : 'View Summary & Next Steps';
}
}
Adjusting Javascript
Adjusting the LWC Code to Include Different Criteria for Customer Call Rating
When changing the criteria used to rate calls (such as adding "Objection Handling", "Customer Experience", "Listening", etc.), the primary areas to adjust in your JavaScript code will be where data is retrieved from the aiResults object and how it's displayed on the chart. Below are the necessary adjustments, with an example for adding new criteria like Problem-Solving and Patience.
Key Variables to Update
ratings Array: This array holds the values for each rating (e.g., Customer Experience, Empathy). You need to add new entries for the additional criteria.
feedbackArray: This array holds the feedback strings associated with each rating. You must extend it with new feedback fields for the new criteria.
backgroundColors: If desired, update the background colors for each new rating to distinguish them on the chart.
labels: Add new labels to the chart for the additional criteria.
Example Code Snippet: Adding "Problem-Solving" and "Patience" Criteria
// Inside initializeChart()
const ratings = [
this.aiResults[0].Customer_Experience_Rating__c || 0,
this.aiResults[0].Empathy_Rating__c || 0,
this.aiResults[0].Listening_Capability_Rating__c || 0,
this.aiResults[0].Objection_Handling_Rating__c || 0,
this.aiResults[0].Politeness_Rating__c || 0,
this.aiResults[0].Upsell_Capability_Rating__c || 0,
this.aiResults[0].Problem_Solving_Rating__c || 0, // New rating
this.aiResults[0].Patience_Rating__c || 0 // New rating
];
const feedbackArray = [
this.aiResults[0].Customer_Experience_Feedback__c || '',
this.aiResults[0].Empathy_Feedback__c || '',
this.aiResults[0].Listening_Feedback__c || '',
this.aiResults[0].Objection_Handling_Feedback__c || '',
this.aiResults[0].Politeness_Feedback__c || '',
this.aiResults[0].Upsell_Capability_Feedback__c || '',
this.aiResults[0].Problem_Solving_Feedback__c || '', // New feedback
this.aiResults[0].Patience_Feedback__c || '' // New feedback
];
const backgroundColors = [
'rgb(255, 99, 132)', // Customer Experience
'rgb(75, 192, 192)', // Empathy
'rgb(255, 205, 86)', // Listening Capability
'rgb(201, 203, 207)', // Objection Handling
'rgb(54, 162, 235)', // Politeness
'rgb(153, 102, 255)', // Upsell Capability
'rgb(255, 159, 64)', // Problem-Solving - New color
'rgb(128, 128, 255)' // Patience - New color
];
const data = {
labels: [
'Customer Experience',
'Empathy',
'Listening Capability',
'Objection Handling',
'Politeness',
'Upsell Capability',
'Problem-Solving', // New label
'Patience' // New label
],
datasets: [{
label: 'Ratings',
data: ratings,
backgroundColor: backgroundColors
}]
};
Explanation of Changes
Ratings Array:
This array defines the scores for each rating. To add new criteria (e.g., Problem-Solving and Patience), you need to append new fields from the aiResults object, similar to the existing fields.
Feedback Array:
Similar to the ratings array, you need to add feedback strings associated with each new rating, such as Problem-Solving Feedback and Patience Feedback.
Chart Labels:
Update the labels array inside the data object to include the names of the new criteria (Problem-Solving and Patience). These labels will appear on the polar area chart.
Background Colors:
Optionally, add distinct colors for the new ratings. This helps differentiate them visually on the chart.
Where to Modify Data Sources (Apex and LWC Communication)
Backend/Apex Changes: Ensure that the new criteria (e.g., Problem_Solving_Rating__c and Patience_Rating__c) exist on the VoiceCall or Task records and are retrieved correctly by the getNatterboxAIResults Apex method.
LWC Updates: Include the new fields in any getters that may need to return the new data, such as get problemSolvingRating() or get patienceRating().
By adjusting the code in these sections, you can easily extend the chart to handle additional rating criteria while keeping the same structure and logic.
HTML File
Purpose: Defines the structure and layout of the component's user interface.
Usage: Contains HTML tags and LWC tags to create visual elements.
In this LWC:
Displays overall call rating and AI call outcome.
Includes a toggle button to switch views.
Shows a polar area chart for call ratings.
Conditionally renders sections based on user interaction.
Displays summary, next steps, and individual ratings with feedback.
Shows images and dynamically computed properties.
Variables from JavaScript:
callResolution, overallCallRating, toggleButtonLabel, isFlipped, callSummary, nextSteps, customerExperienceRating, empathyRating, listeningCapabilityRating, objectionHandlingRating, politenessRating, upsellCapabilityRating, summaryImageUrl, nextStepsImageUrl, nbheaderUrl, displayRatingName, displayRatingScore, displayFeedback, toggleCard
Variables from CSS:
.custom-card-container, .small-chart
aiScorecard.html with detailed comments
<template>
<!-- Main container for the custom card component -->
<div class="custom-card-container">
<!-- Lightning card element that provides a consistent UI structure -->
<lightning-card>
<!-- Padding added around the content for spacing -->
<div class="slds-var-p-around_medium">
<!-- Header Section: Contains an image (e.g., Natterbox logo or header) -->
<div class="slds-text-align_center slds-var-m-bottom_medium">
<!-- Header image with a fixed max width to ensure responsiveness -->
<img src={nbheaderUrl} alt="Natterbox Header" style="max-width:300px;" />
</div>
<!-- Horizontal line separator for better visual division -->
<hr class="slds-var-m-vertical_medium"/>
<!-- Top Section: Displays the AI Call Outcome (call resolution) -->
<div class="slds-text-heading_small slds-text-align_center slds-var-m-bottom_medium">
<!-- Text showing the AI-determined outcome of the call -->
AI Call Outcome: <span>{callResolution}</span>
</div>
<hr class="slds-var-m-vertical_medium"/>
<!-- Top Section: Displays the overall call rating -->
<div class="slds-text-heading_small slds-var-m-bottom_medium slds-text-align_center">
<!-- Strongly emphasized overall call rating value -->
<strong> Overall Call Rating: <span>{overallCallRating}</span></strong>
</div>
<hr class="slds-var-m-vertical_medium"/>
<!-- Toggle Button Section: Allows users to toggle between different views -->
<div class="slds-text-align_center slds-var-m-bottom_medium">
<!-- Button that triggers the 'toggleCard' function when clicked -->
<lightning-button label={toggleButtonLabel} onclick={toggleCard} size="small" class="small-button"></lightning-button>
</div>
<!-- Separator added for a clear distinction between sections -->
<hr class="slds-var-m-vertical_medium"/>
<!-- Conditionally rendered content based on the 'isFlipped' property -->
<template if:true={isFlipped}>
<!-- Bottom Section: Displays call summary and next steps when the card is flipped -->
<div class="slds-text-align_center slds-var-m-bottom_medium">
<!-- Displays an image associated with the summary -->
<img src={summaryImageUrl} alt="Summary Image" style="width:50px;height:50px;" />
<div>
<!-- Call summary title -->
<strong>Summary:</strong>
<!-- Call summary content formatted as text -->
<div>
<lightning-formatted-text value={callSummary}></lightning-formatted-text>
</div>
</div>
</div>
<hr class="slds-var-m-vertical_medium"/>
<!-- Section for displaying the next steps after the call -->
<div class="slds-text-align_center slds-var-m-bottom_medium">
<!-- Next steps image (e.g., an icon related to next actions) -->
<img src={nextStepsImageUrl} alt="Next Steps Image" style="width:50px;height:50px;" />
<div>
<!-- Next steps title -->
<strong>Next Steps:</strong>
<!-- Next steps content formatted as text -->
<div>
<lightning-formatted-text value={nextSteps}></lightning-formatted-text>
</div>
</div>
</div>
</template>
<!-- Content shown when the card is not flipped (default view) -->
<template if:false={isFlipped}>
<!-- Middle Section: Displays a Polar Area Chart -->
<div class="slds-var-m-bottom_medium slds-text-align_center">
<!-- Chart container with manual DOM insertion for the canvas element -->
<div class="chart-container" style="position:relative;display:flex;justify-content:center;align-items:center;">
<!-- Placeholder for the polar area chart -->
<canvas class="polar-area-chart small-chart" lwc:dom="manual"></canvas>
</div>
</div>
<hr class="slds-var-m-vertical_medium"/>
<!-- Conditionally rendered content based on 'isDisplayingSegment' -->
<template if:false={isDisplayingSegment}>
<!-- Section displaying AI call ratings as a legend -->
<div class="slds-text-heading_small slds-text-align_center slds-var-m-bottom_medium" style="font-size:14.5px;">
<strong>Natterbox AI Call Ratings</strong>
</div>
<hr class="slds-var-m-vertical_medium"/>
<!-- Legend Section: Lists individual AI call rating categories and their scores -->
<div class="slds-grid slds-wrap slds-var-m-bottom_medium slds-grid_align-center" style="font-size:14.5px;">
<!-- Left Column: Displays ratings for Customer Experience, Empathy, and Listening Capability -->
<div class="slds-col slds-shrink slds-text-align_left slds-var-m-right_small slds-var-m-right_medium">
<ul class="slds-list_vertical slds-m-around_none">
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<!-- Rating score for Customer Experience -->
<strong>{customerExperienceRating}</strong>
<!-- Color box for Customer Experience legend entry -->
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(255, 99, 132);width:14.5px;height:14.5px;"></span>
Customer Experience
</li>
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<!-- Rating score for Empathy -->
<strong>{empathyRating}</strong>
<!-- Color box for Empathy legend entry -->
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(75, 192, 192);width:14.5px;height:14.5px;"></span>
Empathy
</li>
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<!-- Rating score for Listening Capability -->
<strong>{listeningCapabilityRating}</strong>
<!-- Color box for Listening Capability legend entry -->
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(255, 205, 86);width:14.5px;height:14.5px;"></span>
Listening Capability
</li>
</ul>
</div>
<!-- Right Column: Displays ratings for Objection Handling, Politeness, and Upsell Capability -->
<div class="slds-col slds-shrink slds-text-align_left slds-var-m-left_small slds-var-m-left_medium">
<ul class="slds-list_vertical slds-m-around_none">
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<!-- Rating score for Objection Handling -->
<strong>{objectionHandlingRating}</strong>
<!-- Color box for Objection Handling legend entry -->
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(201, 203, 207);width:14.5px;height:14.5px;"></span>
Objection Handling
</li>
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<!-- Rating score for Politeness -->
<strong>{politenessRating}</strong>
<!-- Color box for Politeness legend entry -->
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(54, 162, 235);width:14.5px;height:14.5px;"></span>
Politeness
</li>
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<!-- Rating score for Upsell Capability -->
<strong>{upsellCapabilityRating}</strong>
<!-- Color box for Upsell Capability legend entry -->
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(153, 102, 255);width:14.5px;height:14.5px;"></span>
Upsell Capability
</li>
</ul>
</div>
</div>
</template>
<!-- Section displaying a single rating and its feedback when 'isDisplayingSegment' is true -->
<template if:true={isDisplayingSegment}>
<!-- Displays the selected rating's name and score -->
<div class="slds-text-heading_small slds-text-align_center slds-var-m-bottom_medium" style="white-space:nowrap;">
<strong>{displayRatingName} Rating: {displayRatingScore}</strong>
</div>
<hr class="slds-var-m-vertical_medium"/>
<!-- Feedback associated with the selected rating -->
<div class="slds-text-align_center slds-var-m-around_medium">
<div class="slds-var-m-top_small" style="font-size:14.5px;">
<!-- Formatted feedback text related to the selected rating -->
<lightning-formatted-text value={displayFeedback}></lightning-formatted-text>
</div>
</div>
</template>
</template>
</div>
</lightning-card>
</div>
</template>
Adjusting html file
To adjust the .html file based on the changes in the aiScorecard.js for adding new criteria like Problem-Solving and Patience, you will need to modify the section that displays the labels and ratings in the UI. Specifically, you'll need to:
Add new list items under the chart's legend for Problem-Solving and Patience.
Update the template logic to dynamically display these new fields based on their values.
Adjustments in the .html File
Add New Fields in the Left and Right Column Lists
You need to extend the existing HTML list elements to include the new fields Problem-Solving and Patience. Below is an example of how to modify the .html to accommodate these new fields.
Updated HTML for Left and Right Columns (with new fields)
<!-- Left Column Labels and Values -->
<div class="slds-col slds-shrink slds-text-align_left slds-var-m-right_small slds-var-m-right_medium">
<ul class="slds-list_vertical slds-m-around_none">
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<strong>{customerExperienceRating}</strong>
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(255, 99, 132);width:14.5px;height:14.5px;"></span>
Customer Experience
</li>
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<strong>{empathyRating}</strong>
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(75, 192, 192);width:14.5px;height:14.5px;"></span>
Empathy
</li>
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<strong>{listeningCapabilityRating}</strong>
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(255, 205, 86);width:14.5px;height:14.5px;"></span>
Listening Capability
</li>
<!-- New Field for Problem-Solving -->
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<strong>{problemSolvingRating}</strong>
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(255, 159, 64);width:14.5px;height:14.5px;"></span>
Problem-Solving
</li>
</ul>
</div>
<!-- Right Column Labels and Values -->
<div class="slds-col slds-shrink slds-text-align_left slds-var-m-left_small slds-var-m-left_medium">
<ul class="slds-list_vertical slds-m-around_none">
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<strong>{objectionHandlingRating}</strong>
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(201, 203, 207);width:14.5px;height:14.5px;"></span>
Objection Handling
</li>
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<strong>{politenessRating}</strong>
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(54, 162, 235);width:14.5px;height:14.5px;"></span>
Politeness
</li>
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<strong>{upsellCapabilityRating}</strong>
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(153, 102, 255);width:14.5px;height:14.5px;"></span>
Upsell Capability
</li>
<!-- New Field for Patience -->
<li class="slds-var-m-bottom_small" style="white-space:nowrap;font-size:14.5px;">
<strong>{patienceRating}</strong>
<span class="slds-show_inline-block slds-var-m-left_x-small slds-var-m-right_x-small" style="background-color:rgb(128, 128, 255);width:14.5px;height:14.5px;"></span>
Patience
</li>
</ul>
</div>
Explanation of Changes
Left Column Adjustments:
Added a new <li> item for Problem-Solving. The color rgb(255, 159, 64) matches the corresponding rating field in the aiScorecard.js.
Right Column Adjustments:
Added a new <li> item for Patience. The color rgb(128, 128, 255) matches the corresponding field.
Dynamic Values:
The new fields {problemSolvingRating} and {patienceRating} are dynamically bound, just like the existing ratings.
Consistent Styling:
The new fields are styled similarly to the existing fields to maintain consistency in the UI.
By adding the above HTML elements, the new ratings for Problem-Solving and Patience will be displayed correctly on the page, matching the functionality in the JavaScript file. This ensures that the new criteria are visualized in both the chart and the legend.
XML Configuration File
Purpose: Defines the metadata for the component.
Usage: Specifies where the component can be used and which objects it supports.
In this LWC:
Specifies that the component is exposed and can be used on VoiceCall and Task Records.
Key Variables:
apiVersion, isExposed, targets, targetConfig, objects
aiScorecard.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- This is the Lightning Component Bundle configuration -->
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<!-- API version used for this component -->
<apiVersion>58.0</apiVersion>
<!-- Expose the component to be used in Lightning App Builder -->
<isExposed>true</isExposed>
<targets>
<!-- Specify the target where this component can be used -->
<target>lightning__RecordPage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<!-- Specify the objects that this component is configured for -->
<objects>
<object>VoiceCall</object>
<object>Task</object>
</objects>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
CSS File
Purpose: Defines the styling and appearance of the component.
Usage: Contains CSS rules to style the HTML elements.
In this LWC:
Styles the component for a consistent and visually appealing appearance outside of the SLDS prebuilt classes being used.
Key Variables:
.custom-card-container, .small-chart
:ai-card {
border-radius: 0.5rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
background-size: cover; /* Ensure the background image covers the entire div */
background-repeat: no-repeat; /* Ensure the background image does not repeat */
background-position: top; /* Center the background image */
background-image: url('/resource/white');
}
.custom-card-container {
overflow: visible;
}
Apex Controller
Purpose: Provides server-side logic and data fetching capabilities.
Usage: Contains methods to interact with Salesforce data and return it to the LWC.
In this LWC:
Fetches data from Salesforce for the AI Call Coaching component.
Key Method:
getNatterboxAIResults
Objects and Fields:
VoiceCall: callreporting__c
Task: nbavs__call_reporting__c
nbavs__CallReporting__c: Natterbox_AI_Results__c
Natterbox_AI_Results__c:
Customer_Experience_Rating__c, Empathy_Rating__c, Listening_Capability_Rating__c, Objection_Handling_Rating__c, Overall_Call_Rating__c, Politeness_Rating__c, Upsell_Capability_Rating__c, Call_Summary__c, Next_Steps__c, Customer_Experience_Feedback__c, Empathy_Feedback__c, Listening_Feedback__c, Objection_Handling_Feedback__c, Politeness_Feedback__c, Questioning_Capability_Feedback__c, Upsell_Capability_Feedback__c, Call_Resolution__c
public with sharing class NatterboxAIResultsController {
@AuraEnabled(cacheable=true)
public static List<Natterbox_AI_Results__c> getNatterboxAIResults(List<Id> recordIds) {
try {
// Determine the record type based on the prefix
String recordPrefix = String.valueOf(recordIds[0]).substring(0, 3);
List<Id> callReportingIds = new List<Id>();
if (recordPrefix == '0LQ') {
// Handle VoiceCall records
List<VoiceCall> voiceCalls = [
SELECT callreporting__c
FROM VoiceCall
WHERE Id IN :recordIds
];
for (VoiceCall vc : voiceCalls) {
if (vc.callreporting__c != null) {
callReportingIds.add(vc.callreporting__c);
}
}
} else if (recordPrefix == '00T') {
// Handle Task records
List<Task> tasks = [
SELECT nbavs__call_reporting__c
FROM Task
WHERE Id IN :recordIds
];
for (Task t : tasks) {
if (t.nbavs__call_reporting__c != null) {
callReportingIds.add(t.nbavs__call_reporting__c);
}
}
} else {
throw new AuraHandledException('Unsupported record type');
}
System.debug('Call Reporting IDs: ' + callReportingIds);
// Get the Natterbox AI Results IDs from the Call Reporting records
List<nbavs__CallReporting__c> callReportings = [
SELECT Natterbox_AI_Results__c
FROM nbavs__CallReporting__c
WHERE Id IN :callReportingIds
];
List<Id> aiResultsIds = new List<Id>();
for (nbavs__CallReporting__c cr : callReportings) {
if (cr.Natterbox_AI_Results__c != null) {
aiResultsIds.add(cr.Natterbox_AI_Results__c);
}
}
System.debug('AI Results IDs: ' + aiResultsIds);
// Get the required fields from the Natterbox AI Results records
List<Natterbox_AI_Results__c> aiResults = [
SELECT Customer_Experience_Rating__c, Empathy_Rating__c, Listening_Capability_Rating__c,
Objection_Handling_Rating__c, Overall_Call_Rating__c, Politeness_Rating__c,
Upsell_Capability_Rating__c, Call_Summary__c, Next_Steps__c,
Customer_Experience_Feedback__c, Empathy_Feedback__c, Listening_Feedback__c,
Objection_Handling_Feedback__c, Politeness_Feedback__c,
Questioning_Capability_Feedback__c, Upsell_Capability_Feedback__c, Call_Resolution__c
FROM Natterbox_AI_Results__c
WHERE Id IN :aiResultsIds
];
System.debug('AI Results: ' + aiResults);
return aiResults;
} catch (AuraHandledException e) {
System.debug('Caught AuraHandledException: ' + e.getMessage());
throw e;
} catch (Exception e) {
System.debug('Caught Exception: ' + e.getMessage());
throw new AuraHandledException('Error fetching contact details: ' + e.getMessage());
}
}
}
Adjusting Apex Controller
To adjust the NatterboxAIResultsController Apex class to include the new criteria such as Problem-Solving and Patience, you need to:
Update the SOQL query to include the fields for Problem-Solving and Patience from the Natterbox_AI_Results__c object.
Return the additional feedback fields associated with these new criteria, similar to how existing fields like Customer_Experience_Feedback__c and Empathy_Feedback__c are handled.
Updated Sections of the Apex Controller
SOQL Query Update
Add the new fields for Problem-Solving and Patience to the SOQL query. This ensures that these ratings and feedback values are fetched from the database.
Updated SOQL Query:
List<Natterbox_AI_Results__c> aiResults = [
SELECT Customer_Experience_Rating__c, Empathy_Rating__c, Listening_Capability_Rating__c,
Objection_Handling_Rating__c, Overall_Call_Rating__c, Politeness_Rating__c,
Upsell_Capability_Rating__c, Problem_Solving_Rating__c, Patience_Rating__c, // New fields
Call_Summary__c, Next_Steps__c,
Customer_Experience_Feedback__c, Empathy_Feedback__c, Listening_Feedback__c,
Objection_Handling_Feedback__c, Politeness_Feedback__c,
Upsell_Capability_Feedback__c, Problem_Solving_Feedback__c, Patience_Feedback__c, // New fields
Call_Resolution__c
FROM Natterbox_AI_Results__c
WHERE Id IN :aiResultsIds
];
Explanation of Changes
New Rating Fields: Problem_Solving_Rating__c and Patience_Rating__c are now included in the SELECT statement. These fields will hold the numeric values for these ratings.
New Feedback Fields: Problem_Solving_Feedback__c and Patience_Feedback__c are included to capture the text feedback for these new criteria.
Full Explanation of Code Changes:
SOQL Query: The query is extended to fetch the newly added rating fields (Problem_Solving_Rating__c, Patience_Rating__c) and their respective feedback fields (Problem_Solving_Feedback__c, Patience_Feedback__c). These fields must already exist in the Natterbox_AI_Results__c object.
Returning Data: The List<Natterbox_AI_Results__c> returned by the method now includes the new criteria fields. These can be accessed by the LWC to display the new ratings and feedback in the UI.
With these changes, your Apex controller will now be able to retrieve and send back the additional rating and feedback criteria, allowing the frontend (LWC) to process and display the new Problem-Solving and Patience ratings and feedback.
Apex Test Class
Purpose: Ensures the Apex Controller works as expected.
Usage: Contains test methods to validate the functionality of the Apex Controller.
In this LWC:
Validates the functionality of the NatterboxAIResultsController.
Test Methods:
testGetNatterboxAIResults_VoiceCall
testGetNatterboxAIResults_Task
Objects and Fields:
VoiceCall: callreporting__c (Lookup to nbavs__CallReporting__c)
Task: nbavs__call_reporting__c (Lookup to nbavs__CallReporting__c)
nbavs__CallReporting__c: Natterbox_AI_Results__c (Lookup to Natterbox_AI_Results__c)
Natterbox_AI_Results__c:
Customer_Experience_Rating__c, Empathy_Rating__c, Listening_Capability_Rating__c, Objection_Handling_Rating__c, Overall_Call_Rating__c, Politeness_Rating__c, Upsell_Capability_Rating__c, Call_Summary__c, Next_Steps__c, Customer_Experience_Feedback__c, Empathy_Feedback__c, Listening_Feedback__c, Objection_Handling_Feedback__c, Politeness_Feedback__c, Questioning_Capability_Feedback__c, Upsell_Capability_Feedback__c, Call_Resolution__c
@isTest
public class NatterboxAIResultsControllerTest {
@testSetup
static void setupTestData() {
// Create test Natterbox AI Results
Natterbox_AI_Results__c aiResult1 = createTestNatterboxAIResults();
// Create test Call Reporting
nbavs__CallReporting__c callReporting1 = new nbavs__CallReporting__c(
Natterbox_AI_Results__c = aiResult1.Id
);
insert callReporting1;
// Create test VoiceCall
VoiceCall voiceCall1 = new VoiceCall(
CallCenterId = '04v8d000000oOydAAE', // Replace with a valid CallCenterId
VendorType = 'ContactCenter', // Replace with a valid VendorType
CallStartDateTime = System.now(),
CallEndDateTime = System.now().addMinutes(5),
FromPhoneNumber = '1234567890',
ToPhoneNumber = '0987654321',
CallType = 'Inbound',
callreporting__c = callReporting1.Id
);
insert voiceCall1;
// Create test Task
Task task1 = new Task(
Subject = 'Test Task',
Status = 'Not Started',
Priority = 'Normal',
nbavs__call_reporting__c = callReporting1.Id
);
insert task1;
}
// Helper method to create Natterbox AI Results record with integer ratings
static Natterbox_AI_Results__c createTestNatterboxAIResults() {
Natterbox_AI_Results__c aiResult = new Natterbox_AI_Results__c(
Customer_Experience_Rating__c = 4,
Empathy_Rating__c = 4,
Listening_Capability_Rating__c = 5,
Objection_Handling_Rating__c = 4,
Politeness_Rating__c = 5,
Upsell_Capability_Rating__c = 4,
Call_Summary__c = 'Great Call',
Next_Steps__c = 'Follow-up required',
Customer_Experience_Feedback__c = 'Excellent',
Empathy_Feedback__c = 'Good',
Listening_Feedback__c = 'Attentive',
Objection_Handling_Feedback__c = 'Effective',
Politeness_Feedback__c = 'Very polite',
Questioning_Capability_Feedback__c = 'Good questioning',
Upsell_Capability_Feedback__c = 'Effective upsell'
);
insert aiResult;
return aiResult;
}
@isTest
static void testGetNatterboxAIResults_VoiceCall() {
// Fetch the test VoiceCall created in the setup
VoiceCall testVoiceCall = [SELECT Id FROM VoiceCall LIMIT 1];
// Call the method to get Natterbox AI Results by VoiceCall ID
Test.startTest();
List<Natterbox_AI_Results__c> results = NatterboxAIResultsController.getNatterboxAIResults(new List<Id> { testVoiceCall.Id });
Test.stopTest();
// Validate the results
System.assertNotEquals(null, results, 'Results should not be null');
System.assertEquals(1, results.size(), 'There should be 1 result');
// System.assertEquals('Great Call', results[0].Call_Summary__c, 'Call Summary should be "Great Call"');
// System.assertEquals('Follow-up required', results[0].Next_Steps__c, 'Next Steps should be "Follow-up required"');
}
@isTest
static void testGetNatterboxAIResults_Task() {
// Fetch the test Task created in the setup
Task testTask = [SELECT Id FROM Task LIMIT 1];
// Call the method to get Natterbox AI Results by Task ID
Test.startTest();
List<Natterbox_AI_Results__c> results = NatterboxAIResultsController.getNatterboxAIResults(new List<Id> { testTask.Id });
Test.stopTest();
// Validate the results
System.assertNotEquals(null, results, 'Results should not be null');
System.assertEquals(1, results.size(), 'There should be 1 result');
//System.assertEquals('Great Call', results[0].Call_Summary__c, 'Call Summary should be "Great Call"');
//System.assertEquals('Follow-up required', results[0].Next_Steps__c, 'Next Steps should be "Follow-up required"');
}
}
Adjusting Apex Test Class
To adjust the test class NatterboxAIResultsControllerTest to accommodate the new criteria like Problem-Solving and Patience, you need to:
Update the test data creation to include the new fields in the Natterbox_AI_Results__c object.
Validate the new fields in the test methods to ensure the values for the new criteria are correctly returned.
Here’s what needs to be changed in the test class:
Update Test Data Creation
In the createTestNatterboxAIResults helper method, you will need to add the new fields for Problem-Solving and Patience to the test Natterbox_AI_Results__c record.
Updated createTestNatterboxAIResults Method:
// Helper method to create Natterbox AI Results record with integer ratings
static Natterbox_AI_Results__c createTestNatterboxAIResults() {
Natterbox_AI_Results__c aiResult = new Natterbox_AI_Results__c(
Customer_Experience_Rating__c = 4,
Empathy_Rating__c = 4,
Listening_Capability_Rating__c = 5,
Objection_Handling_Rating__c = 4,
Politeness_Rating__c = 5,
Upsell_Capability_Rating__c = 4,
Problem_Solving_Rating__c = 5, // New field
Patience_Rating__c = 3, // New field
Call_Summary__c = 'Great Call',
Next_Steps__c = 'Follow-up required',
Customer_Experience_Feedback__c = 'Excellent',
Empathy_Feedback__c = 'Good',
Listening_Feedback__c = 'Attentive',
Objection_Handling_Feedback__c = 'Effective',
Politeness_Feedback__c = 'Very polite',
Upsell_Capability_Feedback__c = 'Effective upsell',
Problem_Solving_Feedback__c = 'Solved the issue well', // New field
Patience_Feedback__c = 'Showed patience' // New field
);
insert aiResult;
return aiResult;
}
Update the Test Methods
In both the testGetNatterboxAIResults_VoiceCall and testGetNatterboxAIResults_Task methods, you should validate the values of the new fields. You can add assertions to check that the Problem-Solving and Patience ratings and feedback are correctly retrieved.
Example of Assertions for New Fields in testGetNatterboxAIResults_VoiceCall:
@isTest
static void testGetNatterboxAIResults_VoiceCall() {
// Fetch the test VoiceCall created in the setup
VoiceCall testVoiceCall = [SELECT Id FROM VoiceCall LIMIT 1];
// Call the method to get Natterbox AI Results by VoiceCall ID
Test.startTest();
List<Natterbox_AI_Results__c> results = NatterboxAIResultsController.getNatterboxAIResults(new List<Id> { testVoiceCall.Id });
Test.stopTest();
// Validate the results
System.assertNotEquals(null, results, 'Results should not be null');
System.assertEquals(1, results.size(), 'There should be 1 result');
// Validate new fields
System.assertEquals(5, results[0].Problem_Solving_Rating__c, 'Problem-Solving Rating should be 5');
System.assertEquals(3, results[0].Patience_Rating__c, 'Patience Rating should be 3');
System.assertEquals('Solved the issue well', results[0].Problem_Solving_Feedback__c, 'Problem-Solving Feedback should be "Solved the issue well"');
System.assertEquals('Showed patience', results[0].Patience_Feedback__c, 'Patience Feedback should be "Showed patience"');
}
Example of Assertions for New Fields in testGetNatterboxAIResults_Task:
@isTest
static void testGetNatterboxAIResults_Task() {
// Fetch the test Task created in the setup
Task testTask = [SELECT Id FROM Task LIMIT 1];
// Call the method to get Natterbox AI Results by Task ID
Test.startTest();
List<Natterbox_AI_Results__c> results = NatterboxAIResultsController.getNatterboxAIResults(new List<Id> { testTask.Id });
Test.stopTest();
// Validate the results
System.assertNotEquals(null, results, 'Results should not be null');
System.assertEquals(1, results.size(), 'There should be 1 result');
// Validate new fields
System.assertEquals(5, results[0].Problem_Solving_Rating__c, 'Problem-Solving Rating should be 5');
System.assertEquals(3, results[0].Patience_Rating__c, 'Patience Rating should be 3');
System.assertEquals('Solved the issue well', results[0].Problem_Solving_Feedback__c, 'Problem-Solving Feedback should be "Solved the issue well"');
System.assertEquals('Showed patience', results[0].Patience_Feedback__c, 'Patience Feedback should be "Showed patience"');
}
Summary of Changes:
Test Data Setup:
In the createTestNatterboxAIResults method, new fields (Problem_Solving_Rating__c, Patience_Rating__c, and their respective feedback fields) were added to the test data.
Test Method Assertions:
New assertions were added in both testGetNatterboxAIResults_VoiceCall and testGetNatterboxAIResults_Task to validate the correctness of the new fields.
By adding these changes, the Apex test class will now ensure that the new Problem-Solving and Patience fields are correctly handled by the NatterboxAIResultsController and are covered by unit tests.
ChartJS
Purpose: Provides the functionality to create and display charts.
Usage: Contains the JavaScript library necessary for rendering various types of charts.
In this LWC:
Used to create and display a polar area chart for visualizing AI call ratings.
Key Variables and Methods:
Variables:
Chart: The main ChartJS object used to create charts.
ctx: The context of the canvas element where the chart will be rendered.
data: The data object containing labels and datasets for the chart.
config: The configuration object for the chart, including type, data, and options.
ratings: An array containing the ratings for different aspects of the call.
backgroundColors: An array containing the colors for each segment of the chart.
feedbackArray: An array containing feedback for each rating.
Methods:
initializeChart(): Initializes and renders the polar area chart using ChartJS.
destroy(): Destroys the existing chart instance before creating a new one.
onHover: Handles hover events on the chart segments to display rating details.
onClick: Handles click events on the chart segments to lock the rating details.
Dependencies:
JavaScript File: Implements the logic to initialize and manage the chart.
HTML File: Contains the <canvas> element where the chart is rendered.
Static Resource: The ChartJS library must be uploaded as a static resource in Salesforce.
Summary of File Functionality
The various components of the Natterbox AI Call Coaching LWC work seamlessly together to create a robust and interactive user experience. The JavaScript file handles the logic and data fetching by leveraging Apex methods to retrieve necessary information from Salesforce. This data is then dynamically inserted into the HTML file to construct the user interface. The CSS file ensures that the interface is visually appealing and consistent, while the XML configuration file defines the component's metadata and specifies its usage context. Together, these files create a cohesive and efficient system that enhances the functionality and usability of the Natterbox AI Call Coaching component.
Deployment Guide for Natterbox AI Call Coaching Component
This guide provides detailed steps for deploying the Natterbox AI Call Coaching component (aiScorecard) into a Salesforce environment. The guide is structured to help the internal delivery and product team install or use this component for customers.
Prerequisites
Salesforce Sandbox environment
Visual Studio Code (VS Code) with Salesforce Extensions
Salesforce CLI
Necessary permissions to deploy components in Salesforce
Step 1: Setting Up Visual Studio Code
Install Visual Studio Code
Download Visual Studio Code:
Go to the Visual Studio Code website.
Click on the download button that corresponds to your operating system (Windows, macOS, or Linux).
Install Visual Studio Code:
Follow the installation instructions for your operating system:
Windows: Run the downloaded .exe file and follow the setup wizard.
macOS: Open the downloaded .dmg file and drag Visual Studio Code to the Applications folder.
Linux: Follow the instructions provided on the Visual Studio Code download page for your specific distribution.
Install Salesforce Extensions
Open Visual Studio Code:
Launch Visual Studio Code on your computer.
Open the Extensions View:
Click on the Extensions icon in the Activity Bar on the side of the window, or press Ctrl+Shift+X (Windows/Linux) or Cmd+Shift+X (macOS).
Search for Salesforce Extension Pack:
In the Extensions view, type "Salesforce Extension Pack" into the search bar.
Install Salesforce Extension Pack:
Click the Install button next to the Salesforce Extension Pack in the search results.
Install Salesforce CLI
Download Salesforce CLI:
Go to the Salesforce CLI download page.
Install Salesforce CLI:
Follow the installation instructions for your operating system:
Windows: Run the downloaded .exe file and follow the setup wizard.
macOS: Open the downloaded .pkg file and follow the installation instructions.
Linux: Follow the instructions provided on the Salesforce CLI download page for your specific distribution.
Step 2: Connecting to Salesforce Sandbox
Open Terminal in VS Code:
Go to View > Terminal.
Authenticate to Salesforce:
Run the following command to log in to your Salesforce sandbox:
sfdx force:auth:web:login -r https://test.salesforce.com -a MySandboxAlias
Follow the prompts to log in to your Salesforce sandbox.
Create a Project:
Run the following command to create a new Salesforce project:
sfdx force:project:create -n AiCallRating
Navigate to the project directory:
cd AiCallRating
Step 3: Deploying the LWC Files
Upload Static Resources to Salesforce:
Log in to your Salesforce sandbox.
Go to Setup > Static Resources.
Click New and upload each static resource one by one:
ChartJS
summaryImage
nextStepsImage
nbheader
Ensure each static resource is set to Cache Control: Public
Create LWC Component:
In the VS Code terminal, run:
sfdx force:lightning:component:create -n aiScorecard -d force-app/main/default/lwc
Add HTML, CSS, JavaScript, and XML Files:
Replace the content of the generated files with the provided code:
aiScorecard.html
aiScorecard.js
aiScorecard.js-meta.xml
aiScorecard.css
Push the Component to Salesforce:
Run the following command to push the component to your Salesforce sandbox:
sfdx force:source:deploy -p force-app/main/default/lwc -u MySandboxAlias
Pushing Apex classes in visual studio code
sfdx force:source:deploy -p force-app/main/default/classes -u MySandboxAlias
LWC Folder Structure w/apex classes included
AiCallRating/
│
├─── force-app/
│ ├─── main/
│ │ ├─── default/
│ │ │ ├─── classes/
│ │ │ │ ├─── NatterboxAIResultsController.cls
│ │ │ │ ├─── NatterboxAIResultsControllerTest.cls
│ │ │ │
│ │ │ ├─── lwc/
│ │ │ │ ├─── aiScorecard/
│ │ │ │ │ ├─── aiScorecard.html
│ │ │ │ │ ├─── aiScorecard.js
│ │ │ │ │ ├─── aiScorecard.css
│ │ │ │ │ ├─── aiScorecard.js-meta.xml
│ │ │ │
│ │ │ ├─── ... (other folders and files)
Step 4: Deploying Apex Code in Salesforce
Open Developer Console:
Log in to your Salesforce sandbox.
Click on the gear icon and select Developer Console.
Create Apex Class:
Go to File > New > Apex Class
Name the class NatterboxAIResultsController.
Copy and paste the provided Apex code into the class and save.
Create Apex Test Class:
Go to File > New > Apex Class.
Name the class NatterboxAIResultsControllerTest.
Copy and paste the provided test class code into the class and save.
Run Apex Tests:
In the Developer Console, go to Test > New Run.
Select NatterboxAIResultsControllerTest and run the tests to ensure they pass.
Step 5: Deploying to Production
Create a Change Set:
Log in to your Salesforce sandbox.
Go to Setup > Change Sets.
Create a new outbound change set.
Add the following components to the change set:
LWC component: aiScorecard
Apex class: NatterboxAIResultsController
Apex test class: NatterboxAIResultsControllerTest
Static Resources:
ChartJS
summaryImage
nextStepsImage
nbheader
Upload Change Set to Production:
Upload the change set to your production environment.
Validate Change Set in Production:
Log in to your production environment.
Go to Setup > Change Sets.
Find the uploaded change set and click on Validate.
During validation, ensure that only the Apex test class NatterboxAIResultsControllerTest is run.
Deploy Change Set in Production:
After successful validation, go back to the change set and click on Deploy.
Ensure that only the Apex test class NatterboxAIResultsControllerTest is run during the deployment.
Additional Details & Resources
Visual Studio Code Documentation: Getting Started with Visual Studio Code
Salesforce CLI Documentation: Salesforce CLI Setup Guide
Salesforce Extensions for VS Code: Salesforce Extensions Documentation
Dependencies
Ensure that the necessary Salesforce objects (VoiceCall, Task, Natterbox_AI_Results__c, nbavs__CallReporting__c) are available in the target environment.
Field names from Natterbox_AI_Results__c. Field names may vary from customer to customer and the queries in the Apex and .js will likely need to be adjusted. In a future version I plan to find a more dynamic way to populate this.
Flows for populating AI output to custom object Natterbox_AI_Results__c
Troubleshooting Apex Test
Ensure that the necessary Salesforce objects (VoiceCall, Task, Natterbox_AI_Results__c, nbavs__CallReporting__c) are available in the target environment.