Skip to content

Commit

Permalink
Merge branch 'develop' into fix/dont-notify-error-increments
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] authored Sep 27, 2024
2 parents 20ea317 + 5965a1d commit ad40187
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 136 deletions.
22 changes: 0 additions & 22 deletions .github/workflows/vulnerabilities-jira-integration.yml

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ yarn-error.log*
.env.test.local
.env.production.local

storybook-static
# turbo
.turbo

Expand Down
14 changes: 7 additions & 7 deletions apps/meteor/app/livechat/server/api/v1/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Match, check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';

import { API } from '../../../../api/server';
import { Contacts, createContact, updateContact } from '../../lib/Contacts';
import { Contacts, createContact, updateContact, isSingleContactEnabled } from '../../lib/Contacts';

API.v1.addRoute(
'omnichannel/contact',
Expand Down Expand Up @@ -96,8 +96,8 @@ API.v1.addRoute(
{ authRequired: true, permissionsRequired: ['create-livechat-contact'], validateParams: isPOSTOmnichannelContactsProps },
{
async post() {
if (process.env.TEST_MODE?.toUpperCase() !== 'TRUE') {
throw new Meteor.Error('error-not-allowed', 'This endpoint is only allowed in test mode');
if (!isSingleContactEnabled()) {
return API.v1.unauthorized();
}
const contactId = await createContact({ ...this.bodyParams, unknown: false });

Expand All @@ -111,8 +111,8 @@ API.v1.addRoute(
{ authRequired: true, permissionsRequired: ['update-livechat-contact'], validateParams: isPOSTUpdateOmnichannelContactsProps },
{
async post() {
if (process.env.TEST_MODE?.toUpperCase() !== 'TRUE') {
throw new Meteor.Error('error-not-allowed', 'This endpoint is only allowed in test mode');
if (!isSingleContactEnabled()) {
return API.v1.unauthorized();
}

const contact = await updateContact({ ...this.bodyParams });
Expand All @@ -127,8 +127,8 @@ API.v1.addRoute(
{ authRequired: true, permissionsRequired: ['view-livechat-contact'], validateParams: isGETOmnichannelContactsProps },
{
async get() {
if (process.env.TEST_MODE?.toUpperCase() !== 'TRUE') {
throw new Meteor.Error('error-not-allowed', 'This endpoint is only allowed in test mode');
if (!isSingleContactEnabled()) {
return API.v1.unauthorized();
}
const contact = await LivechatContacts.findOneById(this.queryParams.contactId);

Expand Down
87 changes: 38 additions & 49 deletions apps/meteor/app/livechat/server/lib/Contacts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
AtLeast,
ILivechatContact,
ILivechatContactChannel,
ILivechatCustomField,
Expand Down Expand Up @@ -113,41 +114,8 @@ export const Contacts = {
}
}

const allowedCF = LivechatCustomField.findByScope<Pick<ILivechatCustomField, '_id' | 'label' | 'regexp' | 'required' | 'visibility'>>(
'visitor',
{
projection: { _id: 1, label: 1, regexp: 1, required: 1 },
},
false,
);

const livechatData: Record<string, string> = {};

for await (const cf of allowedCF) {
if (!customFields.hasOwnProperty(cf._id)) {
if (cf.required) {
throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label }));
}
continue;
}
const cfValue: string = trim(customFields[cf._id]);

if (!cfValue || typeof cfValue !== 'string') {
if (cf.required) {
throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label }));
}
continue;
}

if (cf.regexp) {
const regex = new RegExp(cf.regexp);
if (!regex.test(cfValue)) {
throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label }));
}
}

livechatData[cf._id] = cfValue;
}
const allowedCF = await getAllowedCustomFields();
const livechatData: Record<string, string> = validateCustomFields(allowedCF, customFields, { ignoreAdditionalFields: true });

const fieldsToRemove = {
// if field is explicitely set to empty string, remove
Expand Down Expand Up @@ -202,15 +170,20 @@ export const Contacts = {
},
};

export function isSingleContactEnabled(): boolean {
// The Single Contact feature is not yet available in production, but can already be partially used in test environments.
return process.env.TEST_MODE?.toUpperCase() === 'TRUE';
}

export async function createContact(params: CreateContactParams): Promise<string> {
const { name, emails, phones, customFields = {}, contactManager, channels, unknown } = params;
const { name, emails, phones, customFields: receivedCustomFields = {}, contactManager, channels, unknown } = params;

if (contactManager) {
await validateContactManager(contactManager);
}

const allowedCustomFields = await getAllowedCustomFields();
validateCustomFields(allowedCustomFields, customFields);
const customFields = validateCustomFields(allowedCustomFields, receivedCustomFields);

const { insertedId } = await LivechatContacts.insertOne({
name,
Expand All @@ -226,7 +199,7 @@ export async function createContact(params: CreateContactParams): Promise<string
}

export async function updateContact(params: UpdateContactParams): Promise<ILivechatContact> {
const { contactId, name, emails, phones, customFields, contactManager, channels } = params;
const { contactId, name, emails, phones, customFields: receivedCustomFields, contactManager, channels } = params;

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

Expand All @@ -238,17 +211,21 @@ export async function updateContact(params: UpdateContactParams): Promise<ILivec
await validateContactManager(contactManager);
}

if (customFields) {
const allowedCustomFields = await getAllowedCustomFields();
validateCustomFields(allowedCustomFields, customFields);
}
const customFields = receivedCustomFields && validateCustomFields(await getAllowedCustomFields(), receivedCustomFields);

const updatedContact = await LivechatContacts.updateContact(contactId, { name, emails, phones, contactManager, channels, customFields });
const updatedContact = await LivechatContacts.updateContact(contactId, {
name,
emails,
phones,
contactManager,
channels,
customFields,
});

return updatedContact;
}

async function getAllowedCustomFields(): Promise<ILivechatCustomField[]> {
async function getAllowedCustomFields(): Promise<Pick<ILivechatCustomField, '_id' | 'label' | 'regexp' | 'required'>[]> {
return LivechatCustomField.findByScope(
'visitor',
{
Expand All @@ -258,7 +235,13 @@ async function getAllowedCustomFields(): Promise<ILivechatCustomField[]> {
).toArray();
}

export function validateCustomFields(allowedCustomFields: ILivechatCustomField[], customFields: Record<string, string | unknown>) {
export function validateCustomFields(
allowedCustomFields: AtLeast<ILivechatCustomField, '_id' | 'label' | 'regexp' | 'required'>[],
customFields: Record<string, string | unknown>,
options?: { ignoreAdditionalFields?: boolean },
): Record<string, string> {
const validValues: Record<string, string> = {};

for (const cf of allowedCustomFields) {
if (!customFields.hasOwnProperty(cf._id)) {
if (cf.required) {
Expand All @@ -281,14 +264,20 @@ export function validateCustomFields(allowedCustomFields: ILivechatCustomField[]
throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label }));
}
}

validValues[cf._id] = cfValue;
}

const allowedCustomFieldIds = new Set(allowedCustomFields.map((cf) => cf._id));
for (const key in customFields) {
if (!allowedCustomFieldIds.has(key)) {
throw new Error(i18n.t('error-custom-field-not-allowed', { key }));
if (!options?.ignoreAdditionalFields) {
const allowedCustomFieldIds = new Set(allowedCustomFields.map((cf) => cf._id));
for (const key in customFields) {
if (!allowedCustomFieldIds.has(key)) {
throw new Error(i18n.t('error-custom-field-not-allowed', { key }));
}
}
}

return validValues;
}

export async function validateContactManager(contactManagerUserId: string) {
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/livechat/server/lib/LivechatTyped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import * as Mailer from '../../../mailer/server/api';
import { metrics } from '../../../metrics/server';
import { settings } from '../../../settings/server';
import { businessHourManager } from '../business-hour';
import { createContact } from './Contacts';
import { createContact, isSingleContactEnabled } from './Contacts';
import { parseAgentCustomFields, updateDepartmentAgents, validateEmail, normalizeTransferredByData } from './Helper';
import { QueueManager } from './QueueManager';
import { RoutingManager } from './RoutingManager';
Expand Down Expand Up @@ -669,7 +669,7 @@ class LivechatClass {
}
}

if (process.env.TEST_MODE?.toUpperCase() === 'TRUE') {
if (isSingleContactEnabled()) {
const contactId = await createContact({
name: name ?? (visitorDataToUpdate.username as string),
emails: email ? [email] : [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import type { Mongo } from 'meteor/mongo';
import { useEffect, useMemo } from 'react';

import { ChatRoom } from '../../../../../app/models/client';
Expand All @@ -12,7 +13,7 @@ const sortRoomByLastMessage = (a: IRoom, b: IRoom) => {
if (!b.lm) {
return -1;
}
return new Date(b.lm).toUTCString().localeCompare(new Date(a.lm).toUTCString());
return b.lm.getTime() - a.lm.getTime();
};

export const useTeamsListChildrenUpdate = (
Expand All @@ -23,7 +24,7 @@ export const useTeamsListChildrenUpdate = (
const queryClient = useQueryClient();

const query = useMemo(() => {
const query: Parameters<typeof ChatRoom.find>[0] = {
const query: Mongo.Selector<IRoom> = {
$or: [
{
_id: parentRid,
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/server/models/raw/LivechatCustomField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export class LivechatCustomFieldRaw extends BaseRaw<ILivechatCustomField> implem
return [{ key: { scope: 1 } }];
}

findByScope(
findByScope<T extends ILivechatCustomField>(
scope: ILivechatCustomField['scope'],
options?: FindOptions<ILivechatCustomField>,
includeHidden = true,
): FindCursor<ILivechatCustomField> {
return this.find({ scope, ...(includeHidden === true ? {} : { visibility: { $ne: 'hidden' } }) }, options);
): FindCursor<T> {
return this.find<T>({ scope, ...(includeHidden === true ? {} : { visibility: { $ne: 'hidden' } }) }, options);
}

findMatchingCustomFields(
Expand Down
2 changes: 1 addition & 1 deletion packages/release-action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ inputs:
required: false

runs:
using: "node16"
using: "node20"
main: "dist/index.js"

branding:
Expand Down
102 changes: 52 additions & 50 deletions packages/ui-composer/package.json
Original file line number Diff line number Diff line change
@@ -1,52 +1,54 @@
{
"name": "@rocket.chat/ui-composer",
"version": "0.3.0-rc.0",
"private": true,
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"files": [
"/dist"
],
"scripts": {
"lint": "eslint --ext .js,.jsx,.ts,.tsx .",
"lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix",
"build": "rm -rf dist && tsc -p tsconfig.build.json",
"typecheck": "tsc --noEmit",
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
"storybook": "start-storybook -p 6006"
},
"devDependencies": {
"@babel/core": "~7.22.20",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/icons": "~0.38.0",
"@storybook/addon-actions": "~6.5.16",
"@storybook/addon-docs": "~6.5.16",
"@storybook/addon-essentials": "~6.5.16",
"@storybook/builder-webpack4": "~6.5.16",
"@storybook/manager-webpack4": "~6.5.16",
"@storybook/react": "~6.5.16",
"@storybook/testing-library": "~0.0.13",
"@types/react": "~17.0.80",
"@types/react-dom": "~17.0.25",
"eslint": "~8.45.0",
"eslint-plugin-react": "~7.32.2",
"eslint-plugin-react-hooks": "~4.6.2",
"eslint-plugin-storybook": "~0.6.15",
"react": "~17.0.2",
"react-docgen-typescript-plugin": "~1.0.8",
"react-dom": "~17.0.2",
"typescript": "~5.5.4"
},
"peerDependencies": {
"@react-aria/toolbar": "*",
"@rocket.chat/fuselage": "*",
"@rocket.chat/icons": "*",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"volta": {
"extends": "../../package.json"
}
"name": "@rocket.chat/ui-composer",
"version": "0.3.0-rc.0",
"private": true,
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"files": [
"/dist"
],
"scripts": {
"lint": "eslint --ext .js,.jsx,.ts,.tsx .",
"lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix",
"build": "rm -rf dist && tsc -p tsconfig.build.json",
"typecheck": "tsc --noEmit",
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
"storybook": "start-storybook -p 6006",
"build-preview": "build-storybook",
".:build-preview-move": "mkdir -p ../../.preview/ && cp -r ./storybook-static ../../.preview/ui-composer"
},
"devDependencies": {
"@babel/core": "~7.22.20",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/icons": "~0.38.0",
"@storybook/addon-actions": "~6.5.16",
"@storybook/addon-docs": "~6.5.16",
"@storybook/addon-essentials": "~6.5.16",
"@storybook/builder-webpack4": "~6.5.16",
"@storybook/manager-webpack4": "~6.5.16",
"@storybook/react": "~6.5.16",
"@storybook/testing-library": "~0.0.13",
"@types/react": "~17.0.80",
"@types/react-dom": "~17.0.25",
"eslint": "~8.45.0",
"eslint-plugin-react": "~7.32.2",
"eslint-plugin-react-hooks": "~4.6.2",
"eslint-plugin-storybook": "~0.6.15",
"react": "~17.0.2",
"react-docgen-typescript-plugin": "~1.0.8",
"react-dom": "~17.0.2",
"typescript": "~5.5.4"
},
"peerDependencies": {
"@react-aria/toolbar": "*",
"@rocket.chat/fuselage": "*",
"@rocket.chat/icons": "*",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"volta": {
"extends": "../../package.json"
}
}

0 comments on commit ad40187

Please sign in to comment.