Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/atomic-react/src/components/commerce/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
AtomicIcon as LitAtomicIcon,
AtomicInsightGenerateAnswerButton as LitAtomicInsightGenerateAnswerButton,
AtomicInsightInterface as LitAtomicInsightInterface,
AtomicInsightUserActionsToggle as LitAtomicInsightUserActionsToggle,
AtomicLayoutSection as LitAtomicLayoutSection,
AtomicNumericRange as LitAtomicNumericRange,
AtomicProduct as LitAtomicProduct,
Expand Down Expand Up @@ -251,6 +252,12 @@ export const AtomicInsightInterface = createComponent({
elementClass: LitAtomicInsightInterface,
});

export const AtomicInsightUserActionsToggle = createComponent({
tagName: 'atomic-insight-user-actions-toggle',
react: React,
elementClass: LitAtomicInsightUserActionsToggle,
});

export const AtomicLayoutSection = createComponent({
tagName: 'atomic-layout-section',
react: React,
Expand Down
7 changes: 7 additions & 0 deletions packages/atomic-react/src/components/search/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
AtomicIcon as LitAtomicIcon,
AtomicInsightGenerateAnswerButton as LitAtomicInsightGenerateAnswerButton,
AtomicInsightInterface as LitAtomicInsightInterface,
AtomicInsightUserActionsToggle as LitAtomicInsightUserActionsToggle,
AtomicLayoutSection as LitAtomicLayoutSection,
AtomicLoadMoreResults as LitAtomicLoadMoreResults,
AtomicNoResults as LitAtomicNoResults,
Expand Down Expand Up @@ -207,6 +208,12 @@ export const AtomicInsightInterface = createComponent({
elementClass: LitAtomicInsightInterface,
});

export const AtomicInsightUserActionsToggle = createComponent({
tagName: 'atomic-insight-user-actions-toggle',
react: React,
elementClass: LitAtomicInsightUserActionsToggle,
});

export const AtomicLayoutSection = createComponent({
tagName: 'atomic-layout-section',
react: React,
Expand Down
53 changes: 0 additions & 53 deletions packages/atomic/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,24 +565,6 @@ export namespace Components {
*/
"userId": string;
}
/**
* Internal component of the atomic-insight-interface
* The `atomic-insight-user-actions-toggle` component displays a button that opens a modal containing the user actions timeline component.
*/
interface AtomicInsightUserActionsToggle {
/**
* The names of custom events to exclude.
*/
"excludedCustomActions": string[];
/**
* The date and time when the case was created. For example "2024-01-01T00:00:00Z"
*/
"ticketCreationDateTime": string;
/**
* The ID of the user whose actions are being displayed.
*/
"userId": string;
}
interface AtomicIpxBody {
"displayFooterSlot": boolean;
"isOpen"?: boolean;
Expand Down Expand Up @@ -1420,16 +1402,6 @@ declare global {
prototype: HTMLAtomicInsightUserActionsTimelineElement;
new (): HTMLAtomicInsightUserActionsTimelineElement;
};
/**
* Internal component of the atomic-insight-interface
* The `atomic-insight-user-actions-toggle` component displays a button that opens a modal containing the user actions timeline component.
*/
interface HTMLAtomicInsightUserActionsToggleElement extends Components.AtomicInsightUserActionsToggle, HTMLStencilElement {
}
var HTMLAtomicInsightUserActionsToggleElement: {
prototype: HTMLAtomicInsightUserActionsToggleElement;
new (): HTMLAtomicInsightUserActionsToggleElement;
};
interface HTMLAtomicIpxBodyElementEventMap {
"animationEnded": never;
}
Expand Down Expand Up @@ -1798,7 +1770,6 @@ declare global {
"atomic-insight-user-actions-modal": HTMLAtomicInsightUserActionsModalElement;
"atomic-insight-user-actions-session": HTMLAtomicInsightUserActionsSessionElement;
"atomic-insight-user-actions-timeline": HTMLAtomicInsightUserActionsTimelineElement;
"atomic-insight-user-actions-toggle": HTMLAtomicInsightUserActionsToggleElement;
"atomic-ipx-body": HTMLAtomicIpxBodyElement;
"atomic-ipx-button": HTMLAtomicIpxButtonElement;
"atomic-ipx-embedded": HTMLAtomicIpxEmbeddedElement;
Expand Down Expand Up @@ -2346,24 +2317,6 @@ declare namespace LocalJSX {
*/
"userId": string;
}
/**
* Internal component of the atomic-insight-interface
* The `atomic-insight-user-actions-toggle` component displays a button that opens a modal containing the user actions timeline component.
*/
interface AtomicInsightUserActionsToggle {
/**
* The names of custom events to exclude.
*/
"excludedCustomActions"?: string[];
/**
* The date and time when the case was created. For example "2024-01-01T00:00:00Z"
*/
"ticketCreationDateTime": string;
/**
* The ID of the user whose actions are being displayed.
*/
"userId": string;
}
interface AtomicIpxBody {
"displayFooterSlot"?: boolean;
"isOpen"?: boolean;
Expand Down Expand Up @@ -2887,7 +2840,6 @@ declare namespace LocalJSX {
"atomic-insight-user-actions-modal": AtomicInsightUserActionsModal;
"atomic-insight-user-actions-session": AtomicInsightUserActionsSession;
"atomic-insight-user-actions-timeline": AtomicInsightUserActionsTimeline;
"atomic-insight-user-actions-toggle": AtomicInsightUserActionsToggle;
"atomic-ipx-body": AtomicIpxBody;
"atomic-ipx-button": AtomicIpxButton;
"atomic-ipx-embedded": AtomicIpxEmbedded;
Expand Down Expand Up @@ -2970,11 +2922,6 @@ declare module "@stencil/core" {
* @example <AtomicInsightUserActionsTimeline userId={'123'} caseCreationDate={'2024-08-15T10:00:00Z'} />
*/
"atomic-insight-user-actions-timeline": LocalJSX.AtomicInsightUserActionsTimeline & JSXBase.HTMLAttributes<HTMLAtomicInsightUserActionsTimelineElement>;
/**
* Internal component of the atomic-insight-interface
* The `atomic-insight-user-actions-toggle` component displays a button that opens a modal containing the user actions timeline component.
*/
"atomic-insight-user-actions-toggle": LocalJSX.AtomicInsightUserActionsToggle & JSXBase.HTMLAttributes<HTMLAtomicInsightUserActionsToggleElement>;
"atomic-ipx-body": LocalJSX.AtomicIpxBody & JSXBase.HTMLAttributes<HTMLAtomicIpxBodyElement>;
"atomic-ipx-button": LocalJSX.AtomicIpxButton & JSXBase.HTMLAttributes<HTMLAtomicIpxButtonElement>;
"atomic-ipx-embedded": LocalJSX.AtomicIpxEmbedded & JSXBase.HTMLAttributes<HTMLAtomicIpxEmbeddedElement>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Meta } from '@storybook/addon-docs/blocks';
import * as AtomicInsightUserActionsToggleStories from './atomic-insight-user-actions-toggle.new.stories';
import { AtomicDocTemplate } from '@/storybook-utils/documentation/atomic-doc-template';

<Meta of={ AtomicInsightUserActionsToggleStories } />

<AtomicDocTemplate
stories={ AtomicInsightUserActionsToggleStories }
githubPath="insight/atomic-insight-user-actions-toggle/atomic-insight-user-actions-toggle.ts"
tagName="atomic-insight-user-actions-toggle"
className="AtomicInsightUserActionsToggle"
>

The `atomic-insight-user-actions-toggle` component displays a button that, when clicked, opens a modal containing a user actions timeline. This component is designed for Insight Panel interfaces where agents need to view customer activity history related to a support case.

When this component is clicked, an `atomic-insight-user-actions-modal` is created and displayed, showing the timeline of user actions.

This component should be placed within the `atomic-insight-interface`. You must provide the `user-id` and `ticket-creation-date-time` properties to identify the user and case context:

```html
<atomic-insight-interface>
<atomic-insight-user-actions-toggle
user-id="user123@example.com"
ticket-creation-date-time="2024-01-15T10:30:00Z"
></atomic-insight-user-actions-toggle>
</atomic-insight-interface>
```



</AtomicDocTemplate>
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type {Meta, StoryObj as Story} from '@storybook/web-components-vite';
import {getStorybookHelpers} from '@wc-toolkit/storybook-helpers';
import {MockMachineLearningApi} from '@/storybook-utils/api/machinelearning/mock';
import {parameters} from '@/storybook-utils/common/common-meta-parameters';
import {wrapInInsightInterface} from '@/storybook-utils/insight/insight-interface-wrapper';

const mockMachineLearningApi = new MockMachineLearningApi();

const {decorator, play} = wrapInInsightInterface();
const {events, args, argTypes, template} = getStorybookHelpers(
'atomic-insight-user-actions-toggle',
Expand All @@ -11,7 +14,7 @@ const {events, args, argTypes, template} = getStorybookHelpers(

const meta: Meta = {
component: 'atomic-insight-user-actions-toggle',
title: 'Insight/UserActionsToggle',
title: 'Insight/User Actions Toggle',
id: 'atomic-insight-user-actions-toggle',

render: (args) => template(args),
Expand All @@ -21,6 +24,7 @@ const meta: Meta = {
actions: {
handles: events,
},
msw: {handlers: [...mockMachineLearningApi.handlers]},
},
args,
argTypes,
Expand All @@ -29,6 +33,4 @@ const meta: Meta = {

export default meta;

export const Default: Story = {
name: 'atomic-insight-user-actions-toggle',
};
export const Default: Story = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import {
buildUserActions as buildInsightUserActions,
type UserActionsState as InsightUserActionsState,
} from '@coveo/headless/insight';
import {html} from 'lit';
import {describe, expect, it, vi} from 'vitest';
import {userEvent} from 'vitest/browser';
import {
defaultBindings,
renderInAtomicInsightInterface,
} from '@/vitest-utils/testing-helpers/fixtures/atomic/insight/atomic-insight-interface-fixture';
import {buildFakeUserActions} from '@/vitest-utils/testing-helpers/fixtures/headless/insight/user-actions-controller';
import type {AtomicInsightUserActionsToggle} from './atomic-insight-user-actions-toggle';
import './atomic-insight-user-actions-toggle';

vi.mock('@coveo/headless/insight', {spy: true});

describe('atomic-insight-user-actions-toggle', () => {
const renderUserActionsToggle = async ({
userId = 'test-user',
ticketCreationDateTime = '2024-01-01T00:00:00Z',
excludedCustomActions = [] as string[],
userActionsState = {} as Partial<InsightUserActionsState>,
} = {}) => {
vi.mocked(buildInsightUserActions).mockReturnValue(
buildFakeUserActions(userActionsState)
);

const {element, atomicInterface} =
await renderInAtomicInsightInterface<AtomicInsightUserActionsToggle>({
template: html`
<atomic-insight-user-actions-toggle
.userId=${userId}
.ticketCreationDateTime=${ticketCreationDateTime}
.excludedCustomActions=${excludedCustomActions}
></atomic-insight-user-actions-toggle>
`,
selector: 'atomic-insight-user-actions-toggle',
bindings: defaultBindings,
});

const getButton = () => element.shadowRoot?.querySelector('button');
const getModal = () =>
atomicInterface.querySelector('atomic-insight-user-actions-modal');

return {element, atomicInterface, getButton, getModal};
};

describe('#initialize', () => {
it('should call buildInsightUserActions with the engine and options', async () => {
const {atomicInterface} = await renderUserActionsToggle({
userId: 'test-user',
ticketCreationDateTime: '2024-01-01T00:00:00Z',
excludedCustomActions: ['custom-event'],
});

expect(buildInsightUserActions).toHaveBeenCalledWith(
atomicInterface.bindings.engine,
{
options: {
ticketCreationDate: '2024-01-01T00:00:00Z',
excludedCustomActions: ['custom-event'],
},
}
);
});

it('should set this.userActions to the user actions controller', async () => {
const {element} = await renderUserActionsToggle();

expect(element.userActions).toBe(
vi.mocked(buildInsightUserActions).mock.results[0].value
);
});

it('should bind state to controller', async () => {
const {element} = await renderUserActionsToggle({
userActionsState: {
loading: true,
excludedCustomActions: ['test-event'],
},
});

expect(element.userActionsState.loading).toBe(true);
expect(element.userActionsState.excludedCustomActions).toEqual([
'test-event',
]);
});
});

describe('when rendering', () => {
it('should render the button with the correct aria-label', async () => {
const {getButton} = await renderUserActionsToggle();

expect(getButton()).toHaveAttribute('aria-label', 'User actions');
});

it('should render the button with the correct title', async () => {
const {getButton} = await renderUserActionsToggle();

expect(getButton()).toHaveAttribute('title', 'User actions');
});

it('should render the button with the correct part prefix', async () => {
const {element} = await renderUserActionsToggle();
const container = element.shadowRoot?.querySelector(
'[part="insight-user-actions-toggle-container"]'
);

expect(container).toBeInTheDocument();
});

it('should render the icon within the button', async () => {
const {element} = await renderUserActionsToggle();
const icon = element.shadowRoot?.querySelector('atomic-icon');

expect(icon).toBeInTheDocument();
expect(icon).toHaveAttribute('part', 'insight-user-actions-toggle-icon');
});
});

describe('modal creation and interaction', () => {
it('should create the modal when rendered', async () => {
const {getModal} = await renderUserActionsToggle();

expect(getModal()).toBeInTheDocument();
});

it('should set the openButton of the modal to the button', async () => {
const {getButton, getModal} = await renderUserActionsToggle();

expect(getModal()?.openButton).toBe(getButton());
});

it('should set the userId of the modal to the component prop', async () => {
const {getModal} = await renderUserActionsToggle({userId: 'custom-user'});

expect(getModal()?.userId).toBe('custom-user');
});

it('should set the ticketCreationDateTime of the modal to the component prop', async () => {
const {getModal} = await renderUserActionsToggle({
ticketCreationDateTime: '2024-06-15T12:00:00Z',
});

expect(getModal()?.ticketCreationDateTime).toBe('2024-06-15T12:00:00Z');
});

it('should set the excludedCustomActions of the modal to the component prop', async () => {
const {getModal} = await renderUserActionsToggle({
excludedCustomActions: ['action1', 'action2'],
});

expect(getModal()?.excludedCustomActions).toEqual(['action1', 'action2']);
});

it('should open the modal when the button is clicked', async () => {
const {getButton, getModal} = await renderUserActionsToggle();

await userEvent.click(getButton()!);

expect(getModal()?.isOpen).toBe(true);
});

it('should call logOpenUserActions when the button is clicked', async () => {
const {element, getButton} = await renderUserActionsToggle();
const logOpenUserActionsSpy = vi.spyOn(
element.userActions,
'logOpenUserActions'
);

await userEvent.click(getButton()!);

expect(logOpenUserActionsSpy).toHaveBeenCalled();
});
});
});
Loading