<commit_before>openerp_mail_followers = function(session, mail) {
    var _t = session.web._t,
       _lt = session.web._lt;

    var mail_followers = session.mail_followers = {};

    /**
     * ------------------------------------------------------------
     * mail_followers Widget
     * ------------------------------------------------------------
     *
     * This widget handles the display of a list of records as a vertical
     * list, with an image on the left. The widget itself is a floatting
     * right-sided box.
     * This widget is mainly used to display the followers of records
     * in OpenChatter.
     */

    /* Add the widget to registry */
    session.web.form.widgets.add('mail_followers', 'openerp.mail_followers.Followers');

    mail_followers.Followers = session.web.form.AbstractField.extend({
        template: 'mail.followers',

        init: function() {
            this._super.apply(this, arguments);
            this.image = this.node.attrs.image || 'image_small';
            this.comment = this.node.attrs.help || false;
            this.displayed_limit = this.node.attrs.displayed_nb || 10;
            this.displayed_nb = this.displayed_limit;
            this.ds_model = new session.web.DataSetSearch(this, this.view.model);
            this.ds_users = new session.web.DataSetSearch(this, 'res.users');

            this.value = [];
            this.followers = [];

            this.view_is_editable = this.__parentedParent.is_action_enabled('edit');
        },

        start: function() {
            // use actual_mode property on view to know if the view is in create mode anymore
            this.view.on("change:actual_mode", this, this.on_check_visibility_mode);
            this.on_check_visibility_mode();
            this.reinit();
            this.bind_events();
            this._super();
        },

        on_check_visibility_mode: function () {
            this.set({"force_invisible": this.view.get("actual_mode") == "create"});
        },

        set_value: function(_value) {
            this.value = _value;
            this._super(_value);
        },

        reinit: function() {
            this.message_is_follower == undefined;
            this.display_buttons();
        },

        bind_events: function() {
            var self = this;
            // event: click on '(Un)Follow' button, that toggles the follow for uid
            this.$('.oe_follower').on('click', function (event) {
                if($(this).hasClass('oe_notfollow'))
                    self.do_follow();
                else
                    self.do_unfollow();
            });
            // event: click on a subtype, that (un)subscribe for this subtype
            this.$el.on('click', '.oe_subtype_list input', function(event) {
                self.do_update_subscription(event);
                var $list = self.$('.oe_subtype_list');
                if(!$list.hasClass('open')) {
                    $list.addClass('open');
                }
                if(self.$('.oe_subtype_list ul')[0].children.length < 1) {
                    $list.removeClass('open');
                }
                event.stopPropagation();
            });
            // event: click on 'invite' button, that opens the invite wizard
            this.$('.oe_invite').on('click', self.on_invite_follower);
            // event: click on 'edit_subtype(pencil)' button to edit subscription
            this.$el.on('click', '.oe_edit_subtype', self.on_edit_subtype);
            this.$el.on('click', '.oe_remove_follower', self.on_remove_follower);
            this.$el.on('click', '.oe_show_more', self.on_show_more_followers)
        },

        on_edit_subtype: function(event) {
            var self = this;
            var $currentTarget = $(event.currentTarget);
            var user_pid = $currentTarget.data('id');
            $('div.oe_edit_actions').remove();
            self.$dialog = new session.web.dialog($('
'), {
                            modal: true,
                            width: 'auto',
                            height: 'auto',
                            title: _t('Edit Subscription of ') + $currentTarget.siblings('a').text(),
                            buttons: [
                                    { text: _t("Apply"), click: function() {
                                        self.do_update_subscription(event, user_pid);
                                        $(this).dialog("close");
                                    }},
                                    { text: _t("Cancel"), click: function() { $(this).dialog("close"); }}
                                ],
                    });
            return self.fetch_subtypes(user_pid);
        },

        on_invite_follower: function (event) {
            var self = this;
            var action = {
                type: 'ir.actions.act_window',
                res_model: 'mail.wizard.invite',
                view_mode: 'form',
                view_type: 'form',
                views: [[false, 'form']],
                target: 'new',
                context: {
                    'default_res_model': this.view.dataset.model,
                    'default_res_id': this.view.datarecord.id,
                },
            }
            this.do_action(action, {
                on_close: function() {
                    self.read_value();
                },
            });
        },

        on_show_more_followers: function (event) {
            this.displayed_nb += this.displayed_limit;
            this.display_followers(false);
        },

        on_remove_follower: function (event) {
            var partner_id = $(event.target).data('id');
            var name = $(event.target).parent().find("a").html();
            if (confirm(_.str.sprintf(_t("Warning! \n %s won't be notified of any email or discussion on this document. Do you really want to remove him from the followers ?"), name))) {
                var context = new session.web.CompoundContext(this.build_context(), {});
                return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id], [partner_id], context])
                    .then(this.proxy('read_value'));
            }
        },

        read_value: function () {
            var self = this;
            this.displayed_nb = this.displayed_limit;
            return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).then(function (results) {
                self.value = results[0].message_follower_ids;
                self.render_value();
            });
        },

        render_value: function () {
            this.reinit();
            return this.fetch_followers(this.value);
        },

        fetch_followers: function (value_) {
            this.value = value_ || [];
            return this.ds_model.call('read_followers_data', [this.value])
                .then(this.proxy('display_followers'), this.proxy('fetch_generic'))
                .then(this.proxy('display_buttons'))
                .then(this.proxy('fetch_subtypes'));
        },

        /** Read on res.partner failed: fall back on a generic case
            - fetch current user partner_id (call because no other smart solution currently) FIXME
            - then display a generic message about followers */
        fetch_generic: function (error, event) {
            var self = this;
            event.preventDefault();
            return this.ds_users.call('read', [this.session.uid, ['partner_id']]).then(function (results) {
                var pid = results['partner_id'][0];
                self.message_is_follower = (_.indexOf(self.value, pid) != -1);
            }).then(self.proxy('display_generic'));
        },

        _format_followers: function(count){
            var str = '';
            if(count <= 0){
                str = _t('No followers');
            }else if(count === 1){
                str = _t('One follower');
            }else{
                str = ''+count+' '+_t('followers');
            }
            return str;
        },

        /* Display generic info about follower, for people not having access to res_partner */
        display_generic: function () {
            var self = this;
            var node_user_list = this.$('.oe_follower_list').empty();
            this.$('.oe_follower_title').html(this._format_followers(this.value.length));
        },

        /** Display the followers */
        display_followers: function (records) {
            var self = this;
            this.message_is_follower = false;
            this.followers = records || this.followers;
            // clean and display title
            var node_user_list = this.$('.oe_follower_list').empty();
            this.$('.oe_follower_title').html(this._format_followers(this.followers.length));
            self.message_is_follower = _.indexOf(this.followers.map(function (rec) { return rec[2]['is_uid']}), true) != -1;
            // truncate number of displayed followers
            var truncated = this.followers.slice(0, this.displayed_nb);
            _(truncated).each(function (record) {
                partner = {
                    'id': record[0],
                    'name': record[1],
                    'is_uid': record[2]['is_uid'],
                    'is_editable': record[2]['is_editable'],
                    'avatar_url': mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record[0]),
                }
                $(session.web.qweb.render('mail.followers.partner', {'record': partner, 'widget': self})).appendTo(node_user_list);
                // On mouse-enter it will show the edit_subtype pencil.
                if (partner.is_editable) {
                    self.$('.oe_follower_list').on('mouseenter mouseleave', function(e) {
                        self.$('.oe_edit_subtype').toggleClass('oe_hidden', e.type == 'mouseleave');
                        self.$('.oe_follower_list').find('.oe_partner').toggleClass('oe_partner_name', e.type == 'mouseenter');
                    });
                }
            });
            // FVA note: be sure it is correctly translated
            if (truncated.length < this.followers.length) {
                $(session.web.qweb.render('mail.followers.show_more', {'number': (this.followers.length - truncated.length)} )).appendTo(node_user_list);
            }
        },

        display_buttons: function () {
            if (this.message_is_follower) {
                this.$('button.oe_follower').removeClass('oe_notfollow').addClass('oe_following');
            }
            else {
                this.$('button.oe_follower').removeClass('oe_following').addClass('oe_notfollow');
            }

            if (this.view.is_action_enabled('edit'))
                this.$('span.oe_mail_invite_wrapper').hide();
            else
                this.$('span.oe_mail_invite_wrapper').show();
        },

        /** Fetch subtypes, only if current user is follower */
        fetch_subtypes: function (user_pid) {
            var self = this;
            var dialog = false;
            if (user_pid) {
                dialog = true;
            } else {
                var subtype_list_ul = this.$('.oe_subtype_list ul').empty();
                if (! this.message_is_follower) return;
            }
            var id = this.view.datarecord.id;
            this.ds_model.call('message_get_subscription_data', [[id], user_pid, new session.web.CompoundContext(this.build_context(), {})])
                .then(function (data) {self.display_subtypes(data, id, dialog);});
        },

        /** Display subtypes: {'name': default, followed} */
        display_subtypes:function (data, id, dialog) {
            var self = this;
            if (dialog) {
                var $list = self.$dialog;
            }
            else {
                var $list = this.$('.oe_subtype_list ul');
            }
            var records = data[id].message_subtype_data;
            this.records_length = $.map(records, function(value, index) { return index; }).length;
            if (this.records_length > 1) { self.display_followers(); }
            var old_model = '';
            _(records).each(function (record, record_name) {
                if (old_model != record.parent_model){
                    if (old_model != ''){
                        var index = $($list).find('.oe_subtype').length;
                        $($($list).find('.oe_subtype')[index-1]).addClass('subtype-border');
                    }
                    old_model = record.parent_model;
                }
                record.name = record_name;
                record.followed = record.followed || undefined;
                $(session.web.qweb.render('mail.followers.subtype', {'record': record, 'dialog': dialog})).appendTo($list);
            });
        },

        do_follow: function () {
            var context = new session.web.CompoundContext(this.build_context(), {});
            this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], undefined, context])
                .then(this.proxy('read_value'));

            _.each(this.$('.oe_subtype_list input'), function (record) {
                $(record).attr('checked', 'checked');
            });
        },

        do_unfollow: function (user_pid) {
            var self = this;
            if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) {
                _(this.$('.oe_msg_subtype_check')).each(function (record) {
                    $(record).attr('checked',false);
                });
            var action_unsubscribe = 'message_unsubscribe_users';
            var follower_ids = [this.session.uid];
            if (user_pid) {
                action_unsubscribe = 'message_unsubscribe';
                follower_ids = [user_pid];
            }
                var context = new session.web.CompoundContext(this.build_context(), {});
                return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id], follower_ids, context])
                     .then(this.proxy('read_value'));
            }
            return false;
        },

        do_update_subscription: function (event, user_pid) {
            var self = this;

            var action_subscribe = 'message_subscribe_users';
            var follower_ids = [this.session.uid];
            var oe_action = this.$('.oe_actions input[type="checkbox"]');
            if (user_pid) {
                action_subscribe = 'message_subscribe';
                follower_ids = [user_pid];
                oe_action = $('.oe_edit_actions input[type="checkbox"]');
            }
            var checklist = new Array();
            _(oe_action).each(function (record) {
                if ($(record).is(':checked')) {
                    checklist.push(parseInt($(record).data('id')));
                }
            });

            if (!checklist.length) {
                if (!this.do_unfollow(user_pid)) {
                    $(event.target).attr("checked", "checked");
                } else {
                      self.$('.oe_subtype_list ul').empty();
                }
            } else {
                var context = new session.web.CompoundContext(this.build_context(), {});
                return this.ds_model.call(action_subscribe, [[this.view.datarecord.id], follower_ids, checklist, context])
                    .then(this.proxy('read_value'));
            }
        },
    });
};
<commit_msg>[IMP]disabled dropdown button when there is no item in dropdown.<commit_after>openerp_mail_followers = function(session, mail) {
    var _t = session.web._t,
       _lt = session.web._lt;

    var mail_followers = session.mail_followers = {};

    /**
     * ------------------------------------------------------------
     * mail_followers Widget
     * ------------------------------------------------------------
     *
     * This widget handles the display of a list of records as a vertical
     * list, with an image on the left. The widget itself is a floatting
     * right-sided box.
     * This widget is mainly used to display the followers of records
     * in OpenChatter.
     */

    /* Add the widget to registry */
    session.web.form.widgets.add('mail_followers', 'openerp.mail_followers.Followers');

    mail_followers.Followers = session.web.form.AbstractField.extend({
        template: 'mail.followers',

        init: function() {
            this._super.apply(this, arguments);
            this.image = this.node.attrs.image || 'image_small';
            this.comment = this.node.attrs.help || false;
            this.displayed_limit = this.node.attrs.displayed_nb || 10;
            this.displayed_nb = this.displayed_limit;
            this.ds_model = new session.web.DataSetSearch(this, this.view.model);
            this.ds_users = new session.web.DataSetSearch(this, 'res.users');

            this.value = [];
            this.followers = [];

            this.view_is_editable = this.__parentedParent.is_action_enabled('edit');
        },

        start: function() {
            // use actual_mode property on view to know if the view is in create mode anymore
            this.view.on("change:actual_mode", this, this.on_check_visibility_mode);
            this.on_check_visibility_mode();
            this.reinit();
            this.bind_events();
            this._super();
        },

        on_check_visibility_mode: function () {
            this.set({"force_invisible": this.view.get("actual_mode") == "create"});
        },

        set_value: function(_value) {
            this.value = _value;
            this._super(_value);
        },

        reinit: function() {
            this.message_is_follower == undefined;
            this.display_buttons();
        },

        bind_events: function() {
            var self = this;
            // event: click on '(Un)Follow' button, that toggles the follow for uid
            this.$('.oe_follower').on('click', function (event) {
                if($(this).hasClass('oe_notfollow'))
                    self.do_follow();
                else
                    self.do_unfollow();
            });
            // event: click on a subtype, that (un)subscribe for this subtype
            this.$el.on('click', '.oe_subtype_list input', function(event) {
                self.do_update_subscription(event);
                var $list = self.$('.oe_subtype_list');
                if(!$list.hasClass('open')) {
                    $list.addClass('open');
                }
                if(self.$('.oe_subtype_list ul')[0].children.length < 1) {
                    $list.removeClass('open');
                }
                event.stopPropagation();
            });
            // event: click on 'invite' button, that opens the invite wizard
            this.$('.oe_invite').on('click', self.on_invite_follower);
            // event: click on 'edit_subtype(pencil)' button to edit subscription
            this.$el.on('click', '.oe_edit_subtype', self.on_edit_subtype);
            this.$el.on('click', '.oe_remove_follower', self.on_remove_follower);
            this.$el.on('click', '.oe_show_more', self.on_show_more_followers)
        },

        on_edit_subtype: function(event) {
            var self = this;
            var $currentTarget = $(event.currentTarget);
            var user_pid = $currentTarget.data('id');
            $('div.oe_edit_actions').remove();
            self.$dialog = new session.web.dialog($('
'), {
                            modal: true,
                            width: 'auto',
                            height: 'auto',
                            title: _t('Edit Subscription of ') + $currentTarget.siblings('a').text(),
                            buttons: [
                                    { text: _t("Apply"), click: function() {
                                        self.do_update_subscription(event, user_pid);
                                        $(this).dialog("close");
                                    }},
                                    { text: _t("Cancel"), click: function() { $(this).dialog("close"); }}
                                ],
                    });
            return self.fetch_subtypes(user_pid);
        },

        on_invite_follower: function (event) {
            var self = this;
            var action = {
                type: 'ir.actions.act_window',
                res_model: 'mail.wizard.invite',
                view_mode: 'form',
                view_type: 'form',
                views: [[false, 'form']],
                target: 'new',
                context: {
                    'default_res_model': this.view.dataset.model,
                    'default_res_id': this.view.datarecord.id,
                },
            }
            this.do_action(action, {
                on_close: function() {
                    self.read_value();
                },
            });
        },

        on_show_more_followers: function (event) {
            this.displayed_nb += this.displayed_limit;
            this.display_followers(false);
        },

        on_remove_follower: function (event) {
            var partner_id = $(event.target).data('id');
            var name = $(event.target).parent().find("a").html();
            if (confirm(_.str.sprintf(_t("Warning! \n %s won't be notified of any email or discussion on this document. Do you really want to remove him from the followers ?"), name))) {
                var context = new session.web.CompoundContext(this.build_context(), {});
                return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id], [partner_id], context])
                    .then(this.proxy('read_value'));
            }
        },

        read_value: function () {
            var self = this;
            this.displayed_nb = this.displayed_limit;
            return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).then(function (results) {
                self.value = results[0].message_follower_ids;
                self.render_value();
            });
        },

        render_value: function () {
            this.reinit();
            return this.fetch_followers(this.value);
        },

        fetch_followers: function (value_) {
            this.value = value_ || [];
            return this.ds_model.call('read_followers_data', [this.value])
                .then(this.proxy('display_followers'), this.proxy('fetch_generic'))
                .then(this.proxy('display_buttons'))
                .then(this.proxy('fetch_subtypes'));
        },

        /** Read on res.partner failed: fall back on a generic case
            - fetch current user partner_id (call because no other smart solution currently) FIXME
            - then display a generic message about followers */
        fetch_generic: function (error, event) {
            var self = this;
            event.preventDefault();
            return this.ds_users.call('read', [this.session.uid, ['partner_id']]).then(function (results) {
                var pid = results['partner_id'][0];
                self.message_is_follower = (_.indexOf(self.value, pid) != -1);
            }).then(self.proxy('display_generic'));
        },

        _format_followers: function(count){
            var str = '';
            if(count <= 0){
                str = _t('No followers');
            }else if(count === 1){
                str = _t('One follower');
            }else{
                str = ''+count+' '+_t('followers');
            }
            return str;
        },

        /* Display generic info about follower, for people not having access to res_partner */
        display_generic: function () {
            var self = this;
            var node_user_list = this.$('.oe_follower_list').empty();
            this.$('.oe_follower_title').html(this._format_followers(this.value.length));
        },

        /** Display the followers */
        display_followers: function (records) {
            var self = this;
            this.message_is_follower = false;
            this.followers = records || this.followers;
            // clean and display title
            var node_user_list = this.$('.oe_follower_list').empty();
            this.$('.oe_follower_title').html(this._format_followers(this.followers.length));
            self.message_is_follower = _.indexOf(this.followers.map(function (rec) { return rec[2]['is_uid']}), true) != -1;
            // truncate number of displayed followers
            var truncated = this.followers.slice(0, this.displayed_nb);
            _(truncated).each(function (record) {
                partner = {
                    'id': record[0],
                    'name': record[1],
                    'is_uid': record[2]['is_uid'],
                    'is_editable': record[2]['is_editable'],
                    'avatar_url': mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record[0]),
                }
                $(session.web.qweb.render('mail.followers.partner', {'record': partner, 'widget': self})).appendTo(node_user_list);
                // On mouse-enter it will show the edit_subtype pencil.
                if (partner.is_editable) {
                    self.$('.oe_follower_list').on('mouseenter mouseleave', function(e) {
                        self.$('.oe_edit_subtype').toggleClass('oe_hidden', e.type == 'mouseleave');
                        self.$('.oe_follower_list').find('.oe_partner').toggleClass('oe_partner_name', e.type == 'mouseenter');
                    });
                }
            });
            // FVA note: be sure it is correctly translated
            if (truncated.length < this.followers.length) {
                $(session.web.qweb.render('mail.followers.show_more', {'number': (this.followers.length - truncated.length)} )).appendTo(node_user_list);
            }
        },

        display_buttons: function () {
            if (this.message_is_follower) {
                this.$('button.oe_follower').removeClass('oe_notfollow').addClass('oe_following');
            }
            else {
                this.$('button.oe_follower').removeClass('oe_following').addClass('oe_notfollow');
            }

            if (this.view.is_action_enabled('edit'))
                this.$('span.oe_mail_invite_wrapper').hide();
            else
                this.$('span.oe_mail_invite_wrapper').show();
        },

        /** Fetch subtypes, only if current user is follower */
        fetch_subtypes: function (user_pid) {
            var self = this;
            var dialog = false;
            if (user_pid) {
                dialog = true;
            } else {
                var subtype_list_ul = this.$('.oe_subtype_list ul').empty();
                if (! this.message_is_follower) {
                    this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', true);
                    return;
                }
                else {
                    this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', false);
                }
            }
            var id = this.view.datarecord.id;
            this.ds_model.call('message_get_subscription_data', [[id], user_pid, new session.web.CompoundContext(this.build_context(), {})])
                .then(function (data) {self.display_subtypes(data, id, dialog);});
        },

        /** Display subtypes: {'name': default, followed} */
        display_subtypes:function (data, id, dialog) {
            var self = this;
            if (dialog) {
                var $list = self.$dialog;
            }
            else {
                var $list = this.$('.oe_subtype_list ul');
            }
            var records = data[id].message_subtype_data;
            this.records_length = $.map(records, function(value, index) { return index; }).length;
            if (this.records_length > 1) { self.display_followers(); }
            var old_model = '';
            _(records).each(function (record, record_name) {
                if (old_model != record.parent_model){
                    if (old_model != ''){
                        var index = $($list).find('.oe_subtype').length;
                        $($($list).find('.oe_subtype')[index-1]).addClass('subtype-border');
                    }
                    old_model = record.parent_model;
                }
                record.name = record_name;
                record.followed = record.followed || undefined;
                $(session.web.qweb.render('mail.followers.subtype', {'record': record, 'dialog': dialog})).appendTo($list);
            });
        },

        do_follow: function () {
            var context = new session.web.CompoundContext(this.build_context(), {});
            this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', false);
            this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], undefined, context])
                .then(this.proxy('read_value'));

            _.each(this.$('.oe_subtype_list input'), function (record) {
                $(record).attr('checked', 'checked');
            });
        },

        do_unfollow: function (user_pid) {
            var self = this;
            if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) {
                _(this.$('.oe_msg_subtype_check')).each(function (record) {
                    $(record).attr('checked',false);
                });
            var action_unsubscribe = 'message_unsubscribe_users';
            this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', true);
            var follower_ids = [this.session.uid];
            if (user_pid) {
                action_unsubscribe = 'message_unsubscribe';
                follower_ids = [user_pid];
            }
                var context = new session.web.CompoundContext(this.build_context(), {});
                return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id], follower_ids, context])
                     .then(this.proxy('read_value'));
            }
            return false;
        },

        do_update_subscription: function (event, user_pid) {
            var self = this;

            var action_subscribe = 'message_subscribe_users';
            var follower_ids = [this.session.uid];
            var oe_action = this.$('.oe_actions input[type="checkbox"]');
            if (user_pid) {
                action_subscribe = 'message_subscribe';
                follower_ids = [user_pid];
                oe_action = $('.oe_edit_actions input[type="checkbox"]');
            }
            var checklist = new Array();
            _(oe_action).each(function (record) {
                if ($(record).is(':checked')) {
                    checklist.push(parseInt($(record).data('id')));
                }
            });

            if (!checklist.length) {
                if (!this.do_unfollow(user_pid)) {
                    $(event.target).attr("checked", "checked");
                } else {
                      self.$('.oe_subtype_list ul').empty();
                }
            } else {
                var context = new session.web.CompoundContext(this.build_context(), {});
                return this.ds_model.call(action_subscribe, [[this.view.datarecord.id], follower_ids, checklist, context])
                    .then(this.proxy('read_value'));
            }
        },
    });
};
<|endoftext|>


<commit_before>/**
 * @package     hubzero-cms
 * @file        components/com_wiki/wiki.js
 * @copyright   Copyright 2005-2011 Purdue University. All rights reserved.
 * @license     http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3
 */

//-----------------------------------------------------------
//  Ensure we have our namespace
//-----------------------------------------------------------
if (!HUB) {
	var HUB = {};
}

//----------------------------------------------------------
// Resource Ranking pop-ups
//----------------------------------------------------------
if (!jq) {
	var jq = $;
}

HUB.Wiki = {
	jQuery: jq,

	getTemplate: function() {
		var $ = this.jQuery;

		var id = $('#templates');
		if (id.val() != 'tc') {
			var hi = $(id.val()).val();
			var co = $('#pagetext');
			co.val(hi);

			var ji = $('#'+id.val()+'_tags').val();
			var jo = $('#actags');
			jo.val(ji);

			if ($('#maininput-actags') && jo) {
				/*var ul = $($('#maininput-actags').parent().parent());
				var label = $($('#maininput-actags').parent().parent().parent());
				label.remove(ul);

				var actags = new AppleboxList(jo, {'hideempty': false, 'resizable': {'step': 8}});

				var actkn = '';
				if ($('actkn')) {
					//actkn = '&'+$('actkn').value+'=1';
					actkn = '&admin=true';
				}

				var completer2 = new Autocompleter.MultiSelectable.Ajax.Json($('maininput'), '/index.php?option=com_tags&no_html=1&task=autocomplete'+actkn, {
					'tagger': actags,
					'minLength': 1, // We wait for at least one character
					'overflow': true, // Overflow for more entries
					'wrapSelectionsWithSpacesInQuotes': false
				});*/
			}
		} else {
			$('#pagetext').val('');
		}
	},

	initialize: function() {
		var $ = this.jQuery;

		if ($('#templates')) {
			$('#templates').bind('change', HUB.Wiki.getTemplate);
		}

		var mode = $('#params_mode');
		if (mode) {
			mode.bind('change', HUB.Wiki.checkMode);
		}

		if ($('#file-uploader')) {
			HUB.Wiki.updateFileList();
		}

		if (typeof(qq) != 'undefined') {
			var uploader = new qq.FileUploader({
				element: $('#file-uploader')[0],
				action: $('#file-uploader').attr('data-action'),
				multiple: true,
				debug: false,
				onSubmit: function(id, file) {
					//$("#ajax-upload-left").append("
");
				},
				onComplete: function(id, file, response) {
					$('.qq-upload-list').empty();
					HUB.Wiki.updateFileList();
				}
			});
		}
	},

	updateFileList: function() {
		var $ = HUB.Wiki.jQuery;

		if ($('#file-uploader')) {
			$.get($('#file-uploader').attr('data-list'), {}, function(data) {
				$('#file-uploader-list').html(data);
				$('a.delete')
					.unbind('click')
					.on('click', function(event){
						event.preventDefault();
						$.get($(this).attr('href'), {}, function(data) {
							HUB.Wiki.updateFileList();
						});
					});
			});
		}
	},

	checkMode: function() {
		var $ = this.jQuery;

		var mode = $('#params_mode');
		if (mode.val() != 'knol') {
			$($('#params_authors').parent()).addClass('hide');
			$($('#params_hide_authors').parent()).addClass('hide');
			$($('#params_allow_changes').parent()).addClass('hide');
			$($('#params_allow_comments').parent()).addClass('hide');
		} else {
			if ($($('#params_authors').parent()).hasClass('hide')) {
				$($('#params_authors').parent()).removeClass('hide');
			}
			if ($($('#params_hide_authors').parent()).hasClass('hide')) {
				$($('#params_hide_authors').parent()).removeClass('hide');
			}
			if ($($('#params_allow_changes').parent()).hasClass('hide')) {
				$($('#params_allow_changes').parent()).removeClass('hide');
			}
			if ($($('#params_allow_comments').parent()).hasClass('hide')) {
				$($('#params_allow_comments').parent()).removeClass('hide');
			}
		}
	}
}

jQuery(document).ready(function($){
	HUB.Wiki.initialize();
});

<commit_msg>Bug fixes for selecting a template<commit_after>/**
 * @package     hubzero-cms
 * @file        components/com_wiki/wiki.js
 * @copyright   Copyright 2005-2011 Purdue University. All rights reserved.
 * @license     http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3
 */

//-----------------------------------------------------------
//  Ensure we have our namespace
//-----------------------------------------------------------
if (!HUB) {
	var HUB = {};
}

//----------------------------------------------------------
// Resource Ranking pop-ups
//----------------------------------------------------------
if (!jq) {
	var jq = $;
}

HUB.Wiki = {
	jQuery: jq,

	getTemplate: function() {
		var $ = HUB.Wiki.jQuery
			hi = '';

		var id = $('#templates');
		if (id.val() != 'tc') {
			var hi = $('#'+id.val()).val();
			var co = $('#pagetext');
			co.val(hi);

			var ji = $('#'+id.val()+'_tags').val();
			var jo = $('#actags');
			jo.val(ji);

			if ($('#token-input-actags') && jo) {
				var data = [];
				if (ji) {
					if (ji.indexOf(',') == -1) {
						var values = [ji];
					} else {
						var values = ji.split(',');
					}
					$(values).each(function(i, v){
						v = v.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
						var id = null,
							name = null;
						if (v.match(/(.+?) \((.+?)\)/ig)) {
							id = v.replace(/(.+?) \((.+?)\)/ig, '$2');
						    name = v.replace(/(.+?) \((.+?)\)/ig, '$1');
						}
						id = (id) ? id : v;
						name = (name) ? name : id;

						data[i] = {
							'id': id,
							'name': name
						};
						jo.tokenInput('add', data[i]);
					});
				}

				/*jo.tokenInput('/index.php?option=com_tags&no_html=1&task=autocomplete', {
	                theme: 'act',
					hintText: 'Type in a search term',
					prePopulate: data,
					tokenLimit: null,
					preventDuplicates: true,
					resultsFormatter: function(item){
						if (option != 'tags') {
							var html = "
";
							if (item['picture']) {
								html += '';
							}
							html += item[this.propertyToSearch]+ " ("+item['id']+")";
							if (item['org']) {
								html += '' + item['org'] + '';
							}
							if (item['picture']) {
								html += '

';
							}
							html += "
";
							return html;
						}
						return "
" + item[this.propertyToSearch]+ "
";
					},
					onAdd: function(){
						if (wsel) {
							$.getJSON('/index.php?option=com_groups&no_html=1&task=memberslist&group=' + $('#'+id).val(), function(data) {
								HUB.Plugins.Autocomplete.writeSelectList(data.members, wsel);
							});
						}
					}
	            });*/
				//jo.tokenInput().add();
				/*var ul = $($('#maininput-actags').parent().parent());
				var label = $($('#maininput-actags').parent().parent().parent());
				label.remove(ul);

				var actags = new AppleboxList(jo, {'hideempty': false, 'resizable': {'step': 8}});

				var actkn = '';
				if ($('actkn')) {
					//actkn = '&'+$('actkn').value+'=1';
					actkn = '&admin=true';
				}

				var completer2 = new Autocompleter.MultiSelectable.Ajax.Json($('maininput'), '/index.php?option=com_tags&no_html=1&task=autocomplete'+actkn, {
					'tagger': actags,
					'minLength': 1, // We wait for at least one character
					'overflow': true, // Overflow for more entries
					'wrapSelectionsWithSpacesInQuotes': false
				});*/
			}
		} else {
			$('#pagetext').val('');
		}
		if (typeof(wykiwygs) === 'undefined') {
			return;
		}
		if (wykiwygs.length) {
			for (i=0; i<wykiwygs.length; i++)
			{
				wykiwygs[i].t.value = hi;
				wykiwygs[i].e.body.innerHTML = wykiwygs[i].makeHtml(wykiwygs[i].t.value);
			}
		}
	},

	initialize: function() {
		var $ = this.jQuery;

		if ($('#templates').length > 0) {
			$('#templates').on('change', HUB.Wiki.getTemplate);
		}

		var mode = $('#params_mode');
		if (mode) {
			mode.bind('change', HUB.Wiki.checkMode);
		}

		if ($('#file-uploader')) {
			HUB.Wiki.updateFileList();
		}

		if (typeof(qq) != 'undefined') {
			var uploader = new qq.FileUploader({
				element: $('#file-uploader')[0],
				action: $('#file-uploader').attr('data-action'),
				multiple: true,
				debug: false,
				onSubmit: function(id, file) {
					//$("#ajax-upload-left").append("
");
				},
				onComplete: function(id, file, response) {
					$('.qq-upload-list').empty();
					HUB.Wiki.updateFileList();
				}
			});
		}
	},

	updateFileList: function() {
		var $ = HUB.Wiki.jQuery;

		if ($('#file-uploader')) {
			$.get($('#file-uploader').attr('data-list'), {}, function(data) {
				$('#file-uploader-list').html(data);
				$('a.delete')
					.unbind('click')
					.on('click', function(event){
						event.preventDefault();
						$.get($(this).attr('href'), {}, function(data) {
							HUB.Wiki.updateFileList();
						});
					});
			});
		}
	},

	checkMode: function() {
		var $ = this.jQuery;

		var mode = $('#params_mode');
		if (mode.val() != 'knol') {
			$($('#params_authors').parent()).addClass('hide');
			$($('#params_hide_authors').parent()).addClass('hide');
			$($('#params_allow_changes').parent()).addClass('hide');
			$($('#params_allow_comments').parent()).addClass('hide');
		} else {
			if ($($('#params_authors').parent()).hasClass('hide')) {
				$($('#params_authors').parent()).removeClass('hide');
			}
			if ($($('#params_hide_authors').parent()).hasClass('hide')) {
				$($('#params_hide_authors').parent()).removeClass('hide');
			}
			if ($($('#params_allow_changes').parent()).hasClass('hide')) {
				$($('#params_allow_changes').parent()).removeClass('hide');
			}
			if ($($('#params_allow_comments').parent()).hasClass('hide')) {
				$($('#params_allow_comments').parent()).removeClass('hide');
			}
		}
	}
}

jQuery(document).ready(function($){
	HUB.Wiki.initialize();
});

<|endoftext|>


<commit_before>import {
  BaseModel, identifiedBy, field, identifier, hasMany, belongsTo, computed,
} from 'shared/model';
import Exercises from '../../exercises';
import { filter, sum, sumBy, find, isNil, isEmpty, compact, sortBy, get, includes, some, reduce, every } from 'lodash';
import DroppedQuestion from './dropped_question';
import S from '../../../helpers/string';

@identifiedBy('task-plan/scores/student-question')
class TaskPlanScoreStudentQuestion extends BaseModel {
  @identifier question_id;
  @field exercise_id;
  @field is_completed = false;
  @field points;
  @field selected_answer_id;
  @field is_correct;
  @field free_response;
  @field task_step_id;
  @field needs_grading;
  @field grader_points;
  @field grader_comments;

  @belongsTo({ model: 'task-plan/scores/student' }) student;

  @computed get gradedPoints() {
    return isNil(this.grader_points) ? this.points : this.grader_points;
  }

  @computed get gradedComments() {
    return isNil(this.grader_comments) ? this.comments : this.grader_comments;
  }

  @computed get isPlaceHolder() {
    return !this.exercise_id;
  }

  @computed get index() {
    return this.student.questions.indexOf(this);
  }

  @computed get questionHeading() {
    // First try to find the heading by question_id
    const heading = this.student.tasking.question_headings.find(
      qh => qh.question_id == this.question_id
    );
    if (!isNil(heading)) {
      return heading;
    }

    // For Tutor-assigned questions, each student may get a question with a different ID
    // so we can't really do better than using the index in this case
    if (this.student.tasking.question_headings.length > this.index) {
      return this.student.tasking.question_headings[this.index];
    }

    return null;
  }

  @computed get availablePoints() {
    return get(this.questionHeading, 'points', 1.0);
  }

  @computed get isManuallyGraded() {
    return !isNil(this.grader_points);
  }

  @computed get isTrouble() {
    return !this.needs_grading && !isNil(this.gradedPoints) && (this.gradedPoints / this.availablePoints) < 0.5;
  }

  @computed get displayValue() {
    const { dropped } = this.questionHeading || {};
    const pending = '---';

    if (this.needs_grading) { return pending; }

    if (dropped && this.is_completed) {
      return S.numberWithOneDecimalPlace(
        dropped.drop_method == 'full_credit' ? this.availablePoints : 0
      );
    }

    if (!isNil(this.gradedPoints)) { return S.numberWithOneDecimalPlace(this.gradedPoints); }

    return pending;
  }
}

@identifiedBy('task-plan/scores/student')
class TaskPlanScoreStudent extends BaseModel {
  @identifier role_id;
  @field task_id;
  @field first_name;
  @field last_name;
  @field student_identifier;
  @field is_dropped;
  @field is_late;
  @field available_points;
  @field total_points;
  @field total_fraction;
  @field late_work_point_penalty;
  @field grades_need_publishing;
  @field questions_need_grading;

  @hasMany({ model: TaskPlanScoreStudentQuestion, inverseOf: 'student' }) questions;
  @belongsTo({ model: 'task-plan/scores/tasking' }) tasking;

  resultForHeading(heading) {
    return this.questions.length > heading.index ? this.questions[heading.index] : null;
  }

  @computed get name() {
    return `${this.last_name}, ${this.first_name}`;
  }

  @computed get reversedName() {
    return `${this.first_name} ${this.last_name}`;
  }

  @computed get extension() {
    return this.tasking.scores.taskPlan.extensions.find(ex => ex.role_id == this.role_id);
  }

}

@identifiedBy('task-plan/scores/question')
class TaskPlanScoreHeading extends BaseModel {
  @identifier title;
  @field exercise_id;
  @field question_id;
  @field type;
  @field points;
  @field points_without_dropping;
  @field ecosystem_id;

  @computed get isCore() {
    return 'Tutor' !== this.type;
  }

  @computed get index() {
    return this.tasking && this.tasking.question_headings.indexOf(this);
  }

  @computed get studentResponses() {
    if (isNil(this.question_id)) {
      // For Tutor-assigned questions, each student may get a question with a different ID
      // so we can't really do better than using the index in this case
      return compact(this.tasking.students.map(s => s.questions[this.index]));
    }
    else {
      return compact(
        this.tasking.students.map(s => s.questions.find(q => q.question_id == this.question_id))
      );
    }
  }

  @computed get exercise() {
    return Exercises.get(this.exercise_id);
  }

  @computed get question() {
    return this.exercise && this.exercise.content.questions.find(q => q.id == this.question_id);
  }

  @computed get dropped() {
    return this.tasking.scores.dropped_questions.find(drop => drop.question_id == this.question_id);
  }

  @computed get gradedProgress() {
    return `(${this.gradedStats.completed}/${this.gradedStats.total})`;
  }

  @computed get responseStats() {
    const responses = this.studentResponses;
    const responsesInAvgs = filter(responses, response => !isNil(response.gradedPoints));
    return {
      completed: filter(responses, 'is_completed').length,
      hasFreeResponse: Boolean(find(responses, 'free_response')),
      availablePoints: this.points,
      averageGradedPoints: sumBy(responsesInAvgs, 'gradedPoints') / responsesInAvgs.length,
      correct: filter(responses, 'is_correct').length,
    };
  }

  @computed get gradedStats() {
    // Filter students who has completed the question
    const studentWithResponses = filter(this.studentResponses, 'is_completed');
    const remaining = filter(studentWithResponses, 'needs_grading').length;
    return {
      total: studentWithResponses.length,
      completed: studentWithResponses.length - remaining,
      remaining,
      complete: remaining == 0,
    };
  }

  @computed get displayPoints() {
    const { dropped } = this;
    return dropped ?
      (dropped.drop_method == 'zeroed' ? 0 : this.points_without_dropping) : this.points;
  }

  @computed get averageGradedPoints() {
    return this.responseStats.averageGradedPoints;
  }
}

@identifiedBy('task-plan/scores/tasking')
class TaskPlanScoresTasking extends BaseModel {
  @identifier id;
  @field period_id;
  @field period_name;

  @field({ type: 'object' }) average_score;
  @field({ type: 'object' }) available_points;
  @belongsTo({ model: 'task-plan/scores' }) plan;
  @hasMany({ model: TaskPlanScoreHeading, inverseOf: 'tasking', extend: {
    gradable() { return filter(this, h => h.question && h.question.isOpenEnded); },
    core() { return filter(this, h => h.type != 'Tutor'); },
  } }) question_headings;
  @hasMany({ model: TaskPlanScoreStudent, inverseOf: 'tasking' }) students;

  @computed get availablePoints() {
    return sumBy(this.question_headings, 'points');
  }

  // this returns all of the questions that were assigned
  // this is trickier than just using the index from headings because tutor assigned questions
  // will be in different order and different students will get different ones
  @computed get questionsInfo() {
    const info = {};

    for (const student of this.students) {
      for (const studentQuestion of student.questions) {
        const exercise = Exercises.get(studentQuestion.exercise_id);
        if (exercise) {
          const question = exercise.content.questions.find(q => q.id == studentQuestion.question_id);

          // while rare, heading will be null if this student received more exercises than others
          const heading = studentQuestion.questionHeading;
          const questionInfo = info[question.id] || (info[question.id] = {
            id: question.id,
            key: question.id,
            points: studentQuestion.points,
            availablePoints: heading ? heading.points : 1.0,
            averagePoints: heading ? heading.averageGradedPoints : studentQuestion.points,
            remaining: heading ? heading.gradedStats.remaining : 0,
            index: studentQuestion.index,
            exercise,
            question,
            responses: [],
          });
          questionInfo.responses.push(studentQuestion);
        }
      }
    }

    // add their stats once all the questions are gathered
    return sortBy(Object.values(info).map((qi) => {
      for (const answer of qi.question.answers) {
        answer.selected_count = filter(qi.responses, r => r.selected_answer_id == answer.id).length,
        answer.answered_count = qi.responses.length;
      }
      return {
        ...qi,
        hasFreeResponse: !!find(qi.responses, 'free_response'),
        completed: filter(qi.responses, 'is_completed').length,
        points: sumBy(qi.responses, 'points'),
        totalPoints: qi.points * qi.responses.length,
      };
    }), 'index');
  }

  @computed get hasEqualTutorQuestions() {
    for (const student of this.students) {
      if (student.questions.length != this.question_headings.length) {
        return false;
      }
    }
    return true;
  }

  @computed get hasUnPublishedScores() {
    return Boolean(
      find(this.students, student => find(student.questions, question => !isNil(question.gradedPoints))),
    );
  }

  @computed get isManuallyGraded() {
    return this.question_headings.gradable().length > 0;
  }

  @computed get totalAverageScoreInPoints() {
    const totals = compact(this.students.map(s => s.total_points));
    let value;
    if (isEmpty(totals)) {
      value = 0;
    } else {
      value = sum(totals) / totals.length;
    }
    return S.numberWithOneDecimalPlace(value);
  }

  @computed get totalAverageScoreInPercent() {
    const totals = compact(this.students.map(s => s.total_fraction));
    let value;
    if (isEmpty(totals)) {
      value = 0;
    } else {
      value = sum(totals) / totals.length;
    }
    return `${S.asPercent(value)}%`;
  }

  @computed get hasUngradedStudents() {
    return some(this.students, 'questions_need_grading');
  }

  @computed get allStudentQuestionStatus() {
    return reduce(this.students, (result, student) => {
      student.questions.forEach(question => {
        result.push(question);
      });
      return result;
    }, []);
  }

  @computed get hasUngradedQuestions() {
    const questions = filter(this.allStudentQuestionStatus, s => s.questionHeading.type === 'WRQ');
    return some(questions, q => q.is_completed && q.needs_grading);
  }

  @computed get hasFinishedGrading() {
    const completedWrmQuestions = filter(this.allStudentQuestionStatus, s => s.is_completed && s.questionHeading.type === 'WRQ');
    return every(completedWrmQuestions, q => q.is_completed && !q.needs_grading);
  }

  @computed get canGrade() {
    const wrmQuestions = filter(this.allStudentQuestionStatus, s => s.questionHeading.type === 'WRQ');
    return some(wrmQuestions, q => q.is_completed);
  }
}

export default
@identifiedBy('task-plan/scores')
class TaskPlanScores extends BaseModel {

  @identifier id;
  @field title;
  @field description;
  @field type;
  @field ecosystem_id;

  @belongsTo({ model: 'task-plans/teacher/plan' }) taskPlan;

  @hasMany({ model: DroppedQuestion }) dropped_questions;
  @hasMany({ model: TaskPlanScoresTasking, inverseOf: 'scores', extend: {
    forPeriod(period) { return find(this, { period_id: period.id }); },
  }  }) tasking_plans;
  @field({ model: 'grading/template' }) grading_template;

  @computed get exerciseIds() {
    const ids = [];
    for (const tasking of this.tasking_plans) {
      for (const student of tasking.students) {
        for (const question of student.questions) {
          if (!isNil(question.exercise_id)) {
            ids.push(question.exercise_id);
          }
        }
      }
    }
    return ids;
  }

  async ensureExercisesLoaded() {
    if (this.exerciseIds.length) {
      await Exercises.ensureExercisesLoaded({
        course: this.course, ecosystem_id: this.ecosystem_id, exercise_ids: this.exerciseIds, task_plan_id: this.id,
      });
    }
  }

  @computed get isHomework() {
    return 'homework' == this.type;
  }

  @computed get isExternal() {
    return 'external' == this.type;
  }

  fetch() { return { id: this.id }; }

  get course() {
    return this.taskPlan.course;
  }
}
<commit_msg>Removed unused questions_need_grading field<commit_after>import {
  BaseModel, identifiedBy, field, identifier, hasMany, belongsTo, computed,
} from 'shared/model';
import Exercises from '../../exercises';
import { filter, sum, sumBy, find, isNil, isEmpty, compact, sortBy, get, includes, some, reduce, every } from 'lodash';
import DroppedQuestion from './dropped_question';
import S from '../../../helpers/string';

@identifiedBy('task-plan/scores/student-question')
class TaskPlanScoreStudentQuestion extends BaseModel {
  @identifier question_id;
  @field exercise_id;
  @field is_completed = false;
  @field points;
  @field selected_answer_id;
  @field is_correct;
  @field free_response;
  @field task_step_id;
  @field needs_grading;
  @field grader_points;
  @field grader_comments;

  @belongsTo({ model: 'task-plan/scores/student' }) student;

  @computed get gradedPoints() {
    return isNil(this.grader_points) ? this.points : this.grader_points;
  }

  @computed get gradedComments() {
    return isNil(this.grader_comments) ? this.comments : this.grader_comments;
  }

  @computed get isPlaceHolder() {
    return !this.exercise_id;
  }

  @computed get index() {
    return this.student.questions.indexOf(this);
  }

  @computed get questionHeading() {
    // First try to find the heading by question_id
    const heading = this.student.tasking.question_headings.find(
      qh => qh.question_id == this.question_id
    );
    if (!isNil(heading)) {
      return heading;
    }

    // For Tutor-assigned questions, each student may get a question with a different ID
    // so we can't really do better than using the index in this case
    if (this.student.tasking.question_headings.length > this.index) {
      return this.student.tasking.question_headings[this.index];
    }

    return null;
  }

  @computed get availablePoints() {
    return get(this.questionHeading, 'points', 1.0);
  }

  @computed get isManuallyGraded() {
    return !isNil(this.grader_points);
  }

  @computed get isTrouble() {
    return !this.needs_grading && !isNil(this.gradedPoints) && (this.gradedPoints / this.availablePoints) < 0.5;
  }

  @computed get displayValue() {
    const { dropped } = this.questionHeading || {};
    const pending = '---';

    if (this.needs_grading) { return pending; }

    if (dropped && this.is_completed) {
      return S.numberWithOneDecimalPlace(
        dropped.drop_method == 'full_credit' ? this.availablePoints : 0
      );
    }

    if (!isNil(this.gradedPoints)) { return S.numberWithOneDecimalPlace(this.gradedPoints); }

    return pending;
  }
}

@identifiedBy('task-plan/scores/student')
class TaskPlanScoreStudent extends BaseModel {
  @identifier role_id;
  @field task_id;
  @field first_name;
  @field last_name;
  @field student_identifier;
  @field is_dropped;
  @field is_late;
  @field available_points;
  @field total_points;
  @field total_fraction;
  @field late_work_point_penalty;
  @field grades_need_publishing;

  @hasMany({ model: TaskPlanScoreStudentQuestion, inverseOf: 'student' }) questions;
  @belongsTo({ model: 'task-plan/scores/tasking' }) tasking;

  resultForHeading(heading) {
    return this.questions.length > heading.index ? this.questions[heading.index] : null;
  }

  @computed get name() {
    return `${this.last_name}, ${this.first_name}`;
  }

  @computed get reversedName() {
    return `${this.first_name} ${this.last_name}`;
  }

  @computed get extension() {
    return this.tasking.scores.taskPlan.extensions.find(ex => ex.role_id == this.role_id);
  }

}

@identifiedBy('task-plan/scores/question')
class TaskPlanScoreHeading extends BaseModel {
  @identifier title;
  @field exercise_id;
  @field question_id;
  @field type;
  @field points;
  @field points_without_dropping;
  @field ecosystem_id;

  @computed get isCore() {
    return 'Tutor' !== this.type;
  }

  @computed get index() {
    return this.tasking && this.tasking.question_headings.indexOf(this);
  }

  @computed get studentResponses() {
    if (isNil(this.question_id)) {
      // For Tutor-assigned questions, each student may get a question with a different ID
      // so we can't really do better than using the index in this case
      return compact(this.tasking.students.map(s => s.questions[this.index]));
    }
    else {
      return compact(
        this.tasking.students.map(s => s.questions.find(q => q.question_id == this.question_id))
      );
    }
  }

  @computed get exercise() {
    return Exercises.get(this.exercise_id);
  }

  @computed get question() {
    return this.exercise && this.exercise.content.questions.find(q => q.id == this.question_id);
  }

  @computed get dropped() {
    return this.tasking.scores.dropped_questions.find(drop => drop.question_id == this.question_id);
  }

  @computed get gradedProgress() {
    return `(${this.gradedStats.completed}/${this.gradedStats.total})`;
  }

  @computed get responseStats() {
    const responses = this.studentResponses;
    const responsesInAvgs = filter(responses, response => !isNil(response.gradedPoints));
    return {
      completed: filter(responses, 'is_completed').length,
      hasFreeResponse: Boolean(find(responses, 'free_response')),
      availablePoints: this.points,
      averageGradedPoints: sumBy(responsesInAvgs, 'gradedPoints') / responsesInAvgs.length,
      correct: filter(responses, 'is_correct').length,
    };
  }

  @computed get gradedStats() {
    // Filter students who has completed the question
    const studentWithResponses = filter(this.studentResponses, 'is_completed');
    const remaining = filter(studentWithResponses, 'needs_grading').length;
    return {
      total: studentWithResponses.length,
      completed: studentWithResponses.length - remaining,
      remaining,
      complete: remaining == 0,
    };
  }

  @computed get displayPoints() {
    const { dropped } = this;
    return dropped ?
      (dropped.drop_method == 'zeroed' ? 0 : this.points_without_dropping) : this.points;
  }

  @computed get averageGradedPoints() {
    return this.responseStats.averageGradedPoints;
  }
}

@identifiedBy('task-plan/scores/tasking')
class TaskPlanScoresTasking extends BaseModel {
  @identifier id;
  @field period_id;
  @field period_name;

  @field({ type: 'object' }) average_score;
  @field({ type: 'object' }) available_points;
  @belongsTo({ model: 'task-plan/scores' }) plan;
  @hasMany({ model: TaskPlanScoreHeading, inverseOf: 'tasking', extend: {
    gradable() { return filter(this, h => h.question && h.question.isOpenEnded); },
    core() { return filter(this, h => h.type != 'Tutor'); },
  } }) question_headings;
  @hasMany({ model: TaskPlanScoreStudent, inverseOf: 'tasking' }) students;

  @computed get availablePoints() {
    return sumBy(this.question_headings, 'points');
  }

  // this returns all of the questions that were assigned
  // this is trickier than just using the index from headings because tutor assigned questions
  // will be in different order and different students will get different ones
  @computed get questionsInfo() {
    const info = {};

    for (const student of this.students) {
      for (const studentQuestion of student.questions) {
        const exercise = Exercises.get(studentQuestion.exercise_id);
        if (exercise) {
          const question = exercise.content.questions.find(q => q.id == studentQuestion.question_id);

          // while rare, heading will be null if this student received more exercises than others
          const heading = studentQuestion.questionHeading;
          const questionInfo = info[question.id] || (info[question.id] = {
            id: question.id,
            key: question.id,
            points: studentQuestion.points,
            availablePoints: heading ? heading.points : 1.0,
            averagePoints: heading ? heading.averageGradedPoints : studentQuestion.points,
            remaining: heading ? heading.gradedStats.remaining : 0,
            index: studentQuestion.index,
            exercise,
            question,
            responses: [],
          });
          questionInfo.responses.push(studentQuestion);
        }
      }
    }

    // add their stats once all the questions are gathered
    return sortBy(Object.values(info).map((qi) => {
      for (const answer of qi.question.answers) {
        answer.selected_count = filter(qi.responses, r => r.selected_answer_id == answer.id).length,
        answer.answered_count = qi.responses.length;
      }
      return {
        ...qi,
        hasFreeResponse: !!find(qi.responses, 'free_response'),
        completed: filter(qi.responses, 'is_completed').length,
        points: sumBy(qi.responses, 'points'),
        totalPoints: qi.points * qi.responses.length,
      };
    }), 'index');
  }

  @computed get hasEqualTutorQuestions() {
    for (const student of this.students) {
      if (student.questions.length != this.question_headings.length) {
        return false;
      }
    }
    return true;
  }

  @computed get hasUnPublishedScores() {
    return Boolean(
      find(this.students, student => find(student.questions, question => !isNil(question.gradedPoints))),
    );
  }

  @computed get isManuallyGraded() {
    return this.question_headings.gradable().length > 0;
  }

  @computed get totalAverageScoreInPoints() {
    const totals = compact(this.students.map(s => s.total_points));
    let value;
    if (isEmpty(totals)) {
      value = 0;
    } else {
      value = sum(totals) / totals.length;
    }
    return S.numberWithOneDecimalPlace(value);
  }

  @computed get totalAverageScoreInPercent() {
    const totals = compact(this.students.map(s => s.total_fraction));
    let value;
    if (isEmpty(totals)) {
      value = 0;
    } else {
      value = sum(totals) / totals.length;
    }
    return `${S.asPercent(value)}%`;
  }

  @computed get allStudentQuestionStatus() {
    return reduce(this.students, (result, student) => {
      student.questions.forEach(question => {
        result.push(question);
      });
      return result;
    }, []);
  }

  @computed get hasUngradedQuestions() {
    const questions = filter(this.allStudentQuestionStatus, s => s.questionHeading.type === 'WRQ');
    return some(questions, q => q.is_completed && q.needs_grading);
  }

  @computed get hasFinishedGrading() {
    const completedWrmQuestions = filter(this.allStudentQuestionStatus, s => s.is_completed && s.questionHeading.type === 'WRQ');
    return every(completedWrmQuestions, q => q.is_completed && !q.needs_grading);
  }

  @computed get canGrade() {
    const wrmQuestions = filter(this.allStudentQuestionStatus, s => s.questionHeading.type === 'WRQ');
    return some(wrmQuestions, q => q.is_completed);
  }
}

export default
@identifiedBy('task-plan/scores')
class TaskPlanScores extends BaseModel {

  @identifier id;
  @field title;
  @field description;
  @field type;
  @field ecosystem_id;

  @belongsTo({ model: 'task-plans/teacher/plan' }) taskPlan;

  @hasMany({ model: DroppedQuestion }) dropped_questions;
  @hasMany({ model: TaskPlanScoresTasking, inverseOf: 'scores', extend: {
    forPeriod(period) { return find(this, { period_id: period.id }); },
  }  }) tasking_plans;
  @field({ model: 'grading/template' }) grading_template;

  @computed get exerciseIds() {
    const ids = [];
    for (const tasking of this.tasking_plans) {
      for (const student of tasking.students) {
        for (const question of student.questions) {
          if (!isNil(question.exercise_id)) {
            ids.push(question.exercise_id);
          }
        }
      }
    }
    return ids;
  }

  async ensureExercisesLoaded() {
    if (this.exerciseIds.length) {
      await Exercises.ensureExercisesLoaded({
        course: this.course, ecosystem_id: this.ecosystem_id, exercise_ids: this.exerciseIds, task_plan_id: this.id,
      });
    }
  }

  @computed get isHomework() {
    return 'homework' == this.type;
  }

  @computed get isExternal() {
    return 'external' == this.type;
  }

  fetch() { return { id: this.id }; }

  get course() {
    return this.taskPlan.course;
  }
}
<|endoftext|>


<commit_before>"use strict"

var config = {
    options: {
        "local": {
            "url": "http://example.com",
            "version": "1.3",
            "userId": "00000000-0000-0000-0000-000000000000"
        },
        "dropbox":{
            "url": "http://example.com",
            "version": "1.3"
        }
    }
};

//chai.use(chaiAsPromised);
var assert = chai.assert;

var providers = ['dropbox', 'local'];
var testPcapi = pcapi;

//initialize pcpapi with local provider
testPcapi.init(config.options[providers[1]]);
//set the provider
testPcapi.setProvider(providers[1]);
//set the userid
testPcapi.setCloudLogin(config.options[providers[1]]["userId"]);

describe('#checkProviders', function(){
    var server, fakeData;

    beforeEach(function(){
        fakeData = {"dropbox": ["oauth", "search", "synchronize", "delete"], "local": ["search", "synchronize", "delete"]};
    });

    it('check for the providers', function(){
        sinon.stub(testPcapi, 'getProviders').returns(fakeData);
        expect(testPcapi.getProviders()).to.have.keys(providers);
    });

    //get the selected provider
    it('check for selected provider', function(done){
        expect(testPcapi.getProvider()).to.equal(providers[1]);
        done();
    });
});

//user
describe('#User functions', function(){
    //getuserid
    it('get user id', function(done) {
        expect(testPcapi.getUserId()).to.equal(config.options[providers[1]]["userId"]);
        done();
    });
});

//buildUrls
describe('#URLS', function(){
    //base url
    it('base url check', function(done){
        var url = "http://example.com";
        assert.equal(testPcapi.getBaseUrl(), url);
        done();
    });

    //cloud provider url
    it('cloud provider url check', function(done){
        var url = "http://example.com/1.3/pcapi";
        assert.equal(testPcapi.getCloudProviderUrl(), url);
        done();
    });

    it('cloud provider url check with no version number', function(done){
        var options = {
            "url": "http://example.com",
            "userId": "00000000-0000-0000-0000-000000000000"
        };
        testPcapi.init(options);
        var url = "http://example.com";
        assert.equal(testPcapi.getCloudProviderUrl(), url);
        testPcapi.init(config.options[providers[1]]);
        done();
    });

    //buildURL
    it('buildURL, works for editors, records', function(done){
        var url = "http://example.com/1.3/pcapi/records/local/00000000-0000-0000-0000-000000000000/Text (20-08-2014 16h18m18s)";
        assert.equal(testPcapi.buildUrl("records", record1.name), url);
        done();
    });

    //buildUserUrl
    it('buildUserURL, works for editors, records', function(done){
        var url = "http://example.com/1.3/pcapi/records/local/xxxxxx/Text (20-08-2014 16h18m18s)";
        assert.equal(testPcapi.buildUserUrl("xxxxxx", "records", record1.name), url);
        done();
    });

    //buildFSUrl
    it('buildFSURL, works for any folder', function(done){
        var url = "http://example.com/1.3/pcapi/fs/local/00000000-0000-0000-0000-000000000000/records/Text (20-08-2014 16h18m18s)";
        assert.equal(testPcapi.buildFSUrl("records", record1.name), url);
        done();
    });

    //buildFSUserUrl
    it('buildFSUserURL, works for any folder', function(done){
        var url = "http://example.com/1.3/pcapi/fs/local/xxx/records/Text (20-08-2014 16h18m18s)";
        assert.equal(testPcapi.buildFSUserUrl("xxx", "records", record1.name), url);
        done();
    });
});

describe('#Record', function(){
    var fakeData;

    it('upload a fresh record', function(done){
        fakeData = {"msg": "File uploaded", "path": "/records/"+record1.name+"/record.json", "error": 0};
        sinon.stub(testPcapi, 'saveItem').returns(Promise.resolve(fakeData));
        var options = {
            "remoteDir": "records",
            "path": record1.name,
            "data": record1
        };

        testPcapi.saveItem(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData);
                testPcapi.saveItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //check the saveItem for when the userId is not email or HEX
    it('upload a fresh record with invalid userId', function(done){
        fakeData = {msg: "'Illegal userid: xxx -- should either HEX or EMAIL'", error: 1};
        sinon.stub(testPcapi, 'saveItem').returns(Promise.resolve(fakeData));
        var options = {
            "remoteDir": "records",
            "path": record1.name,
            "data": record1,
            "userId": "xxx"
        };

        testPcapi.saveItem(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData);
                testPcapi.saveItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    it('upload an existing record', function(done){
        fakeData = {"msg": "File uploaded", "path": "/records/"+record1.name+" (1)/record.json", "error": 0};
        var res = {"msg": "File uploaded", "path": "/records/"+record1.name+"/record.json", "error": 0};
        sinon.stub(testPcapi, 'saveItem').returns(Promise.resolve(fakeData));

        var options = {
            "remoteDir": "records",
            "path": record1.name,
            "data": record1
        };
        testPcapi.saveItem(options).then(function(result){
            if(result.msg === res.msg && result.error === 0 && result.path !== res.path){
                testPcapi.saveItem.restore();
                assert.ok(true, "The record is uploaded but renamed");
                done();
            }
        }, function(error){
            throw error;
            done();
        });
    });

    it('export record', function(done){
        var userId = 'abc';
        sinon.stub(testPcapi, 'doRequest', function(options){
            var l = config.options.local;
            var expectedUrl = l.url + '/' +  l.version + '/pcapi/records/local/' + userId + "/" + record1.name + "?ogc_sync=true";
            assert.equal(options.url, expectedUrl);
            done();
        });
        testPcapi.exportRecord(userId, record1.name);
    });

    it('delete a record', function(done){
        var fakeData = {msg: "records/Text (20-08-2014 16h18m18s) (1) deleted", error: 0};
        sinon.stub(testPcapi, 'deleteItem').returns(Promise.resolve(fakeData));
        testPcapi.deleteItem("records", record1.name+ " (1)").then(function(result) {
            //always write catch, otherwise is not catching the error and gives timeout error
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.deleteItem.restore();
                done();
            } catch (x) {
                done(x);
            }
        }, function(error){
            throw error;
            done(error)
        });
    });

    //update record
    it('update a record', function(done){
        var fakeData = {"msg": "File uploaded", "path": "/records/Text (20-08-2014 16h18m18s)/record.json", "error": 0};
        sinon.stub(testPcapi, 'updateItem').returns(Promise.resolve(fakeData));
        record1.properties.editor = "text1.edtr";
        var updateOptions = {
            "remoteDir": "records",
            "path": record1.name+"/record.json",
            "data": record1
        };
        testPcapi.updateItem(updateOptions).then(function(result){
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.updateItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        }, function(error){
            throw(error);
            done();
        });
    });

    //get All records (getItems/getFSItems)
    it('get all records', function(done){
        var options = {
            "remoteDir": "records"
        };
        var fakeData = {"records": [{"Text (20-10-2014 16h18m18s)": {"geometry": {"type": "Point", "coordinates": [5.466076115152644, 51.90957813315172]}, "type": "Feature", "properties": {"fields": [{"id": "fieldcontain-image-1", "val": "1409925195999.jpg", "label": "Image"}], "editor": "myEditor.edtr", "timestamp": "2014-10-20T14:18:25.514Z"}, "name": "Text (20-10-2014 16h18m18s)"}}, {"Text (20-08-2014 16h18m18s)": {"geometry": {"type": "Point", "coordinates": [5.366076115152644, 51.80957813315172]}, "type": "Feature", "properties": {"fields": [], "editor": "text1.edtr", "timestamp": "2014-08-20T14:18:25.514Z"}, "name": "Text (20-08-2014 16h18m18s)"}}], "error": 0};
        sinon.stub(testPcapi, 'getItems').returns(Promise.resolve(fakeData));

        testPcapi.getItems(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.getItems.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //filter Records (getItems)
    it('filter records', function(done){
        var options = {
            "remoteDir": "records",
            "extras": "",
            "filters": {"filter":"editor", "id": "myEditor.edtr"}
        };
        var fakeData = {"records": [{"Text (20-10-2014 16h18m18s)": {"geometry": {"type": "Point", "coordinates": [5.466076115152644, 51.90957813315172]}, "type": "Feature", "properties": {"fields": [{"id": "fieldcontain-image-1", "val": "1409925195999.jpg", "label": "Image"}], "editor": "myEditor.edtr", "timestamp": "2014-10-20T14:18:25.514Z"}, "name": "Text (20-10-2014 16h18m18s)"}}], "error": 0};
        sinon.stub(testPcapi, 'getItems').returns(Promise.resolve(fakeData));

        testPcapi.getItems(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.getItems.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //get FS items
    it('get fs all records', function(done){
        var fakeData = {"metadata": ["/records/Text (20-10-2014 16h18m18s)", "/records/Text (20-08-2014 16h18m18s)"], "error": 0}
        sinon.stub(testPcapi, 'getFSItems').returns(Promise.resolve(fakeData));

        testPcapi.getFSItems('records').then(function(result) {
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.getFSItems.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //getItem
    it('get item', function(done){
        var fakeData = {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [
                    5.366076115152644,
                    51.80957813315172
                ]
            },
            "properties": {
                "editor": "text1.edtr",
                "fields": [],
                "timestamp": "2014-08-20T14:18:25.514Z"
            },
            "name": "Text (20-08-2014 16h18m18s)"
        };
        sinon.stub(testPcapi, 'getItem').returns(Promise.resolve(fakeData));

        var options = {
            "remoteDir": "records",
            "item": record1.name
        };
        testPcapi.getItem(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData, "The record is the right one");
                testPcapi.getItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //getFSItem
    it('get fs item', function(done) {
        var res = {"metadata": ["/records/Text (20-08-2014 16h18m18s)/record.json"], "error": 0};
        var options = {
            "remoteDir": "records",
            "item": record1.name
        };
        sinon.stub(testPcapi, 'getFSItem').returns(Promise.resolve(fakeData));

        testPcapi.getFSItem(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData, "The record is the right one");
                testPcapi.getFSItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //getAssets
    it('get all images', function(done){
        var fakeData = {"records": ["Text (20-10-2014 16h18m18s)/1409925195999.jpg"], "error": 0};
        sinon.stub(testPcapi, 'getAssets').returns(Promise.resolve(fakeData));

        testPcapi.getAssets().then(function(result){
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.getAssets.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    it('logout', function(done){
        var clearCookies = false;
        sinon.stub(window, 'open', function(url){
            var target = config.options.local.url + '/' +
                config.options.local.version + '/auth/local';
            assert.equal(url, 'http://example.com/1.3/pcapi/auth/local');
            return{
                addEventListener: function(){
                    clearCookies = true;
                }
            }
        });

        localStorage.setItem('cloud-user', 'xyz');
        testPcapi.logoutCloud(true);
        assert.equal(localStorage.getItem('cloud-user'), '{}');
        assert.isTrue(clearCookies);

        clearCookies = false;
        localStorage.setItem('cloud-user', 'xyz');
        testPcapi.logoutCloud(false);
        assert.equal(localStorage.getItem('cloud-user'), '{}');
        assert.isFalse(clearCookies);

        done();
    });

});
<commit_msg>fix url assert test: https://github.com/edina/fieldtrip-cobweb-project/issues/130<commit_after>"use strict"

var config = {
    options: {
        "local": {
            "url": "http://example.com",
            "version": "1.3",
            "userId": "00000000-0000-0000-0000-000000000000"
        },
        "dropbox":{
            "url": "http://example.com",
            "version": "1.3"
        }
    }
};

//chai.use(chaiAsPromised);
var assert = chai.assert;

var providers = ['dropbox', 'local'];
var testPcapi = pcapi;

//initialize pcpapi with local provider
testPcapi.init(config.options[providers[1]]);
//set the provider
testPcapi.setProvider(providers[1]);
//set the userid
testPcapi.setCloudLogin(config.options[providers[1]]["userId"]);

describe('#checkProviders', function(){
    var server, fakeData;

    beforeEach(function(){
        fakeData = {"dropbox": ["oauth", "search", "synchronize", "delete"], "local": ["search", "synchronize", "delete"]};
    });

    it('check for the providers', function(){
        sinon.stub(testPcapi, 'getProviders').returns(fakeData);
        expect(testPcapi.getProviders()).to.have.keys(providers);
    });

    //get the selected provider
    it('check for selected provider', function(done){
        expect(testPcapi.getProvider()).to.equal(providers[1]);
        done();
    });
});

//user
describe('#User functions', function(){
    //getuserid
    it('get user id', function(done) {
        expect(testPcapi.getUserId()).to.equal(config.options[providers[1]]["userId"]);
        done();
    });
});

//buildUrls
describe('#URLS', function(){
    //base url
    it('base url check', function(done){
        var url = "http://example.com";
        assert.equal(testPcapi.getBaseUrl(), url);
        done();
    });

    //cloud provider url
    it('cloud provider url check', function(done){
        var url = "http://example.com/1.3/pcapi";
        assert.equal(testPcapi.getCloudProviderUrl(), url);
        done();
    });

    it('cloud provider url check with no version number', function(done){
        var options = {
            "url": "http://example.com",
            "userId": "00000000-0000-0000-0000-000000000000"
        };
        testPcapi.init(options);
        var url = "http://example.com";
        assert.equal(testPcapi.getCloudProviderUrl(), url);
        testPcapi.init(config.options[providers[1]]);
        done();
    });

    //buildURL
    it('buildURL, works for editors, records', function(done){
        var url = "http://example.com/1.3/pcapi/records/local/00000000-0000-0000-0000-000000000000/Text (20-08-2014 16h18m18s)";
        assert.equal(testPcapi.buildUrl("records", record1.name), url);
        done();
    });

    //buildUserUrl
    it('buildUserURL, works for editors, records', function(done){
        var url = "http://example.com/1.3/pcapi/records/local/xxxxxx/Text (20-08-2014 16h18m18s)";
        assert.equal(testPcapi.buildUserUrl("xxxxxx", "records", record1.name), url);
        done();
    });

    //buildFSUrl
    it('buildFSURL, works for any folder', function(done){
        var url = "http://example.com/1.3/pcapi/fs/local/00000000-0000-0000-0000-000000000000/records/Text (20-08-2014 16h18m18s)";
        assert.equal(testPcapi.buildFSUrl("records", record1.name), url);
        done();
    });

    //buildFSUserUrl
    it('buildFSUserURL, works for any folder', function(done){
        var url = "http://example.com/1.3/pcapi/fs/local/xxx/records/Text (20-08-2014 16h18m18s)";
        assert.equal(testPcapi.buildFSUserUrl("xxx", "records", record1.name), url);
        done();
    });
});

describe('#Record', function(){
    var fakeData;

    it('upload a fresh record', function(done){
        fakeData = {"msg": "File uploaded", "path": "/records/"+record1.name+"/record.json", "error": 0};
        sinon.stub(testPcapi, 'saveItem').returns(Promise.resolve(fakeData));
        var options = {
            "remoteDir": "records",
            "path": record1.name,
            "data": record1
        };

        testPcapi.saveItem(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData);
                testPcapi.saveItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //check the saveItem for when the userId is not email or HEX
    it('upload a fresh record with invalid userId', function(done){
        fakeData = {msg: "'Illegal userid: xxx -- should either HEX or EMAIL'", error: 1};
        sinon.stub(testPcapi, 'saveItem').returns(Promise.resolve(fakeData));
        var options = {
            "remoteDir": "records",
            "path": record1.name,
            "data": record1,
            "userId": "xxx"
        };

        testPcapi.saveItem(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData);
                testPcapi.saveItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    it('upload an existing record', function(done){
        fakeData = {"msg": "File uploaded", "path": "/records/"+record1.name+" (1)/record.json", "error": 0};
        var res = {"msg": "File uploaded", "path": "/records/"+record1.name+"/record.json", "error": 0};
        sinon.stub(testPcapi, 'saveItem').returns(Promise.resolve(fakeData));

        var options = {
            "remoteDir": "records",
            "path": record1.name,
            "data": record1
        };
        testPcapi.saveItem(options).then(function(result){
            if(result.msg === res.msg && result.error === 0 && result.path !== res.path){
                testPcapi.saveItem.restore();
                assert.ok(true, "The record is uploaded but renamed");
                done();
            }
        }, function(error){
            throw error;
            done();
        });
    });

    it('export record', function(done){
        var userId = 'abc';
        sinon.stub(testPcapi, 'doRequest', function(options){
            var l = config.options.local;
            var expectedUrl = l.url + '/' +  l.version + '/pcapi/records/local/' + userId + "/" + record1.name + "?ogc_sync=true";
            assert.equal(options.url, expectedUrl);
            done();
        });
        testPcapi.exportRecord(userId, record1.name);
    });

    it('delete a record', function(done){
        var fakeData = {msg: "records/Text (20-08-2014 16h18m18s) (1) deleted", error: 0};
        sinon.stub(testPcapi, 'deleteItem').returns(Promise.resolve(fakeData));
        testPcapi.deleteItem("records", record1.name+ " (1)").then(function(result) {
            //always write catch, otherwise is not catching the error and gives timeout error
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.deleteItem.restore();
                done();
            } catch (x) {
                done(x);
            }
        }, function(error){
            throw error;
            done(error)
        });
    });

    //update record
    it('update a record', function(done){
        var fakeData = {"msg": "File uploaded", "path": "/records/Text (20-08-2014 16h18m18s)/record.json", "error": 0};
        sinon.stub(testPcapi, 'updateItem').returns(Promise.resolve(fakeData));
        record1.properties.editor = "text1.edtr";
        var updateOptions = {
            "remoteDir": "records",
            "path": record1.name+"/record.json",
            "data": record1
        };
        testPcapi.updateItem(updateOptions).then(function(result){
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.updateItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        }, function(error){
            throw(error);
            done();
        });
    });

    //get All records (getItems/getFSItems)
    it('get all records', function(done){
        var options = {
            "remoteDir": "records"
        };
        var fakeData = {"records": [{"Text (20-10-2014 16h18m18s)": {"geometry": {"type": "Point", "coordinates": [5.466076115152644, 51.90957813315172]}, "type": "Feature", "properties": {"fields": [{"id": "fieldcontain-image-1", "val": "1409925195999.jpg", "label": "Image"}], "editor": "myEditor.edtr", "timestamp": "2014-10-20T14:18:25.514Z"}, "name": "Text (20-10-2014 16h18m18s)"}}, {"Text (20-08-2014 16h18m18s)": {"geometry": {"type": "Point", "coordinates": [5.366076115152644, 51.80957813315172]}, "type": "Feature", "properties": {"fields": [], "editor": "text1.edtr", "timestamp": "2014-08-20T14:18:25.514Z"}, "name": "Text (20-08-2014 16h18m18s)"}}], "error": 0};
        sinon.stub(testPcapi, 'getItems').returns(Promise.resolve(fakeData));

        testPcapi.getItems(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.getItems.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //filter Records (getItems)
    it('filter records', function(done){
        var options = {
            "remoteDir": "records",
            "extras": "",
            "filters": {"filter":"editor", "id": "myEditor.edtr"}
        };
        var fakeData = {"records": [{"Text (20-10-2014 16h18m18s)": {"geometry": {"type": "Point", "coordinates": [5.466076115152644, 51.90957813315172]}, "type": "Feature", "properties": {"fields": [{"id": "fieldcontain-image-1", "val": "1409925195999.jpg", "label": "Image"}], "editor": "myEditor.edtr", "timestamp": "2014-10-20T14:18:25.514Z"}, "name": "Text (20-10-2014 16h18m18s)"}}], "error": 0};
        sinon.stub(testPcapi, 'getItems').returns(Promise.resolve(fakeData));

        testPcapi.getItems(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.getItems.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //get FS items
    it('get fs all records', function(done){
        var fakeData = {"metadata": ["/records/Text (20-10-2014 16h18m18s)", "/records/Text (20-08-2014 16h18m18s)"], "error": 0}
        sinon.stub(testPcapi, 'getFSItems').returns(Promise.resolve(fakeData));

        testPcapi.getFSItems('records').then(function(result) {
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.getFSItems.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //getItem
    it('get item', function(done){
        var fakeData = {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [
                    5.366076115152644,
                    51.80957813315172
                ]
            },
            "properties": {
                "editor": "text1.edtr",
                "fields": [],
                "timestamp": "2014-08-20T14:18:25.514Z"
            },
            "name": "Text (20-08-2014 16h18m18s)"
        };
        sinon.stub(testPcapi, 'getItem').returns(Promise.resolve(fakeData));

        var options = {
            "remoteDir": "records",
            "item": record1.name
        };
        testPcapi.getItem(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData, "The record is the right one");
                testPcapi.getItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //getFSItem
    it('get fs item', function(done) {
        var res = {"metadata": ["/records/Text (20-08-2014 16h18m18s)/record.json"], "error": 0};
        var options = {
            "remoteDir": "records",
            "item": record1.name
        };
        sinon.stub(testPcapi, 'getFSItem').returns(Promise.resolve(fakeData));

        testPcapi.getFSItem(options).then(function(result){
            try {
                assert.deepEqual(result, fakeData, "The record is the right one");
                testPcapi.getFSItem.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    //getAssets
    it('get all images', function(done){
        var fakeData = {"records": ["Text (20-10-2014 16h18m18s)/1409925195999.jpg"], "error": 0};
        sinon.stub(testPcapi, 'getAssets').returns(Promise.resolve(fakeData));

        testPcapi.getAssets().then(function(result){
            try {
                assert.deepEqual(result, fakeData, fakeData.msg);
                testPcapi.getAssets.restore();
                done();
            } catch(x) {
                done(x);
            }
        });
    });

    it('logout', function(done){
        var clearCookies = false;
        sinon.stub(window, 'open', function(url){
            var target = config.options.local.url + '/' +
                config.options.local.version + '/pcapi/auth/local';
            assert.equal(url, target);
            return{
                addEventListener: function(){
                    clearCookies = true;
                }
            }
        });

        localStorage.setItem('cloud-user', 'xyz');
        testPcapi.logoutCloud(true);
        assert.equal(localStorage.getItem('cloud-user'), '{}');
        assert.isTrue(clearCookies);

        clearCookies = false;
        localStorage.setItem('cloud-user', 'xyz');
        testPcapi.logoutCloud(false);
        assert.equal(localStorage.getItem('cloud-user'), '{}');
        assert.isFalse(clearCookies);

        done();
    });

});
<|endoftext|>


<commit_before>const shell = require('electron').shell;

class Utils {
  /** Overrides original event and opens URL in default browser */
  static _openInDefaultBrowser(evt) {
    evt.preventDefault();
    shell.openExternal(evt.target.href);
  }

  /** Create anchor tag for URLs and opens in default browser */
  static anchorTag(href) {
    if (href) {
      return `<a href="${href}" onclick="Utils._openInDefaultBrowser(event)">${href}</a>`;
    }
    return '';
  }

  /**
   * Map each row to the given attribute value, and sanitize invalid values.
   *
   * This method is needed for two reasons:
   * 1) It converts null data (this includes empty arrays) to 'No Value Detected'
   * 2) It grabs the particular attribute from the value which represents a row
   *    in the database
   */
  static getAttributeValues(values, attribute) {
    const validatedValues = [];
    let attributeValue = null;

    for (let i = 0; i < values.length; i++) {
      attributeValue = values[i][attribute];
      const fileType = values[i].type;

      if (!Array.isArray(attributeValue) || attributeValue.length === 0) {
        attributeValue = [attributeValue];
      }

      for (let j = 0; j < attributeValue.length; j++) {
        const val = attributeValue[j];
        if (!Utils.isValid(val) && attribute === 'packages_type' && fileType === 'directory') {
          continue;
        }
        validatedValues.push(
          Utils.isValid(val) ?
            val : 'No Value Detected');
      }
    }
    return validatedValues;
  }

  static isValid(value) {
    if (Array.isArray(value)) {
      return value.length > 0 &&
                value.every((element) => Utils.isValid(element));
    } else {
      return value !== null;
    }
  }

  /**
   * Opens a dialog to choose a file to open.
   *
   * @returns Promise with the name of the file to open.
   */
  static showOpenDialog(dialog, config) {
    return new Promise((resolve) => {
      dialog.showOpenDialog(config, (fileNames) => {
        resolve(fileNames || fileNames[0]);
      });
    });
  }

  /**
   * Opens a dialog to choose a file to save to.
   *
   * @returns Promise with the name of the file to save to.
   */
  static showSaveDialog(dialog, config) {
    return new Promise((resolve) => {
      dialog.showSaveDialog(config, (newFileName) => {
        resolve(newFileName);
      });
    });
  }
}

module.exports = Utils;
<commit_msg>Fix broken barchart counts #285<commit_after>const shell = require('electron').shell;

class Utils {
  /** Overrides original event and opens URL in default browser */
  static _openInDefaultBrowser(evt) {
    evt.preventDefault();
    shell.openExternal(evt.target.href);
  }

  /** Create anchor tag for URLs and opens in default browser */
  static anchorTag(href) {
    if (href) {
      return `<a href="${href}" onclick="Utils._openInDefaultBrowser(event)">${href}</a>`;
    }
    return '';
  }

  /**
   * Map each row to the given attribute value, and sanitize invalid values.
   *
   * This method is needed for two reasons:
   * 1) It converts null data (this includes empty arrays) to 'No Value Detected'
   * 2) It grabs the particular attribute from the value which represents a row
   *    in the database
   */
  static getAttributeValues(values, attribute) {
    const validatedValues = [];
    let attributeValue = null;

    for (let i = 0; i < values.length; i++) {
      attributeValue = values[i][attribute];
      const fileType = values[i].type;

      // dedupe entries to prevent overcounting files. See https://github.com/nexB/scancode-workbench/issues/285
      if (Array.isArray(attributeValue)) {
        attributeValue = Array.from(new Set(attributeValue));
      }

      if (!Array.isArray(attributeValue) || attributeValue.length === 0) {
        attributeValue = [attributeValue];
      }

      for (let j = 0; j < attributeValue.length; j++) {
        const val = attributeValue[j];
        if (!Utils.isValid(val) && attribute === 'packages_type' && fileType === 'directory') {
          continue;
        }
        validatedValues.push(
          Utils.isValid(val) ?
            val : 'No Value Detected');
      }
    }
    return validatedValues;
  }

  static isValid(value) {
    if (Array.isArray(value)) {
      return value.length > 0 &&
                value.every((element) => Utils.isValid(element));
    } else {
      return value !== null;
    }
  }

  /**
   * Opens a dialog to choose a file to open.
   *
   * @returns Promise with the name of the file to open.
   */
  static showOpenDialog(dialog, config) {
    return new Promise((resolve) => {
      dialog.showOpenDialog(config, (fileNames) => {
        resolve(fileNames || fileNames[0]);
      });
    });
  }

  /**
   * Opens a dialog to choose a file to save to.
   *
   * @returns Promise with the name of the file to save to.
   */
  static showSaveDialog(dialog, config) {
    return new Promise((resolve) => {
      dialog.showSaveDialog(config, (newFileName) => {
        resolve(newFileName);
      });
    });
  }
}

module.exports = Utils;
<|endoftext|>