Skip to content
Merged
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
5 changes: 3 additions & 2 deletions packages/contact-center/cc-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"dependencies": {
"@momentum-ui/illustrations": "^1.24.0",
"@r2wc/react-to-web-component": "2.0.3",
"@webex/cc-store": "workspace:*"
"@webex/cc-store": "workspace:*",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing this ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its not removed, just added another dependency and this line it added a comma that why it is showing like this

"@webex/cc-ui-logging": "workspace:*"
},
"devDependencies": {
"@babel/core": "7.25.2",
Expand Down Expand Up @@ -75,4 +76,4 @@
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
updateDialNumberLabel,
handleCCSignoutKeyDown,
} from './station-login.utils';
import {withMetrics} from '@webex/cc-ui-logging';

const StationLoginComponent: React.FunctionComponent<StationLoginComponentProps> = (props) => {
const {
Expand Down Expand Up @@ -344,4 +345,5 @@ const StationLoginComponent: React.FunctionComponent<StationLoginComponentProps>
);
};

export default StationLoginComponent;
const StationLoginComponentWithMetrics = withMetrics(StationLoginComponent, 'StationLogin');
export default StationLoginComponentWithMetrics;
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getSelectedKey,
buildDropdownItems,
} from './user-state.utils';
import {withMetrics} from '@webex/cc-ui-logging';

const UserStateComponent: React.FunctionComponent<UserStateComponentsProps> = (props) => {
const {
Expand Down Expand Up @@ -103,4 +104,5 @@ const UserStateComponent: React.FunctionComponent<UserStateComponentsProps> = (p
);
};

export default UserStateComponent;
const UserStateComponentWithMetrics = withMetrics(UserStateComponent, 'UserState');
export default UserStateComponentWithMetrics;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
filterButtonsForConsultation,
updateCallStateFromTask,
} from './call-control.utils';
import {withMetrics} from '@webex/cc-ui-logging';

function CallControlComponent(props: CallControlComponentProps) {
const [selectedWrapupReason, setSelectedWrapupReason] = useState<string | null>(null);
Expand Down Expand Up @@ -322,4 +323,5 @@ function CallControlComponent(props: CallControlComponentProps) {
);
}

export default CallControlComponent;
const CallControlComponentWithMetrics = withMetrics(CallControlComponent, 'CallControl');
export default CallControlComponentWithMetrics;
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
CUSTOMER_NAME,
RONA,
} from '../constants';
import {withMetrics} from '@webex/cc-ui-logging';

const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) => {
const {
Expand Down Expand Up @@ -235,4 +236,5 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
);
};

export default CallControlCADComponent;
const CallControlCADComponentWithMetrics = withMetrics(CallControlCADComponent, 'CallControlCAD');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CallControlComponent is already wrapper in withMetrics, and we are again wrapping the component here with withMetrics. See if the logging is happening twice? If it is then it is wrong. we only need it once

export default CallControlCADComponentWithMetrics;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import {IncomingTaskComponentProps, MEDIA_CHANNEL} from '../task.types';
import Task from '../Task';
import {withMetrics} from '@webex/cc-ui-logging';
import {extractIncomingTaskData} from './incoming-task.utils';

const IncomingTaskComponent: React.FunctionComponent<IncomingTaskComponentProps> = (props) => {
Expand Down Expand Up @@ -37,4 +38,5 @@ const IncomingTaskComponent: React.FunctionComponent<IncomingTaskComponentProps>
);
};

export default IncomingTaskComponent;
const IncomingTaskComponentWithMetrics = withMetrics(IncomingTaskComponent, 'IncomingTask');
export default IncomingTaskComponentWithMetrics;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {useState} from 'react';
import {OutdialCallComponentProps} from '../task.types';
import './outdial-call.style.scss';
import {withMetrics} from '@webex/cc-ui-logging';

const OutdialCallComponent: React.FunctionComponent<OutdialCallComponentProps> = (props) => {
const {startOutdial} = props;
Expand Down Expand Up @@ -49,4 +50,5 @@ const OutdialCallComponent: React.FunctionComponent<OutdialCallComponentProps> =
);
};

export default OutdialCallComponent;
const OutdialCallComponentWithMetrics = withMetrics(OutdialCallComponent, 'OutdialCall');
export default OutdialCallComponentWithMetrics;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
isCurrentTaskSelected,
} from './task-list.utils';
import './styles.scss';
import {withMetrics} from '@webex/cc-ui-logging';

const TaskListComponent: React.FunctionComponent<TaskListComponentProps> = (props) => {
const {currentTask, taskList, acceptTask, declineTask, isBrowser, onTaskSelect, logger} = props;
Expand Down Expand Up @@ -57,4 +58,5 @@ const TaskListComponent: React.FunctionComponent<TaskListComponentProps> = (prop
);
};

export default TaskListComponent;
const TaskListComponentWithMetrics = withMetrics(TaskListComponent, 'TaskList');
export default TaskListComponentWithMetrics;
2 changes: 1 addition & 1 deletion packages/contact-center/cc-widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,4 @@
"^.+\\.(css|less|scss)$": "babel-jest"
}
}
}
}
2 changes: 1 addition & 1 deletion packages/contact-center/station-login/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const StationLogin: React.FunctionComponent<StationLoginProps> = observer(
teamId,
setTeamId,
} = store;

const result = useStationLogin({
cc,
onLogin,
Expand Down Expand Up @@ -52,6 +53,7 @@ const StationLogin: React.FunctionComponent<StationLoginProps> = observer(
logger,
profileMode,
};

return <StationLoginComponent {...props} />;
}
);
Expand Down
2 changes: 1 addition & 1 deletion packages/contact-center/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@
"webpack-cli": "5.1.4",
"webpack-merge": "6.0.1"
}
}
}
2 changes: 1 addition & 1 deletion packages/contact-center/task/package.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why were these changes introduced? Can we revert them ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are linting changes, this needs to be fixed in the tooling. We already have a task for this

1 change: 1 addition & 0 deletions packages/contact-center/task/src/CallControl/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const CallControl: React.FunctionComponent<CallControlProps> = observer(
allowConsultToQueue,
logger,
};

return <CallControlComponent {...result} />;
}
);
Expand Down
5 changes: 4 additions & 1 deletion packages/contact-center/task/tests/OutdialCall/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {OutdialCall} from '../../src/OutdialCall';
jest.mock('@webex/cc-store', () => ({
cc: {},
logger: {
// Adding mock logger methods
info: jest.fn(),
log: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
},
}));

Expand All @@ -23,6 +24,8 @@ describe('OutdialCall Component', () => {
logger: {
info: expect.any(Function),
error: expect.any(Function),
log: expect.any(Function),
warn: expect.any(Function),
},
});
});
Expand Down
16 changes: 0 additions & 16 deletions packages/contact-center/task/tests/TaskList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {render, screen, cleanup} from '@testing-library/react';
import '@testing-library/jest-dom';
import {TaskList} from '../../src/TaskList';
import * as helper from '../../src/helper';
import * as components from '@webex/cc-components';
import store from '@webex/cc-store';

// Mock `@webex/cc-store`.
Expand Down Expand Up @@ -31,7 +30,6 @@ jest.mock('@webex/cc-store', () => ({
}));

describe('TaskList Component', () => {
const taskListComponentSpy = jest.spyOn(components, 'TaskListComponent');
const helperSpy = jest.spyOn(helper, 'useTaskList');
afterEach(cleanup);

Expand All @@ -42,20 +40,6 @@ describe('TaskList Component', () => {
const taskListPresentational = screen.getByTestId('task-list');
expect(taskListPresentational).toBeInTheDocument();

// Verify that `TaskListPresentational` is called with the correct props.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this removed ?

expect(taskListComponentSpy).toHaveBeenCalledWith(
{
currentTask: undefined,
isBrowser: true,
logger: store.logger,
taskList: taskListMock,
acceptTask: expect.any(Function),
declineTask: expect.any(Function),
onTaskSelect: expect.any(Function),
},
{}
);

// Verify that `useTaskList` is called with the correct arguments.
expect(helperSpy).toHaveBeenCalledWith({
cc: store.cc,
Expand Down
6 changes: 6 additions & 0 deletions packages/contact-center/ui-logging/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const jestConfig = require('../../../jest.config.js');

jestConfig.rootDir = '../../../';
jestConfig.testMatch = ['**/ui-logging/tests/**/*.ts', '**/ui-logging/tests/**/*.tsx'];

module.exports = jestConfig;
36 changes: 36 additions & 0 deletions packages/contact-center/ui-logging/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@webex/cc-ui-logging",
"version": "1.0.0",
"description": "UI metrics tracking for Webex widgets",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build:src": "webpack --mode=development",
"clean": "rm -rf dist",
"clean:dist": "rm -rf dist",
"test:unit": "jest --coverage",
"test:styles": "echo 'No styles to test'"
},
"dependencies": {
"@webex/cc-store": "workspace:*"
},
"devDependencies": {
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.2",
"@testing-library/react": "16.0.1",
"@types/jest": "29.5.14",
"@types/react-test-renderer": "18",
"@webex/test-fixtures": "workspace:*",
"babel-jest": "29.7.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"typescript": "^5.6.3",
"uuid": "^9.0.0",
"webpack": "^5.96.1",
"webpack-cli": "^5.1.4"
},
"peerDependencies": {
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
}
5 changes: 5 additions & 0 deletions packages/contact-center/ui-logging/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import withMetrics from './withMetrics';
import {WidgetMetrics} from './metricsLogger';

export {withMetrics};
export type {WidgetMetrics};
102 changes: 102 additions & 0 deletions packages/contact-center/ui-logging/src/metricsLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import store from '@webex/cc-store';

export type WidgetMetrics = {
widgetName: string;
event: 'WIDGET_MOUNTED' | 'ERROR' | 'WIDGET_UNMOUNTED' | 'PROPS_UPDATED';
props?: Record<string, any>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no way to avoid using any for props and additionalContext ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we dont for now. props can be from any widget. If I use the actual types from the widgets it would end up creating a circular dependency. 1 thing we can do is move all the types to store but that would go against out design

timestamp: number;
additionalContext?: Record<string, any>;
};

/**
* Logs UI metrics for contact center widgets.
*
* This function logs widget lifecycle events and errors to help monitor
* widget performance and user interactions. If no logger is available,
* it will emit a warning and skip logging.
*
* @param metric - The metrics data to be logged
* @param metric.widgetName - Name of the widget generating the metric
* @param metric.event - Type of event being logged
* @param metric.props - Optional properties associated with the widget
* @param metric.timestamp - Unix timestamp when the event occurred
* @param metric.additionalContext - Optional additional context data
*
* @example
* ```typescript
* logMetrics({
* widgetName: 'CallControl',
* event: 'WIDGET_MOUNTED',
* props: { callId: '123' },
* timestamp: Date.now(),
* additionalContext: { userId: 'user123' }
* });
* ```
*/
export const logMetrics = (metric: WidgetMetrics) => {
if (!store.logger) {
console.warn('CC-Widgets: UI Metrics: No logger found');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid console.warn ? We can use logger.warn

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this scenario is for when logger is not present. so we are just logging a warning

return;
}
store.logger.log(`CC-Widgets: UI Metrics: ${JSON.stringify(metric, null, 2)}`, {
module: 'metricsLogger.tsx',
method: 'logMetrics',
});
};

/**
* Determines if props have changed between two objects using shallow comparison.
*
* This function performs a shallow comparison between two objects to detect changes.
* It compares object keys and primitive values, but does not recursively compare
* nested objects. This is useful for determining when to log metrics based on prop changes.
*
* @param prev - The previous props object
* @param next - The next props object to compare against
* @returns `true` if the props have changed, `false` otherwise
*
* @example
* ```typescript
* const oldProps = { name: 'John', age: 30 };
* const newProps = { name: 'John', age: 31 };
*
* if (havePropsChanged(oldProps, newProps)) {
* // Props have changed, log metrics
* logMetrics({
* widgetName: 'UserProfile',
* event: 'WIDGET_MOUNTED',
* props: newProps,
* timestamp: Date.now()
* });
* } * ```
*
* @remarks
* The function is important as we dont sanitize our props right now.
* Once we start sanitizing we can do a deep comparison. This is used to only re-render
* the HOC if the props have changed.
*/
export function havePropsChanged(prev: any, next: any): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here about any

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

if (prev === next) return false;

// Do shallow comparison
if (typeof prev !== typeof next) return true;
if (!prev || !next) return prev !== next;

const prevKeys = Object.keys(prev);
const nextKeys = Object.keys(next);

if (prevKeys.length !== nextKeys.length) return true;

// Check if any primitive values changed
for (const key of prevKeys) {
const prevVal = prev[key];
const nextVal = next[key];

if (prevVal === nextVal) continue;
if (typeof prevVal !== 'object' || prevVal === null) return true;
if (typeof nextVal !== 'object' || nextVal === null) return true;
}

// All shallow comparisons passed, consider props unchanged
return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do we log metrics for prop updated case or is that what that JIra is added in comment ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JIRA has been added @ packages/contact-center/ui-logging/src/withMetrics.tsx

}
Loading
Loading