RocketChat/Rocket.Chat

View on GitHub
apps/meteor/ee/server/models/raw/LivechatRooms.ts

Summary

Maintainability
F
5 days
Test Coverage
import type {
    ILivechatPriority,
    IOmnichannelRoom,
    IOmnichannelServiceLevelAgreements,
    RocketChatRecordDeleted,
    ReportResult,
} from '@rocket.chat/core-typings';
import { LivechatPriorityWeight, DEFAULT_SLA_CONFIG } from '@rocket.chat/core-typings';
import type { ILivechatRoomsModel } from '@rocket.chat/model-typings';
import type { Updater } from '@rocket.chat/models';
import type { FindCursor, UpdateResult, Document, FindOptions, Db, Collection, Filter, AggregationCursor } from 'mongodb';

import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred';
import { LivechatRoomsRaw } from '../../../../server/models/raw/LivechatRooms';

declare module '@rocket.chat/model-typings' {
    interface ILivechatRoomsModel {
        associateRoomsWithDepartmentToUnit: (departments: string[], unit: string) => Promise<void>;
        removeUnitAssociationFromRooms: (unit: string) => Promise<void>;
        updateDepartmentAncestorsById: (rid: string, ancestors?: string[]) => Promise<UpdateResult>;
        unsetPredictedVisitorAbandonmentByRoomId(rid: string): Promise<UpdateResult>;
        findAbandonedOpenRooms(date: Date, extraQuery?: Filter<IOmnichannelRoom>): FindCursor<IOmnichannelRoom>;
        setPredictedVisitorAbandonmentByRoomId(roomId: string, date: Date): Promise<UpdateResult>;
        getPredictedVisitorAbandonmentByRoomIdUpdateQuery(date: Date, roomUpdater: Updater<IOmnichannelRoom>): Updater<IOmnichannelRoom>;
        unsetAllPredictedVisitorAbandonment(): Promise<void>;
        setOnHoldByRoomId(roomId: string): Promise<UpdateResult>;
        unsetOnHoldByRoomId(roomId: string): Promise<UpdateResult>;
        unsetOnHoldAndPredictedVisitorAbandonmentByRoomId(roomId: string): Promise<UpdateResult>;
        setSlaForRoomById(
            roomId: string,
            sla: Pick<IOmnichannelServiceLevelAgreements, '_id' | 'dueTimeInMinutes'>,
        ): Promise<UpdateResult | Document>;
        removeSlaFromRoomById(roomId: string): Promise<UpdateResult | Document>;
        bulkRemoveSlaFromRoomsById(slaId: string): Promise<UpdateResult | Document>;
        findOpenBySlaId(
            slaId: string,
            options: FindOptions<IOmnichannelRoom>,
            extraQuery?: Filter<IOmnichannelRoom>,
        ): FindCursor<IOmnichannelRoom>;
        setPriorityByRoomId(roomId: string, priority: Pick<ILivechatPriority, '_id' | 'sortItem'>): Promise<UpdateResult>;
        unsetPriorityByRoomId(roomId: string): Promise<UpdateResult>;
        countPrioritizedRooms(): Promise<number>;
        countRoomsWithSla(): Promise<number>;
        countRoomsWithPdfTranscriptRequested(): Promise<number>;
        countRoomsWithTranscriptSent(): Promise<number>;
        getConversationsBySource(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): AggregationCursor<ReportResult>;
        getConversationsByStatus(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): AggregationCursor<ReportResult>;
        getConversationsByDepartment(
            start: Date,
            end: Date,
            sort: Record<string, 1 | -1>,
            extraQuery: Filter<IOmnichannelRoom>,
        ): AggregationCursor<ReportResult>;
        getConversationsByTags(
            start: Date,
            end: Date,
            sort: Record<string, 1 | -1>,
            extraQuery: Filter<IOmnichannelRoom>,
        ): AggregationCursor<ReportResult>;
        getConversationsByAgents(
            start: Date,
            end: Date,
            sort: Record<string, 1 | -1>,
            extraQuery: Filter<IOmnichannelRoom>,
        ): AggregationCursor<ReportResult>;
        getConversationsWithoutTagsBetweenDate(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): Promise<number>;
        getTotalConversationsWithoutAgentsBetweenDate(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): Promise<number>;
        getTotalConversationsWithoutDepartmentBetweenDates(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): Promise<number>;
    }
}

export class LivechatRoomsRawEE extends LivechatRoomsRaw implements ILivechatRoomsModel {
    constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<IOmnichannelRoom>>) {
        super(db, trash);
    }

    countPrioritizedRooms(): Promise<number> {
        return this.col.countDocuments({ priorityId: { $exists: true } });
    }

    countRoomsWithSla(): Promise<number> {
        return this.col.countDocuments({ slaId: { $exists: true } });
    }

    countRoomsWithPdfTranscriptRequested(): Promise<number> {
        return this.col.countDocuments({ pdfTranscriptRequested: true });
    }

    countRoomsWithTranscriptSent(): Promise<number> {
        return this.col.countDocuments({ pdfTranscriptFileId: { $exists: true } });
    }

    async unsetAllPredictedVisitorAbandonment(): Promise<void> {
        return this.updateMany(
            {
                'open': true,
                't': 'l',
                'omnichannel.predictedVisitorAbandonmentAt': { $exists: true },
            },
            {
                $unset: { 'omnichannel.predictedVisitorAbandonmentAt': 1 },
            },
        ).then();
    }

    setOnHoldByRoomId(roomId: string): Promise<UpdateResult> {
        return this.updateOne({ _id: roomId }, { $set: { onHold: true } });
    }

    unsetOnHoldByRoomId(roomId: string): Promise<UpdateResult> {
        return this.updateOne({ _id: roomId }, { $unset: { onHold: 1 } });
    }

    unsetOnHoldAndPredictedVisitorAbandonmentByRoomId(roomId: string): Promise<UpdateResult> {
        return this.updateOne(
            {
                _id: roomId,
            },
            {
                $unset: {
                    'omnichannel.predictedVisitorAbandonmentAt': 1,
                    'onHold': 1,
                },
            },
        );
    }

    setSlaForRoomById(
        roomId: string,
        sla: Pick<IOmnichannelServiceLevelAgreements, '_id' | 'dueTimeInMinutes'>,
    ): Promise<UpdateResult | Document> {
        const { _id: slaId, dueTimeInMinutes } = sla;

        return this.updateOne(
            {
                _id: roomId,
            },
            {
                $set: {
                    slaId,
                    estimatedWaitingTimeQueue: dueTimeInMinutes,
                },
            },
        );
    }

    removeSlaFromRoomById(roomId: string): Promise<UpdateResult | Document> {
        return this.updateOne(
            {
                _id: roomId,
            },
            {
                $unset: {
                    slaId: 1,
                },
                $set: {
                    estimatedWaitingTimeQueue: DEFAULT_SLA_CONFIG.ESTIMATED_WAITING_TIME_QUEUE,
                },
            },
        );
    }

    bulkRemoveSlaFromRoomsById(slaId: string): Promise<UpdateResult | Document> {
        return this.updateMany(
            {
                open: true,
                t: 'l',
                slaId,
            },
            {
                $unset: { slaId: 1 },
                $set: {
                    estimatedWaitingTimeQueue: DEFAULT_SLA_CONFIG.ESTIMATED_WAITING_TIME_QUEUE,
                },
            },
        );
    }

    findOpenBySlaId(
        slaId: string,
        options: FindOptions<IOmnichannelRoom>,
        extraQuery?: Filter<IOmnichannelRoom>,
    ): FindCursor<IOmnichannelRoom> {
        const query = {
            t: 'l' as const,
            open: true,
            slaId,
            ...extraQuery,
        };

        return this.find(query, options);
    }

    async setPriorityByRoomId(roomId: string, priority: Pick<ILivechatPriority, '_id' | 'sortItem'>): Promise<UpdateResult> {
        const { _id: priorityId, sortItem: priorityWeight } = priority;

        return this.updateOne({ _id: roomId }, { $set: { priorityId, priorityWeight } });
    }

    async unsetPriorityByRoomId(roomId: string): Promise<UpdateResult> {
        return this.updateOne(
            { _id: roomId },
            {
                $unset: {
                    priorityId: 1,
                },
                $set: {
                    priorityWeight: LivechatPriorityWeight.NOT_SPECIFIED,
                },
            },
        );
    }

    getPredictedVisitorAbandonmentByRoomIdUpdateQuery(
        date: Date,
        roomUpdater: Updater<IOmnichannelRoom> = this.getUpdater(),
    ): Updater<IOmnichannelRoom> {
        return roomUpdater.set('omnichannel.predictedVisitorAbandonmentAt', date);
    }

    setPredictedVisitorAbandonmentByRoomId(rid: string, willBeAbandonedAt: Date): Promise<UpdateResult> {
        const query = {
            _id: rid,
        };
        const update = {
            $set: {
                'omnichannel.predictedVisitorAbandonmentAt': willBeAbandonedAt,
            },
        };

        return this.updateOne(query, update);
    }

    findAbandonedOpenRooms(date: Date, extraQuery?: Filter<IOmnichannelRoom>): FindCursor<IOmnichannelRoom> {
        return this.find({
            'omnichannel.predictedVisitorAbandonmentAt': { $lte: date },
            'waitingResponse': { $exists: false },
            'closedAt': { $exists: false },
            'open': true,
            ...extraQuery,
        });
    }

    async unsetPredictedVisitorAbandonmentByRoomId(roomId: string): Promise<UpdateResult> {
        return this.updateOne(
            {
                _id: roomId,
            },
            {
                $unset: { 'omnichannel.predictedVisitorAbandonmentAt': 1 },
            },
        );
    }

    async associateRoomsWithDepartmentToUnit(departments: string[], unitId: string): Promise<void> {
        const query = {
            $and: [
                {
                    departmentId: { $in: departments },
                },
                {
                    $or: [
                        {
                            departmentAncestors: { $exists: false },
                        },
                        {
                            $and: [
                                {
                                    departmentAncestors: { $exists: true },
                                },
                                {
                                    departmentAncestors: { $ne: unitId },
                                },
                            ],
                        },
                    ],
                },
            ],
        };
        const update = { $set: { departmentAncestors: [unitId] } };
        await this.updateMany(query, update);

        const queryToDisassociateOldRoomsConnectedToUnit = {
            departmentAncestors: unitId,
            departmentId: { $nin: departments },
        };
        const updateToDisassociateRooms = { $unset: { departmentAncestors: 1 } };
        await this.updateMany(queryToDisassociateOldRoomsConnectedToUnit, updateToDisassociateRooms);
    }

    async removeUnitAssociationFromRooms(unitId: string): Promise<void> {
        const query = {
            departmentAncestors: unitId,
        };
        const update = { $unset: { departmentAncestors: 1 } };
        await this.updateMany(query, update);
    }

    async updateDepartmentAncestorsById(rid: string, departmentAncestors?: string[]) {
        const query = {
            _id: rid,
        };
        const update = departmentAncestors ? { $set: { departmentAncestors } } : { $unset: { departmentAncestors: 1 } };
        return this.updateOne(query, update);
    }

    getConversationsBySource(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): AggregationCursor<ReportResult> {
        return this.col.aggregate(
            [
                {
                    $match: {
                        source: {
                            $exists: true,
                        },
                        t: 'l',
                        ts: {
                            $gte: start,
                            $lt: end,
                        },
                        ...extraQuery,
                    },
                },
                {
                    $group: {
                        _id: {
                            type: '$source.type',
                            alias: '$source.alias',
                        },
                        value: { $sum: 1 },
                    },
                },
                {
                    $sort: { value: -1 },
                },
                {
                    $group: {
                        _id: null,
                        total: { $sum: '$value' },
                        data: {
                            $push: {
                                label: {
                                    $ifNull: ['$_id.alias', '$_id.type'],
                                },
                                value: '$value',
                            },
                        },
                    },
                },
                {
                    $project: {
                        _id: 0,
                    },
                },
            ],
            { hint: 'source_1_ts_1', readPreference: readSecondaryPreferred() },
        );
    }

    getConversationsByStatus(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): AggregationCursor<ReportResult> {
        return this.col.aggregate(
            [
                {
                    $match: {
                        t: 'l',
                        ts: {
                            $gte: start,
                            $lt: end,
                        },
                        ...extraQuery,
                    },
                },
                {
                    $group: {
                        _id: null,
                        total: { $sum: 1 },
                        open: {
                            $sum: {
                                $cond: [
                                    {
                                        $and: [
                                            { $eq: ['$open', true] },
                                            {
                                                $or: [{ $not: ['$onHold'] }, { $eq: ['$onHold', false] }],
                                            },
                                            { $ifNull: ['$servedBy', false] },
                                        ],
                                    },
                                    1,
                                    0,
                                ],
                            },
                        },
                        closed: {
                            $sum: {
                                $cond: [
                                    {
                                        $ifNull: ['$metrics.chatDuration', false],
                                    },
                                    1,
                                    0,
                                ],
                            },
                        },
                        queued: {
                            $sum: {
                                $cond: [
                                    {
                                        $and: [
                                            { $eq: ['$open', true] },
                                            {
                                                $eq: [
                                                    {
                                                        $ifNull: ['$servedBy', null],
                                                    },
                                                    null,
                                                ],
                                            },
                                        ],
                                    },
                                    1,
                                    0,
                                ],
                            },
                        },
                        onhold: {
                            $sum: {
                                $cond: [{ $eq: ['$onHold', true] }, 1, 0],
                            },
                        },
                    },
                },
                {
                    $project: {
                        total: 1,
                        data: [
                            { label: 'Open', value: '$open' },
                            { label: 'Closed', value: '$closed' },
                            { label: 'Queued', value: '$queued' },
                            { label: 'On_Hold', value: '$onhold' },
                        ],
                    },
                },
                {
                    $unwind: '$data',
                },
                {
                    $sort: { 'data.value': -1 },
                },
                {
                    $group: {
                        _id: '$_id',
                        total: { $first: '$total' },
                        data: { $push: '$data' },
                    },
                },
                {
                    $project: {
                        _id: 0,
                    },
                },
            ],
            { readPreference: readSecondaryPreferred() },
        );
    }

    getConversationsByDepartment(
        start: Date,
        end: Date,
        sort: Record<string, 1 | -1>,
        extraQuery: Filter<IOmnichannelRoom>,
    ): AggregationCursor<ReportResult> {
        return this.col.aggregate(
            [
                {
                    $match: {
                        t: 'l',
                        departmentId: {
                            $exists: true,
                        },
                        ts: {
                            $lt: end,
                            $gte: start,
                        },
                        ...extraQuery,
                    },
                },
                {
                    $group: {
                        _id: '$departmentId',
                        total: { $sum: 1 },
                    },
                },
                {
                    $lookup: {
                        from: 'rocketchat_livechat_department',
                        localField: '_id',
                        foreignField: '_id',
                        as: 'department',
                    },
                },
                {
                    $group: {
                        _id: {
                            $arrayElemAt: ['$department.name', 0],
                        },
                        total: {
                            $sum: '$total',
                        },
                    },
                },
                {
                    $match: {
                        _id: {
                            $ne: null,
                        },
                    },
                },
                {
                    $sort: sort || { total: 1 },
                },
                {
                    $group: {
                        _id: null,
                        total: { $sum: '$total' },
                        data: {
                            $push: {
                                label: '$_id',
                                value: '$total',
                            },
                        },
                    },
                },
                {
                    $project: {
                        _id: 0,
                    },
                },
            ],
            { hint: 'departmentId_1_ts_1', readPreference: readSecondaryPreferred() },
        );
    }

    getTotalConversationsWithoutDepartmentBetweenDates(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): Promise<number> {
        return this.col.countDocuments({
            t: 'l',
            departmentId: {
                $exists: false,
            },
            ts: {
                $gte: start,
                $lt: end,
            },
            ...extraQuery,
        });
    }

    getConversationsByTags(
        start: Date,
        end: Date,
        sort: Record<string, 1 | -1>,
        extraQuery: Filter<IOmnichannelRoom>,
    ): AggregationCursor<ReportResult> {
        return this.col.aggregate(
            [
                {
                    $match: {
                        t: 'l',
                        ts: {
                            $lt: end,
                            $gte: start,
                        },
                        tags: {
                            $exists: true,
                            $ne: [],
                        },
                        ...extraQuery,
                    },
                },
                {
                    $group: {
                        _id: '$tags',
                        total: {
                            $sum: 1,
                        },
                    },
                },
                {
                    $unwind: '$_id',
                },
                {
                    $group: {
                        _id: '$_id',
                        total: { $sum: '$total' },
                    },
                },
                {
                    $sort: sort || { total: 1 },
                },
                {
                    $group: {
                        _id: null,
                        total: { $sum: '$total' },
                        data: {
                            $push: {
                                label: '$_id',
                                value: '$total',
                            },
                        },
                    },
                },
                {
                    $project: {
                        _id: 0,
                    },
                },
            ],
            { hint: 'tags.0_1_ts_1', readPreference: readSecondaryPreferred() },
        );
    }

    getConversationsWithoutTagsBetweenDate(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): Promise<number> {
        return this.col.countDocuments({
            t: 'l',
            ts: {
                $gte: start,
                $lt: end,
            },
            $or: [
                {
                    tags: {
                        $exists: false,
                    },
                },
                {
                    tags: {
                        $eq: [],
                    },
                },
            ],
            ...extraQuery,
        });
    }

    getConversationsByAgents(
        start: Date,
        end: Date,
        sort: Record<string, 1 | -1>,
        extraQuery: Filter<IOmnichannelRoom>,
    ): AggregationCursor<ReportResult> {
        return this.col.aggregate(
            [
                {
                    $match: {
                        t: 'l',
                        ts: {
                            $gte: start,
                            $lt: end,
                        },
                        servedBy: {
                            $exists: true,
                        },
                        ...extraQuery,
                    },
                },
                {
                    $group: {
                        _id: '$servedBy._id',
                        total: { $sum: 1 },
                    },
                },
                {
                    $lookup: {
                        from: 'users',
                        localField: '_id',
                        foreignField: '_id',
                        as: 'agent',
                    },
                },
                {
                    $set: {
                        agent: { $first: '$agent' },
                    },
                },
                {
                    $addFields: {
                        name: {
                            $ifNull: ['$agent.name', '$_id'],
                        },
                    },
                },
                {
                    $sort: sort || { name: 1 },
                },
                {
                    $group: {
                        _id: null,
                        total: { $sum: '$total' },
                        data: {
                            $push: {
                                label: '$name',
                                value: '$total',
                            },
                        },
                    },
                },
                {
                    $project: {
                        _id: 0,
                    },
                },
            ],
            { hint: 'servedBy_1_ts_1', readPreference: readSecondaryPreferred() },
        );
    }

    getTotalConversationsWithoutAgentsBetweenDate(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): Promise<number> {
        return this.col.countDocuments({
            t: 'l',
            ts: {
                $gte: start,
                $lt: end,
            },
            servedBy: {
                $exists: false,
            },
            ...extraQuery,
        });
    }
}