htdocs/assets/js/views/searches/searches.js

Summary

Maintainability
C
1 day
Test Coverage
"use strict";
define(function(require) {
    var $ = require('jquery'),
        _ = require('underscore'),
        View = require('view'),
        ModalView = require('views/modal'),
        NavbarView = require('views/navbar'),
        CollectionView = require('views/collection'),
        TableView = require('views/table'),
        Templates = require('templates'),
        Util = require('util'),

        Search = require('models/search');


    var SearchesCreateModal = ModalView.extend({
        title: 'Create',
        subTemplate: Templates['searches/createmodal'],
        buttons: [
            {name: 'Create', type: 'success', action: 'create'},
        ],
        events: {
            'click .create-button': 'create',
        },
        initialize: function() {
            ModalView.prototype.initialize.call(this);
            this.vars = {types: Search.Data().Types};
        },
        create: function() {
            var form = this.$('form');
            var query = Util.serializeForm(form, true);

            this.App.Router.navigate('/searches/new?' + decodeURIComponent($.param(query)), {trigger: true});
            return false;
        }
    });

    var SearchesSearchModal = ModalView.extend({
        title: 'Search',
        subTemplate: Templates['searches/searchmodal'],
        buttons: [
            {name: 'Clear', type: 'default', action: 'clear', persist: true, clear: true},
            {name: 'Search', type: 'primary', icon: 'search', action: 'search'},
        ],
        events: {
            'click .search-button': 'search',
            'click .clear-button': 'clear',
        },
        initialize: function() {
            ModalView.prototype.initialize.call(this);
            this.vars = {
                types: Search.Data().Types,
                categories: Search.Data().Categories,
                priorities: Search.Data().Priorities,
            };
        },
        _render: function() {
            ModalView.prototype._render.call(this);

            Util.initTags(this.registerElement('.tags'));
            Util.initAssigneeSelect(
                this.registerElement('input[name=assignee]'),
                this.App.Data.Users, this.App.Data.Groups, true
            );
            Util.initUserSelect(
                this.registerElement('input[name=owner]'),
                this.App.Data.Users, true
            );
        },
        clear: function() {
            // A reset button will clear all the normal fields.
            // We need to manually clear any js form elements here.
            this.$('.tags').select2('data', null);
            this.$('input[name=assignee]').select2('data', null);
            this.$('input[name=owner]').select2('data', null);
        },
        search: function() {
            var form = this.$('form');
            var query = Util.serializeForm(form, true);
            if('tags' in query) {
                query.tags = query.tags.split(',');
            }
            if('assignee' in query) {
                var assignee = Util.parseAssignee(query.assignee);
                query.assignee_type = assignee[0];
                query.assignee = assignee[1];
            }

            this.App.Router.navigate('/searches?' + decodeURIComponent($.param(query)));
            this.App.Bus.trigger('route');
            return false;
        }
    });

    var SearchesNavbarView = NavbarView.extend({
        title: 'Searches',
        links: [
            {name: 'Compact View', action: 'toggle'}
        ],
        sidelinks: [
            {name: 'Create', icon: 'file', action: 'create'},
            {name: 'Search', icon: 'search', action: 'search'},
        ],
        events: {
            'click .toggle-button': 'toggleText',
            'click .create-button': 'showCreate',
            'click .search-button': 'showSearch',
        },
        toggle: false,
        _render: function() {
            NavbarView.prototype._render.call(this);

            this.App.registerSelectableKbdShortcut('l', 'clone', 'Clone the current item', false);
        },
        toggleText: function() {
            this.toggle = !this.toggle;
            this.$('.toggle-button').text(this.toggle ? 'Detailed View':'Compact View');
            this.App.Bus.trigger('toggle', this.toggle);
        },
        showCreate: function() {
            this.App.setModal(new SearchesCreateModal(this.App));
        },
        showSearch: function() {
            this.App.setModal(new SearchesSearchModal(this.App));
        }
    });

    // A function to filter the collection. Used by both collection views.
    var filterSearchesFunc = function(collection, query) {
        for(var k in query) {
            if(!_.isArray(query[k])) {
                query[k] = [query[k]];
            }
        }

        var map_bool = _.partial(_.map, _, function(x) { return !!parseInt(x, 10); });
        var map_int = _.partial(_.map, _, function(x) { return parseInt(x, 10); });
        // Much ado about types. Make sure we convert to the correct data types.
        if('enabled' in query) {
            // Cast from "0"|"1" string to bool.
            query.enabled = map_bool(query.enabled);
        }
        if('priority' in query) {
            query.priority = map_int(query.priority);
        }
        if('assignee' in query) {
            query.assignee_type = map_int(query.assignee_type);
            query.assignee = map_int(query.assignee);
        }
        if('owner' in query) {
            query.owner = map_int(query.owner);
        }

        // Filter on the query.
        var models = _.isEmpty(query) ?
            collection.models:
            collection.filter(function(model) {
                for(var k in query) {
                    var fval = query[k];
                    var mval = model.get(k);
                    mval = _.isArray(mval) ? mval:[mval];
                    var match = _.any(_.map(mval, _.partial(_.contains, fval)));
                    if(!match) {
                        return false;
                    }
                }
                return true;
            });
        return models;
    };

    var filterSearchesQueryFunc = function(collection) {
        return filterSearchesFunc(collection, Util.parseQuery(window.location.href));
    };

    var SearchListEntryView = View.extend({
        template: Templates['searches/searchlistentry'],
        events: {
            'change .status-button': 'toggleStatus',
        },
        _render: function() {
            var vars = this.model.toJSON();
            vars.categories = Search.Data().Categories;
            vars.priorities = Search.Data().Priorities;
            vars.types = Search.Data().Types;
            vars.owner_name = Util.getUserName(this.model.get('owner'), this.App.Data.Users);
            vars.assignee_name = Util.getAssigneeName(
                this.model.get('assignee_type'), this.model.get('assignee'),
                this.App.Data.Users, this.App.Data.Groups,
                undefined, true
            );
            var last_exec_date = this.model.get('last_execution_date');
            var last_fail_date = this.model.get('last_failure_date');
            vars.healthy = !(last_exec_date === last_fail_date && last_exec_date > 0);

            this.$el.html(this.template(vars));
        },
        selectAction: function(action) {
            switch(action) {
                case 'open': this.open(); break;
                case 'clone': this.clone(); break;
            }
        },
        open: function() {
            this.$('a')[0].click();
        },
        clone: function() {
            this.$('.clone-button').click();
        },
        /**
         * Toggle the enabled state of the Search.
         */
        toggleStatus: function(e) {
            var enabled = e.target.checked;
            var change_desc = (enabled ? 'Enabled':'Disabled') + ' search';

            this.model.save({enabled: enabled, change_description: change_desc}, {
                success: this.cbRendered(function(resp) {
                    this.App.addMessage('Updated search', 2);
                })
            });
        }
    });

    var SearchesListView = CollectionView.extend({
        subView: SearchListEntryView,
        selectable: true,
        initializeCollectionData: function(params) {
            var arr = CollectionView.prototype.initializeCollectionData.call(this);
            var children = arr[1].childNodes;
            var count = children.length;

            // Insert a div after every 3 Views to line up the results.
            for(var i = count - 1; i >= 2; --i) {
                if((i + 1) % 3 === 0) {
                    $(children[i]).after($('<div class="clearfix">'));
                }
            }

            return arr;
        },
        filterCollection: filterSearchesQueryFunc,
        setSelectableDisplay: function(sel, selected, down) {
            $(sel.el).find('.panel')
                .toggleClass('panel-primary', selected)
                .toggleClass('panel-default', !selected);
        }
    });

    var SearchTableEntryView = View.extend({
        tagName: 'tr',
        template: Templates['searches/searchtableentry'],
        _render: function() {
            var vars = this.model.toJSON();
            vars.categories = Search.Data().Categories;
            vars.priorities = Search.Data().Priorities;
            vars.types = Search.Data().Types;

            var last_exec_date = this.model.get('last_execution_date');
            var last_fail_date = this.model.get('last_failure_date');
            vars.healthy = !(last_exec_date === last_fail_date && last_exec_date > 0);
            this.$el.html(this.template(vars));
        },
        selectAction: function(action) {
            switch(action) {
                case 'open': this.open(); break;
                case 'clone': this.clone(); break;
            }
        },
        open: function() {
            this.$('a')[0].click();
        },
        clone: function() {
            this.$('.clone-button').click();
        },
    });

    var SearchesTableView = TableView.extend({
        subView: SearchTableEntryView,
        events: {
            'click .update-button': 'updateSearches',
        },
        columns: [
            {name: '', sorter: 'false'},
            {name: '', sorter: 'false'},
            {name: 'Name', width: 50},
            {name: 'Category'},
            {name: 'Tags', width: 40},
            {name: 'Type'},
            {name: 'Priority'},
            {name: 'Health'},
            {name: 'Enabled', sorter: 'check'},
        ],
        sortColumn: 2,
        selectable: true,
        buttons: [
            {name: 'Update', type: 'primary', icon: 'arrow-up', action: 'update'}
        ],
        filterCollection: filterSearchesQueryFunc,
        /**
         * Update the status (enabled/disabled) of the Searches on the page.
         */
        updateSearches: function() {
            var raw = Util.serializeForm(this.$('table'));
            var data = [];
            for(var k in raw) {
                var enabled = !!raw[k];
                var change_desc = (enabled ? 'Enabled':'Disabled') + ' search';
                var search = this.App.Data.Searches.get(k);
                if(search.get('enabled') != enabled) {
                    data.push({id: k, enabled: enabled, change_description: change_desc});
                }
            }
            this.App.showLoader();

            this.App.Data.Searches.save(data, {
                success: this.cbRendered(function(resp) {
                    this.App.addMessage('Updated ' + resp.length + ' searches', 2);
                }),
                complete: $.proxy(this.App.hideLoader, this.App)
            });
        }
    });

    /**
     * The searches View
     */
    var SearchesView = View.extend({
        _load: function() {
            this.loadCollections([this.App.Data.Users, this.App.Data.Groups]);
        },
        _render: function() {
            this.App.setTitle('Searches');
            this.registerView(new SearchesNavbarView(this.App), true);
            this.toggleView();

            // The navbar will update the url, firing off a route event when it does so.
            // Update the View when this happens.
            this.listenTo(this.App.Bus, 'route', this.update);
            this.listenTo(this.App.Bus, 'toggle', this.toggleView);

            this.App.hideLoader();
        },
        toggleView: function(toggle) {
            if(this.getView('collection')) {
                this.destroyView('collection');
            }
            var newView = toggle ? SearchesListView:SearchesTableView;

            this.registerView(new newView(
                this.App, {collection: this.App.Data.Searches}
            ), true, undefined, 'collection');
        },
        update: function() {
            var view = this.getView('collection');
            if(view) {
                view.update();
            }
        }
    }, {
        filterSearchesFunc: filterSearchesFunc
    });

    return SearchesView;
});