Skip to main content

Challenge

Creating a Star Rating Widget

Creating a Star Rating Widget

12 Tasks

30 mins

Visible to: All users
Advanced
English

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 Utilities 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 Utilities 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!

You must initiate your own Pega instance to complete this Challenge.

Initialization may take up to 5 minutes so please be patient.

Detailed Tasks

1 Clone the challenge Git repository and update the dependencies

Caution: It is important to do the tasks in order because the components are published to specific Ruleset versions. Changes in the same Rule in a lower Ruleset will not be reflected in the component. 
Note: To avoid unnecessary repetition, assume that all tasks should be completed using the Visual Studio Code editor and the embedded terminal unless stated otherwise.   
  1. 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. 
  2. In the new WidgetChallenges folder, open Visual Studio Code.
  3. 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.
    1. Clone the challenge repository. 
      This clone provides a foundation for all the following tasks.
      git clone https://github.com/ricmars/constellation-extensions-challenge.git sldxcomponents
      Note: If you must use SSH with Git, you must adjust the Git clone statement accordingly.
      cd sldxcomponents
    2. Update your package dependencies from npm by running the following command:
      npm update
  4. Start Storybook, and then review the existing stories. 
  5. 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: 

    Storybook story view before any changes from the sample component.

    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.
  6. Initialize and start your Pega Infinity instance.
  7. 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.
  8. 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.
  9. 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.
  10. In the browser window that launches, enter the following credentials:
    1. In the user name line, enter author@sl.
    2. In the password line, enter pega123!.
  11. 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.
  12. Close the browser tab or window that opened for authentication
  13. In the same terminal window, enter the following command: npm run publish.
  14. When prompted to select the type of component you wish to publish, select the Sl_DXExtensions_StarRatingWidget option.
  15. 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

Caution: Each task has an associated branch in the Git repository. Ensure that you run the specified Git commands for each task before continuing. 
  1. Open the sldxcomponents folder that you cloned in the first task.

    Tip: It is inside the WidgetChallenges folder.
  2. Open a terminal, and then enter the following commands in order:

    git stash

    Switch the Git branch to the one relevant to the next task:

    git switch 01_refactor_ootb_case_widget
  3. 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.
  4. Open the mock.historyData.ts and historyData.tsx files.
  5. 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. 
  6. 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.
  7. 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';
  8. 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';
  9. Save the index.tsx file.
  10. Review the story in the Storybook browser tab, and then in the navigation pane, click the story to refresh it.
    Storbook story showing incorrect rating data in table with new column headings.
    Observe that the data for the Table columns is not correct.
     
  11. 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.
  12. 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.
    Storybook story displayed with mock rating data and new rating column headings.

    This component is the final component that you use to display any non-interactive ratings.

3 Replace Table with SummaryList

  1. Open the sldxcomponents folder that you cloned in the first task.

    Tip: It is inside the WidgetChallenges folder.
  2. Open a terminal, and then enter the following commands:

    git stash
    git switch 02_tabletosummarylist
  3. 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.
  4. Open the src/components/Sl_DXExtensions_StarRatingWidget folder, and then open the index.tsx file.
  5. Uncomment the following block of code:
    // import type { SummaryListItem } from '@pega/cosmos-react-core';
    // import {
    //   SummaryList,
    //   withConfiguration
    // } from '@pega/cosmos-react-core';
  6. Comment out the following block of code:
    import { Table, withConfiguration } from "@pega/cosmos-react-core";
  7. Comment out the following block of code:
    import type { TableProps } from '@pega/cosmos-react-core/lib/components/Table/Table';
  8. Uncomment the following code:
    // import type { RatingDataItem as DataItem } from './ratingData';
    // import { mapRatingDataItem as mapDataItem } from './ratingData';
  9. 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';
  10. Comment out the following line of code:
    const [data, setData] = useState<TableProps<TableRow>['data']>();
  11. Uncomment the following line of code:
    // const [data, setData] = useState<SummaryListItem[]>();
  12. Comment out the following line of code:
    const columns = createTableSchema(getPConnect);
  13. Comment out the following block of code:
    return (
         <Table
           title={pConn.getLocalizedValue(label, "", "")}
           columns={columns}
           data={data}
           loading={isLoading}
           loadingMessage={pConn.getLocalizedValue("Loading data ...")}
         />
    );
  14. Uncomment the following line of code:
    return <SummaryList name={label} items={data ?? []} loading={isLoading} />;
  15. Save the index.tsx file.
  16. 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. 
  17. Open the ratingData.tsx file.
  18. 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.
     
  19. Uncomment the following block of code to add the SummaryListItem type import:
    // import type { SummaryListItem } from '@pega/cosmos-react-core';
  20. Comment out the following block of code:
    export interface RatingTableRow extends DefaultRowData {
      caseId: string;
      rating: number | JSX.Element;
      updated: string;
      customerId: string;
    }
  21. 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}`
    // });
  22. 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
        }
      ];
    };
  23. Save the ratingData.tsx file.
  24. Navigate to the Storybook story in your browser. 
    The story should resemble the following figure: 
    Storybook story displaying the SummaryList with the mocked up rating data using the Rating component.

    A list of ratings is displayed in the SummaryList in Storybook.

4 Update the SummaryList display with MetaList

  1. Open the sldxcomponents folder that you cloned in the first task.
  2. Open a terminal, and then enter the following commands:
    git switch 03_updatesummarylist
    git stash
  3. Open the src/components/Sl_DXExtensions_StarRatingWidget folder, and then open the index.tsx file.  
  4. 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'
  5. Save the index.tsx file.
  6. In a terminal window, enter npm run startStorybook.

    ​​​​​A star icon is now displayed in the SummaryList heading, as shown in the following figure:

    Storybook story showing the star icon being displayed next to the SummaryList heading.
  7. 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';
  8. Comment out the following import statement:
    import { Rating } from '@pega/cosmos-react-core';
  9. 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>
    //       ]}
    //     />
    //   )
    // });
  10. 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}`
    });
  11. Save your file.
  12. Open the Storybook story. 
    The story should resemble the following figure: 
    Storybook story showing mock rating data displayed in a SummaryList with Rating component and MetaList for each item

    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

  1. Open the sldxcomponents folder that you cloned in the first task.
  2. Open a terminal, and then enter the following commands:
    git stash

    git switch 04_fulldatasummarylist

    The original code has been cleaned up and refactored. The code includes some new files to accelerate the learning process.

  3. 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.".
  4. Open the index.tsx file.
  5. Delete the first line:
    /* eslint-disable @typescript-eslint/no-unused-vars */
  6. 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);    
  7. Comment out the following block of code:
       actionId && (
         <Text
           variant="h1"
           onClick={() => setActionId(undefined)}
         >{`Click me to dismiss: ${actionId}`}</Text>
       )
  8.  Uncomment out the following block of code:
       // {
       //   actionTarget && (
       //   <Text
       //     variant='h1'
       //     onClick={() => setActionTarget(null)}
       //   >{`Click me to dismiss: ${actionId}${dataItem ? ':'+dataItem.CaseID : '' }:`}</Text>
       // )
  9. Save the index.tsx file.
  10. Review the Storybook story.
    The Edit action with the pencil icon is now displayed, as shown in the following figure:
    Storybook story showing button as link with pencil icon and tooltip
    This action is displayed only against the current case (this is provided from the mock API response in the demo.stories.tsx file).
     
  11. 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

  1. Navigate to the sldxcomponents folder cloned in the first task.
  2. Open a terminal, and then enter the following commands:
    git stash​​​​​
    git switch 05_summarylist
    npm update
    Note: 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.
  3. 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. 
  4. 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.
  5. Open the src/components/Sl_DXExtensions_StarRatingWidget folder.
  6. 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 file
    export 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. The CustomerRating property stores the rating value and  NumberOfStars ensures that you know how many stars the rating was originally set with. You use the autogenerated pyGUID as a unique key. You use pxUpdateDateTime to 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.     
  7. Open the index.tsx file.
  8. In the component return function in the attributes of the Slider component, comment out the following line of code:
    () => {}
  9. Uncomment the following line of code:
    // (changeValue: number) => setValue(changeValue)
  10. Save the index.tsx file.
    The Slider control now functions correctly, as shown in the following figure:
    Storybpook story depicting a Popover component with an interactive Slider component.
    Note: The Slider works, but nothing happens when clicking the Popover submit button. 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.
  11. In the index.tsx file, at the top of the file, delete the first line of code:
    /* eslint-disable @typescript-eslint/no-unused-vars */
  12. 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);
  13. Save the index.tsx file.
  14. Open Storybook, click the Add 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.

  1. Open the sldxcomponents folder that you cloned in the first task. 
  2. Open a terminal, and then enter the following commands:
    git stash
    git switch 06_slider_to_starrating
    npm update
    Caution: Ensure that you run the npm update command before proceeding because a new dependency exists.
  3. Open the src/components/Sl_DXExtensions_StarRatingWidget folder.
  4. Open the index.tsx file.
  5. Comment out the following line:
    Slider,
  6. Uncomment the following line:
    // import StarRating from '../Sl_DXExtensions_StarRating';,
  7. 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} />.
  8. Save the index.tsx file.
  9. In a terminal, start Storybook if it is not already running by entering npm run startStorybook.
  10. 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.
  11. 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.
  12. 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:
    Storybook story showing a Popover with the interactive StarRating component replacing the Slider components.

8 Add support for View All

In this task, you will add View all support to the SummaryList.
"... a View all Button 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."

  1. Open the sldxcomponents folder that you cloned in the first task. 
  2. Open a terminal, and then enter the following commands:
    git stash
    git switch 07_view_all_implementation
  3. 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.
  4. Open the index.tsx file.
  5. Comment out the items line located in the following block of code:
    items = {
      items,
    // items.slice(0, 3)
    };
  6. 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)
    }
  7. Save the index.tsx file.
  8. If Storybook is not already running, in a terminal, enter npm run startStorybook.
  9. 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:
    Storybook view showing new stories added to demonstrate View All functionality
    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.
  10. Open the index.tsx file.
  11. 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.
    //       }
    //     }
    //   );
    // };
  12. Comment out the following:
    () => {}
  13. Uncomment the following line:
    // openViewAll
  14. Save the index.tsx file.
  15. 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:
    Storybook showing View All implementation of modal manager displaying search box allowing  across all ratings .
Caution: If you have many items to display when adopting this pattern for Utilities ​​​​​​widgets, a different data-loading strategy must be adopted to avoid performance issues. Consider using data page paging when fetching data and fetching only the first page when the widget loads for display in the View all dialog box. After the modal loads, you can load further pages or reduce the number of items by using a filter. For more information about using Paging and Filter options, see getDataAsync.

9 Build the UI authoring experience (component definition)

  1. Open the sldxcomponents folder that you cloned in the first task.
  2. Open a terminal, and then enter the following commands:
    git stash
    git switch 09_configjson
  3. Start your Pega Infinity challenge instance.
  4. In Visual Studio Code, open the src/component/Sl_DXExtensions_StarRatingWidget folder.
  5. 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.

  6. 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.

  1. 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": []
     },
     ]
  2. 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.
  3. 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
     }
     }
    }
  4. 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"
        }
      ]
    }
  ],
  1. Add the defaultConfig property in the end block of the config.json file:
  "defaultConfig": {
    "label": "@L $this.label",
    "ratingDataClass": " ",
    "iconName": "star"
  }
Tip: There is a complete config.json file in the component source directory that you can use to copy from or compare against.
  1. In your challenge instance, copy the URL, up to and including prweb.
  2. In a terminal window, enter npm run authenticate.
  3. In your challenge instance, enter the following credentials: You will now publish and configure your component.
    1. In the User name field, enter author@sl.
    2. In the Password field, enter pega123!.
  4. In a terminal, enter npn run publish.
  5. Select the Sl_DXExtensions_StarRatingWidget.
  6. Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
  7. In the "Generate development build ?" prompt, select "y".
  8. In the navigation pane of App Studio, click Case types > Incident.
  9. Click the UX tab.
  10. On the Full Page View tab, in the Utilities section, click Add, and then in the filter, enter Rating.
  11. Select Star Rating Widget, and then click Add.
  12. Click the Gear icon to configure the Ratings Widget.
  13. Perform the following configuration:
    1. In the Customer unique id property of the Case, navigate to the Customer page in the drop-down and select Globally unique id.
    2. In the Data pages for rating class group, in the Select the rating class list, select Customer Rating.
    3. For the lookup, list and savable data page CONTENTPICKERS select the only item available for each.
    4. Click Save.
      Your component configuration should resemble the following figure:
      AppStudio authoring view when configuring Star Rating widget
  14. In the upper-right corner, click Save.
  15. In the header of App Studio, click Preview.
    AppStudio Authoring highlighting save and preview
    .
  16. 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.
  17. 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.
  18. Ensure that the Console tab is visible so that you can see errors.
  19. 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:
    AppStudio preview showing caseview banner error and widget error
    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).
  20. Click the reference to load the index.tsx source code.
  21. On the Sources tab of the Developer Tools, on the index.tsx tab, add a breakpoint by selecting line 63.
  22. 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:
    AppStudio preview in debug mode showing props in hover mode in developer tools

    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.
  23. 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.

  1. Open the sldxcomponents folder that you cloned in the first task.
  2. Open a terminal, and then enter the following commands:
    git stash
    git switch 10_configjson_toprops
  3. Start your Pega Infinity challenge instance.
  4. Open the src/component/Sl_DXExtensions_StarRatingWidget folder.
  5. Open the index.tsx file.
  6.  Uncomment the following block of code:
       export interface SlDxExtensionsStarRatingWidgetProps extends PConnFieldProps {
         customerId?: string;
         // ratingDataClass: string;
         // ratingLookupDatapage: string[];
         // ratingListDatapage: string[];
         // ratingSavableDatapage: string[];
       }​​​
  7. Uncomment the following block of code:
       const SlDxExtensionsStarRatingWidget = ({
         // ratingDataClass,
         // ratingLookupDatapage,
         // ratingListDatapage,
         // ratingSavableDatapage,
         getPConnect,
         label,
         customerId
       }: SlDxExtensionsStarRatingWidgetProps) => {   
  8. Comment out the following two lines of code:
       const list = "D_List";
       const savable = "D_Savable";
  9. 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);
  10. Save the index.tsx file.
  11. In a terminal window, enter npm run authenticate.
  12. In your challenge instance, enter the following credentials:
    1. In the User name field, enter author@sl.
    2. In the Password field, enter pega123!.
    You will now publish and configure the component.
  13. In a terminal, enter npn run publish.
  14. Select the Sl_DXExtensions_StarRatingWidget.
  15. Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
  16. In the "Generate development build ?" prompt, select "y".
  17. In the header of App Studio, click Preview 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)
  18. 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.
  19. On the Star Rating Widget, click the Add icon to add a new rating for this Case and customer.
  20. Observe that that new rating is displayed and editable, as shown in the following figure:
    App Studio preview of Case view showing the Star Rating Widget being able to create a new Rating, but only in memory.

    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

  1. Open the sldxcomponents folder that you cloned in the first task.
  2. Open a terminal, and then enter the following commands:
    git stash
    git switch 11_dataoperations_upsert
  3. Start your Pega Infinity challenge instance.
  4. Open the src/component/Sl_DXExtensions_StarRatingWidget folder.
  5. Open the ratingData.ts file.
  6. 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;
    // }
  7. 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;  
  8. Save the ratingData.ts file.
  9. In a terminal window, enter npm run authenticate.
  10. In your challenge instance, enter the following credentials:
    1. In the User name field, enter author@sl.
    2. In the Password field, enter pega123!.
      You will now publish and configure your component.
  11. In a terminal, enter npn run publish.
  12. Select the Sl_DXExtensions_StarRatingWidget.
  13. Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
  14. In the "Generate development build ?" prompt, select "y".
  15. In App Studio, exit Preview mode, and then enter Preview again. 
    Tip: This step forces a reload of Constellation and updates your DX component in App Studio
  16. 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:
    AppStudio preview depicting a new star rating being added and persisted for a case
  17. In the navigation pane of App Studio, click Data 
  18. 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:
    AppStudio rating data type records view showing newly created instance
    Note: Currently, the system updates the rating in memory, but changes do not persist in the data class. 
  19. Open the ratingData.ts file.
  20. 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;
    // }
  21. 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;
  22. Save the ratingData.ts file.
  23. In a terminal window, enter npm run authenticate.
  24. In your challenge instance, enter the following credentials: You will now publish and configure your component.
    1. In the User name field, enter author@sl.
    2. In the Password field, enter pega123!.
  25. In a terminal, enter npn run publish.
  26. Select the Sl_DXExtensions_StarRatingWidget.
  27. Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
  28. In the "Generate development build ?" prompt, select "y".
  29. In App Studio, exit Preview mode, and then enter Preview again.
  30. 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.
  31. 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

  1. Open the sldxcomponents folder that you cloned in the first task.
  2. Open a terminal, and then enter the following commands:
    git stash
    git switch 12_improve_secondary_display
  3. Start your Pega Infinity challenge instance.
  4. Open the src/component/Sl_DXExtensions_StarRatingWidget folder.
  5. Open the ratingItems.tsx file.
  6. Uncomment the Link and comment out the Text in the following block of code:
    import {
      // Link,
      Text,
  7. 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
    //   }
    // );
  8.    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>,
  9. Comment out the following block of code:
    <Text>{dataItem.caseClass}</Text>,
    <Text>{dataItem.caseId}</Text>
  10. Save the ratingItems.tsx file.
  11. In a terminal window, enter npm run startStorybook.
  12. On the Storybook tab, review the stories, as shown in the following figure:
    Storybook story showing improved star rating widget using metalist and semantic links.
  13. In a terminal window, enter npm run authenticate.
  14. In your challenge instance, enter the following credentials:
    1. In the User name field, enter author@sl.
    2. In the Password field, enter pega123!.
    You will now publish and configure your component.
  15. In a terminal, enter npn run publish.
  16. Select the Sl_DXExtensions_StarRatingWidget.
  17. Accept the defaults for "Enter ruleset name" and "Enter ruleset version".
  18. In the "Generate development build ?" prompt, select "y".
  19. In App Studio, exit Preview mode, and then enter Preview again.
  20. 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:
    AppStudio showing star rating widget with preview and open in new tab feature.
    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:

If you are having problems with your training, please review the Pega Academy Support FAQs.

Did you find this content helpful?

Want to help us improve this content?

We'd prefer it if you saw us at our best.

Pega Academy has detected you are using a browser which may prevent you from experiencing the site as intended. To improve your experience, please update your browser.

Close Deprecation Notice