Skip to content

Commit

Permalink
Merge branch 'develop' into feat/sidepanelNavigation-feature-preview
Browse files Browse the repository at this point in the history
  • Loading branch information
ggazzo authored Aug 29, 2024
2 parents d27d9d4 + 0f5a39e commit c6356e9
Show file tree
Hide file tree
Showing 43 changed files with 460 additions and 234 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-rings-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Restored tooltips to the unit edit department field selected options
5 changes: 5 additions & 0 deletions .changeset/wise-avocados-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixes an issue where multi-step modals were closing unexpectedly
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const GenericModal = ({
{tagline && <Modal.Tagline>{tagline}</Modal.Tagline>}
<Modal.Title id={`${genericModalId}-title`}>{title ?? t('Are_you_sure')}</Modal.Title>
</Modal.HeaderText>
<Modal.Close aria-label={t('Close')} onClick={handleCloseButtonClick} />
{onClose && <Modal.Close aria-label={t('Close')} onClick={handleCloseButtonClick} />}
</Modal.Header>
<Modal.Content fontScale='p2'>{children}</Modal.Content>
<Modal.Footer justifyContent={dontAskAgain ? 'space-between' : 'end'}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import { Skeleton } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import React from 'react';

import GenericModal from './GenericModal';

const GenericModalSkeleton = ({ onClose, ...props }: ComponentProps<typeof GenericModal>) => {
const t = useTranslation();

return (
<GenericModal
{...props}
variant='warning'
onClose={onClose}
title={<Skeleton width='50%' />}
confirmText={t('Cancel')}
onConfirm={onClose}
>
<Skeleton width='full' />
</GenericModal>
);
};
const GenericModalSkeleton = (props: ComponentProps<typeof GenericModal>) => (
<GenericModal {...props} icon={null} title={<Skeleton width='50%' />}>
<Skeleton width='full' />
</GenericModal>
);

export default GenericModalSkeleton;
Original file line number Diff line number Diff line change
@@ -1,53 +1,29 @@
import { UserStatus, type IOmnichannelRoomFromAppSource } from '@rocket.chat/core-typings';
import { type IOmnichannelSourceFromApp } from '@rocket.chat/core-typings';
import { Icon, Box } from '@rocket.chat/fuselage';
import type { ComponentProps, ReactElement } from 'react';
import type { ComponentProps } from 'react';
import React from 'react';

import { AsyncStatePhase } from '../../../lib/asyncState/AsyncStatePhase';
import { useOmnichannelRoomIcon } from './context/OmnichannelRoomIconContext';

const colors = {
busy: 'status-font-on-danger',
away: 'status-font-on-warning',
online: 'status-font-on-success',
offline: 'annotation',
disabled: 'annotation',
type OmnichannelAppSourceRoomIconProps = {
source: IOmnichannelSourceFromApp;
color: ComponentProps<typeof Box>['color'];
size: ComponentProps<typeof Icon>['size'];
placement: 'sidebar' | 'default';
};

const convertBoxSizeToNumber = (boxSize: ComponentProps<typeof Icon>['size']): number => {
switch (boxSize) {
case 'x20': {
return 20;
}
case 'x24': {
return 24;
}
case 'x16':
default: {
return 16;
}
}
};
export const OmnichannelAppSourceRoomIcon = ({ source, color, size, placement }: OmnichannelAppSourceRoomIconProps) => {
const icon = (placement === 'sidebar' && source.sidebarIcon) || source.defaultIcon;
const { phase, value } = useOmnichannelRoomIcon(source.id, icon || '');

export const OmnichannelAppSourceRoomIcon = ({
room,
size = 16,
placement = 'default',
}: {
room: IOmnichannelRoomFromAppSource;
size: ComponentProps<typeof Icon>['size'];
placement: 'sidebar' | 'default';
}): ReactElement => {
const color = colors[room.v.status || UserStatus.OFFLINE];
const icon = (placement === 'sidebar' && room.source.sidebarIcon) || room.source.defaultIcon;
const { phase, value } = useOmnichannelRoomIcon(room.source.id, icon || '');
const fontSize = convertBoxSizeToNumber(size);
if ([AsyncStatePhase.REJECTED, AsyncStatePhase.LOADING].includes(phase)) {
return <Icon name='headset' size={size} color={color} />;
}

return (
<Box size={fontSize} color={color}>
<Box is='svg' size={fontSize} aria-hidden='true'>
<Box size={size} color={color}>
<Box is='svg' size={size} aria-hidden='true'>
<Box is='use' href={`#${value}`} />
</Box>
</Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import type { IOmnichannelSource } from '@rocket.chat/core-typings';
import { Icon } from '@rocket.chat/fuselage';
import type { ComponentProps, ReactElement } from 'react';
import type { ComponentProps } from 'react';
import React from 'react';

const colors = {
busy: 'status-font-on-danger',
away: 'status-font-on-warning',
online: 'status-font-on-success',
offline: 'annotation',
disabled: 'annotation',
};

const iconMap = {
widget: 'livechat',
email: 'mail',
Expand All @@ -20,13 +12,13 @@ const iconMap = {
other: 'headset',
} as const;

export const OmnichannelCoreSourceRoomIcon = ({
room,
size = 'x16',
}: {
room: IOmnichannelRoom;
type OmnichannelCoreSourceRoomIconProps = {
source: IOmnichannelSource;
color: ComponentProps<typeof Icon>['color'];
size: ComponentProps<typeof Icon>['size'];
}): ReactElement => {
const icon = iconMap[room.source?.type || 'other'] || 'headset';
return <Icon name={icon} size={size} color={colors[room.v?.status || 'offline']} />;
};

export const OmnichannelCoreSourceRoomIcon = ({ source, color, size }: OmnichannelCoreSourceRoomIconProps) => {
const icon = iconMap[source?.type || 'other'] || 'headset';
return <Icon name={icon} size={size} color={color} />;
};
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import { isOmnichannelRoomFromAppSource } from '@rocket.chat/core-typings';
import type { IOmnichannelSource } from '@rocket.chat/core-typings';
import { UserStatus, isOmnichannelSourceFromApp } from '@rocket.chat/core-typings';
import type { Icon } from '@rocket.chat/fuselage';
import type { ComponentProps, ReactElement } from 'react';
import type { ComponentProps } from 'react';
import React from 'react';

import { OmnichannelAppSourceRoomIcon } from './OmnichannelAppSourceRoomIcon';
import { OmnichannelCoreSourceRoomIcon } from './OmnichannelCoreSourceRoomIcon';

export const OmnichannelRoomIcon = ({
room,
size,
placement = 'default',
}: {
room: IOmnichannelRoom;
const colors = {
busy: 'status-font-on-danger',
away: 'status-font-on-warning',
online: 'status-font-on-success',
offline: 'annotation',
disabled: 'annotation',
} as const;

type OmnichannelRoomIconProps = {
source: IOmnichannelSource;
color?: ComponentProps<typeof Icon>['color'];
status?: UserStatus;
size: ComponentProps<typeof Icon>['size'];
placement: 'sidebar' | 'default';
}): ReactElement => {
if (isOmnichannelRoomFromAppSource(room)) {
return <OmnichannelAppSourceRoomIcon placement={placement} room={room} size={size} />;
placement?: 'sidebar' | 'default';
};

export const OmnichannelRoomIcon = ({ source, color, status, size = 'x16', placement = 'default' }: OmnichannelRoomIconProps) => {
const iconColor = color ?? colors[status || UserStatus.OFFLINE];

if (isOmnichannelSourceFromApp(source)) {
return <OmnichannelAppSourceRoomIcon source={source} placement={placement} color={iconColor} size={size} />;
}
return <OmnichannelCoreSourceRoomIcon room={room} size={size} />;

return <OmnichannelCoreSourceRoomIcon source={source} color={iconColor} size={size} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import DOMPurify from 'dompurify';

import { sdk } from '../../../../../app/utils/client/lib/SDKClient';

const OmnichannelRoomIcon = new (class extends Emitter {
const OmnichannelRoomIconManager = new (class extends Emitter {
icons = new Map<string, string>();

constructor() {
Expand All @@ -23,7 +23,7 @@ const OmnichannelRoomIcon = new (class extends Emitter {
sdk.rest
.send(`/apps/public/${appId}/get-sidebar-icon?icon=${icon}`, 'GET')
.then((response: any) => {
response.text().then((text: any) => {
response.text().then((text: string) => {
this.icons.set(
`${appId}-${icon}`,
DOMPurify.sanitize(text, {
Expand All @@ -44,4 +44,4 @@ const OmnichannelRoomIcon = new (class extends Emitter {
}
})();

export default OmnichannelRoomIcon;
export default OmnichannelRoomIconManager;
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim';
import type { AsyncState } from '../../../../lib/asyncState/AsyncState';
import { AsyncStatePhase } from '../../../../lib/asyncState/AsyncStatePhase';
import { OmnichannelRoomIconContext } from '../context/OmnichannelRoomIconContext';
import OmnichannelRoomIcon from '../lib/OmnichannelRoomIcon';
import OmnichannelRoomIconManager from '../lib/OmnichannelRoomIconManager';

let icons = Array.from(OmnichannelRoomIcon.icons.values());
let icons = Array.from(OmnichannelRoomIconManager.icons.values());

type OmnichannelRoomIconProviderProps = {
children?: ReactNode;
Expand All @@ -18,8 +18,8 @@ export const OmnichannelRoomIconProvider = ({ children }: OmnichannelRoomIconPro
const svgIcons = useSyncExternalStore(
useCallback(
(callback): (() => void) =>
OmnichannelRoomIcon.on('change', () => {
icons = Array.from(OmnichannelRoomIcon.icons.values());
OmnichannelRoomIconManager.on('change', () => {
icons = Array.from(OmnichannelRoomIconManager.icons.values());
callback();
}),
[],
Expand All @@ -31,7 +31,7 @@ export const OmnichannelRoomIconProvider = ({ children }: OmnichannelRoomIconPro
<OmnichannelRoomIconContext.Provider
value={useMemo(() => {
const extractSnapshot = (app: string, iconName: string): AsyncState<string> => {
const icon = OmnichannelRoomIcon.get(app, iconName);
const icon = OmnichannelRoomIconManager.get(app, iconName);

if (icon) {
return {
Expand All @@ -57,7 +57,7 @@ export const OmnichannelRoomIconProvider = ({ children }: OmnichannelRoomIconPro
iconName: string,
): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => AsyncState<string>] => [
(callback): (() => void) =>
OmnichannelRoomIcon.on(`${app}-${iconName}`, () => {
OmnichannelRoomIconManager.on(`${app}-${iconName}`, () => {
snapshots.set(`${app}-${iconName}`, extractSnapshot(app, iconName));

// Then we call the callback (onStoreChange), signaling React to re-render
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/client/components/RoomIcon/RoomIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IOmnichannelRoom, IRoom } from '@rocket.chat/core-typings';
import type { IRoom } from '@rocket.chat/core-typings';
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
import { Icon } from '@rocket.chat/fuselage';
import type { ComponentProps, ReactElement } from 'react';
Expand All @@ -24,8 +24,8 @@ export const RoomIcon = ({
return <Icon name='phone' size={size} />;
}

if (isOmnichannelRoom(room as IRoom)) {
return <OmnichannelRoomIcon placement={placement} room={room as IOmnichannelRoom} size={size} />;
if (isOmnichannelRoom(room)) {
return <OmnichannelRoomIcon placement={placement} source={room.source} status={room.v?.status} size={size} />;
}

if (isValidElement<any>(iconPropsOrReactNode)) {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/omnichannel/units/UnitEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ const UnitEdit = ({ unitData, unitMonitors, unitDepartments }: UnitEditProps) =>
value={value}
onChange={onChange}
onBlur={onBlur}
withTitle={false}
withTitle
filter={departmentsFilter}
setFilter={setDepartmentsFilter}
options={departmentsOptions}
Expand Down
67 changes: 67 additions & 0 deletions apps/meteor/client/sidebar/RoomMenu.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { RoomType } from '@rocket.chat/core-typings';
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import userEvent from '@testing-library/user-event';
import React from 'react';

import RoomMenu from './RoomMenu';

jest.mock('../../client/lib/rooms/roomCoordinator', () => ({
roomCoordinator: {
getRoomDirectives: () => ({
getUiText: () => 'leaveWarning',
}),
},
}));

jest.mock('../../app/ui-utils/client', () => ({
LegacyRoomManager: {
close: jest.fn(),
},
}));

const defaultProps = {
rid: 'roomId',
type: 'c' as RoomType,
hideDefaultOptions: false,
placement: 'right-start',
};

const renderOptions = {
wrapper: mockAppRoot()
.withTranslations('en', 'core', {
Hide: 'Hide',
Mark_unread: 'Mark Unread',
Favorite: 'Favorite',
Leave_room: 'Leave',
})
.withSetting('Favorite_Rooms', true)
.withPermission('leave-c')
.withPermission('leave-p')
.build(),
legacyRoot: true,
};

it('should display all the menu options for regular rooms', async () => {
render(<RoomMenu {...defaultProps} />, renderOptions);

const menu = screen.queryByRole('button');
await userEvent.click(menu as HTMLElement);

expect(await screen.findByRole('option', { name: 'Hide' })).toBeInTheDocument();
expect(await screen.findByRole('option', { name: 'Favorite' })).toBeInTheDocument();
expect(await screen.findByRole('option', { name: 'Mark Unread' })).toBeInTheDocument();
expect(await screen.findByRole('option', { name: 'Leave' })).toBeInTheDocument();
});

it('should display only mark unread and favorite for omnichannel rooms', async () => {
render(<RoomMenu {...defaultProps} type='l' />, renderOptions);

const menu = screen.queryByRole('button');
await userEvent.click(menu as HTMLElement);

expect(await screen.findAllByRole('option')).toHaveLength(2);
expect(screen.queryByRole('option', { name: 'Hide' })).not.toBeInTheDocument();
expect(screen.queryByRole('option', { name: 'Leave' })).not.toBeInTheDocument();
});
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const SourceField = ({ room }: SourceFieldProps) => {
<Label>{t('Channel')}</Label>
<Info>
<Box display='flex' alignItems='center'>
<OmnichannelRoomIcon room={room} size='x24' placement='default' />
<OmnichannelRoomIcon source={room.source} status={room.v.status} size='x24' />
<Label mi={8} mbe='0'>
{defaultTypesLabels[room.source.type] || roomSource}
</Label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export const Default: ComponentStory<typeof CounterContainer> = (args) => <Count
Default.storyName = 'CounterContainer';
Default.args = {
initialData: [
{ title: 'total conversations', value: 10 },
{ title: 'open conversations', value: 10 },
{ title: 'total messages', value: 10 },
{ title: 'total visitors' },
{ title: 'Total_conversations', value: 10 },
{ title: 'Open_conversations', value: 10 },
{ title: 'Total_messages', value: 10 },
{ title: 'Total_visitors', value: 0 },
],
data: [],
data: { totalizers: [] },
};
Loading

0 comments on commit c6356e9

Please sign in to comment.