Skip to content

Commit

Permalink
Merge branch 'develop' into feat/cloud-workspace-id
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] authored May 2, 2024
2 parents 65d1bc6 + 972b5b8 commit 0f1bebf
Show file tree
Hide file tree
Showing 34 changed files with 794 additions and 215 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-starfishes-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixed a problem in how server was processing errors that was sending 2 ephemeral error messages when @all or @here were used while they were disabled via permissions
6 changes: 6 additions & 0 deletions .changeset/lazy-gorilas-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/i18n": patch
---

Fixed an issue with object storage settings that was not allowing admins to decide if files generated via "Export conversation" feature were being proxied through server or not.
5 changes: 5 additions & 0 deletions .changeset/shiny-crabs-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/fuselage-ui-kit': patch
---

Fix translation param on video conf joined message
3 changes: 2 additions & 1 deletion apps/meteor/app/lib/server/methods/sendMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ export async function executeSendMessage(uid: IUser['_id'], message: AtLeast<IMe
SystemLogger.error({ msg: 'Error sending message:', err });

const errorMessage = typeof err === 'string' ? err : err.error || err.message;
const errorContext = err.details ?? {};
void api.broadcast('notify.ephemeralMessage', uid, message.rid, {
msg: i18n.t(errorMessage, { lng: user.language }),
msg: i18n.t(errorMessage, errorContext, user.language),
});

if (typeof err === 'string') {
Expand Down
18 changes: 18 additions & 0 deletions apps/meteor/client/components/WarningModal.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { render, screen } from '@testing-library/react';
import React from 'react';

import WarningModal from './WarningModal';

import '@testing-library/jest-dom';

it('should look good', async () => {
render(<WarningModal text='text' confirmText='confirm' cancelText='cancel' confirm={() => undefined} close={() => undefined} />, {
wrapper: mockAppRoot().build(),
});

expect(screen.getByRole('heading')).toHaveTextContent('Are_you_sure');
expect(screen.getByRole('button', { name: 'cancel' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'confirm' })).toBeInTheDocument();
expect(screen.getByText('text')).toBeInTheDocument();
});
14 changes: 7 additions & 7 deletions apps/meteor/client/components/WarningModal.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { Button, Modal } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import type { ReactElement, ReactNode } from 'react';
import React from 'react';

type WarningModalProps = {
text: string;
confirmText: string;
text: ReactNode;
confirmText: ReactNode;
cancelText?: ReactNode;
confirm: () => void;
cancel?: () => void;
close: () => void;
cancel: () => void;
cancelText: string;
confirm: () => Promise<void>;
};

const WarningModal = ({ text, confirmText, close, cancel, cancelText, confirm, ...props }: WarningModalProps): ReactElement => {
const t = useTranslation();
return (
<Modal {...props}>
<Modal open {...props}>
<Modal.Header>
<Modal.Icon color='danger' name='modal-warning' />
<Modal.Title>{t('Are_you_sure')}</Modal.Title>
Expand Down
24 changes: 24 additions & 0 deletions apps/meteor/client/contexts/AppsContext.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
import type { AppClientManager } from '@rocket.chat/apps-engine/client/AppClientManager';
import type { IPermission } from '@rocket.chat/apps-engine/definition/permissions/IPermission';
import type { ISetting } from '@rocket.chat/apps-engine/definition/settings';
import type { Serialized } from '@rocket.chat/core-typings';
import { createContext } from 'react';

import type { IAppExternalURL, ICategory } from '../../ee/client/apps/@types/IOrchestrator';
import type { AsyncState } from '../lib/asyncState';
import { AsyncStatePhase } from '../lib/asyncState';
import type { App } from '../views/marketplace/types';

export interface IAppsOrchestrator {
load(): Promise<void>;
getAppClientManager(): AppClientManager;
handleError(error: unknown): void;
getInstalledApps(): Promise<App[]>;
getAppsFromMarketplace(isAdminUser?: boolean): Promise<App[]>;
getAppsOnBundle(bundleId: string): Promise<App[]>;
getApp(appId: string): Promise<App>;
setAppSettings(appId: string, settings: ISetting[]): Promise<void>;
installApp(appId: string, version: string, permissionsGranted?: IPermission[]): Promise<App>;
updateApp(appId: string, version: string, permissionsGranted?: IPermission[]): Promise<App>;
buildExternalUrl(appId: string, purchaseType?: 'buy' | 'subscription', details?: boolean): Promise<IAppExternalURL>;
buildExternalAppRequest(appId: string): Promise<{ url: string }>;
buildIncompatibleExternalUrl(appId: string, appVersion: string, action: string): Promise<IAppExternalURL>;
getCategories(): Promise<Serialized<ICategory[]>>;
}

export type AppsContextValue = {
installedApps: Omit<AsyncState<{ apps: App[] }>, 'error'>;
marketplaceApps: Omit<AsyncState<{ apps: App[] }>, 'error'>;
privateApps: Omit<AsyncState<{ apps: App[] }>, 'error'>;
reload: () => Promise<void>;
orchestrator?: IAppsOrchestrator;
};

export const AppsContext = createContext<AppsContextValue>({
Expand All @@ -25,4 +48,5 @@ export const AppsContext = createContext<AppsContextValue>({
value: undefined,
},
reload: () => Promise.resolve(),
orchestrator: undefined,
});
10 changes: 8 additions & 2 deletions apps/meteor/client/providers/AppsProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useDebouncedCallback } from '@rocket.chat/fuselage-hooks';
import { usePermission, useStream } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import type { FC } from 'react';
import type { ReactNode } from 'react';
import React, { useEffect } from 'react';

import { AppClientOrchestratorInstance } from '../../ee/client/apps/orchestrator';
Expand All @@ -28,7 +28,11 @@ const getAppState = (
value: { apps: apps || [] },
});

const AppsProvider: FC = ({ children }) => {
type AppsProviderProps = {
children: ReactNode;
};

const AppsProvider = ({ children }: AppsProviderProps) => {
const isAdminUser = usePermission('manage-apps');

const queryClient = useQueryClient();
Expand Down Expand Up @@ -160,8 +164,10 @@ const AppsProvider: FC = ({ children }) => {
reload: async () => {
await Promise.all([queryClient.invalidateQueries(['marketplace'])]);
},
orchestrator: AppClientOrchestratorInstance,
}}
/>
);
};

export default AppsProvider;
1 change: 0 additions & 1 deletion apps/meteor/client/sidebar/RoomMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ const RoomMenu = ({
text={t(warnText as TranslationKey, name)}
confirmText={t('Leave_room')}
close={closeModal}
cancel={closeModal}
cancelText={t('Cancel')}
confirm={leave}
/>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { faker } from '@faker-js/faker';
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { render, screen } from '@testing-library/react';
import React from 'react';
import '@testing-library/jest-dom';

import { mockedAppsContext } from '../../../../../../tests/mocks/client/marketplace';
import { createFakeApp, createFakeLicenseInfo } from '../../../../../../tests/mocks/data';
import AppStatus from './AppStatus';

it('should look good', async () => {
const app = createFakeApp();

render(<AppStatus app={app} showStatus isAppDetailsPage />, {
wrapper: mockAppRoot()
.withJohnDoe()
.withEndpoint('GET', '/apps/count', async () => ({
maxMarketplaceApps: faker.number.int({ min: 0 }),
installedApps: faker.number.int({ min: 0 }),
maxPrivateApps: faker.number.int({ min: 0 }),
totalMarketplaceEnabled: faker.number.int({ min: 0 }),
totalPrivateEnabled: faker.number.int({ min: 0 }),
}))
.withEndpoint('GET', '/v1/licenses.info', async () => ({
license: createFakeLicenseInfo(),
}))
.wrap(mockedAppsContext)
.build(),
});

screen.getByRole('button', { name: 'Request' }).click();
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import semver from 'semver';
import { useIsEnterprise } from '../../../../../hooks/useIsEnterprise';
import type { appStatusSpanResponseProps } from '../../../helpers';
import { appButtonProps, appMultiStatusProps } from '../../../helpers';
import { marketplaceActions } from '../../../helpers/marketplaceActions';
import type { AppInstallationHandlerParams } from '../../../hooks/useAppInstallationHandler';
import { useAppInstallationHandler } from '../../../hooks/useAppInstallationHandler';
import { useMarketplaceActions } from '../../../hooks/useMarketplaceActions';
import AppStatusPriceDisplay from './AppStatusPriceDisplay';

type AppStatusProps = {
Expand Down Expand Up @@ -48,18 +48,22 @@ const AppStatus = ({ app, showStatus = true, isAppDetailsPage, installed, ...pro

const action = button?.action;

const marketplaceActions = useMarketplaceActions();

const confirmAction = useCallback<AppInstallationHandlerParams['onSuccess']>(
async (action, permissionsGranted) => {
if (action !== 'request') {
setPurchased(true);
await marketplaceActions[action]({ ...app, permissionsGranted });
} else {
setEndUserRequested(true);
if (action) {
if (action !== 'request') {
setPurchased(true);
await marketplaceActions[action]({ ...app, permissionsGranted });
} else {
setEndUserRequested(true);
}
}

setLoading(false);
},
[app, setLoading, setPurchased],
[app, marketplaceActions, setLoading, setPurchased],
);

const cancelAction = useCallback(() => {
Expand Down
30 changes: 30 additions & 0 deletions apps/meteor/client/views/marketplace/AppMenu.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { faker } from '@faker-js/faker';
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { render, screen } from '@testing-library/react';
import React from 'react';
import '@testing-library/jest-dom';

import { mockedAppsContext } from '../../../tests/mocks/client/marketplace';
import { createFakeApp } from '../../../tests/mocks/data';
import AppMenu from './AppMenu';

describe('without app details', () => {
it('should look good', async () => {
const app = createFakeApp();

render(<AppMenu app={app} isAppDetailsPage={false} />, {
wrapper: mockAppRoot()
.withEndpoint('GET', '/apps/count', async () => ({
maxMarketplaceApps: faker.number.int({ min: 0 }),
installedApps: faker.number.int({ min: 0 }),
maxPrivateApps: faker.number.int({ min: 0 }),
totalMarketplaceEnabled: faker.number.int({ min: 0 }),
totalPrivateEnabled: faker.number.int({ min: 0 }),
}))
.wrap(mockedAppsContext)
.build(),
});

expect(screen.getByRole('button', { name: 'More_options' })).toBeInTheDocument();
});
});
48 changes: 48 additions & 0 deletions apps/meteor/client/views/marketplace/AppMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { App } from '@rocket.chat/core-typings';
import { MenuItem, MenuItemContent, MenuSection, MenuV2, Skeleton } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { memo } from 'react';

import { useHandleMenuAction } from '../../components/GenericMenu/hooks/useHandleMenuAction';
import type { AppMenuOption } from './hooks/useAppMenu';
import { useAppMenu } from './hooks/useAppMenu';

type AppMenuProps = {
app: App;
isAppDetailsPage: boolean;
};

const AppMenu = ({ app, isAppDetailsPage }: AppMenuProps) => {
const t = useTranslation();

const { isLoading, isAdminUser, sections } = useAppMenu(app, isAppDetailsPage);

const itemsList = sections.reduce((acc, { items }) => [...acc, ...items], [] as AppMenuOption[]);

const onAction = useHandleMenuAction(itemsList);
const disabledKeys = itemsList.filter((item) => item.disabled).map((item) => item.id);

if (isLoading) {
return <Skeleton variant='rect' height='x28' width='x28' />;
}

if (!isAdminUser && app?.installed && sections.length === 0) {
return null;
}

return (
<MenuV2 title={t('More_options')} onAction={onAction} disabledKeys={disabledKeys} detached>
{sections.map(({ items }, idx) => (
<MenuSection key={idx} items={items}>
{items.map((option) => (
<MenuItem key={option.id}>
<MenuItemContent>{option.content}</MenuItemContent>
</MenuItem>
))}
</MenuSection>
))}
</MenuV2>
);
};

export default memo(AppMenu);
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Box, Modal } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import React, { useEffect } from 'react';

const iframeMsgListener = (confirm, cancel) => (e) => {
const iframeMsgListener = (confirm: (data: any) => void, cancel: () => void) => (e: MessageEvent<any>) => {
let data;
try {
data = JSON.parse(e.data);
Expand All @@ -13,7 +14,14 @@ const iframeMsgListener = (confirm, cancel) => (e) => {
data.result ? confirm(data) : cancel();
};

const IframeModal = ({ url, confirm, cancel, wrapperHeight = 'x360', ...props }) => {
type IframeModalProps = {
url: string;
confirm: (data: any) => void;
cancel: () => void;
wrapperHeight?: string;
} & ComponentProps<typeof Modal>;

const IframeModal = ({ url, confirm, cancel, wrapperHeight = 'x360', ...props }: IframeModalProps) => {
const t = useTranslation();

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';

import MarkdownText from '../../../../components/MarkdownText';
import type { MarketplaceRouteContext } from '../../hooks/useAppsCountQuery';

type UninstallGrandfatheredAppModalProps = {
context: 'explore' | 'marketplace' | 'private';
context: MarketplaceRouteContext;
limit: number;
appName: string;
handleUninstall: () => void;
Expand Down
Loading

0 comments on commit 0f1bebf

Please sign in to comment.