superdesk/superdesk-client-core

View on GitHub
scripts/apps/monitoring/services/CardsService.ts

Summary

Maintainability
F
3 days
Test Coverage
import {flatMap, flattenDeep, includes, isNil} from 'lodash';
import {setFilters, IQueryParams} from 'apps/search/services/SearchService';
import {PUBLISHED_STATES} from 'apps/archive/constants';
import {ITEM_STATE} from 'apps/archive/constants';
import {
    DESK_OUTPUT,
    SENT_OUTPUT,
    SCHEDULED_OUTPUT,
} from 'apps/desks/constants';
import {appConfig, extensions} from 'appConfig';
import {IMonitoringFilter, IPersonalSpaceSection} from 'superdesk-api';

export function getExtensionSections() {
    return flatMap(
        Object.values(extensions)
            .map(
                (extension) =>
                    extension.activationResult?.contributions?.personalSpace?.getSections?.() ?? []),
    );
}

export interface ICard {
    _id: string;
    deskId: string;
    fileType: string; // contains JSON array
    contentProfile: string;
    customFilters: string;
    header: string; // example: "Politic Desk"
    subheader: string; // example: "Working Stage"
    type: 'search'
        | 'spike-personal'
        | 'personal'
        | 'stage'
        | 'spike'
        | 'highlights'
        | 'deskOutput'
        | 'sentDeskOutput'
        | 'scheduledDeskOutput'
        | string;
    search?: {
        filter?: {
            query?: {
                repo?: any;
                q?: any;
            };
        };
    };
    max_items?: number;
    singleViewType?: 'desk' | 'stage' | any;
    query: any;
    sent?: boolean;
    markedForMe?: boolean;
    queryParams?: any;
}

CardsService.$inject = ['search', 'session', 'desks', '$location'];
export function CardsService(search, session, desks, $location) {
    this.criteria = getCriteria;
    this.shouldUpdate = shouldUpdate;

    function getCriteriaParams(card: ICard): IQueryParams {
        let params: IQueryParams = {};

        if (card.type === 'search' && card.search && card.search.filter.query) {
            angular.copy(card.search.filter.query, params);
            if (card.query) {
                if (card.search.filter.query.q) {
                    params.q = '(' + card.query + ') ' + card.search.filter.query.q;
                } else {
                    params.q = '(' + card.query + ') ';
                }
            }
        } else {
            params.q = card.query;
        }

        if (card.type === 'spike' || card.type === 'spike-personal') {
            params.spike = 'only';
        } else if (card.type === 'personal' && card.sent) {
            params.spike = 'include';
        } else if (card.type === 'sentDeskOutput') {
            params.spike = 'include';
        }

        return params;
    }

    function filterQueryByCardType(query, queryParam, card: ICard) {
        let deskId;
        const extensionSection = getExtensionSections();
        const section = extensionSection.find((response) => response.id === card.type);

        if (section) {
            query.filter(section.query);
        }

        switch (card.type) {
        case 'search':
            break;

        case 'spike-personal':
        case 'personal':
            query.filter({bool: {
                must_not: {exists: {field: 'task.desk'}},
                should: [
                    {term: {'task.user': session.identity._id}}, // sent to personal
                    {bool: { // just created in personal
                        must: {term: {original_creator: session.identity._id}},
                        must_not: {exists: {field: 'task.user'}},
                    }},
                ],
                minimum_should_match: 1,
            }});
            break;

        case 'sent':
            query.filter({bool: {
                must: [
                    {term: {original_creator: session.identity._id}},
                    {exists: {field: 'task.desk'}},
                ],
            }});
            break;

        case 'spike':
            query.filter({term: {'task.desk': card._id}});
            break;

        case 'highlights':
            query.filter({and: [
                {term: {highlights: queryParam.highlight}},
            ]});
            break;

        case DESK_OUTPUT:
            filterQueryByDeskType(query, card);
            break;

        case SENT_OUTPUT:
            deskId = card._id.substring(0, card._id.indexOf(':'));
            query.filter({bool: {
                filter: {term: {'task.desk_history': deskId}},
                must_not: {term: {'task.desk': deskId}},
            }});
            break;

        case SCHEDULED_OUTPUT:
            deskId = card._id.substring(0, card._id.indexOf(':'));
            query.filter({and: [
                {term: {'task.desk': deskId}},
                {term: {state: 'scheduled'}},
            ]});
            break;

        default:
            if (!isNil(card.singleViewType) && card.singleViewType === 'desk') {
                query.filter({term: {'task.desk': card.deskId}});
            } else if (card._id) {
                query.filter({term: {'task.stage': card._id}});
            }
            break;
        }
    }

    function filterQueryByDeskType(query, card: ICard) {
        var deskId = card._id.substring(0, card._id.indexOf(':'));
        var desk = desks.deskLookup ? desks.deskLookup[deskId] : null;
        var states = PUBLISHED_STATES;

        if (appConfig.monitoring != null && appConfig.monitoring.scheduled) {
            states = PUBLISHED_STATES.filter((state) => state !== ITEM_STATE.SCHEDULED);
        }

        if (desk) {
            const must: Array<{}> = [
                {term: {'task.desk': deskId}},
                {terms: {state: states}},
            ];

            if (desk.desk_type === 'authoring') {
                query.filter({bool: {should: [
                    {term: {'task.last_authoring_desk': deskId}},
                    {bool: {must}},
                ]}});
            } else if (desk.desk_type === 'production') {
                query.filter({bool: {must}});
            }
        }

        if (appConfig.features.nestedItemsInOutputStage) {
            query.setOption('hidePreviousVersions', true);
        }
    }

    function filterQueryByCardFileType(query, card: ICard) {
        if (card.fileType) {
            var termsHighlightsPackage = {and: [
                {bool: {must: {exists: {field: 'highlight'}}}},
                {term: {type: 'composite'}},
            ]};

            var termsFileType: any = {terms: {type: JSON.parse(card.fileType)}};

            // Normal package
            if (includes(JSON.parse(card.fileType), 'composite')) {
                termsFileType = {and: [
                    {bool: {must_not: {exists: {field: 'highlight'}}}},
                    {terms: {type: JSON.parse(card.fileType)}},
                ]};
            }

            if (includes(JSON.parse(card.fileType), 'highlight-pack')) {
                query.filter({or: [
                    termsHighlightsPackage,
                    termsFileType,
                ]});
            } else {
                query.filter(termsFileType);
            }
        }
    }

    function filterQueryByContentProfile(query, card: ICard) {
        if (card.contentProfile) {
            query.filter({terms: {profile: JSON.parse(card.contentProfile)}});
        }
    }

    function filterQueryByCustomQuery(query, card: ICard) {
        if (card.customFilters == null) {
            return;
        }

        var items: {[key: string]: IMonitoringFilter} = JSON.parse(card.customFilters);

        const terms = Object.values(items)
            .reduce((obj1, obj2) => Object.assign(obj1, obj2.query), {});

        Object.keys(terms).forEach((key) => {
            query.filter({terms: {[key]: terms[key]}});
        });
    }

    /**
     * Get items criteria for given card
     *
     * Card can be stage/personal/saved search.
     * There can be also extra string search query
     *
     * @param {Object} card
     * @param {string} queryString
     */
    function getCriteria(card: ICard, queryString?: any, queryParam?: any) {
        var params = getCriteriaParams(card);
        var query = search.query(setFilters(params));
        var criteria: any = {es_highlight: card.query ? search.getElasticHighlight() : 0};

        filterQueryByCardType(query, queryParam, card);
        filterQueryByContentProfile(query, card);
        filterQueryByCardFileType(query, card);
        filterQueryByCustomQuery(query, card);

        if (queryString) {
            query.filter({query: {query_string: {query: queryString, lenient: true}}});
            criteria.es_highlight = search.getElasticHighlight();
        }

        criteria.source = query.getCriteria();
        if (card.type === 'search' && card.search && card.search.filter.query.repo) {
            criteria.repo = card.search.filter.query.repo;
        } else if (desks.isPublishType(card.type)) {
            criteria.repo = 'archive,published';
            if (card.type === 'deskOutput') {
                query.filter({not: {term: {state: 'unpublished'}}});
            }
        }

        criteria.source.from = 0;
        criteria.source.size = card.max_items || 25;
        return criteria;
    }

    function shouldUpdate(card: ICard, data) {
        switch (card.type) {
        case 'stage':
            // refresh stage if it matches updated stage
            return data.stages && !!data.stages[card._id];
        case 'personal':
            return data.user === session.identity._id;
        case DESK_OUTPUT:
        case SENT_OUTPUT:
        case SCHEDULED_OUTPUT:
            var deskId = card._id.substring(0, card._id.indexOf(':'));

            if (deskId) {
                return (
                    data.desks && !!data.desks[deskId]
                ) || (
                    data.from_desk && data.from_desk === deskId
                );
            }

            return false;
        default:
            // no way to determine if item should be visible, refresh
            return true;
        }
    }
}