Challenge
Creating a Star Rating Widget
Creating a Star Rating Widget
12 Tasks
30 mins
Scenario
The business stakeholders want to provide an instant view of the previous customer ratings for incidents (and other Case Types). They want to use an external ratings database that is accessible through Data Pages provided by Pega Infinity™. The stakeholders want a utility widget displayed in the pane next to the Case in their back office Portal. The agents must also be able to create new ratings for existing cases (if a rating does not already exist) or edit an existing rating.
The Constellation design system must be used, and the new widget must provide functionality similar to that of the out-of-the-box widgets.
Constellation design system guidelines:
- Use of SummaryList.
- Use of View All if the list is longer than three items.
- Ability to search items (optional).
- Ability to perform actions from View All.
- Use of actions to display Popover for editing or creating items.
- Use of the icons provided by the Constellation design system.
- Use of pane count and icon display when collapsed.
Specifically for Ratings:
- Use of the Constellation design system component to display ratings.
- Use of an interactive version of the Constellation design system Ratings component created by an in-house team.
The following table provides the credentials you need to complete the challenge:
| Role | User name | Password |
|---|---|---|
| Application Developer | author@sl | pega123! |
Detailed Tasks
1 Clone the challenge Git repository and update the dependencies
- Using the tool of your choice, create a new folder in your file system called WidgetChallenges.
Caution: This folder should not be a child folder of any sldxcomponents projects previously created.
- In the new WidgetChallenges folder, open Visual Studio Code.
- Open a terminal window, and then run the following commands:
In Windows, macOS, or Linux, each block is a separate command that you run in the terminal in order.- Clone the challenge repository.
This clone provides a foundation for all the following tasks.git clone https://github.com/ricmars/constellation-extensions-challenge.git sldxcomponentsNote: If you must use SSH with Git, you must adjust the Git clone statement accordingly.cd sldxcomponents - Update your package dependencies from npm by running the following command:
npm update
- Clone the challenge repository.
- Start Storybook, and then review the existing stories.
- In your sldxcomponents folder, enter the following command: npm run startStorybook.
Storybook launches and displays a component containing a single story, as shown in the following figure:
Leave Storybook running. You are now set up to continue the challenge.
Note: This is a slightly refactored version of the DX Component Builder case widget based on the '24.2 DX Component Builder template. - Initialize and start your Pega Infinity instance.
- After starting your Pega Infinity instance, copy the URL, including https, up to and including prweb.
It will be similar to the following format: https://abcdefgh.pegacademy.net/prweb. - In Visual Studio Code, open a new terminal in the sldxcomponents folder of your cloned repository, and then run the following command: npm run authenticate.
DX Component Builder prompts you for the Pega Infinity server URL the first time you run the authenticate command. - Copy and paste the Pega Infinity URL you copied in the previous step, and then press the Enter key.
DX Component Builder launches an instance of your default browser. - In the browser window that launches, enter the following credentials:
- In the user name line, enter author@sl.
- In the password line, enter pega123!.
- After successful authentication, close the browser tab.
A success message is displayed in your terminal window.Note: Chrome might warn the password is compromised. You can safely ignore these messages because this is a general password and not specific to you. - Close the browser tab or window that opened for authentication
- In the same terminal window, enter the following command: npm run publish.
- When prompted to select the type of component you wish to publish, select the Sl_DXExtensions_StarRatingWidget option.
- Accept all of the defaults by pressing the Enter key on your keyboard.
Your component is now published.
2 Replace history data with mock ratings data
-
Open the sldxcomponents folder that you cloned in the first task.
Tip: It is inside the WidgetChallenges folder. -
Open a terminal, and then enter the following commands in order:
git stashSwitch the Git branch to the one relevant to the next task:
git switch 01_refactor_ootb_case_widget - Open the src/components/Sl_DXExtensions_StarRatingWidget folder.
The folder contains mock data for history and ratings and a historyData.tsx file and a ratingData.tsx file. - Open the mock.historyData.ts and historyData.tsx files.
- If Storybook is not already running, open a new terminal, and then enter npm run startStorybook.
A browser tab opens Storybook, or if Storybook is already running, navigate to the Storybook tab in your browser. - If it is not already displayed in Storybook, click the Star Rating Widget story.
Note: The Storybook story displays the mock history data that is sourced from the mock.historyData.ts file. The historyData.tsx file defines the Table component's column schema, the history data type, and the data mapping function used to map from the Pore data API response to the display columns. The mock data, Table component schema, rating data type, and mapping function have been created for you in the mock.ratingData.ts and ratingData.tsx files.
- Open the index.tsx file, and then uncomment out the following two blocks of code:
// import type {
// RatingDataItem as DataItem,
// RatingTableRow as TableRow
// } from './ratingData';
// import {
// createRatingTableSchema as createTableSchema,
// mapRatingDataItem as mapDataItem
// } from './ratingData'; - Comment out the following two blocks of code:
import {
createHistoryTableSchema as createTableSchema,
mapHistoryDataItem as mapDataItem
} from './historyData';
import type {
HistoryTableRow as TableRow,
HistoryDataItem as DataItem
} from './historyData'; - Save the index.tsx file.
- Review the story in the Storybook browser tab, and then in the navigation pane, click the story to refresh it.
Observe that the data for the Table columns is not correct.
- To display the correct mock rating data, change the value in the listDataPage control value in the Storybook story from D_pyWorkHistory to D_CustomerRatingList.
The rating data is now correctly displayed.Note: In this Storybook story, the values being set in the Controls add-on are passed to the Story and drive whether the story returns the mock history or rating data in the PCore.getDataApiUtils().getData(...) function that is mocked in the demo.stories.tsx file. - In the label control value, enter Rating history to change the table heading text.
The following figure displays the Mock rating data is displayed and formatted in the Table. The Rating component from the Constellation design system is used to display each rating as a set of stars.
This component is the final component that you use to display any non-interactive ratings.
3 Replace Table with SummaryList
-
Open the sldxcomponents folder that you cloned in the first task.
Tip: It is inside the WidgetChallenges folder. -
Open a terminal, and then enter the following commands:
git stashgit switch 02_tabletosummarylist - Click SummaryList properties to open the component on design.pega.com, and then inspect the component API.
Note that the component takes an array of SummaryListItems, and this is the only required property.Note: In the Constellation Design System, you use SummaryList to display data. To effectively use this component to display the same data as the Table component, the data must transform the shape expected by the SummaryList. - Open the src/components/Sl_DXExtensions_StarRatingWidget folder, and then open the index.tsx file.
- Uncomment the following block of code:
// import type { SummaryListItem } from '@pega/cosmos-react-core';
// import {
// SummaryList,
// withConfiguration
// } from '@pega/cosmos-react-core'; - Comment out the following block of code:
import { Table, withConfiguration } from "@pega/cosmos-react-core";
- Comment out the following block of code:
import type { TableProps } from '@pega/cosmos-react-core/lib/components/Table/Table';
- Uncomment the following code:
// import type { RatingDataItem as DataItem } from './ratingData';
// import { mapRatingDataItem as mapDataItem } from './ratingData'; - Comment out the following two blocks of code:
import {
createRatingTableSchema as createTableSchema,
mapRatingDataItem as mapDataItem
} from './ratingData';
import type {
RatingDataItem as DataItem,
RatingTableRow as TableRow
} from './ratingData'; - Comment out the following line of code:
const [data, setData] = useState<TableProps<TableRow>['data']>();
- Uncomment the following line of code:
// const [data, setData] = useState<SummaryListItem[]>();
- Comment out the following line of code:
const columns = createTableSchema(getPConnect);
- Comment out the following block of code:
return (
<Table
title={pConn.getLocalizedValue(label, "", "")}
columns={columns}
data={data}
loading={isLoading}
loadingMessage={pConn.getLocalizedValue("Loading data ...")}
/>
); - Uncomment the following line of code:
return <SummaryList name={label} items={data ?? []} loading={isLoading} />;
- Save the index.tsx file.
- If Storybook is not already running, open a new terminal, and then enter npm run startStorybook.
Note: The index.tsx file contains a Typescript error, but the story can still compile and display the title and a number of blank lines. The Storybook story will be broken while you edit it; this behavior is expected until you get the story working again by introducing the SummaryList component.
- Open the ratingData.tsx file.
- Comment out the following block of code:
import type {
TableProps,
DefaultRowData
} from '@pega/cosmos-react-core/lib/components/Table/Table';Note: The imports for TableProps and DefaultRowData are no longer needed because the component now uses SummaryListItem instead of TableProps.
- Uncomment the following block of code to add the SummaryListItem type import:
// import type { SummaryListItem } from '@pega/cosmos-react-core';
- Comment out the following block of code:
export interface RatingTableRow extends DefaultRowData {
caseId: string;
rating: number | JSX.Element;
updated: string;
customerId: string;
} - Uncomment the following block of code:
// export const mapRatingDataItem = (
// entry: RatingDataItem,
// index: number
// ): SummaryListItem => ({
// primary: (
// <Rating
// value={entry.CustomerRating}
// metaInfo={`${entry.CustomerRating} of ${entry.NumberOfStars}`}
// />
// ),
// id: `ratingDataItem-${index}`
// }); - Comment out the following two blocks of code (until the end of the file):
export const mapRatingDataItem = (
entry: RatingDataItem,
index: number
): RatingTableRow => ({
updated: entry.pxUpdateDateTime
? new Date(entry.pxUpdateDateTime).toLocaleString()
: 'No data',
rating: (
<Rating
value={entry.CustomerRating}
metaInfo={`${entry.CustomerRating} of ${entry.NumberOfStars}`}
/>
),
caseId: entry.CaseID,
customerId: entry.CustomerID,
id: index
});
export const createRatingTableSchema = (
getPConnect: () => typeof PConnect
): TableProps<RatingTableRow>['columns'] => {
return [
{
renderer: 'updated',
label: getPConnect().getLocalizedValue('Updated', '', '')
},
{
renderer: 'rating',
label: getPConnect().getLocalizedValue('Customer Rating', '', ''),
noWrap: true
},
{
renderer: 'caseId',
label: getPConnect().getLocalizedValue('Case ID', '', '')
},
{
renderer: 'customerId',
label: getPConnect().getLocalizedValue('Customer ID', '', ''),
noWrap: true
}
];
}; - Save the ratingData.tsx file.
- Navigate to the Storybook story in your browser.
The story should resemble the following figure:
A list of ratings is displayed in the SummaryList in Storybook.
4 Update the SummaryList display with MetaList
- Open the sldxcomponents folder that you cloned in the first task.
- Open a terminal, and then enter the following commands:
git switch 03_updatesummarylistgit stash
- Open the src/components/Sl_DXExtensions_StarRatingWidget folder, and then open the index.tsx file.
- Search for the following blocks of code, and then uncomment them:
// registerIcon,
// import * as star from '@pega/cosmos-react-core/lib/components/Icon/icons/star.icon';
// registerIcon(star);
// icon='star' - Save the index.tsx file.
- In a terminal window, enter npm run startStorybook.
A star icon is now displayed in the SummaryList heading, as shown in the following figure:
- Open the ratingData.tsx file, and then uncomment the first multi-line import statement:
// import {
// Rating,
// createUID,
// MetaList,
// Text,
// DateTimeDisplay
// } from '@pega/cosmos-react-core'; - Comment out the following import statement:
import { Rating } from '@pega/cosmos-react-core';
- Uncomment the mapRatingDataItem function:
// export const mapRatingDataItem = (entry: RatingDataItem): SummaryListItem => ({
// id: entry.pyGUID ?? createUID(),
// primary: (
// <Rating
// key={`${entry.pyGUID ?? createUID()}-rating`}
// value={entry.CustomerRating}
// metaInfo={`${entry.CustomerRating} of ${entry.NumberOfStars}`}
// />
// ),
// secondary: (
// <MetaList
// key={`${entry.pyGUID ?? createUID()}-metalist`}
// items={[
// <DateTimeDisplay
// value={entry.pxUpdateDateTime}
// variant='datetime'
// format='short'
// />,
// <Text>{entry.CaseClassName}</Text>,
// <Text>{entry.CaseID}</Text>,
// <Text>{entry.CustomerID}</Text>
// ]}
// />
// )
// }); - Comment out the function with the same name immediately after it:
export const mapRatingDataItem = (
entry: RatingDataItem,
index: number
): SummaryListItem => ({
primary: (
<Rating
value={entry.CustomerRating}
metaInfo={`${entry.CustomerRating} of ${entry.NumberOfStars}`}
/>
),
id: `ratingDataItem-${index}`
}); - Save your file.
- Open the Storybook story.
The story should resemble the following figure:Additional rating data is now displayed in a MetaList. The MetaList contains an array of items (components) that are displayed on the same line delimited by a period. This MetaList is used frequently in combination with the SummaryList.
5 Add and edit actions to SummaryList
- Open the sldxcomponents folder that you cloned in the first task.
- Open a terminal, and then enter the following commands:
git stash
git switch 04_fulldatasummarylistThe original code has been cleaned up and refactored. The code includes some new files to accelerate the learning process.
- If Storybook is not already running, open a new terminal in your project root folder (sldcomponents), and then enter npm run startStorybook.
Note: You will now add the Action array used by SummaryListItem; "... actions: A set of Actions to render alongside the item. If more than one action is passed, a consolidated ActionMenu will be generated.".
- Open the index.tsx file.
- Delete the first line:
/* eslint-disable @typescript-eslint/no-unused-vars */
- Uncomment the following block of code:
// useElement,
// const [dataItem, setDataItem] = useState<DataItem>();
// const [actionTarget, setActionTarget] = useElement<HTMLElement>(null
// setActionTarget(menuButton ?? e.currentTarget);
// setDataItem(actionDataItem);
// setActionTarget(menuButton ?? e.currentTarget);
// setDataItem(null); - Comment out the following block of code:
actionId && (
<Text
variant="h1"
onClick={() => setActionId(undefined)}
>{`Click me to dismiss: ${actionId}`}</Text>
) - Uncomment out the following block of code:
// {
// actionTarget && (
// <Text
// variant='h1'
// onClick={() => setActionTarget(null)}
// >{`Click me to dismiss: ${actionId}${dataItem ? ':'+dataItem.CaseID : '' }:`}</Text>
// ) - Save the index.tsx file.
- Review the Storybook story.
The Edit action with the pencil icon is now displayed, as shown in the following figure: This action is displayed only against the current case (this is provided from the mock API response in the demo.stories.tsx file).
- Click the pencil icon, and then observe that a Text component displays the action ID under the SummaryList.
Note: You can click the text to dismiss the message. If you want to try the Add action, you can change the customerId to Q1234 in the Storybook controls section of the story. You will implement the action on the Click handler to launch a component to perform the action
6 Add Popover and update the behavior for actions to SummaryList
- Navigate to the sldxcomponents folder cloned in the first task.
- Open a terminal, and then enter the following commands:
git stashgit switch 05_summarylistnpm updateNote: An implementation of the Popover component has already been provided. Popvers are "... used to provide contextual snippets of rich information, menu options, and lightweight editing." It is displayed when the user selects an Action associated with a SummaryListItem or an Action on the SummaryList itself. Some boilerplate code has been added to demonstrate how to dismiss a Popover in response to certain events.
- If Storybook is not already running, enter the following command in a terminal: npm run startStorybook.
You will see the updated Story that now has the Popover component added. - View the Popover component by clicking the Plus icon that represents the Add action.
The Popover now displays a horizontal Slider control. The ratings have a minimum value of 0 and a maximum value of 5, and the slider is configured accordingly.Note: The Slider component does not yet respond properly to user input. - Open the src/components/Sl_DXExtensions_StarRatingWidget folder.
- Open the ratingData.tsx file.
Note: The simple RatingDataItem Data Model represents the data model that the component expects to map data from. This shadows the external Data Model defined in Pega Infinity, which is why it does not follow the typical lowerCamelCase property naming convention. This external Data Model is then mapped at run time to the internal Data Model of the component. This internal Data Model is the data structure that the component uses to represent a rating data item and adheres to the lowerCamelCase property naming convention.
As you progressively enhance your component, you will represent both the external and internal data models in different data structures and then map between them when performing Create, Read, Update, or Delete (CRUD) operations on the Pega data type.
The following code is the shape of the external data to expect for a RatingDataItem as described in the ratingData.ts fileexport type RatingDataItem = {
CaseClassName: string;
CaseID: string;
CustomerID: string;
CustomerRating: number;
NumberOfStars: number;
pyGUID?: string;
pxUpdateDateTime?: string;
};Note: Each instance of RatingDataItem is linked to a work class name and Case ID. It is also aligned with a specific customer ID. TheCustomerRatingproperty stores the rating value andNumberOfStarsensures that you know how many stars the rating was originally set with. You use the autogeneratedpyGUIDas a unique key. You usepxUpdateDateTimeto map this property from the underlying data source because you use this in our rating display later. In practice, Data Models can be significantly more complex, and this simple Data Model would have much better typescript type definitions. - Open the index.tsx file.
- In the component return function in the attributes of the Slider component, comment out the following line of code:
() => {}
- Uncomment the following line of code:
// (changeValue: number) => setValue(changeValue)
- Save the index.tsx file.
The Slider control now functions correctly, as shown in the following figure:Note: The Slider works, but nothing happens when clicking thebutton. You must add some code to the Button component that is configured as the primary 'submit' button for the Popover. To enable this function in-memory (you will add updates and create support for data type instances in an upcoming task) updates to the data array that will then be used to rebuild the items for display, you must perform the following steps in the index.tsx file. - In the index.tsx file, at the top of the file, delete the first line of code:
/* eslint-disable @typescript-eslint/no-unused-vars */
- Uncomment the following two blocks of code:
// setValue(actionDataItem?.CustomerRating ?? 0);
// // TODO: Only in memory and not persisted for now so that Storybook story
// // works
// const upsertDataItem = (selectedDataItem: DataItem, changedValue: number) => {
// if (selectedDataItem.pyGUID) {
// setData(
// data.map(dataItemToCheck =>
// dataItemToCheck.pyGUID === selectedDataItem.pyGUID
// ? {
// ...dataItemToCheck,
// CustomerRating: changedValue,
// pxUpdateDateTime: new Date().toISOString()
// }
// : dataItemToCheck
// )
// );
// return;
// }
//
// const newDataItem = {
// ...selectedDataItem,
// CustomerRating: changedValue,
// pyGUID: 'NEW',
// pxUpdateDateTime: new Date().toISOString()
// };
//
// setData([...data, newDataItem]);
// };// if (dataItem) upsertDataItem(dataItem, value); - Save the index.tsx file.
- Open Storybook, click the icon, select a rating value, and then click Submit.
You will see that a new item has been appended to the SummaryList because the mock.ratingData.ts file returns a Case key (SL-TELLUSMORE-WORK Z-12345) when the following const caseKey = getPConnect().getCaseInfo().getKey(); function is called. A new data item has been added to the data array that is used to rebuild the items array that contains the SummaryListItems that are displayed in the SummaryList.
The createItems utility function renders the SummaryListItem array derived from the updated data array of RatingDataItem:
const items = createItems(data, getPConnect, mapDataItem, onActionItemClick);
This createItems function has been created for this challenge. It is designed to be as reusable as possible. Feel free to view the implementation. If you opt to use any utility functions provided here, you can, but you must thoroughly test them and also maintain them in your own code base.
In this Git branch, you also added dayjs package to demonstrate displaying ISO 8601 date/times using time zone retrieved from
PCore.getEnvironmentInfo().getTimeZone(). Review the implementation details in the ratingItems.tsx file.
7 Replace the Slider with the StarRating Component
In this task, you add a new component. You will replace the slider with an interactive version of the Rating component that has been built as a DX component specifically for this challenge. The project branch includes this interactive component, Sl_DXExtensions_StarRating, for you.
- Open the sldxcomponents folder that you cloned in the first task.
- Open a terminal, and then enter the following commands:
git stashgit switch 06_slider_to_starratingnpm updateCaution: Ensure that you run the
npm updatecommand before proceeding because a new dependency exists. - Open the src/components/Sl_DXExtensions_StarRatingWidget folder.
- Open the index.tsx file.
- Comment out the following line:
Slider,
- Uncomment the following line:
// import StarRating from '../Sl_DXExtensions_StarRating';,
- In the line where the Slider component is used, replace Slider with StarRating so that <Slider min={0} max={5} value={value} onChange={setValue} /> becomes <StarRating min={0} max={5} value={value} onChange={setValue} />.
- Save the index.tsx file.
- In a terminal, start Storybook if it is not already running by entering npm run startStorybook.
- Open the src/components folder.
The folder includes a newly added component for you: Sl_DXExtensions_StarRating. It is an interactive version of the Rating component created for this challenge, and an updated version is now available in the Constellation UI Gallery - Star rating input. - Review the new stories, and then see how the new component is an interactive version of the Rating component provided with the Constellation design system.
- Click the Star Rating Widget story, and then click Add to create a new rating.
Observe that the Star Rating component is displayed instead of the Slider, as shown in the following figure:
8 Add support for View All
In this task, you will add View all support to the SummaryList.
"... a will render at the bottom of the list. This is handy for delaying API requests to retrieve entire lists of data until an explicit request is made. It is recommended to use the view all components in these situations. This component accepts the same list as the SummaryList, and similarly can take actions and loading as well as props related to the SearchInput component."
- Open the sldxcomponents folder that you cloned in the first task.
- Open a terminal, and then enter the following commands:
git stashgit switch 07_view_all_implementation
-
Open the src/components/Sl_DXExtensions_StarRatingWidget folder.
Note: The Popover code is now in its own component StarRatingPopover.tsx file instead of the index.tsx file. This file better encapsulates the Popover logic and enables you to reuse the component when you launch the View all dialog box. - Open the index.tsx file.
- Comment out the items line located in the following block of code:
items = {
items,
// items.slice(0, 3)
}; - Uncomment the following line of code:
// items.slice(0, 3)
Your final block of code should resemble the following code:
items={
// items
items.slice(0, 3)
} - Save the index.tsx file.
- If Storybook is not already running, in a terminal, enter npm run startStorybook.
- In your browser, click the Storybook tab in your browser if it does not open automatically.
New stories are available in the Storybook, as shown in the following figure:Tip: You might see an error in Storybook. The reason for this error is that you switched branches while Storybook is running, and in the new branch, the stories are called something different as new stories have been added. In the navigation pane, select a story, and then the story is displayed without an error.Note: The View all link is now displayed under the SummaryList items. Only the first three items are displayed. The View all link displays but is not yet functional. The View all link will launch a dialog box that shows a larger number of items and optionally a search box and any top -evel actions. - Open the index.tsx file.
- Uncomment the following block of code:
// useRef,// ModalMethods,// useModalManager,// import { searchByRating, searchByCustomer } from './searchFunctions';// import SummaryListViewAllModal, {
// type SummaryListViewAllProps
// } from './SummaryListViewAllModal';// const modalRef = useRef<ModalMethods<SummaryListViewAllProps>>();// const { create: createModal } = useModalManager();// useEffect(() => {
// modalRef.current?.update({
// items: createItems(data, getPConnect, mapDataItem, onActionItemClick)
// });
// });// const openViewAll = () => {
// // We use a ref here so that we can refresh the modal with any data updates.
// modalRef.current = createModal<SummaryListViewAllProps>(
// SummaryListViewAllModal,
// {
// name: label,
// loading: isLoading,
// items,
// actions,
// searchFunction: customerId ? searchByRating : searchByCustomer,
// currentRating: dataItem,
// onUpdateRating
// },
// {
// onDismiss: () => {
// modalRef.current = undefined; // tidy up if modal is dismissed.
// }
// }
// );
// }; - Comment out the following:
() => {}
- Uncomment the following line:
// openViewAll
- Save the index.tsx file.
- Return to Storybook, and then review the View all implementation.
There is a simple number search (this can be any search filter) that will display a rating less than or equal to the value in the search box. Try it out. You also see that the actions still work, and the Popver is displayed relative to the Modal, as shown in the following figure:
9 Build the UI authoring experience (component definition)
- Open the sldxcomponents folder that you cloned in the first task.
- Open a terminal, and then enter the following commands:
git stashgit switch 09_configjson
- Start your Pega Infinity challenge instance.
- In Visual Studio Code, open the src/component/Sl_DXExtensions_StarRatingWidget folder.
- Open the config.json file.
Note: The component definition (also known as the config.json) drives the UI authoring experience for the component in App Studio.
The component designer's requirements for the Star Rating Widget specify that App Studio authors must be able to select the property from the Case Type that uniquely identifies the customer in the customer class. Also, App Studio authors must be able to select the rating data class that was configured in App Studio and the list, lookup, and savable Data Pages associated with it.
The current config.json file version is very limited and has a single property configured for "label".{
"name": "Sl_DXExtensions_StarRatingsWidget",
"label": "Ratings",
"description": "Ratings",
"organization": "Sl",
"version": "0.0.9",
"library": "DXExtensions",
"allowedApplications": [],
"componentKey": "Sl_DXExtensions_StarRatingsWidget",
"type": "Widget",
"subtype": "CASE",
"properties": [
{
"name": "label",
"label": "Label value",
"format": "TEXT"
}
],
"defaultConfig": {
"label": "@L Ratings"
}
}Note: If the component were published now, then "label" would be the only property that App Studio authors can configure when using the component. As a result, all the logic related to Star Ratings and the Pega Infinity Data Model has to be hard-coded into the component, which makes it very specific to this single implementation.
To improve component reuse across the SL Enterprise, you will use the Component definition reference guide to implement a more configurable authoring experience for our app authors.
To support your authoring experience, a number of challenge-specific Rules have been created. One of these is a new data page (D_GetDataPagesForClassList) that wraps an existing default Data Page. It returns the Data Pages configured for a specific data class that is passed in as a parameter. Currently, sourcing a CONTENTPICKER dynamically from a page-based data page is not supported, so this Data Page creates a list-based data page based on the default page-based Data Page.The first addition to your config.json file is the ability to select a PROPERTY, which provides the value stored in that property to our component at runtime. You use the customerId value to only show ratings related to the current customer to which the current Case is related.
- Add the following property in the "properties" array immediately after the "label" property:
{
"name": "customerId",
"label": "Customer unique id property on case",
"format": "PROPERTY"
}Your properties array should resemble the following block of code:
"properties": [
{
"name": "label",
"label": "Label value",
"format": "TEXT"
},
{
"name": "customerId",
"label": "Customer unique id property on case",
"format": "PROPERTY"
}
],The Constellation orchestration layer resolves all properties configured in the "properties" array when it creates the component instance. It passes the values selected during UI authoring configuration into your component at run time as props.
From this point forward, the configuration is much more advanced. Create a GROUP format that organizes any properties nested in this property together in the UI Authoring experience.
- Under the "customerId" property that you added in step 6, add the following group:
{
"label": "Data pages for rating class",
"format": "GROUP",
"collapsible": true,
"defaultCollapsed": false,
"properties": []
}Tip: Do not forget to add a comma.The properties array must resemble the following block of code:
"properties": [
{
"name": "label",
"label": "Widget heading",
"defaultValue": "Ratings",
"format": "TEXT"
},
{
"name": "customerId",
"label": "Customer unique id property on case",
"format": "PROPERTY"
},
{
"label": "Data pages for rating class",
"format": "GROUP",
"collapsible": true,
"defaultCollapsed": false,
"properties": []
},
] - Inside the properties array in the newly added GROUP object, add a new SELECT property:
{
"format": "SELECT",
"label": "Select the rating class",
"name": "ratingDataClass",
"placeholder": " ",
"defaultValue": " ",
"source": {
"name": "D_pzDataTypesForApplicationNoWork",
"displayProp": "pyLabel",
"valueProp": "pyMetadataKey",
"parameters": {
"AppName": "@ENV APPLICATION_NAME"
},
"filter": "$this.pyMetadataKey NOT_STARTS_WITH 'Data-Admin'"
}
}Note: This GROUP enables the selection of the data class dynamically sourced by using the default D_pzDataTypesForApplicationNoWork Data Page as the source for a format. The parameter uses a run-time annotation called @ENV APPLICATION_NAME that resolves to the current application name when the authoring experience loads for this widget. You also apply a filter to remove irrelevant data classes. - In the same GROUP and properties array, add the following three dynamic CONTENTPICKERS:
{
"format": "CONTENTPICKER",
"label": "Lookup datapage for rating class",
"name": "ratingLookupDatapage",
"visibility": "$this.ratingDataClass !=' '",
"source": {
"name": "D_GetDataPagesForClassList",
"displayProp": "pyLookUpDataPage",
"valueProp": "pyLookUpDataPage",
"parameters": {
"ContextClass": "$this.ratingDataClass",
"ExcludeLookUp": false,
"ExcludeList": true,
"ExcludeSavable": true
}
}
},
{
"format": "CONTENTPICKER",
"label": "List datapage for rating class",
"name": "ratingListDatapage",
"visibility": "$this.ratingDataClass !=' '",
"source": {
"name": "D_GetDataPagesForClassList",
"displayProp": "pyListDataPage",
"valueProp": "pyListDataPage",
"parameters": {
"ContextClass": "$this.ratingDataClass",
"ExcludeLookUp": true,
"ExcludeList": false,
"ExcludeSavable": true
}
}
},
{
"format": "CONTENTPICKER",
"label": "Savable datapage for class",
"name": "ratingSavableDatapage",
"visibility": "$this.ratingDataClass !=' '",
"source": {
"name": "D_GetDataPagesForClassList",
"displayProp": "pySavableDataPage",
"valueProp": "pySavableDataPage",
"parameters": {
"ContextClass": "$this.ratingDataClass",
"ExcludeLookUp": true,
"ExcludeList": true,
"ExcludeSavable": false
}
}
} - After the GROUP, in thetwo following properties:
"name": "iconName",
"label": "Icon Name",
"format": "TEXT",
"required": true,
"visibility": "true == false"
},
{
"label": "Conditions",
"format": "GROUP",
"properties": [
{
"name": "visibility",
"label": "Visibility",
"format": "VISIBILITY"
}
]
}
],
- Add the defaultConfig property in the end block of the config.json file:
"label": "@L $this.label",
"ratingDataClass": " ",
"iconName": "star"
}
- In your challenge instance, copy the URL, up to and including prweb.
- In a terminal window, enter npm run authenticate.
- In your challenge instance, enter the following credentials: You will now publish and configure your component.
- In the User name field, enter author@sl.
- In the Password field, enter pega123!.
- In a terminal, enter npn run publish.
- Select the Sl_DXExtensions_StarRatingWidget.
- Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
- In the "Generate development build ?" prompt, select "y".
- In the navigation pane of App Studio, click .
- Click the UX tab.
- On the Full Page View tab, in the Utilities section, click , and then in the filter, enter Rating.
- Select Star Rating Widget, and then click .
- Click the Gear icon to configure the Ratings Widget.
- Perform the following configuration:
- In the Customer unique id property of the Case, navigate to the Customer page in the drop-down and select Globally unique id.
- In the Data pages for rating class group, in the Select the rating class list, select Customer Rating.
- For the lookup, list and savable data page CONTENTPICKERS select the only item available for each.
- Click .
Your component configuration should resemble the following figure:
- In the upper-right corner, click .
- In the header of App Studio, click .
. - Create an Incident Case by following the prompts in the Create dialog box.
The values added are irrelevant as long as you complete the Create Process and submit the details. After completing the Create flow, the Case View is displayed. - Right-click the Case View, and then click Inspect to open Developer Tools.
This instruction is for Chrome; if you are using a different browser, follow the instructions to open Developer Tools for that browser. - Ensure that the Console tab is visible so that you can see errors.
- On the Console tab, in the Case View Utilities pane, click the Star icon.
In App Studio, an error is displayed in both the widget and the Case View banner, as shown in the following figure: On the Console tab, the oi {message: 'Request failed with status code 404', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …} error message is displayed with a clickable reference on the right side of the source file and line number (for example, index.tsx:166). - Click the reference to load the index.tsx source code.
- On the Sources tab of the Developer Tools, on the index.tsx tab, add a breakpoint by selecting line 63.
- In the Case View, click the Star icon to activate your breakpoint.
If you hover over the props variable under the component function declaration, you can see the properties that you configured in the config.json file populated with the values that you configured in App Studio (or with the default values provided in the config.json file), as shown in the following figure:
The system extracts the relevant props data to show how the config.json file data drives UI authoring that captures the data that passes to the component at run time, as shown in the following data:Object:
"label": "Ratings",
"ratingDataClass": "SL-TellUsMore-Data-CustomerRating",
"iconName": "star",
"customerId": "f926ce4f-8656-41a9-a5ed-fe5d3e6d20b4",
"ratingLookupDatapage": ["D_CustomerRating"],
"ratingListDatapage": ["D_CustomerRatingList"],
"ratingSavableDatapage": ["D_CustomerRatingSavable"],
...Note: As you can see, a subset of this data aligns directly with the config.json file. These props are not yet mapped to variables in our component, so an error occurs when you try to load the component. This error is because the list data page is not yet mapped in your component, which you accomplish in the next task. - Close the tab that has App Studio and any open Developer Tools instance related to this challenge.
10 Link the component definition to the code
In this task, you will map the data provided in the props object to the appropriate variables in our code. The important props for you to map are:
- customerId: This data provides a string that uniquely identifies the currently selected customer in the case.
- ratingLookupDatapage: a string array (of size one) that contains the name of the selected lookup data page for the data class
- ratingListDatapage: a string array (of size one) that contains the name of the selected list data page for the data class
- ratingSavableDatapage: a string array (of size one) that contains the name of the selected savable data page for the data class
You will use the value in customerId as a filter for our list data page so that we only return ratings for the customer selected in the current case. We will map the data page arrays to scalar properties.
- Open the sldxcomponents folder that you cloned in the first task.
- Open a terminal, and then enter the following commands:
git stashgit switch 10_configjson_toprops
- Start your Pega Infinity challenge instance.
- Open the src/component/Sl_DXExtensions_StarRatingWidget folder.
- Open the index.tsx file.
- Uncomment the following block of code:
export interface SlDxExtensionsStarRatingWidgetProps extends PConnFieldProps {
customerId?: string;
// ratingDataClass: string;
// ratingLookupDatapage: string[];
// ratingListDatapage: string[];
// ratingSavableDatapage: string[];
} - Uncomment the following block of code:
const SlDxExtensionsStarRatingWidget = ({
// ratingDataClass,
// ratingLookupDatapage,
// ratingListDatapage,
// ratingSavableDatapage,
getPConnect,
label,
customerId
}: SlDxExtensionsStarRatingWidgetProps) => { - Comment out the following two lines of code:
const list = "D_List";
const savable = "D_Savable"; - Uncomment the following block of code:
// const lookup = ratingLookupDatapage[0];
// const list = ratingListDatapage[0];
// const savable = ratingSavableDatapage[0];
// // eslint-disable-next-line no-console
// console.log(ratingDataClass, lookup, list, savable); - Save the index.tsx file.
- In a terminal window, enter npm run authenticate.
- In your challenge instance, enter the following credentials:
- In the User name field, enter author@sl.
- In the Password field, enter pega123!.
- In a terminal, enter npn run publish.
- Select the Sl_DXExtensions_StarRatingWidget.
- Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
- In the "Generate development build ?" prompt, select "y".
- In the header of App Studio, click to open the Case that you created in the previous task.
Tip: You can do this by selecting the clock icon on the left hand side navigation menu in the Case View (within the Preview pane)
- Expand the Utilities pane, and then observe that there are no errors from the Star Rating Widget.
On the Console tab of the Developer Tools, you will see a console log entry of the data class and associated Data Pages that you selected in the previous task. - On the Star Rating Widget, click the icon to add a new rating for this Case and customer.
- Observe that that new rating is displayed and editable, as shown in the following figure:
However, the rating does not persist because the ability to update the underlying data class has not yet been implemented.
11 Implement the upsert functionality
- Open the sldxcomponents folder that you cloned in the first task.
- Open a terminal, and then enter the following commands:
git stashgit switch 11_dataoperations_upsert
- Start your Pega Infinity challenge instance.
- Open the src/component/Sl_DXExtensions_StarRatingWidget folder.
- Open the ratingData.ts file.
- Locate the createRating function in the file, and then uncomment the following block of code:
// const optionsObject = {
// body: {
// data: {
// CustomerRating: rating.rating,
// NumberOfStars: rating.stars,
// CaseID: rating.caseId,
// CustomerID: rating.customerId,
// CaseClassName: rating.caseClass
// }
// },
// queryPayload: {
// data_view_ID: dataView
// }
// };
//
// const response = await PCore.getRestClient().invokeRestApi(
// 'createDataObject',
// optionsObject,
// context
// );
//
// if (response?.status === 200) {
// if (classId) {
// PCore.getPubSubUtils().publish(
// PCore.getConstants().PUB_SUB_EVENTS.DATA_EVENTS.DATA_OBJECT_CREATED,
// {
// classId
// }
// );
// }
// const {
// CustomerRating,
// NumberOfStars,
// CaseID,
// CustomerID,
// CaseClassName,
// pyGUID,
// pxUpdateDateTime
// }: RatingData = response.data.responseData;
//
// return {
// rating: CustomerRating,
// stars: NumberOfStars,
// caseId: CaseID,
// customerId: CustomerID,
// caseClass: CaseClassName,
// guid: pyGUID,
// udpateDataTime: pxUpdateDateTime
// } as Rating;
// } - Comment out the following block of code located after the block from step 6:
rating.guid = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString();
rating.updateDateTime = new Date().toISOString();
// eslint-disable-next-line no-console
console.log('createRating:', dataView, rating, context, classId);
// Tempoary guid purely for mock implementation.
return rating as Rating; - Save the ratingData.ts file.
- In a terminal window, enter npm run authenticate.
- In your challenge instance, enter the following credentials:
- In the User name field, enter author@sl.
- In the Password field, enter pega123!.
You will now publish and configure your component.
- In a terminal, enter npn run publish.
- Select the Sl_DXExtensions_StarRatingWidget.
- Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
- In the "Generate development build ?" prompt, select "y".
- In App Studio, exit Preview mode, and then enter again.
Tip: This step forces a reload of Constellation and updates your DX component in App Studio
- Click Recents, open an existing Case, and then add a new rating for that Case.
The new rating in the SummaryList is displayed, as shown in the following figure: - In the navigation pane of App Studio, click Data
- Open the Customer Rating data type, and then click the Records tab.
A new rating record is available for the Case, as shown in the following figure:Note: Currently, the system updates the rating in memory, but changes do not persist in the data class. - Open the ratingData.ts file.
- In the updateRating function above the createRating function, uncomment the following block of code:
// const optionsObject = {
// body: {
// data: {
// CustomerRating: rating.rating,
// NumberOfStars: rating.stars,
// CaseID: rating.caseId,
// CustomerID: rating.customerId,
// CaseClassName: rating.caseClass,
// pyGUID: rating.guid
// }
// },
// queryPayload: {
// data_view_ID: dataView
// }
// };
//
// const response = await PCore.getRestClient().invokeRestApi(
// 'updateDataObject',
// optionsObject,
// context
// );
//
// if (response?.status === 200) {
// if (classId) {
// PCore.getPubSubUtils().publish(
// PCore.getConstants().PUB_SUB_EVENTS.DATA_EVENTS.DATA_OBJECT_UPDATED,
// {
// classId
// }
// );
// }
// const {
// CustomerRating,
// NumberOfStars,
// CaseID,
// CustomerID,
// CaseClassName,
// pyGUID,
// pxUpdateDateTime
// }: RatingData = response.data.responseData;
//
// return {
// rating: CustomerRating,
// stars: NumberOfStars,
// caseId: CaseID,
// customerId: CustomerID,
// caseClass: CaseClassName,
// guid: pyGUID,
// udpateDataTime: pxUpdateDateTime
// } as Rating;
// } - Comment out the following block of code:
// eslint-disable-next-line no-console
console.log('createRating:', dataView, rating, context, classId);
rating.updateDateTime = new Date().toISOString();
// Tempoary guid purely for mock implementation.
return rating as Rating; - Save the ratingData.ts file.
- In a terminal window, enter npm run authenticate.
- In your challenge instance, enter the following credentials: You will now publish and configure your component.
- In the User name field, enter author@sl.
- In the Password field, enter pega123!.
- In a terminal, enter npn run publish.
- Select the Sl_DXExtensions_StarRatingWidget.
- Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
- In the "Generate development build ?" prompt, select "y".
- In App Studio, exit Preview mode, and then enter again.
- In App Studio, follow the same process as you did for createRating, but this time, select the Case for which you created a rating in the previous task.
- Edit the rating using the widget, and yhrn observe the rating value change in the SummaryList and the Customer Rating class.
12 Improving the display of the MetaList data
- Open the sldxcomponents folder that you cloned in the first task.
- Open a terminal, and then enter the following commands:
git stashgit switch 12_improve_secondary_display
- Start your Pega Infinity challenge instance.
- Open the src/component/Sl_DXExtensions_StarRatingWidget folder.
- Open the ratingItems.tsx file.
- Uncomment the Link and comment out the Text in the following block of code:
import {
// Link,
Text, -
Uncomment the following block of code:
// const linkURL = PCore.getSemanticUrlUtils().getResolvedSemanticURL(
// PCore.getSemanticUrlUtils().getActions().ACTION_OPENWORKBYHANDLE,
// { caseClassName: dataItem.caseClass },
// {
// workID:
// dataItem.caseId.split(' ').length > 1
// ? dataItem.caseId.split(' ')[1]
// : dataItem.caseId
// }
// ); - Uncomment the following block of code:
// <Link
// href={linkURL}
// variant='link'
// previewable
// onPreview={() =>
// getPConnect().getActionsApi().showCasePreview(dataItem.caseId, {
// caseClassName: dataItem.caseClass
// })
// }
// >
// {dataItem.caseId.split(' ')[1]}
// </Link>, - Comment out the following block of code:
<Text>{dataItem.caseClass}</Text>,
<Text>{dataItem.caseId}</Text> - Save the ratingItems.tsx file.
- In a terminal window, enter npm run startStorybook.
- On the Storybook tab, review the stories, as shown in the following figure:
- In a terminal window, enter npm run authenticate.
- In your challenge instance, enter the following credentials:
- In the User name field, enter author@sl.
- In the Password field, enter pega123!.
- In a terminal, enter npn run publish.
- Select the Sl_DXExtensions_StarRatingWidget.
- Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
- In the "Generate development build ?" prompt, select "y".
- In App Studio, exit Preview mode, and then enter again.
- Open the previous Case, and then observe the updated display for the MetaList that is displayed under the rating, as shown in the following figure:
When you expand the Utilities pane, you can click the Case link to open the Case in a preview or new tab.
Available in the following mission:
Want to help us improve this content?