superdesk/superdesk-client-core

View on GitHub
scripts/core/query-formatting.ts

Summary

Maintainability
D
2 days
Test Coverage
import {IComparison, ILogicalOperator, ISuperdeskQuery} from 'superdesk-api';

function isLogicalOperator(x: ILogicalOperator | IComparison): x is ILogicalOperator {
    return x['$and'] != null || x['$or'] != null;
}

function toElasticFilter(q: ILogicalOperator | IComparison) {
    if (isLogicalOperator(q)) {
        const r = {};

        if (q['$and'] != null) {
            r['and'] = q['$and'].map((_q) => toElasticFilter(_q));
        }
        if (q['$or'] != null) {
            r['or'] = q['$or'].map((_q) => toElasticFilter(_q));
        }

        return r;
    } else {
        return Object.keys(q).reduce((acc, field) => {
            const comparisonOptions = q[field];

            const operator = Object.keys(comparisonOptions)[0];
            const value = comparisonOptions[operator];

            switch (operator) {
            case '$eq':
                return {term: {[field]: value}};
            case '$ne':
                return {not: {term: {[field]: value}}};
            case '$gt':
                return {range: {[field]: {'gt': value}}};
            case '$gte':
                return {range: {[field]: {'gte': value}}};
            case '$lt':
                return {range: {[field]: {'lt': value}}};
            case '$lte':
                return {range: {[field]: {'lte': value}}};
            case '$in':
                return {terms: {[field]: value}};
            case '$exists':
                return {query: {exists: {field: field}}};
            case '$notExists':
                return {query: {bool: {must_not: [{exists: {field: field}}]}}};
            case '$stringContains':
                return {query: {simple_query_string: {query: value.val, fields: [field]}}};
            }

            throw new Error(`Conversion for operator ${operator} is not defined.`);
        }, {});
    }
}

function toPyEveFilter(q: ILogicalOperator | IComparison) {
    if (isLogicalOperator(q)) {
        const r = {};

        if (q['$and'] != null) {
            r['$and'] = q['$and'].map((_q) => toPyEveFilter(_q));
        }
        if (q['$or'] != null) {
            r['$or'] = q['$or'].map((_q) => toPyEveFilter(_q));
        }

        return r;
    } else {
        return Object.keys(q).reduce((acc, field) => {
            const comparisonOptions = q[field];

            const operator = Object.keys(comparisonOptions)[0];
            const value = comparisonOptions[operator];

            switch (operator) {
            case '$eq':
                return {[field]: value};
            case '$ne':
                return {[field]: {$ne: value}};
            case '$gt':
                return {[field]: {$gt: value}};
            case '$gte':
                return {[field]: {$gte: value}};
            case '$lt':
                return {[field]: {$lt: value}};
            case '$lte':
                return {[field]: {$lte: value}};
            case '$in':
                return {[field]: {$in: value}};
            case '$exists':
                return {[field]: {$exists: true}};
            case '$notExists':
                return {[field]: {$exists: false}};
            case '$stringContains':
                return {[field]: {
                    $regex: value.val,
                    $options: '-i',
                }};
            }

            throw new Error(`Conversion for operator ${operator} is not defined.`);
        }, {});
    }
}

export function getQueryFieldsRecursive(q: ILogicalOperator | IComparison): Set<string> {
    var fields = new Set<string>();

    if (isLogicalOperator(q)) {
        if (q['$and'] != null) {
            q['$and'].forEach((q1: ILogicalOperator | IComparison) => {
                getQueryFieldsRecursive(q1).forEach((field) => {
                    fields.add(field);
                });
            });
        }
        if (q['$or'] != null) {
            q['$or'].forEach((q1: ILogicalOperator | IComparison) => {
                getQueryFieldsRecursive(q1).forEach((field) => {
                    fields.add(field);
                });
            });
        }

        return fields;
    } else {
        return new Set<string>(Object.keys(q));
    }
}

// The result should be used as URL parameters for HTTP request
export function toElasticQuery(q: ISuperdeskQuery): {q?: string; source: string} {
    interface IQuery {
        query?: {
            filtered: {
                filter?: {};
                query?: {};
            };
        };
        sort: ISuperdeskQuery['sort'];
        size: number;
        from: number;
    }

    const query: IQuery = {
        sort: q.sort,
        size: q.max_results,
        from: (q.page - 1) * q.max_results,
    };

    const filtered = {};

    if (q.filter != null) {
        filtered['filter'] = toElasticFilter(q.filter);
    }

    if (Object.keys(filtered).length > 0) {
        query['query'] = {filtered: filtered};
    }

    if (q.fullTextSearch) {
        if (query.query == null) {
            query.query = {filtered: {}};
        }

        query.query.filtered.query = {
            query_string: {
                query: q.fullTextSearch,
                lenient: true,
                default_operator: 'AND',
            },
        };
    }

    const result: ReturnType<typeof toElasticQuery> = {
        source: JSON.stringify(query),
    };

    return result;
}

interface IPyEveQuery {
    where?: {};
    sort: string; // ?sort=[("lastname", -1)]
}

// Object must only have one key
function objectToTuple<T>(obj: {[key: string]: T}): [string, T] {
    const key = Object.keys(obj)[0];

    return [key, obj[key]];
}

export function toPyEveQuery(
    filter: ISuperdeskQuery['filter'],
    sort: ISuperdeskQuery['sort'],
): IPyEveQuery {
    const result: IPyEveQuery = {
        sort: `[${
            sort.map((sortOption) => {
                const [fieldId, sortDirection] = objectToTuple(sortOption);

                return `("${fieldId}", ${sortDirection === 'asc' ? '1' : '-1'})`;
            }).join(',')
        }]`,
    };

    if (filter != null) {
        result['where'] = toPyEveFilter(filter);
    }

    return result;
}