superdesk/superdesk-client-core

View on GitHub
scripts/apps/search/directives/SearchParameters.ts

Summary

Maintainability
F
1 wk
Test Coverage
import {getParameters} from 'apps/search/constants';
import {getDateFilters} from './DateFilters';
import _ from 'lodash';

/**
 * @ngdoc directive
 * @module superdesk.apps.search
 * @name sdSearchParameters
 *
 * @requires $location
 * @requires asset
 * @requires tags
 * @requires metadata
 * @requires searchCommon
 * @requires desks
 * @requires userList
 * @requires ingestSources
 * @requires subscribersService
 *
 * @description
 *   A directive that generates search parameter form.
 */
SearchParameters.$inject = [
    '$location', 'asset', 'tags', 'metadata',
    'searchCommon', 'desks', 'userList',
    'ingestSources', 'subscribersService',
    '$templateCache', 'search',
];

export function SearchParameters($location, asset, tags, metadata, common, desks,
    userList, ingestSources, subscribersService, $templateCache, search) {
    return {
        scope: {
            repo: '=',
            context: '=',
            providerType: '=',
        },
        templateUrl: asset.templateUrl('apps/search/views/search-parameters.html'),
        link: function(scope, elem) {
            const PARAMETERS = getParameters();
            var ENTER = 13;

            scope.dateFilters = getDateFilters()
                .filter((dateFilter) => metadata.search_config?.[dateFilter.fieldname] != null);

            scope.keyPressed = function(event) {
                if (event.keyCode === ENTER) {
                    searchParameters();
                    event.preventDefault();
                }
            };

            scope.toggleDateFilter = function(fieldname, predefinedFilters) {
                predefinedFilters.forEach((filters) => {
                    if (filters.key === scope.fields[fieldname]) {
                        filters.active = !filters.active;
                        if (!filters.active) {
                            scope.fields[fieldname] = null;
                        }
                        if (scope.fields[fieldname + 'to']) {
                            scope.fields[fieldname + 'to'] = null;
                        }
                        if (scope.fields[fieldname + 'from']) {
                            scope.fields[fieldname + 'from'] = null;
                        }
                    } else {
                        filters.active = false;
                    }
                });
            };

            scope.togglePredefinedDateFilter = function(dateFilter, predefinedFilter) {
                scope.fields[dateFilter.fieldname] = predefinedFilter;
            };

            scope.clearPredefinedFilters = function(fieldname) {
                const clearDateFilter = scope.dateFilters.find((dateFilter) => dateFilter.fieldname === fieldname);

                scope.fields[fieldname] = null;
                clearDateFilter.predefinedFilters.forEach((predefinedFilter) => predefinedFilter.active = false);
            };

            const getSearchConfig = () => {
                if (scope.isContentApi()) {
                    let searchConfig: any = _.pick(metadata.search_config, ['slugline', 'headline',
                        'byline', 'story_text', 'sign_off', 'firstpublished']);

                    searchConfig.subscribers = 1;
                    return searchConfig;
                }
                return metadata.search_config;
            };

            scope.isContentApi = function() {
                return _.get(scope, 'repo.search') === 'content-api';
            };

            /*
             * init function to setup the directive initial state and called by $locationChangeSuccess event
             * @param {boolean} loadData.
             */
            function init(loadData?) {
                var params = $location.search();

                scope.query = params.q;
                scope.flags = false;
                scope.common = common;
                scope.meta = _.extend({}, common.meta);
                scope.fields = {};
                scope.selecteditems = {};
                scope.selectedCodes = {};
                scope.cvs = metadata.search_cvs;
                scope.search_config = getSearchConfig();
                scope.lookupCvs = {};
                scope.params = params.params ? JSON.parse(params.params) : {};

                if (scope.query && !Object.keys(scope.meta).length) {
                    // get selected parameters as object
                    scope.meta = tags.initSelectedParameters(scope.query, true);
                }

                angular.forEach(scope.cvs, (cv) => {
                    scope.lookupCvs[cv.id] = cv;
                });

                if ($location.search().unique_name) {
                    scope.fields.unique_name = $location.search().unique_name;
                }

                if ($location.search().spike) {
                    scope.fields.spike = $location.search().spike;
                } else if (!scope.isContentApi()) {
                    scope.fields.spike = 'exclude';
                }

                // Date filter start.

                let parameters = getDateFilters()
                    .filter((dateFilter) => metadata.search_config?.[dateFilter.fieldname] != null);
                let paramsParameters = Object.keys($location.search());

                function initialDatePublishedFromTo(fieldname) {
                    scope.fields[fieldname] = $location.search()[fieldname];
                }

                scope.dateFilters.forEach((parameter) => {
                    // 1) Initialize the scope fields for predefined buttons
                    // 2) Setting the active property for the predefined buttons.

                    if (paramsParameters.includes(parameter.fieldname)) {
                        let fieldname = $location.search()[parameter['fieldname']];
                        let _predefinedFilter = parameter.predefinedFilters
                            .find((predefinedFilter) => predefinedFilter.key === fieldname);

                        scope.fields[parameter.fieldname] = fieldname;
                        _predefinedFilter.active = true;
                    } else {
                        parameter.predefinedFilters.forEach((predefinedFilter) => {
                            predefinedFilter.active = false;
                        });
                    }

                    // Initializing the date published from to field.

                    if (paramsParameters.includes(parameter['fieldname'] + 'to')) {
                        initialDatePublishedFromTo(parameter['fieldname'] + 'to');
                    }
                    if (paramsParameters.includes(parameter['fieldname'] + 'from')) {
                        initialDatePublishedFromTo(parameter['fieldname'] + 'from');
                    }
                });

                // Date filter end.

                if ($location.search().featuremedia) {
                    scope.fields.featuremedia = true;
                }

                if (loadData) {
                    fetchMetadata();
                    if (scope.isContentApi()) {
                        fetchSubscribers();
                    } else {
                        fetchUsers();
                        fetchDesks();
                        fetchProviders();
                    }
                } else {
                    if (scope.metadata) {
                        initializeItems();
                    }
                    if (scope.isContentApi()) {
                        initializeSubscriber();
                    } else {
                        initializeDesksDropDown();
                        initializeMarkedDesks();
                        initializeProviders();
                        initializeCreators();
                    }
                }
            }

            init(true);

            /*
             * Initialize the creator drop down selection.
             */
            function fetchUsers() {
                userList.getAll()
                    .then((users) => {
                        scope.userList = users;
                        initializeCreators();
                    });
            }

            /*
             * Initialize the desk drop down
             */
            function fetchDesks() {
                scope.desks = [];
                desks.initialize()
                    .then(() => {
                        scope.desks = desks.desks;
                        initializeDesksDropDown();
                    });
            }

            function fetchSubscribers() {
                if (scope.repo.search !== 'content-api') {
                    return;
                }
                subscribersService.initialize()
                    .then(() => {
                        scope.subscribers = subscribersService.subscribers;
                        initializeSubscriber();
                    });
            }

            /*
             * Initialize the provider dropdown
             */
            function fetchProviders() {
                ingestSources.fetchAllIngestProviders().then((items) => {
                    scope.providers = items.filter((provider) =>
                        provider.content_types.some((type) => type !== 'event' && type !== 'planning'));
                    initializeProviders();
                });
            }

            function initializeProviders() {
                if ($location.search().ingest_provider) {
                    scope.fields.ingest_provider = $location.search().ingest_provider;
                }
            }

            function initializeCreators() {
                if ($location.search().original_creator) {
                    scope.fields.original_creator = $location.search().original_creator;
                }
            }

            function initializeSubscriber() {
                if ($location.search().subscriber) {
                    scope.fields.subscriber = $location.search().subscriber;
                }
            }

            /*
             *  Initialize Desks DropDown
             */
            function initializeDesksDropDown() {
                if (scope.desks && scope.desks._items) {
                    initFromToDesk($location.search().from_desk, 'from_desk');
                    initFromToDesk($location.search().to_desk, 'to_desk');
                }
            }

            function initializeMarkedDesks() {
                if ($location.search().marked_desks) {
                    scope.fields.marked_desks = [];
                    scope.selecteditems.marked_desks = scope.selecteditems.marked_desks || [];
                    var markedDesks = JSON.parse($location.search().marked_desks);

                    markedDesks.map((d) => {
                        scope.selecteditems.marked_desks.push(desks.deskLookup[d]);
                        scope.fields.marked_desks.push(desks.deskLookup[d]);
                        return true;
                    });
                } else {
                    scope.selecteditems.marked_desks = [];
                }
            }

            function initializeItems() {
                angular.forEach(scope.cvs, (cv) => {
                    if ($location.search()[cv.id]) {
                        scope.fields[cv.id] = [];
                        scope.selecteditems[cv.id] = scope.selecteditems[cv.id] || [];
                        var itemList = JSON.parse($location.search()[cv.id]);

                        angular.forEach(itemList, (qcode) => {
                            var match = _.find(scope.metadata[cv.list], (m) => m.qcode === qcode);

                            if (match) {
                                scope.selecteditems[cv.id].push(angular.extend(match, {
                                    scheme: cv.id,
                                }));
                                scope.fields[cv.id].push(match);
                            }
                        });
                    } else {
                        scope.selecteditems[cv.id] = [];
                    }
                });
            }

            /*
             * initialize the desk drop down selection.
             * @param {string} query string parameter from_desk or to_desk
             * @param {field} scope field to be updated.
             */
            function initFromToDesk(param, field) {
                if (param) {
                    var deskParams = param.split('-');

                    if (deskParams.length === 2) {
                        scope.fields[field] = deskParams[0];
                    }
                }
            }

            /*
             * Converting to object and adding pre-selected subject codes, location,
             * genre and service to list in left sidebar.
             */
            function fetchMetadata() {
                metadata
                    .initialize()
                    .then(() => {
                        scope.keywords = metadata.cvs.find((cv) => cv._id === 'keywords');
                        return metadata.fetchSubjectcodes();
                    })
                    .then(() => {
                        scope.subjectcodes = metadata.values.subjectcodes;
                        scope.metadata = metadata.values;
                        return tags.initSelectedFacets();
                    })
                    .then((currentTags) => {
                        initializeMarkedDesks();
                        initializeItems();
                    });
            }

            scope.$on('$locationChangeSuccess', () => {
                if (scope.query !== $location.search().q || isFieldDifferentThanSearch()) {
                    init();
                }
            });

            function isFieldDifferentThanSearch() {
                let params = $location.search();

                return _.some(_.keys(PARAMETERS), (key) => _.get(scope.fields, key) !== _.get(params, key));
            }

            function getFirstKey(data) {
                for (var prop in data) {
                    if (data.hasOwnProperty(prop)) {
                        return prop;
                    }
                }
            }

            function booleanToBinaryString(bool) {
                return Number(bool).toString();
            }

            /*
             * Get Query function build the query string
             */
            function getQuery() {
                var metas = [];
                var pattern = /[()]/g;

                angular.forEach(scope.meta, (val, key) => {
                    // checkbox boolean values.
                    let v = val;

                    if (typeof val === 'boolean') {
                        v = booleanToBinaryString(val);
                    }

                    if (typeof val === 'string') {
                        v = val.replace(pattern, '');
                    }

                    if (key === '_all') {
                        metas.push(v.join(' '));
                    } else if (v) {
                        let k = key;

                        if (key.indexOf('scanpix_') === 0) {
                            k = key.substring(8);
                        }
                        if (typeof v === 'string') {
                            if (v) {
                                metas.push(k + ':(' + v + ')');
                            }
                        } else if (angular.isArray(v)) {
                            angular.forEach(v, (value) => {
                                metas.push(k + ':(' + value.replace(pattern, '') + ')');
                            });
                        } else {
                            var subkey = getFirstKey(v);

                            if (v[subkey]) {
                                metas.push(k + '.' + subkey + ':(' + v[subkey] + ')');
                            }
                        }
                    }
                });

                let fields = ['subject', 'company_codes'];

                angular.forEach(scope.cvs, (cv) => {
                    fields.push(cv.id);
                });

                angular.forEach(scope.fields, (val, key) => {
                    if (key === 'from_desk') {
                        $location.search('from_desk', getDeskParam('from_desk'));
                    } else if (key === 'to_desk') {
                        $location.search('to_desk', getDeskParam('to_desk'));
                    } else if (_.includes(fields, key)) {
                        $location.search(key, JSON.stringify(_.map(val, 'qcode')));
                    } else if (key === 'marked_desks') {
                        $location.search(key, JSON.stringify(_.map(val, '_id')));
                    } else if (key === 'featuremedia') {
                        $location.search(key, val ? true : null);
                    } else {
                        $location.search(key, val);
                    }
                });

                if (metas.length) {
                    return metas.join(' ');
                }

                return null;
            }

            scope.$on('search:parameters', searchParameters);

            function searchParameters() {
                $location.search('q', getQuery());
                $location.search('params', scope.params ? JSON.stringify(scope.params) : null);
                scope.meta = {};
            }

            /*
             * Get the Desk Type
             * @param {string} field from or to
             * @returns {string} desk querystring parameter
             */
            function getDeskParam(field) {
                var deskId = '';

                if (scope.fields[field]) {
                    deskId = scope.fields[field];
                    var deskType = _.result(_.find(scope.desks._items, (item) => item._id === deskId), 'desk_type');

                    return deskId + '-' + deskType;
                }

                return null;
            }

            /*
             * Filter content by subject search
             */
            scope.itemSearch = function(items, type) {
                if (items[type].length) {
                    scope.fields[type] = items[type];
                } else {
                    delete scope.fields[type];
                }
            };

            scope.getTemplate = (providerType) => `search-panel-${providerType}.html`;

            scope.hasTemplate = (providerType) =>
                providerType != null && $templateCache.get(scope.getTemplate(providerType)) != null;

            scope.updateParams = (updates) =>
                // apply to make it work for react components
                scope.$applyAsync(() => Object.assign(scope.params, updates));
        },
    };
}