From a1e9a24eb5321d4157c34ed32498231ca95556cd Mon Sep 17 00:00:00 2001 From: Pamela Iglesias Date: Fri, 5 Apr 2024 10:33:39 -0300 Subject: [PATCH 1/4] Case Launcher widget initial commit --- .../Pega_Extensions_CaseLauncher/Docs.mdx | 14 +++ .../Pega_Extensions_CaseLauncher/config.json | 57 +++++++++++ .../demo.stories.tsx | 65 ++++++++++++ .../demo.test.tsx | 18 ++++ .../Pega_Extensions_CaseLauncher/index.tsx | 98 +++++++++++++++++++ .../Pega_Extensions_CaseLauncher/styles.ts | 22 +++++ 6 files changed, 274 insertions(+) create mode 100644 src/components/Pega_Extensions_CaseLauncher/Docs.mdx create mode 100644 src/components/Pega_Extensions_CaseLauncher/config.json create mode 100644 src/components/Pega_Extensions_CaseLauncher/demo.stories.tsx create mode 100644 src/components/Pega_Extensions_CaseLauncher/demo.test.tsx create mode 100644 src/components/Pega_Extensions_CaseLauncher/index.tsx create mode 100644 src/components/Pega_Extensions_CaseLauncher/styles.ts diff --git a/src/components/Pega_Extensions_CaseLauncher/Docs.mdx b/src/components/Pega_Extensions_CaseLauncher/Docs.mdx new file mode 100644 index 0000000..5ad34ea --- /dev/null +++ b/src/components/Pega_Extensions_CaseLauncher/Docs.mdx @@ -0,0 +1,14 @@ +import { Meta, Primary, Controls, Story } from '@storybook/blocks'; +import * as DemoStories from './demo.stories'; + + + +# Overview + +The Case Launcher widget allows you to create and open a single defined case by clicking the widget button. The widget consists of a card, a header, a case description, and a button with an editable label. You can define which case to associate with the Launcher by selecting the case type in the select control (classFilter). + + + +## Props + + diff --git a/src/components/Pega_Extensions_CaseLauncher/config.json b/src/components/Pega_Extensions_CaseLauncher/config.json new file mode 100644 index 0000000..0f964a3 --- /dev/null +++ b/src/components/Pega_Extensions_CaseLauncher/config.json @@ -0,0 +1,57 @@ +{ + "name": "Pega_Extensions_CaseLauncher", + "label": "Case Launcher", + "description": "Widget will render a heading, text and a button with start an case type flow.", + "organization": "Pega", + "version": "1.0.0", + "library": "Extensions", + "allowedApplications": [], + "componentKey": "Pega_Extensions_CaseLauncher", + "type": "Widget", + "subtype": "PAGE", + "properties": [ + { + "name": "heading", + "label": "Header", + "format": "TEXT", + "defaultValue": "Please insert here a personalized header", + "required": true + }, + { + "name": "description", + "label": "Description", + "format": "TEXT", + "defaultValue": "Please insert here a personalized short description.", + "required": true + }, + { + "name": "classFilter", + "label": "Case type (triggered by primary button)", + "format": "SELECT", + "source": { + "name": "D_pyQuickCreate", + "displayProp": "pyLabel", + "valueProp": "pyClassName" + }, + "required": true + }, + { + "name": "labelPrimaryButton", + "label": "Primary button text", + "format": "TEXT", + "required": true + }, + { + "label": "Conditions", + "format": "GROUP", + "properties": [ + { + "name": "visibility", + "label": "Visibility", + "format": "VISIBILITY" + } + ] + } + ], + "defaultConfig": {} +} diff --git a/src/components/Pega_Extensions_CaseLauncher/demo.stories.tsx b/src/components/Pega_Extensions_CaseLauncher/demo.stories.tsx new file mode 100644 index 0000000..7b12549 --- /dev/null +++ b/src/components/Pega_Extensions_CaseLauncher/demo.stories.tsx @@ -0,0 +1,65 @@ +/* eslint-disable react/jsx-no-useless-fragment */ +// @ts-nocheck +import type { StoryObj } from '@storybook/react'; + +import PegaExtensionsCaseLauncher from './index'; + +export default { + title: 'Widgets/Case Launcher', + component: PegaExtensionsCaseLauncher, + argTypes: { + heading: { + control: { control: 'text' } + }, + description: { + control: { control: 'text' } + }, + classFilter: { + options: ['Work-'], + control: { control: 'select' } + }, + getPConnect: { + table: { + disable: true + } + } + } +}; + +const setPCore = () => { + (window as any).PCore = { + getEnvironmentInfo: () => { + return {}; + } + }; +}; + +type Story = StoryObj; + +export const Default: Story = { + render: args => { + setPCore(); + const props = { + ...args, + getPConnect: () => { + return { + getActionsApi: () => { + return { + createWork: (className: string) => { + // eslint-disable-next-line no-alert + alert(`Create case type with className: ${className}`); + } + }; + } + }; + } + }; + return ; + }, + args: { + heading: 'Start Case', + description: 'Short description about the case that you will be able to start', + classFilter: 'Work-', + labelPrimaryButton: 'Start a case' + } +}; diff --git a/src/components/Pega_Extensions_CaseLauncher/demo.test.tsx b/src/components/Pega_Extensions_CaseLauncher/demo.test.tsx new file mode 100644 index 0000000..088a95e --- /dev/null +++ b/src/components/Pega_Extensions_CaseLauncher/demo.test.tsx @@ -0,0 +1,18 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { composeStories } from '@storybook/react'; +import * as DemoStories from './demo.stories'; + +const { Default } = composeStories(DemoStories); + +test('renders Case Launcher widget with default args', () => { + jest.spyOn(window, 'alert').mockImplementation(() => {}); + render(); + + const headingElement = screen.getByRole('heading'); + expect(headingElement).not.toBeNull(); + + const buttonElement = screen.getByRole('button', { name: 'Start a case' }); + expect(buttonElement).not.toBeNull(); + fireEvent.click(buttonElement); + expect(window.alert).toHaveBeenCalled(); +}); diff --git a/src/components/Pega_Extensions_CaseLauncher/index.tsx b/src/components/Pega_Extensions_CaseLauncher/index.tsx new file mode 100644 index 0000000..b8d2a99 --- /dev/null +++ b/src/components/Pega_Extensions_CaseLauncher/index.tsx @@ -0,0 +1,98 @@ +import StyledPegaExtensionsPreloadCaseStarterWrapper, { StyledCard } from './styles'; +import { useEffect, useState } from 'react'; + +import { Card, CardHeader, CardContent, CardFooter, Text, Button } from '@pega/cosmos-react-core'; + +// interface for props +export type PreloadCaseStarterProps = { + /** Card heading */ + heading: string; + /** Description of the case launched by the widget */ + description: string; + /** Class name of the case; used as property when launching pyStartCase */ + classFilter: any; + /** Primary button label */ + labelPrimaryButton: string; + getPConnect: any; +}; + +// Duplicated runtime code from Constellation Design System Component + +// props passed in combination of props from property panel (config.json) and run time props from Constellation +// any default values in config.pros should be set in defaultProps at bottom of this file +export default function PegaExtensionsCaseLauncher(props: PreloadCaseStarterProps) { + const { heading, description, classFilter, labelPrimaryButton, getPConnect } = props; + const PCore = (window as any).PCore; + const pConn = getPConnect(); + + /* Create a new case on click of the selected button */ + const createCase = (className: any) => { + const options = { + flowType: 'pyStartCase', + containerName: 'primary', + openCaseViewAfterCreate: true + }; + + pConn.getActionsApi().createWork(className, options); + }; + + const [quickCreatecases, setCases] = useState([]); + + /* useEffect for the case type */ + useEffect(() => { + const cases: any[] = []; + const defaultCases: any[] = []; + const envInfo = PCore.getEnvironmentInfo(); + + /* Finds the work types selected to display */ + if (envInfo?.environmentInfoObject?.pyCaseTypeList) { + envInfo.environmentInfoObject.pyCaseTypeList.forEach( + (casetype: { pyWorkTypeName: any; pyWorkTypeImplementationClassName: any }) => { + if (casetype.pyWorkTypeName && casetype.pyWorkTypeImplementationClassName) { + defaultCases.push({ + classname: casetype.pyWorkTypeImplementationClassName + }); + } + } + ); + } else { + defaultCases.push({ + classname: classFilter + }); + } + + if (classFilter?.length > 0) { + defaultCases.forEach((casetype: any) => { + if (casetype.classname === classFilter) { + cases.push(casetype); + } + }); + + setCases(cases); + } + }, []); + + return ( + + + + {heading} + + {description} + + {quickCreatecases?.map(c => ( + + ))} + + + + ); +} diff --git a/src/components/Pega_Extensions_CaseLauncher/styles.ts b/src/components/Pega_Extensions_CaseLauncher/styles.ts new file mode 100644 index 0000000..6bdac1e --- /dev/null +++ b/src/components/Pega_Extensions_CaseLauncher/styles.ts @@ -0,0 +1,22 @@ +// utilizing theming, comment out, if want individual style +import styled, { css } from 'styled-components'; +import { Configuration, defaultThemeProp } from '@pega/cosmos-react-core'; + +export default styled(Configuration)``; + +// individual style, comment out above, and uncomment here and add styles +// import styled, { css } from 'styled-components'; +// +// export default styled.div(() => { +// return css` +// margin: 0px 0; +// `; +// }); +export const StyledCard = styled.article(({ theme }) => { + return css` + background-color: ${theme.base.colors.white}; + border-radius: 0.25rem; + width: 100%; + `; +}); +StyledCard.defaultProps = defaultThemeProp; From 0674e96082ac26997dce39379a0efcebf08ec6e1 Mon Sep 17 00:00:00 2001 From: Pamela Iglesias Date: Fri, 5 Apr 2024 14:17:47 -0300 Subject: [PATCH 2/4] Comment removed from styles.ts; Changed config.json to adjust the dropdown showing the 'choose option...' placeholder; Removed useEffect (index.tsx) code making the component uncontrolled and using the prop classFilter as is --- .../Pega_Extensions_CaseLauncher/config.json | 1 + .../Pega_Extensions_CaseLauncher/index.tsx | 80 ++++++------------- .../Pega_Extensions_CaseLauncher/styles.ts | 15 +--- 3 files changed, 26 insertions(+), 70 deletions(-) diff --git a/src/components/Pega_Extensions_CaseLauncher/config.json b/src/components/Pega_Extensions_CaseLauncher/config.json index 0f964a3..dd28cbb 100644 --- a/src/components/Pega_Extensions_CaseLauncher/config.json +++ b/src/components/Pega_Extensions_CaseLauncher/config.json @@ -28,6 +28,7 @@ "name": "classFilter", "label": "Case type (triggered by primary button)", "format": "SELECT", + "placeholder": "Choose option...", "source": { "name": "D_pyQuickCreate", "displayProp": "pyLabel", diff --git a/src/components/Pega_Extensions_CaseLauncher/index.tsx b/src/components/Pega_Extensions_CaseLauncher/index.tsx index b8d2a99..4bcf46c 100644 --- a/src/components/Pega_Extensions_CaseLauncher/index.tsx +++ b/src/components/Pega_Extensions_CaseLauncher/index.tsx @@ -1,10 +1,15 @@ -import StyledPegaExtensionsPreloadCaseStarterWrapper, { StyledCard } from './styles'; -import { useEffect, useState } from 'react'; - -import { Card, CardHeader, CardContent, CardFooter, Text, Button } from '@pega/cosmos-react-core'; +import { + Card, + CardHeader, + CardContent, + CardFooter, + Text, + Button, + Configuration +} from '@pega/cosmos-react-core'; // interface for props -export type PreloadCaseStarterProps = { +export type CaseLauncherProps = { /** Card heading */ heading: string; /** Description of the case launched by the widget */ @@ -20,9 +25,8 @@ export type PreloadCaseStarterProps = { // props passed in combination of props from property panel (config.json) and run time props from Constellation // any default values in config.pros should be set in defaultProps at bottom of this file -export default function PegaExtensionsCaseLauncher(props: PreloadCaseStarterProps) { +export default function PegaExtensionsCaseLauncher(props: CaseLauncherProps) { const { heading, description, classFilter, labelPrimaryButton, getPConnect } = props; - const PCore = (window as any).PCore; const pConn = getPConnect(); /* Create a new case on click of the selected button */ @@ -36,63 +40,25 @@ export default function PegaExtensionsCaseLauncher(props: PreloadCaseStarterProp pConn.getActionsApi().createWork(className, options); }; - const [quickCreatecases, setCases] = useState([]); - - /* useEffect for the case type */ - useEffect(() => { - const cases: any[] = []; - const defaultCases: any[] = []; - const envInfo = PCore.getEnvironmentInfo(); - - /* Finds the work types selected to display */ - if (envInfo?.environmentInfoObject?.pyCaseTypeList) { - envInfo.environmentInfoObject.pyCaseTypeList.forEach( - (casetype: { pyWorkTypeName: any; pyWorkTypeImplementationClassName: any }) => { - if (casetype.pyWorkTypeName && casetype.pyWorkTypeImplementationClassName) { - defaultCases.push({ - classname: casetype.pyWorkTypeImplementationClassName - }); - } - } - ); - } else { - defaultCases.push({ - classname: classFilter - }); - } - - if (classFilter?.length > 0) { - defaultCases.forEach((casetype: any) => { - if (casetype.classname === classFilter) { - cases.push(casetype); - } - }); - - setCases(cases); - } - }, []); - return ( - - + + {heading} {description} - {quickCreatecases?.map(c => ( - - ))} + - + ); } diff --git a/src/components/Pega_Extensions_CaseLauncher/styles.ts b/src/components/Pega_Extensions_CaseLauncher/styles.ts index 6bdac1e..2fe3501 100644 --- a/src/components/Pega_Extensions_CaseLauncher/styles.ts +++ b/src/components/Pega_Extensions_CaseLauncher/styles.ts @@ -1,22 +1,11 @@ // utilizing theming, comment out, if want individual style import styled, { css } from 'styled-components'; -import { Configuration, defaultThemeProp } from '@pega/cosmos-react-core'; +import { type themeDefinition } from '@pega/cosmos-react-core'; -export default styled(Configuration)``; - -// individual style, comment out above, and uncomment here and add styles -// import styled, { css } from 'styled-components'; -// -// export default styled.div(() => { -// return css` -// margin: 0px 0; -// `; -// }); -export const StyledCard = styled.article(({ theme }) => { +export const StyledCard = styled.article(({ theme }: { theme: typeof themeDefinition }) => { return css` background-color: ${theme.base.colors.white}; border-radius: 0.25rem; width: 100%; `; }); -StyledCard.defaultProps = defaultThemeProp; From 85bba538346e02af8abb12f8f60d39d1982daf64 Mon Sep 17 00:00:00 2001 From: Pamela Iglesias Date: Fri, 5 Apr 2024 15:04:32 -0300 Subject: [PATCH 3/4] Removing unecessary comments; Used export default for styles.ts; Removing some argTypes from config.json; changing type of className prop to be used in createWork api call (index.tsx) --- src/components/Pega_Extensions_CaseLauncher/config.json | 2 +- .../Pega_Extensions_CaseLauncher/demo.stories.tsx | 8 -------- src/components/Pega_Extensions_CaseLauncher/index.tsx | 7 +------ src/components/Pega_Extensions_CaseLauncher/styles.ts | 5 ++--- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/components/Pega_Extensions_CaseLauncher/config.json b/src/components/Pega_Extensions_CaseLauncher/config.json index dd28cbb..fdc1a8f 100644 --- a/src/components/Pega_Extensions_CaseLauncher/config.json +++ b/src/components/Pega_Extensions_CaseLauncher/config.json @@ -1,7 +1,7 @@ { "name": "Pega_Extensions_CaseLauncher", "label": "Case Launcher", - "description": "Widget will render a heading, text and a button with start an case type flow.", + "description": "Widget will render a heading, text and a button to start a case", "organization": "Pega", "version": "1.0.0", "library": "Extensions", diff --git a/src/components/Pega_Extensions_CaseLauncher/demo.stories.tsx b/src/components/Pega_Extensions_CaseLauncher/demo.stories.tsx index 7b12549..28fcd9f 100644 --- a/src/components/Pega_Extensions_CaseLauncher/demo.stories.tsx +++ b/src/components/Pega_Extensions_CaseLauncher/demo.stories.tsx @@ -1,5 +1,3 @@ -/* eslint-disable react/jsx-no-useless-fragment */ -// @ts-nocheck import type { StoryObj } from '@storybook/react'; import PegaExtensionsCaseLauncher from './index'; @@ -8,12 +6,6 @@ export default { title: 'Widgets/Case Launcher', component: PegaExtensionsCaseLauncher, argTypes: { - heading: { - control: { control: 'text' } - }, - description: { - control: { control: 'text' } - }, classFilter: { options: ['Work-'], control: { control: 'select' } diff --git a/src/components/Pega_Extensions_CaseLauncher/index.tsx b/src/components/Pega_Extensions_CaseLauncher/index.tsx index 4bcf46c..8c47ffc 100644 --- a/src/components/Pega_Extensions_CaseLauncher/index.tsx +++ b/src/components/Pega_Extensions_CaseLauncher/index.tsx @@ -21,16 +21,12 @@ export type CaseLauncherProps = { getPConnect: any; }; -// Duplicated runtime code from Constellation Design System Component - -// props passed in combination of props from property panel (config.json) and run time props from Constellation -// any default values in config.pros should be set in defaultProps at bottom of this file export default function PegaExtensionsCaseLauncher(props: CaseLauncherProps) { const { heading, description, classFilter, labelPrimaryButton, getPConnect } = props; const pConn = getPConnect(); /* Create a new case on click of the selected button */ - const createCase = (className: any) => { + const createCase = (className: string) => { const options = { flowType: 'pyStartCase', containerName: 'primary', @@ -49,7 +45,6 @@ export default function PegaExtensionsCaseLauncher(props: CaseLauncherProps) { {description}