Skip to content

Commit

Permalink
Merge branch 'develop' into chore/refactor-session-plim
Browse files Browse the repository at this point in the history
  • Loading branch information
ggazzo authored Dec 18, 2024
2 parents 47b0132 + 037e692 commit 38d94fa
Show file tree
Hide file tree
Showing 26 changed files with 573 additions and 86 deletions.
5 changes: 5 additions & 0 deletions .changeset/lovely-beers-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes messages not being processed for all slack servers
5 changes: 5 additions & 0 deletions .changeset/wet-chicken-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes contact update failing in case a custom field is removed from the workspace
7 changes: 7 additions & 0 deletions .changeset/wicked-socks-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/core-typings": minor
"@rocket.chat/rest-typings": minor
---

Adds a new callout in the subscription page to inform users of subscription upgrade eligibility when applicable.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Cloud, Serialized } from '@rocket.chat/core-typings';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { v, compile } from 'suretype';

import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError';
import { settings } from '../../../../settings/server';

const workspaceSyncPayloadSchema = v.object({
workspaceId: v.string().required(),
publicKey: v.string(),
license: v.string().required(),
});

const assertWorkspaceSyncPayload = compile(workspaceSyncPayloadSchema);

export async function fetchWorkspaceSyncPayload({
token,
data,
}: {
token: string;
data: Cloud.WorkspaceSyncRequestPayload;
}): Promise<Serialized<Cloud.WorkspaceSyncResponse>> {
const workspaceRegistrationClientUri = settings.get<string>('Cloud_Workspace_Registration_Client_Uri');
const response = await fetch(`${workspaceRegistrationClientUri}/sync`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
body: data,
});

if (!response.ok) {
const { error } = await response.json();
throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`);
}

const payload = await response.json();

assertWorkspaceSyncPayload(payload);

return payload;
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,14 @@
import type { Cloud, Serialized } from '@rocket.chat/core-typings';
import { DuplicatedLicenseError } from '@rocket.chat/license';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { v, compile } from 'suretype';
import { Settings } from '@rocket.chat/models';

import { callbacks } from '../../../../../lib/callbacks';
import { CloudWorkspaceAccessError } from '../../../../../lib/errors/CloudWorkspaceAccessError';
import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError';
import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError';
import { SystemLogger } from '../../../../../server/lib/logger/system';
import { settings } from '../../../../settings/server';
import { buildWorkspaceRegistrationData } from '../buildRegistrationData';
import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from '../getWorkspaceAccessToken';
import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus';

const workspaceSyncPayloadSchema = v.object({
workspaceId: v.string().required(),
publicKey: v.string(),
license: v.string().required(),
});

const assertWorkspaceSyncPayload = compile(workspaceSyncPayloadSchema);

const fetchWorkspaceSyncPayload = async ({
token,
data,
}: {
token: string;
data: Cloud.WorkspaceSyncRequestPayload;
}): Promise<Serialized<Cloud.WorkspaceSyncResponse>> => {
const workspaceRegistrationClientUri = settings.get<string>('Cloud_Workspace_Registration_Client_Uri');
const response = await fetch(`${workspaceRegistrationClientUri}/sync`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
body: data,
});

if (!response.ok) {
try {
const { error } = await response.json();
throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`);
} catch (error) {
throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${response.statusText}`);
}
}

const payload = await response.json();

assertWorkspaceSyncPayload(payload);

return payload;
};
import { fetchWorkspaceSyncPayload } from './fetchWorkspaceSyncPayload';

export async function syncCloudData() {
try {
Expand All @@ -67,11 +24,17 @@ export async function syncCloudData() {

const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined);

const { license, removeLicense = false } = await fetchWorkspaceSyncPayload({
const {
license,
removeLicense = false,
cloudSyncAnnouncement,
} = await fetchWorkspaceSyncPayload({
token,
data: workspaceRegistrationData,
});

await Settings.updateValueById('Cloud_Sync_Announcement_Payload', JSON.stringify(cloudSyncAnnouncement ?? null));

if (removeLicense) {
await callbacks.run('workspaceLicenseRemoved');
} else {
Expand Down
28 changes: 24 additions & 4 deletions apps/meteor/app/livechat/server/lib/contacts/updateContact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export type UpdateContactParams = {
export async function updateContact(params: UpdateContactParams): Promise<ILivechatContact> {
const { contactId, name, emails, phones, customFields: receivedCustomFields, contactManager, channels, wipeConflicts } = params;

const contact = await LivechatContacts.findOneById<Pick<ILivechatContact, '_id' | 'name'>>(contactId, {
projection: { _id: 1, name: 1 },
const contact = await LivechatContacts.findOneById<Pick<ILivechatContact, '_id' | 'name' | 'customFields'>>(contactId, {
projection: { _id: 1, name: 1, customFields: 1 },
});

if (!contact) {
Expand All @@ -36,15 +36,35 @@ export async function updateContact(params: UpdateContactParams): Promise<ILivec
await validateContactManager(contactManager);
}

const customFields = receivedCustomFields && validateCustomFields(await getAllowedCustomFields(), receivedCustomFields);
const workspaceAllowedCustomFields = await getAllowedCustomFields();
const workspaceAllowedCustomFieldsIds = workspaceAllowedCustomFields.map((customField) => customField._id);
const currentCustomFieldsIds = Object.keys(contact.customFields || {});
const notRegisteredCustomFields = currentCustomFieldsIds
.filter((customFieldId) => !workspaceAllowedCustomFieldsIds.includes(customFieldId))
.map((customFieldId) => ({ _id: customFieldId }));

const customFieldsToUpdate =
receivedCustomFields &&
validateCustomFields(workspaceAllowedCustomFields, receivedCustomFields, {
ignoreAdditionalFields: !!notRegisteredCustomFields.length,
});

if (receivedCustomFields && customFieldsToUpdate && notRegisteredCustomFields.length) {
const allowedCustomFields = [...workspaceAllowedCustomFields, ...notRegisteredCustomFields];
validateCustomFields(allowedCustomFields, receivedCustomFields);

notRegisteredCustomFields.forEach((notRegisteredCustomField) => {
customFieldsToUpdate[notRegisteredCustomField._id] = contact.customFields?.[notRegisteredCustomField._id] as string;
});
}

const updatedContact = await LivechatContacts.updateContact(contactId, {
name,
emails: emails?.map((address) => ({ address })),
phones: phones?.map((phoneNumber) => ({ phoneNumber })),
contactManager,
channels,
customFields,
customFields: customFieldsToUpdate,
...(wipeConflicts && { conflictingFields: [] }),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { trim } from '../../../../../lib/utils/stringUtils';
import { i18n } from '../../../../utils/lib/i18n';

export function validateCustomFields(
allowedCustomFields: AtLeast<ILivechatCustomField, '_id' | 'label' | 'regexp' | 'required'>[],
allowedCustomFields: AtLeast<ILivechatCustomField, '_id'>[],
customFields: Record<string, string | unknown>,
{
ignoreAdditionalFields = false,
Expand All @@ -16,15 +16,15 @@ export function validateCustomFields(
for (const cf of allowedCustomFields) {
if (!customFields.hasOwnProperty(cf._id)) {
if (cf.required && !ignoreValidationErrors) {
throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label }));
throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label || cf._id }));
}
continue;
}
const cfValue: string = trim(customFields[cf._id]);

if (!cfValue || typeof cfValue !== 'string') {
if (cf.required && !ignoreValidationErrors) {
throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label }));
throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label || cf._id }));
}
continue;
}
Expand All @@ -36,7 +36,7 @@ export function validateCustomFields(
continue;
}

throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label }));
throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label || cf._id }));
}
}

Expand Down
23 changes: 11 additions & 12 deletions apps/meteor/app/slackbridge/server/RocketAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ export default class RocketAdapter {
try {
if (!slack.getSlackChannel(rocketMessageDeleted.rid)) {
// This is on a channel that the rocket bot is not subscribed on this slack server
return;
continue;
}

rocketLogger.debug('onRocketMessageDelete', rocketMessageDeleted);
await slack.postDeleteMessage(rocketMessageDeleted);
} catch (err) {
Expand Down Expand Up @@ -134,22 +135,23 @@ export default class RocketAdapter {
try {
if (!slack.getSlackChannel(rocketMessage.rid)) {
// This is on a channel that the rocket bot is not subscribed
return;
continue;
}
rocketLogger.debug('onRocketMessage', rocketMessage);

if (rocketMessage.editedAt) {
// This is an Edit Event
await this.processMessageChanged(rocketMessage, slack);
return rocketMessage;
continue;
}
// Ignore messages originating from Slack
if (rocketMessage._id.indexOf('slack-') === 0) {
return rocketMessage;
continue;
}

if (rocketMessage.file) {
return this.processFileShare(rocketMessage, slack);
await this.processFileShare(rocketMessage, slack);
continue;
}

// A new message from Rocket.Chat
Expand Down Expand Up @@ -206,10 +208,7 @@ export default class RocketAdapter {
}
}

const message = `${text} ${fileName}`;

rocketMessage.msg = message;
await slack.postMessage(slack.getSlackChannel(rocketMessage.rid), rocketMessage);
await slack.postMessage(slack.getSlackChannel(rocketMessage.rid), { ...rocketMessage, msg: `${text} ${fileName}` });
}
}

Expand Down Expand Up @@ -266,15 +265,15 @@ export default class RocketAdapter {

for await (const slack of this.slackAdapters) {
if (addedRoom) {
return;
continue;
}

const slackChannel = await slack.slackAPI.getRoomInfo(slackChannelID);
if (slackChannel) {
const members = await slack.slackAPI.getMembers(slackChannelID);
if (!members) {
rocketLogger.error('Could not fetch room members');
return;
continue;
}

const rocketRoom = await Rooms.findOneByName(slackChannel.name);
Expand All @@ -288,7 +287,7 @@ export default class RocketAdapter {

if (!rocketUserCreator) {
rocketLogger.error({ msg: 'Could not fetch room creator information', creator: slackChannel.creator });
return;
continue;
}

try {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/components/Page/PageHeaderNoShadow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const PageHeaderNoShadow = ({ children = undefined, title, onClickBack, ...props
useDocumentTitle(typeof title === 'string' ? title : undefined);

return (
<Box is='header' borderBlockEndWidth='default' borderBlockEndColor='transparent' {...props}>
<Box is='header' borderBlockEndWidth='default' pb={8} borderBlockEndColor='transparent' {...props}>
<Box
height='100%'
marginInline={24}
Expand Down
15 changes: 14 additions & 1 deletion apps/meteor/client/hooks/useLicense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,20 @@ export const useLicenseBase = <TData = LicenseDataType>({
};

export const useLicense = (params?: LicenseParams) => {
return useLicenseBase({ params, select: (data) => data.license });
return useLicenseBase({
params,
select: (data) => data.license,
});
};

export const useLicenseWithCloudAnnouncement = (params?: LicenseParams) => {
return useLicenseBase({
params,
select: ({ license, cloudSyncAnnouncement }) => ({
...license,
cloudSyncAnnouncement,
}),
});
};

export const useHasLicense = (): UseQueryResult<boolean> => {
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/client/uikit/hooks/useBannerContextValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const useBannerContextValue = ({ view, values }: UseBannerContextValuePar
},
updateState: (): void => undefined,
appId: view.appId,
viewId: view.viewId,
values,
};
};
20 changes: 14 additions & 6 deletions apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ import PlanCardCommunity from './components/cards/PlanCard/PlanCardCommunity';
import SeatsCard from './components/cards/SeatsCard';
import { useCancelSubscriptionModal } from './hooks/useCancelSubscriptionModal';
import { useWorkspaceSync } from './hooks/useWorkspaceSync';
import { Page, PageHeader, PageScrollableContentWithShadow } from '../../../components/Page';
import UiKitSubscriptionLicense from './surface/UiKitSubscriptionLicense';
import { Page, PageScrollableContentWithShadow } from '../../../components/Page';
import PageBlockWithBorder from '../../../components/Page/PageBlockWithBorder';
import PageHeaderNoShadow from '../../../components/Page/PageHeaderNoShadow';
import { useIsEnterprise } from '../../../hooks/useIsEnterprise';
import { useInvalidateLicense, useLicense } from '../../../hooks/useLicense';
import { useInvalidateLicense, useLicenseWithCloudAnnouncement } from '../../../hooks/useLicense';
import { useRegistrationStatus } from '../../../hooks/useRegistrationStatus';

function useShowLicense() {
Expand All @@ -48,15 +51,15 @@ const SubscriptionPage = () => {
const router = useRouter();
const { data: enterpriseData } = useIsEnterprise();
const { isRegistered } = useRegistrationStatus();
const { data: licensesData, isLoading: isLicenseLoading } = useLicense({ loadValues: true });
const { data: licensesData, isLoading: isLicenseLoading } = useLicenseWithCloudAnnouncement({ loadValues: true });
const syncLicenseUpdate = useWorkspaceSync();
const invalidateLicenseQuery = useInvalidateLicense();

const subscriptionSuccess = useSearchParameter('subscriptionSuccess');

const showSubscriptionCallout = useDebouncedValue(subscriptionSuccess || syncLicenseUpdate.isLoading, 10000);

const { license, limits, activeModules = [] } = licensesData || {};
const { license, limits, activeModules = [], cloudSyncAnnouncement } = licensesData || {};
const { isEnterprise = true } = enterpriseData || {};

const getKeyLimit = (key: 'monthlyActiveContacts' | 'activeUsers') => {
Expand Down Expand Up @@ -99,7 +102,7 @@ const SubscriptionPage = () => {

return (
<Page bg='tint'>
<PageHeader title={t('Subscription')}>
<PageHeaderNoShadow title={t('Subscription')}>
<ButtonGroup>
{isRegistered && (
<Button loading={syncLicenseUpdate.isLoading} icon='reload' onClick={() => handleSyncLicenseUpdate()}>
Expand All @@ -110,7 +113,12 @@ const SubscriptionPage = () => {
{t(isEnterprise ? 'Manage_subscription' : 'Upgrade')}
</UpgradeButton>
</ButtonGroup>
</PageHeader>
</PageHeaderNoShadow>
{cloudSyncAnnouncement && (
<PageBlockWithBorder>
<UiKitSubscriptionLicense key='license' initialView={cloudSyncAnnouncement} />
</PageBlockWithBorder>
)}
<PageScrollableContentWithShadow p={16}>
{(showSubscriptionCallout || syncLicenseUpdate.isLoading) && (
<Callout type='info' title={t('Sync_license_update_Callout_Title')} m={8}>
Expand Down
Loading

0 comments on commit 38d94fa

Please sign in to comment.