<commit_before>/**
 * Quink, Copyright (c) 2013-2014 IMD - International Institute for Management Development, Switzerland.
 *
 * This file is part of Quink.
 *
 * Quink is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Quink is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Quink.  If not, see <http://www.gnu.org/licenses/>.
 */
/*global */
require(['Underscore','jquery','ext/PluginAdapterContext'], function (_, $, Context) {
    'use strict';
    //the iframe component
    var $iframe,
        BODY_TAG_NAME = "body",
        IMAGE_TAG = "<img>",
        imageHTML;
    /**
     * Plugin API method - see Quink-Plugin-Notes document
     * (i) add the plugin markup to the DOM (re-using any plugin artifacts that have been previously downloaded)
     * (ii) show the plugin, ready to be used
     *
     * @param data - used to supply the open function with any data that is to be used by the plugin.
     *
     */
    function open(data) {
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin.open(data) called');
        window.addEventListener('message', handleDeviantArtReady, false);
        //add the iframe to the HTML
        $iframe.appendTo(BODY_TAG_NAME);
        $iframe.removeClass('qk_invisible');
        if (data) {
            imageHTML = data;
        } else {
            console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin publishing opened event');
            Context.publish('opened');
        }
    }
    /**
     * Plugin API method - see Quink-Plugin-Notes document
     *
     * Publish on a saved topic and as part of the publication include the serialised data to be saved
     */
    function save() {
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin.save() called');

        window.addEventListener('message', handleQueryImageReply, false);

        $iframe[0].contentWindow.postMessage({
            type:      'query',
            query:   'image'
        }, '*');
    }

    /**
     * Plugin API method - see Quink-Plugin-Notes document
     *
     * When ready to exit, publish on an exited topic
     */
    function exit() {
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin.exit() called');
        closePlugin('exited');
    }

    /**
     *
     * @param message
     */
    function handleQueryImageReply(message) {
        console.log('[' + new Date().toISOString() + ']' + 'handleQueryImageReply message.data=' + JSON.stringify(message.data));
        //only handle replies to query image
        if (message.data && message.data.image) {
            var $image = $(IMAGE_TAG);
            $image.attr('src', message.data.image);
            console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin publishing saved event');
            closePlugin('saved', $image[0].outerHTML);
        }
    }
    /**
     * deviantArt issues a number of messages. The data.type values encountered during debugging (so actually issued by this implementation) are:
     *
     * gotDeviationId
     *
     * ready
     *
     * @param message
     */
    function handleDeviantArtReady(message) {
        if (message.data && message.data.type === 'ready') {
            if (imageHTML) {
                $iframe[0].contentWindow.postMessage({
                    type: 'command',
                    command: 'importLayer',
                    layerData: {
                        url: $(imageHTML).attr('src'),
                        width: $(imageHTML).width(),
                        height: $(imageHTML).height()
                    }
                }, '*');
            }
            imageHTML = null;
            //only handle replies to query image
            Context.publish('opened');
        }
//
//        if (message.data && message.data.image) {
//            var $image = $(IMAGE_TAG);
//            $image.attr('src', message.data.image);
//            closePlugin('saved', $image[0].outerHTML);
//        }
    }
    /**
     * save() and exit() API functions call this to remove the iframe
     * containing the image uploader so that control can pass back to the main form
     */
    function closePlugin(topic, data) {
        window.removeEventListener('message', handleQueryImageReply, false);
        window.removeEventListener('message', handleDeviantArtReady, false);
        $iframe.addClass('qk_invisible');
        $iframe.detach();
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin publishing ' + topic + ' event');
        Context.publish(topic, data);
    }
    function downloadHTML() {
        var url = Context.adapterUrl('deviantart-muro/deviantart-muro-embed.html');
        $.get(url).done(function (data) {
            $iframe = $(data);
            console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin publishing loaded event');
            //associate methods in this object with the lifecycle callbacks for plugins
            Context.publish('loaded', {
                open: open,
                save: save,
                exit: exit
            });
            console.log('DeviantArtPlugin.fetchPluginArtifacts() download HTML successful');
        }).fail(function (jqxhr, textStatus, error) {
            console.log('DeviantArtPlugin.fetchPluginArtifacts() failed to load deviantart muro markup from: ' + url + '. ' + jqxhr.status + '. ' + error);
        });
    }

    function downloadCSSAndHTML() {
        var url = Context.adapterUrl('deviantart-muro/deviantart-muro-embed.css');
        $.get(url).done(function (data) {
            $('<style>').html(data).appendTo('head');
            console.log('DeviantArtPlugin.fetchPluginArtifacts() download CSS successful');
            downloadHTML();
        }).fail(function (jqxhr, textStatus, error) {
            console.log('Failed to load deviantArt css from: ' + url + '. ' + jqxhr.status + '. ' + error);
        });
    }

    function fetchPluginArtifacts() {
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin.fetchPluginArtifacts() called');
        downloadCSSAndHTML();
    }
    fetchPluginArtifacts();
});
<commit_msg>Tel muro about image size on re-edit<commit_after>/**
 * Quink, Copyright (c) 2013-2014 IMD - International Institute for Management Development, Switzerland.
 *
 * This file is part of Quink.
 *
 * Quink is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Quink is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Quink.  If not, see <http://www.gnu.org/licenses/>.
 */
/*global */
require(['Underscore','jquery','ext/PluginAdapterContext'], function (_, $, Context) {
    'use strict';
    //the iframe component
    var $iframe,
        BODY_TAG_NAME = "body",
        IMAGE_TAG = "<img>",
        imageHTML;
    /**
     * Plugin API method - see Quink-Plugin-Notes document
     * (i) add the plugin markup to the DOM (re-using any plugin artifacts that have been previously downloaded)
     * (ii) show the plugin, ready to be used
     *
     * @param data - used to supply the open function with any data that is to be used by the plugin.
     *
     */
    function open(data) {
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin.open(data) called');
        window.addEventListener('message', handleDeviantArtReady, false);
        //add the iframe to the HTML
        $iframe.appendTo(BODY_TAG_NAME);
        $iframe.removeClass('qk_invisible');
        if (data) {
            imageHTML = data;
        } else {
            console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin publishing opened event');
            Context.publish('opened');
        }
    }
    /**
     * Plugin API method - see Quink-Plugin-Notes document
     *
     * Publish on a saved topic and as part of the publication include the serialised data to be saved
     */
    function save() {
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin.save() called');

        window.addEventListener('message', handleQueryImageReply, false);

        $iframe[0].contentWindow.postMessage({
            type:      'query',
            query:   'image'
        }, '*');
    }

    /**
     * Plugin API method - see Quink-Plugin-Notes document
     *
     * When ready to exit, publish on an exited topic
     */
    function exit() {
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin.exit() called');
        closePlugin('exited');
    }

    /**
     *
     * @param message
     */
    function handleQueryImageReply(message) {
        console.log('[' + new Date().toISOString() + ']' + 'handleQueryImageReply message.data=' + JSON.stringify(message.data));
        //only handle replies to query image
        if (message.data && message.data.image) {
            var $image = $(IMAGE_TAG);
            $image.attr('src', message.data.image);
            console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin publishing saved event');
            closePlugin('saved', $image[0].outerHTML);
        }
    }
    /**
     * deviantArt issues a number of messages. The data.type values encountered during debugging (so actually issued by this implementation) are:
     *
     * gotDeviationId
     *
     * ready
     *
     * @param message
     */
    function handleDeviantArtReady(message) {
        if (message.data && message.data.type === 'ready') {
            if (imageHTML) {
                $iframe[0].contentWindow.postMessage({
                    type: 'command',
                    command: 'importLayer',
                    layerData: {
                        url: $(imageHTML).attr('src'),
                        width: $(imageHTML)[0].width,
                        height: $(imageHTML)[0].height
                    }
                }, '*');
                imageHTML = null;
            }
            //only handle replies to query image
            Context.publish('opened');
        }
//
//        if (message.data && message.data.image) {
//            var $image = $(IMAGE_TAG);
//            $image.attr('src', message.data.image);
//            closePlugin('saved', $image[0].outerHTML);
//        }
    }
    /**
     * save() and exit() API functions call this to remove the iframe
     * containing the image uploader so that control can pass back to the main form
     */
    function closePlugin(topic, data) {
        window.removeEventListener('message', handleQueryImageReply, false);
        window.removeEventListener('message', handleDeviantArtReady, false);
        $iframe.addClass('qk_invisible');
        $iframe.detach();
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin publishing ' + topic + ' event');
        Context.publish(topic, data);
    }
    function downloadHTML() {
        var url = Context.adapterUrl('deviantart-muro/deviantart-muro-embed.html');
        $.get(url).done(function (data) {
            $iframe = $(data);
            console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin publishing loaded event');
            //associate methods in this object with the lifecycle callbacks for plugins
            Context.publish('loaded', {
                open: open,
                save: save,
                exit: exit
            });
            console.log('DeviantArtPlugin.fetchPluginArtifacts() download HTML successful');
        }).fail(function (jqxhr, textStatus, error) {
            console.log('DeviantArtPlugin.fetchPluginArtifacts() failed to load deviantart muro markup from: ' + url + '. ' + jqxhr.status + '. ' + error);
        });
    }

    function downloadCSSAndHTML() {
        var url = Context.adapterUrl('deviantart-muro/deviantart-muro-embed.css');
        $.get(url).done(function (data) {
            $('<style>').html(data).appendTo('head');
            console.log('DeviantArtPlugin.fetchPluginArtifacts() download CSS successful');
            downloadHTML();
        }).fail(function (jqxhr, textStatus, error) {
            console.log('Failed to load deviantArt css from: ' + url + '. ' + jqxhr.status + '. ' + error);
        });
    }

    function fetchPluginArtifacts() {
        console.log('[' + new Date().toISOString() + ']' + 'DeviantArtPlugin.fetchPluginArtifacts() called');
        downloadCSSAndHTML();
    }
    fetchPluginArtifacts();
});
<|endoftext|>


<commit_before>Ext.define('SlateAdmin.controller.progress.Interims', {
    extend: 'Ext.app.Controller',

    views: [
        'progress.interims.Manager'
    ],

    stores: [
        'Terms',
        'progress.interims.Sections',
        'progress.interims.Students',
        'progress.interims.Reports'
    ],

    refs: {
        progressNavPanel: 'progress-navpanel',

        managerCt: {
            selector: 'progress-interims-manager',
            autoCreate: true,

            xtype: 'progress-interims-manager'
        },
        myClassesOnlyCheckbox: 'progress-interims-sectionsgrid checkboxfield[name=myClassesOnly]',
        termSelector: 'progress-interims-sectionsgrid #termSelector',
        sectionsGrid: 'progress-interims-sectionsgrid',
        studentsGrid: 'progress-interims-studentsgrid',
        editorForm: 'progress-interims-editorform',
        revertChangesBtn: 'progress-interims-editorform button#revertChangesBtn',
        saveDraftBtn: 'progress-interims-editorform button#saveDraftBtn',
        saveFinishedBtn: 'progress-interims-editorform button#saveFinishedBtn'
    },

    routes: {
        'progress/interims': 'showInterims'
    },

    control: {
        managerCt: {
            activate: 'onManagerActivate'
        },

        myClassesOnlyCheckbox: {
            change: 'onMyClassesOnlyCheckboxChange'
        },
        termSelector: {
            beforeselect: 'onBeforeTermSelect',
            change: 'onTermChange'
        },
        sectionsGrid: {
            beforeselect: 'onBeforeSectionSelect',
            select: 'onSectionSelect'
        },

        studentsGrid: {
            beforeselect: 'onBeforeStudentSelect',
            select: 'onStudentSelect'
        },
        sectionNotesForm: {
            dirtychange: 'onSectionNotesFormDirtyChange'
        },
        sectionNotesRevertBtn: {
            click: 'onSectionNotesRevertBtnClick'
        },
        sectionNotesSaveBtn: {
            click: 'onSectionNotesSaveBtnClick'
        },

        editorForm: {
            dirtychange: 'onEditorFormDirtyChange',
            validitychange: 'onEditorFormValidityChange'
        },
        'progress-interims-editorform combo': {
            select: 'onComboSelect'
        },
        revertChangesBtn: {
            click: 'onRevertChangesClick'
        },
        saveDraftBtn: {
            click: 'onSaveDraftClick'
        },
        saveFinishedBtn: {
            click: 'onSaveFinishedClick'
        }
    },

    listen: {
        store: {
            '#progress.interims.Students': {
                load: 'onStudentsStoreLoad'
            }
        }
    },

    // route handlers
    showInterims: function () {
        var me = this,
            navPanel = me.getProgressNavPanel();

        Ext.suspendLayouts();

        Ext.util.History.suspendState();
        navPanel.setActiveLink('progress/interims');
        navPanel.expand();
        Ext.util.History.resumeState(false); // false to discard any changes to state

        me.application.getController('Viewport').loadCard(me.getManagerCt());

        Ext.resumeLayouts(true);
    },

    // event handlers
    onManagerActivate: function () {
        this.syncSections();
    },

    onMyClassesOnlyCheckboxChange: function () {
        this.syncSections();
    },

    onBeforeTermSelect: function(termSelector, term) {
        var me = this,
            editorForm = me.getEditorForm(),
            loadedReport = editorForm.getRecord();

        if (loadedReport && editorForm.isDirty()) {
            Ext.Msg.confirm('Unsaved Changes', 'You have unsaved changes to this report.

Do you want to continue without saving them?', function (btn) {
                if (btn != 'yes') {
                    return;
                }

                editorForm.reset();
                termSelector.select(term);
            });

            return false;
        }
    },

    onTermChange: function () {
        this.syncSections();
    },

    onBeforeSectionSelect: function(sectionsSelModel, section) {
        var me = this,
            editorForm = me.getEditorForm(),
            loadedReport = editorForm.getRecord();

        if (loadedReport && editorForm.isDirty()) {
            Ext.Msg.confirm('Unsaved Changes', 'You have unsaved changes to this report.

Do you want to continue without saving them?', function (btn) {
                if (btn != 'yes') {
                    return;
                }

                editorForm.reset();
                sectionsSelModel.select([section]);
            });

            return false;
        }
    },

    onSectionSelect: function (sectionsGrid, section) {
        var me = this,
            studentsGrid = me.getStudentsGrid(),
            studentsStore = studentsGrid.getStore(),
            reportsStore = me.getProgressInterimsReportsStore(),
            reportsProxy = reportsStore.getProxy(),
            editorForm = me.getEditorForm(),
            termHandle = me.getTermSelector().getValue(),
            sectionCode = section.get('Code');

        // reset stores
        studentsStore.removeAll();
        reportsStore.removeAll();

        // reset form
        editorForm.disable();
        editorForm.reset(true);

        // configure grid
        studentsGrid.enable();

        // configure and load stores
        studentsStore.getProxy().setUrl('/sections/'+section.get('Code')+'/students');
        studentsStore.load();

        reportsProxy.setExtraParam('term', termHandle);
        reportsProxy.setExtraParam('course_section', sectionCode);
        reportsStore.load();
    },

    onStudentsStoreLoad: function() {
        var me = this,
            studentsView = me.getStudentsGrid().getView(),
            reportsStore = me.getProgressInterimsReportsStore();

        if (reportsStore.isLoading()) {
            studentsView.setLoading('Loading reports…');
            reportsStore.on('load', function() {
                studentsView.setLoading(false);

                // restore original loading text
                studentsView.loadMask.msg = studentsView.loadingText;

                me.syncReportsToStudents();
            }, me, { single: true });
        } else {
            me.syncReportsToStudents();
        }
    },

    onBeforeStudentSelect: function (studentsSelModel, student) {
        var me = this,
            editorForm = me.getEditorForm(),
            loadedReport = editorForm.getRecord();

        if (loadedReport && editorForm.isDirty()) {
            Ext.Msg.confirm('Unsaved Changes', 'You have unsaved changes to this report.

Do you want to continue without saving them?', function (btn) {
                if (btn != 'yes') {
                    return;
                }

                editorForm.reset();
                studentsSelModel.select([student]);
            });

            return false;
        }
    },

    onStudentSelect: function (studentsGrid, student) {
        var me = this,
            editorForm = me.getEditorForm(),
            report = student.get('report'),
            section, term;

        if (!report) {
            section = me.getSectionsGrid().getSelection()[0];
            term = me.getTermSelector().getSelection();

            report = me.getProgressInterimsReportsStore().add({
                StudentID: student.getId(),
                SectionID: section.getId(),
                TermID: term.getId(),

                student: student,
                section: section,
                term: term
            })[0];

            student.set('report', report, { dirty: false });
        }

        me.fireEvent('beforereportload', report);

        editorForm.enable();
        editorForm.setScrollY(0, true);
        editorForm.loadRecord(report);
        me.syncFormButtons();

        me.fireEvent('reportload', report);
    },

    onSectionNotesFormDirtyChange: function() {
        this.syncSectionNotesFormButtons();
    },

    onSectionNotesRevertBtnClick: function() {
        this.getSectionNotesForm().reset();
    },

    onSectionNotesSaveBtnClick: function() {
        var sectionNotesForm = this.getSectionNotesForm(),
            sectionNotes = sectionNotesForm.getRecord();

        sectionNotesForm.updateRecord(sectionNotes);

        if (!sectionNotes.dirty) {
            return;
        }

        sectionNotesForm.setLoading('Saving notes…');
        sectionNotes.save({
            callback: function(sectionNotes, operation, success) {
                sectionNotesForm.setLoading(false);

                if (success) {
                    sectionNotesForm.loadRecord(sectionNotes);
                } else {
                    Ext.Msg.alert('Failed to save section notes', 'The section notes failed to save to the server:

' + (operation.getError() || 'Unknown reason, try again or contact support'));
                }
            }
        });
    },

    onEditorFormDirtyChange: function() {
        this.syncFormButtons();
    },

    onEditorFormValidityChange: function() {
        this.syncFormButtons();
    },

    onComboSelect: function (combo, newValue) {
        var fields = this.getEditorForm().query('field, htmleditor'),
            nextFieldIndex = fields.indexOf(combo) + 1;

        if (nextFieldIndex < fields.length) {
            fields[nextFieldIndex].focus(true, 100);
        }
    },

    onRevertChangesClick: function() {
        var me = this;

        Ext.Msg.confirm('Reverting Changes', 'Are you sure you want to revert your changes', function (btn) {
            if (btn == 'yes') {
                me.getEditorForm().reset();
            }
        });
    },

    onSaveDraftClick: function () {
        this.saveReport('draft');
    },

    onSaveFinishedClick: function () {
        this.saveReport('published');
    },

    saveReport: function (newStatus) {
        var me = this,
            managerCt = me.getManagerCt(),
            editorForm = me.getEditorForm(),
            formData = editorForm.getValues(),
            report = editorForm.getRecord();

        report.beginEdit();

        if (newStatus) {
            report.set('Status', newStatus);
        }

        report.set(formData);

        // TODO: remove this check and instead publish a cancelable beforesave event for plugin to handle this
        // if (report.get('Status') == 'Published' && !report.get('Grade')) {
        //     Ext.Msg.alert('Report not saved', 'Narrative report cannot be marked finished without a grade');
        //     return false;
        // }

        report.endEdit();

        managerCt.setLoading('Saving report&hellip');

        me.fireEvent('beforereportsave', report);

        report.save({
            callback: function(report, operation, success) {
                var student = report.get('student');

                managerCt.setLoading(false);

                if (success) {
                    student.beginEdit();
                    me.syncStudent(student);
                    student.endEdit();

                    editorForm.loadRecord(report);

                    me.fireEvent('reportsave', report);

                    if (!me.getStudentsGrid().getSelectionModel().selectNext()) {
                        me.syncFormButtons();
                    }
                } else {
                    Ext.Msg.alert('Failed to save report', 'This report failed to save to the server:

' + (operation.getError() || 'Unknown reason, try again or contact support'));
                }
            }
        });
    },

    // controller methods
    syncSections: function() {
        var me = this,
            sectionsStore = me.getProgressInterimsSectionsStore(),
            sectionsProxy = sectionsStore.getProxy(),
            managerCt = me.getManagerCt(),
            myClassesOnlyCheckbox = me.getMyClassesOnlyCheckbox(),
            termSelector = me.getTermSelector(),
            termsStore = termSelector.getStore(),
            term = termSelector.getValue();

        // ensure terms are loaded
        if (!termsStore.isLoaded()) {
            termsStore.on('load', function() {
                managerCt.setLoading(false);
                me.syncSections();
            }, me, { single: true });

            if (!termsStore.isLoading()) {
                termsStore.load();
            }

            return;
        }

        // ensure a term is selected
        if (!term) {
            term = termsStore.getReportingTerm() || termsStore.getCurrentTerm();
            if (term) {
                termSelector.setValue(term);
            } else {
                Ext.Msg.alert('No term available', 'No current or reporting term could be detected, please select a term');
            }

            return; // setting the term will call this function again via the change event
        }

        sectionsProxy.setExtraParam('enrolled_user', myClassesOnlyCheckbox.getValue() ? 'current' : '');
        sectionsProxy.setExtraParam('term', term);
        sectionsStore.loadIfDirty();
    },

    syncReportsToStudents: function() {
        var me = this,
            section = me.getSectionsGrid().getSelection()[0],
            termsStore = me.getTermsStore(),
            studentsStore = me.getProgressInterimsStudentsStore(),
            reportsStore = me.getProgressInterimsReportsStore(),
            studentsLength = studentsStore.getCount(), i = 0, student,
            report;

        studentsStore.beginUpdate();
        reportsStore.beginUpdate();

        for (; i < studentsLength; i++) {
            student = studentsStore.getAt(i);
            report = reportsStore.query('StudentID', student.getId()).first();

            student.beginEdit();
            student.set('report', report || null, { dirty: false });
            me.syncStudent(student);
            student.endEdit();

            if (report) {
                report.set({
                    student: student,
                    section: section,
                    term: termsStore.getById(report.get('TermID')) || null
                }, { dirty: false });
            }
        }

        reportsStore.endUpdate();
        studentsStore.endUpdate();
    },

    syncSectionNotesFormButtons: function() {
        var me = this,
            isDirty = this.getSectionNotesForm().isDirty();

        me.getSectionNotesRevertBtn().setDisabled(!isDirty);
        me.getSectionNotesSaveBtn().setDisabled(!isDirty);
    },

    syncFormButtons: function() {
        var me = this,
            editorForm = me.getEditorForm(),
            report = editorForm.getRecord(),
            reportStatus = report && report.get('Status'),
            isDirty = editorForm.isDirty(),
            isValid = editorForm.isValid();

        if (!report) {
            return;
        }

        me.getRevertChangesBtn().setDisabled(!isDirty);
        me.getSaveDraftBtn().setDisabled((!isDirty && reportStatus == 'draft') || !isValid);
        me.getSaveFinishedBtn().setDisabled((!isDirty && reportStatus == 'published') || !isValid);
    },

    syncStudent: function(student) {
        var report = student.get('report');

        student.set({
            report_status: report ? report.get('Status') : null,
            report_modified: report ? report.get('Modified') || report.get('Created') : null
        }, { dirty: false });
    }
});<commit_msg>Reselect section on section store reload<commit_after>Ext.define('SlateAdmin.controller.progress.Interims', {
    extend: 'Ext.app.Controller',

    views: [
        'progress.interims.Manager'
    ],

    stores: [
        'Terms',
        'progress.interims.Sections',
        'progress.interims.Students',
        'progress.interims.Reports'
    ],

    refs: {
        progressNavPanel: 'progress-navpanel',

        managerCt: {
            selector: 'progress-interims-manager',
            autoCreate: true,

            xtype: 'progress-interims-manager'
        },
        myClassesOnlyCheckbox: 'progress-interims-sectionsgrid checkboxfield[name=myClassesOnly]',
        termSelector: 'progress-interims-sectionsgrid #termSelector',
        sectionsGrid: 'progress-interims-sectionsgrid',
        studentsGrid: 'progress-interims-studentsgrid',
        editorForm: 'progress-interims-editorform',
        revertChangesBtn: 'progress-interims-editorform button#revertChangesBtn',
        saveDraftBtn: 'progress-interims-editorform button#saveDraftBtn',
        saveFinishedBtn: 'progress-interims-editorform button#saveFinishedBtn'
    },

    routes: {
        'progress/interims': 'showInterims'
    },

    control: {
        managerCt: {
            activate: 'onManagerActivate'
        },

        myClassesOnlyCheckbox: {
            change: 'onMyClassesOnlyCheckboxChange'
        },
        termSelector: {
            beforeselect: 'onBeforeTermSelect',
            change: 'onTermChange'
        },
        sectionsGrid: {
            beforeselect: 'onBeforeSectionSelect',
            select: 'onSectionSelect'
        },

        studentsGrid: {
            beforeselect: 'onBeforeStudentSelect',
            select: 'onStudentSelect'
        },
        sectionNotesForm: {
            dirtychange: 'onSectionNotesFormDirtyChange'
        },
        sectionNotesRevertBtn: {
            click: 'onSectionNotesRevertBtnClick'
        },
        sectionNotesSaveBtn: {
            click: 'onSectionNotesSaveBtnClick'
        },

        editorForm: {
            dirtychange: 'onEditorFormDirtyChange',
            validitychange: 'onEditorFormValidityChange'
        },
        'progress-interims-editorform combo': {
            select: 'onComboSelect'
        },
        revertChangesBtn: {
            click: 'onRevertChangesClick'
        },
        saveDraftBtn: {
            click: 'onSaveDraftClick'
        },
        saveFinishedBtn: {
            click: 'onSaveFinishedClick'
        }
    },

    listen: {
        store: {
            '#progress.interims.Sections': {
                load: 'onSectionsStoreLoad'
            },
            '#progress.interims.Students': {
                load: 'onStudentsStoreLoad'
            }
        }
    },

    // route handlers
    showInterims: function () {
        var me = this,
            navPanel = me.getProgressNavPanel();

        Ext.suspendLayouts();

        Ext.util.History.suspendState();
        navPanel.setActiveLink('progress/interims');
        navPanel.expand();
        Ext.util.History.resumeState(false); // false to discard any changes to state

        me.application.getController('Viewport').loadCard(me.getManagerCt());

        Ext.resumeLayouts(true);
    },

    // event handlers
    onManagerActivate: function () {
        this.syncSections();
    },

    onMyClassesOnlyCheckboxChange: function () {
        this.syncSections();
    },

    onBeforeTermSelect: function(termSelector, term) {
        var me = this,
            editorForm = me.getEditorForm(),
            loadedReport = editorForm.getRecord();

        if (loadedReport && editorForm.isDirty()) {
            Ext.Msg.confirm('Unsaved Changes', 'You have unsaved changes to this report.

Do you want to continue without saving them?', function (btn) {
                if (btn != 'yes') {
                    return;
                }

                editorForm.reset();
                termSelector.select(term);
            });

            return false;
        }
    },

    onTermChange: function () {
        this.syncSections();
    },

    onSectionsStoreLoad: function() {
        var sectionsGrid = this.getSectionsGrid(),
            section = sectionsGrid.getSelection()[0];

        // reselect section if already selected
        if (section) {
            sectionsGrid.setSelection(null);
            sectionsGrid.setSelection(section);
        }
    },

    onBeforeSectionSelect: function(sectionsSelModel, section) {
        var me = this,
            editorForm = me.getEditorForm(),
            loadedReport = editorForm.getRecord();

        if (loadedReport && editorForm.isDirty()) {
            Ext.Msg.confirm('Unsaved Changes', 'You have unsaved changes to this report.

Do you want to continue without saving them?', function (btn) {
                if (btn != 'yes') {
                    return;
                }

                editorForm.reset();
                sectionsSelModel.select([section]);
            });

            return false;
        }
    },

    onSectionSelect: function (sectionsGrid, section) {
        var me = this,
            studentsGrid = me.getStudentsGrid(),
            studentsStore = studentsGrid.getStore(),
            reportsStore = me.getProgressInterimsReportsStore(),
            reportsProxy = reportsStore.getProxy(),
            editorForm = me.getEditorForm(),
            termHandle = me.getTermSelector().getValue(),
            sectionCode = section.get('Code');

        // reset stores
        studentsStore.removeAll();
        reportsStore.removeAll();

        // reset form
        editorForm.disable();
        editorForm.reset(true);

        // configure grid
        studentsGrid.enable();

        // configure and load stores
        studentsStore.getProxy().setUrl('/sections/'+section.get('Code')+'/students');
        studentsStore.load();

        reportsProxy.setExtraParam('term', termHandle);
        reportsProxy.setExtraParam('course_section', sectionCode);
        reportsStore.load();
    },

    onStudentsStoreLoad: function() {
        var me = this,
            studentsView = me.getStudentsGrid().getView(),
            reportsStore = me.getProgressInterimsReportsStore();

        if (reportsStore.isLoading()) {
            studentsView.setLoading('Loading reports…');
            reportsStore.on('load', function() {
                studentsView.setLoading(false);

                // restore original loading text
                studentsView.loadMask.msg = studentsView.loadingText;

                me.syncReportsToStudents();
            }, me, { single: true });
        } else {
            me.syncReportsToStudents();
        }
    },

    onBeforeStudentSelect: function (studentsSelModel, student) {
        var me = this,
            editorForm = me.getEditorForm(),
            loadedReport = editorForm.getRecord();

        if (loadedReport && editorForm.isDirty()) {
            Ext.Msg.confirm('Unsaved Changes', 'You have unsaved changes to this report.

Do you want to continue without saving them?', function (btn) {
                if (btn != 'yes') {
                    return;
                }

                editorForm.reset();
                studentsSelModel.select([student]);
            });

            return false;
        }
    },

    onStudentSelect: function (studentsGrid, student) {
        var me = this,
            editorForm = me.getEditorForm(),
            report = student.get('report'),
            section, term;

        if (!report) {
            section = me.getSectionsGrid().getSelection()[0];
            term = me.getTermSelector().getSelection();

            report = me.getProgressInterimsReportsStore().add({
                StudentID: student.getId(),
                SectionID: section.getId(),
                TermID: term.getId(),

                student: student,
                section: section,
                term: term
            })[0];

            student.set('report', report, { dirty: false });
        }

        me.fireEvent('beforereportload', report);

        editorForm.enable();
        editorForm.setScrollY(0, true);
        editorForm.loadRecord(report);
        me.syncFormButtons();

        me.fireEvent('reportload', report);
    },

    onSectionNotesFormDirtyChange: function() {
        this.syncSectionNotesFormButtons();
    },

    onSectionNotesRevertBtnClick: function() {
        this.getSectionNotesForm().reset();
    },

    onSectionNotesSaveBtnClick: function() {
        var sectionNotesForm = this.getSectionNotesForm(),
            sectionNotes = sectionNotesForm.getRecord();

        sectionNotesForm.updateRecord(sectionNotes);

        if (!sectionNotes.dirty) {
            return;
        }

        sectionNotesForm.setLoading('Saving notes…');
        sectionNotes.save({
            callback: function(sectionNotes, operation, success) {
                sectionNotesForm.setLoading(false);

                if (success) {
                    sectionNotesForm.loadRecord(sectionNotes);
                } else {
                    Ext.Msg.alert('Failed to save section notes', 'The section notes failed to save to the server:

' + (operation.getError() || 'Unknown reason, try again or contact support'));
                }
            }
        });
    },

    onEditorFormDirtyChange: function() {
        this.syncFormButtons();
    },

    onEditorFormValidityChange: function() {
        this.syncFormButtons();
    },

    onComboSelect: function (combo, newValue) {
        var fields = this.getEditorForm().query('field, htmleditor'),
            nextFieldIndex = fields.indexOf(combo) + 1;

        if (nextFieldIndex < fields.length) {
            fields[nextFieldIndex].focus(true, 100);
        }
    },

    onRevertChangesClick: function() {
        var me = this;

        Ext.Msg.confirm('Reverting Changes', 'Are you sure you want to revert your changes', function (btn) {
            if (btn == 'yes') {
                me.getEditorForm().reset();
            }
        });
    },

    onSaveDraftClick: function () {
        this.saveReport('draft');
    },

    onSaveFinishedClick: function () {
        this.saveReport('published');
    },

    saveReport: function (newStatus) {
        var me = this,
            managerCt = me.getManagerCt(),
            editorForm = me.getEditorForm(),
            formData = editorForm.getValues(),
            report = editorForm.getRecord();

        report.beginEdit();

        if (newStatus) {
            report.set('Status', newStatus);
        }

        report.set(formData);

        // TODO: remove this check and instead publish a cancelable beforesave event for plugin to handle this
        // if (report.get('Status') == 'Published' && !report.get('Grade')) {
        //     Ext.Msg.alert('Report not saved', 'Narrative report cannot be marked finished without a grade');
        //     return false;
        // }

        report.endEdit();

        managerCt.setLoading('Saving report&hellip');

        me.fireEvent('beforereportsave', report);

        report.save({
            callback: function(report, operation, success) {
                var student = report.get('student');

                managerCt.setLoading(false);

                if (success) {
                    student.beginEdit();
                    me.syncStudent(student);
                    student.endEdit();

                    editorForm.loadRecord(report);

                    me.fireEvent('reportsave', report);

                    if (!me.getStudentsGrid().getSelectionModel().selectNext()) {
                        me.syncFormButtons();
                    }
                } else {
                    Ext.Msg.alert('Failed to save report', 'This report failed to save to the server:

' + (operation.getError() || 'Unknown reason, try again or contact support'));
                }
            }
        });
    },

    // controller methods
    syncSections: function() {
        var me = this,
            sectionsStore = me.getProgressInterimsSectionsStore(),
            sectionsProxy = sectionsStore.getProxy(),
            managerCt = me.getManagerCt(),
            myClassesOnlyCheckbox = me.getMyClassesOnlyCheckbox(),
            termSelector = me.getTermSelector(),
            termsStore = termSelector.getStore(),
            term = termSelector.getValue();

        // ensure terms are loaded
        if (!termsStore.isLoaded()) {
            termsStore.on('load', function() {
                managerCt.setLoading(false);
                me.syncSections();
            }, me, { single: true });

            if (!termsStore.isLoading()) {
                termsStore.load();
            }

            return;
        }

        // ensure a term is selected
        if (!term) {
            term = termsStore.getReportingTerm() || termsStore.getCurrentTerm();
            if (term) {
                termSelector.setValue(term);
            } else {
                Ext.Msg.alert('No term available', 'No current or reporting term could be detected, please select a term');
            }

            return; // setting the term will call this function again via the change event
        }

        sectionsProxy.setExtraParam('enrolled_user', myClassesOnlyCheckbox.getValue() ? 'current' : '');
        sectionsProxy.setExtraParam('term', term);
        sectionsStore.loadIfDirty();
    },

    syncReportsToStudents: function() {
        var me = this,
            section = me.getSectionsGrid().getSelection()[0],
            termsStore = me.getTermsStore(),
            studentsStore = me.getProgressInterimsStudentsStore(),
            reportsStore = me.getProgressInterimsReportsStore(),
            studentsLength = studentsStore.getCount(), i = 0, student,
            report;

        studentsStore.beginUpdate();
        reportsStore.beginUpdate();

        for (; i < studentsLength; i++) {
            student = studentsStore.getAt(i);
            report = reportsStore.query('StudentID', student.getId()).first();

            student.beginEdit();
            student.set('report', report || null, { dirty: false });
            me.syncStudent(student);
            student.endEdit();

            if (report) {
                report.set({
                    student: student,
                    section: section,
                    term: termsStore.getById(report.get('TermID')) || null
                }, { dirty: false });
            }
        }

        reportsStore.endUpdate();
        studentsStore.endUpdate();
    },

    syncSectionNotesFormButtons: function() {
        var me = this,
            isDirty = this.getSectionNotesForm().isDirty();

        me.getSectionNotesRevertBtn().setDisabled(!isDirty);
        me.getSectionNotesSaveBtn().setDisabled(!isDirty);
    },

    syncFormButtons: function() {
        var me = this,
            editorForm = me.getEditorForm(),
            report = editorForm.getRecord(),
            reportStatus = report && report.get('Status'),
            isDirty = editorForm.isDirty(),
            isValid = editorForm.isValid();

        if (!report) {
            return;
        }

        me.getRevertChangesBtn().setDisabled(!isDirty);
        me.getSaveDraftBtn().setDisabled((!isDirty && reportStatus == 'draft') || !isValid);
        me.getSaveFinishedBtn().setDisabled((!isDirty && reportStatus == 'published') || !isValid);
    },

    syncStudent: function(student) {
        var report = student.get('report');

        student.set({
            report_status: report ? report.get('Status') : null,
            report_modified: report ? report.get('Modified') || report.get('Created') : null
        }, { dirty: false });
    }
});<|endoftext|>


<commit_before>const axios = require('axios');
const { RateLimiterMemory } = require('rate-limiter-flexible');

const globals = require('../../globals');

let rateLimiterFailedReloads;
let rateLimiterAbortedReloads;

if (globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.rateLimit')) {
    rateLimiterFailedReloads = new RateLimiterMemory({
        points: 1,
        duration: globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.rateLimit'),
    });
} else {
    rateLimiterFailedReloads = new RateLimiterMemory({
        points: 1,
        duration: 300,
    });
}

if (globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.rateLimit')) {
    rateLimiterAbortedReloads = new RateLimiterMemory({
        points: 1,
        duration: globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.rateLimit'),
    });
} else {
    rateLimiterAbortedReloads = new RateLimiterMemory({
        points: 1,
        duration: 300,
    });
}

function getReloadFailedNotificationConfigOk() {
    try {
        // First make sure this tool is enabled in the config file and that we have needed parameters
        if (
            !globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure') ||
            !globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.enable') ||
            !globals.config.has('Butler.incidentTool.signl4.url')
        ) {
            // Not enough info in config file
            globals.logger.error('SIGNL4: Reload failure SIGNL4 config info missing in Butler config file');
            return false;
        }

        return {
            event: 'Qlik Sense reload task failed',
            url: globals.config.get('Butler.incidentTool.signl4.url'),
            rateLimit: globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.rateLimit')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.rateLimit')
                : '',
            severity: globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.severity')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.severity')
                : 99,
            serviceName: globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.serviceName')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.serviceName')
                : 'Default service name',
            // autoCloseOnLaterReloadSuccess: globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.autoCloseOnLaterReloadSuccess')
            //     ? globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.autoCloseOnLaterReloadSuccess')
            //     : false,
        };
    } catch (err) {
        globals.logger.error(`SIGNL4: ${err}`);
        return false;
    }
}

function getReloadAbortedNotificationConfigOk() {
    try {
        // First make sure this tool is enabled in the config file and that we have needed parameters
        if (
            !globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted') ||
            !globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.enable') ||
            !globals.config.has('Butler.incidentTool.signl4.url')
        ) {
            // Not enough info in config file
            globals.logger.error('SIGNL4: Reload aborted SIGNL4 config info missing in Butler config file');
            return false;
        }

        return {
            event: 'Qlik Sense task aborted',
            url: globals.config.get('Butler.incidentTool.signl4.url'),
            rateLimit: globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.rateLimit')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.rateLimit')
                : '',
            severity: globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.severity')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.severity')
                : 99,
            serviceName: globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.serviceName')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.serviceName')
                : 'Default service name',
            // autoCloseOnLaterReloadSuccess: globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.autoCloseOnLaterReloadSuccess')
            //     ? globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.autoCloseOnLaterReloadSuccess')
            //     : false,
        };
    } catch (err) {
        globals.logger.error(`SIGNL4: ${err}`);
        return false;
    }
}

async function sendSignl4(incidentConfig, reloadParams) {
    try {
        // Build body for HTTP POST
        const axiosRequest = {
            url: incidentConfig.url,
            method: 'post',
            timeout: 10000,
            data: {
                Title: incidentConfig.event,
                Message: `Task: ${reloadParams.taskName}`,
                Severity: incidentConfig.severity,
                'X-S4-Service': incidentConfig.serviceName,
                'X-S4-ExternalID': reloadParams.taskId,
                'X-S4-Status': 'new',
            },
            headers: { 'Content-Type': 'application/json' },
        };

        const response = await axios.request(axiosRequest);
        globals.logger.debug(`SIGNL4: Webhook response: ${response}`);
    } catch (err) {
        globals.logger.error(`SIGNL4: ${JSON.stringify(err, null, 2)}`);
    }
}

function sendReloadTaskFailureNotification(reloadParams) {
    rateLimiterFailedReloads
        .consume(reloadParams.taskId, 1)
        .then(async (rateLimiterRes) => {
            try {
                globals.logger.info(
                    `TASK FAILED ALERT SIGNL4: Rate limiting ok: Sending reload failure notification to Signl4 for task "${reloadParams.taskName}"`
                );
                globals.logger.verbose(`TASK FAILED ALERT SIGNL4: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`);

                // Make sure Slack sending is enabled in the config file and that we have all required settings
                const incidentConfig = getReloadFailedNotificationConfigOk();
                if (incidentConfig === false) {
                    return 1;
                }

                sendSignl4(incidentConfig, reloadParams);
                return null;
            } catch (err) {
                globals.logger.error(`TASK FAILED ALERT SIGNL4: ${err}`);
                return null;
            }
        })
        .catch((rateLimiterRes) => {
            globals.logger.verbose(
                `TASK FAILED ALERT SIGNL4: Rate limiting failed. Not sending reload failure notification to Signl4 for task "${reloadParams.taskName}"`
            );
            globals.logger.verbose(`TASK FAILED ALERT SIGNL4: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`);
        });
}

function sendReloadTaskAbortedNotification(reloadParams) {
    rateLimiterAbortedReloads
        .consume(reloadParams.taskId, 1)
        .then(async (rateLimiterRes) => {
            try {
                globals.logger.info(
                    `SIGNL4ABORTED: Rate limiting ok: Sending reload aborted notification to Signl4 for task "${reloadParams.taskName}"`
                );
                globals.logger.verbose(`SIGNL4ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`);

                // Make sure outgoing webhooks are enabled in the config file and that we have all required settings
                const incidentConfig = getReloadAbortedNotificationConfigOk();
                if (incidentConfig === false) {
                    return 1;
                }

                sendSignl4(incidentConfig, reloadParams);
                return null;
            } catch (err) {
                globals.logger.error(`SIGNL4ABORTED: ${err}`);
                return null;
            }
        })
        .catch((rateLimiterRes) => {
            globals.logger.verbose(
                `SIGNL4ABORTED: Rate limiting failed. Not sending reload aborted notification to Signl4 for task "${reloadParams.taskName}"`
            );
            globals.logger.verbose(`SIGNL4ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`);
        });
}

module.exports = {
    sendReloadTaskFailureNotification,
    sendReloadTaskAbortedNotification,
};
<commit_msg>Refactor function names in Signl4 integration<commit_after>const axios = require('axios');
const { RateLimiterMemory } = require('rate-limiter-flexible');

const globals = require('../../globals');

let rateLimiterFailedReloads;
let rateLimiterAbortedReloads;

if (globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.rateLimit')) {
    rateLimiterFailedReloads = new RateLimiterMemory({
        points: 1,
        duration: globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.rateLimit'),
    });
} else {
    rateLimiterFailedReloads = new RateLimiterMemory({
        points: 1,
        duration: 300,
    });
}

if (globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.rateLimit')) {
    rateLimiterAbortedReloads = new RateLimiterMemory({
        points: 1,
        duration: globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.rateLimit'),
    });
} else {
    rateLimiterAbortedReloads = new RateLimiterMemory({
        points: 1,
        duration: 300,
    });
}

function getReloadFailedEventConfig() {
    try {
        // First make sure this tool is enabled in the config file and that we have needed parameters
        if (
            !globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure') ||
            !globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.enable') ||
            !globals.config.has('Butler.incidentTool.signl4.url')
        ) {
            // Not enough info in config file
            globals.logger.error('SIGNL4: Reload failure SIGNL4 config info missing in Butler config file');
            return false;
        }

        return {
            event: 'Qlik Sense reload task failed',
            url: globals.config.get('Butler.incidentTool.signl4.url'),
            rateLimit: globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.rateLimit')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.rateLimit')
                : '',
            severity: globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.severity')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.severity')
                : 99,
            serviceName: globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.serviceName')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.serviceName')
                : 'Default service name',
            // autoCloseOnLaterReloadSuccess: globals.config.has('Butler.incidentTool.signl4.reloadTaskFailure.autoCloseOnLaterReloadSuccess')
            //     ? globals.config.get('Butler.incidentTool.signl4.reloadTaskFailure.autoCloseOnLaterReloadSuccess')
            //     : false,
        };
    } catch (err) {
        globals.logger.error(`SIGNL4: ${err}`);
        return false;
    }
}

function getReloadAbortedEventConfig() {
    try {
        // First make sure this tool is enabled in the config file and that we have needed parameters
        if (
            !globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted') ||
            !globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.enable') ||
            !globals.config.has('Butler.incidentTool.signl4.url')
        ) {
            // Not enough info in config file
            globals.logger.error('SIGNL4: Reload aborted SIGNL4 config info missing in Butler config file');
            return false;
        }

        return {
            event: 'Qlik Sense task aborted',
            url: globals.config.get('Butler.incidentTool.signl4.url'),
            rateLimit: globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.rateLimit')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.rateLimit')
                : '',
            severity: globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.severity')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.severity')
                : 99,
            serviceName: globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.serviceName')
                ? globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.serviceName')
                : 'Default service name',
            // autoCloseOnLaterReloadSuccess: globals.config.has('Butler.incidentTool.signl4.reloadTaskAborted.autoCloseOnLaterReloadSuccess')
            //     ? globals.config.get('Butler.incidentTool.signl4.reloadTaskAborted.autoCloseOnLaterReloadSuccess')
            //     : false,
        };
    } catch (err) {
        globals.logger.error(`SIGNL4: ${err}`);
        return false;
    }
}

async function sendSignl4(incidentConfig, reloadParams) {
    try {
        // Build body for HTTP POST
        const axiosRequest = {
            url: incidentConfig.url,
            method: 'post',
            timeout: 10000,
            data: {
                Title: incidentConfig.event,
                Message: `Task: ${reloadParams.taskName}`,
                Severity: incidentConfig.severity,
                'X-S4-Service': incidentConfig.serviceName,
                'X-S4-ExternalID': reloadParams.taskId,
                'X-S4-Status': 'new',
            },
            headers: { 'Content-Type': 'application/json' },
        };

        const response = await axios.request(axiosRequest);
        globals.logger.debug(`SIGNL4: Webhook response: ${response}`);
    } catch (err) {
        globals.logger.error(`SIGNL4: ${JSON.stringify(err, null, 2)}`);
    }
}

function sendReloadTaskFailureNotification(reloadParams) {
    rateLimiterFailedReloads
        .consume(reloadParams.taskId, 1)
        .then(async (rateLimiterRes) => {
            try {
                globals.logger.info(
                    `TASK FAILED ALERT SIGNL4: Rate limiting ok: Sending reload failure notification to Signl4 for task "${reloadParams.taskName}"`
                );
                globals.logger.verbose(`TASK FAILED ALERT SIGNL4: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`);

                // Make sure Slack sending is enabled in the config file and that we have all required settings
                const incidentConfig = getReloadFailedEventConfig();
                if (incidentConfig === false) {
                    return 1;
                }

                sendSignl4(incidentConfig, reloadParams);
                return null;
            } catch (err) {
                globals.logger.error(`TASK FAILED ALERT SIGNL4: ${err}`);
                return null;
            }
        })
        .catch((rateLimiterRes) => {
            globals.logger.verbose(
                `TASK FAILED ALERT SIGNL4: Rate limiting failed. Not sending reload failure notification to Signl4 for task "${reloadParams.taskName}"`
            );
            globals.logger.verbose(`TASK FAILED ALERT SIGNL4: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`);
        });
}

function sendReloadTaskAbortedNotification(reloadParams) {
    rateLimiterAbortedReloads
        .consume(reloadParams.taskId, 1)
        .then(async (rateLimiterRes) => {
            try {
                globals.logger.info(
                    `SIGNL4ABORTED: Rate limiting ok: Sending reload aborted notification to Signl4 for task "${reloadParams.taskName}"`
                );
                globals.logger.verbose(`SIGNL4ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`);

                // Make sure outgoing webhooks are enabled in the config file and that we have all required settings
                const incidentConfig = getReloadAbortedEventConfig();
                if (incidentConfig === false) {
                    return 1;
                }

                sendSignl4(incidentConfig, reloadParams);
                return null;
            } catch (err) {
                globals.logger.error(`SIGNL4ABORTED: ${err}`);
                return null;
            }
        })
        .catch((rateLimiterRes) => {
            globals.logger.verbose(
                `SIGNL4ABORTED: Rate limiting failed. Not sending reload aborted notification to Signl4 for task "${reloadParams.taskName}"`
            );
            globals.logger.verbose(`SIGNL4ABORTED: Rate limiting details "${JSON.stringify(rateLimiterRes, null, 2)}"`);
        });
}

module.exports = {
    sendReloadTaskFailureNotification,
    sendReloadTaskAbortedNotification,
};
<|endoftext|>


<commit_before>(function () {
  'use strict';

  /*-----------------------
  --------- IMPORT --------
  -----------------------*/
  var eventListeners = window.flatworld.eventListeners;
  var mapStates = window.flatworld.mapStates;
  var mapEvents = window.flatworld.mapEvents;
  var utils = window.flatworld.utils;
  var Hammer = window.flatworld_libraries.Hammer;
  var Hamster = window.flatworld_libraries.Hamster;

  /*-----------------------
  ---------- API ----------
  -----------------------*/
  window.flatworld.extensions.baseEventlisteners = baseEventlistenersModule();

  /*-----------------------
  -------- PUBLIC ---------
  -----------------------*/
  /**
   * Core plugin. Houses the default eventlisteners used in the map. When plugins are added to the map this class can be used for the
   * eventlistener management. Creates window.flatworld.setFullScreen function to be used when switching to fullscreen.
   *
   * @namespace flatworld
   * @class extensions.baseEventlisteners
   * @requires Hammer.js                    (for touch events)
   * @requires Hamster.js                   (for good cross-browser mousewheel events)
   * @param {HTMLElement} canvasElement     The canvas element we listen events from. Will try to search the first canvas in the DOM,
   * if none is provided
   */
  function baseEventlistenersModule() {
    var hammer, hamster, mapInstance;

    /*---------------------------
    ----------- API -------------
    ---------------------------*/
    return {
      init,
      toggleFullSize,
      toggleFullscreen,
      toggleZoom,
      toggleDrag,
      toggleSelect,
      toggleOrder,
      toggleMouseTextSelection,

      /**
       * Plugins name
       *
       * @attribute pluginName
       * @type {String}
       */
      pluginName: "baseEventlisteners"
    };

    /**
     * Initialize plugin
     *
     * @method init
     */
    function init(map) {
      const orderToggle = toggleOrder();
      const selectToggle = toggleSelect();

      mapInstance = map;
      hammer = new Hammer.Manager(map.canvas);
      hamster = new Hamster(map.canvas);

      eventListeners.setDetector("fullSize", toggleFullSize().on, toggleFullSize().off);
      eventListeners.setDetector("fullscreen", toggleFullscreen().on, toggleFullscreen().off);
      eventListeners.setDetector("zoom", toggleZoom().on, toggleZoom().off);
      eventListeners.setDetector("drag", toggleDrag().on, toggleDrag().off);
      eventListeners.setDetector("select", selectToggle.on, selectToggle.off);
      eventListeners.setDetector("order", orderToggle.on, orderToggle.off);

      eventListeners.on("fullSize", _resizeCanvas);
      map.setPrototype("setFullScreen", () => {
        eventListeners.on("fullscreen", _setFullScreen);
      });
    }

    /**
     * Sets the canvas to fullsize as in the same size of the window / content area. But not fullscreen. Note that
     *
     * @method toggleFullSize
     */
    function toggleFullSize() {
      var activeCB;

      return {
        on: (cb) => {
          activeCB = cb;

          window.addEventListener("resize", activeCB);
        },
        off: () => {
          window.removeEventListener("resize", activeCB);
        }
      };
    }
    /**
     * Sets the browser in fullscreen mode.
     *
     * @method toggleFullscreen
     * @param {Function} cb     Callback that fires when this event activates
     * @return {Boolean}        Return the state of this event
     */
    function toggleFullscreen() {
      var activeCB;

      return {
        on: (cb) => {
          activeCB = cb;

          window.addEventListener("fullscreen", activeCB);
        },
        off: () => {
          window.removeEventListener("fullscreen", activeCB);
        }
      };
    }
    /**
     * Zoom the map. Mousewheel (desktop) and pinch (mobile)
     *
     * @method toggleZoom
     * @param {Function} cb         Callback that fires when this event activates
     * @return {Boolean}            Return the state of this event
     */
    function toggleZoom() {
      var activeCB;

      return {
        on: (cb) => {
          var pinch = new Hammer.Pinch();
          activeCB = cb;

          hammer.add(pinch);
          hammer.on("pinch", activeCB);
          /* Hamster handles wheel events really nicely */
          hamster.wheel(activeCB);
        },
        off: () => {
          hammer.on("pinch", activeCB);
          hamster.unwheel(activeCB);
        }
      };
    }
    /**
     * DragListener (normally used for moving the map)
     *
     * @method toggleDrag
     * @param {Function} cb     Callback that fires when this event activates
     * @return {Boolean}        Return the state of this event
     */
    function toggleDrag() {
      var activeCB;

      return {
        on: (cb) => {
          var pan = new Hammer.Pan({
            pointers: 1,
            threshold: 5,
            direction:  Hammer.DIRECTION_ALL });
          activeCB = cb;

          hammer.add(pan);
          hammer.on("pan", activeCB);
        },
        off: () => {
          hammer.off("pan", activeCB);
        }
      };
    }
    /**
     * Selecting something from the map
     *
     * @method toggleSelect
     * @param {Function} cb     Callback that fires when this event activates
     * @return {Boolean}        Return the state of this event
     */
    function toggleSelect() {
      var activeCB;

      return {
        on: (cb) => {
          var tap = new Hammer.Tap();
          activeCB = cb;

          hammer.add(tap);
          hammer.on("tap", activeCB);
        },
        off: () => {
          hammer.off("tap", activeCB);
        }
      };
    }
    /**
     * Selecting something from the map
     *
     * @method toggleOrder
     * @param {Function} cb     Callback that fires when this event activates
     * @return {Boolean}        Return the state of this event
     */
    function toggleOrder() {
      var activeCB;

      return {
        on: (cb) => {
          activeCB = cb;

          if (mapInstance.isSupportedTouch) {
            var tap = new Hammer.Tap();

            hammer.add(tap);
            hammer.on("tap", clickListener);
          } else {
            mapInstance.canvas.addEventListener("contextmenu", clickListener, true);
          }
        },
        off: () => {
          hammer.off("tap", clickListener);
          mapInstance.canvas.removeEventListener("contextmenu", clickListener, true);
        }
      };

      function clickListener(e) {
        /* Prevent right clicks default menu */
        e.preventDefault();

        /* Check that finite state is correct and that if desktop, the user clicked right button */
        if (! mapStates.can("objectOrder") && ( mapInstance.isSupportedTouch || utils.mouse.isRightClick(e))) {
          return false;
        }

        activeCB(e);
      }
    }
    /**
     * Deactivate the selection of text, by dragging
     *
     * @method toggleMouseTextSelection
     */
    function toggleMouseTextSelection() {
      var bodyStyles = document.getElementsByTagName("body")[0].style;

      bodyStyles.webkitTouchCallout = "none";
      bodyStyles.webkitUserSelect = "none";
      bodyStyles.khtmlUserSelect = "none";
      bodyStyles.mozUserSelect = "none";
      bodyStyles.msUserSelect = "none";
      bodyStyles.userSelect = "none";
    }

    /**
     * Activate the browsers fullScreen mode and expand the canvas to fullsize
     *
     * @private
     * @method _setFullScreen
     */
    function _setFullScreen() {
      utils.resize.toggleFullScreen();
      mapEvents.publish("mapResized");
      _resizeCanvas();
    }
    /**
     * Resizes the canvas to the current most wide and high element status. Basically canvas size === window size.
     *
     * @private
     * @method _resizeCanvas
     */
    function _resizeCanvas() {
      var windowSize = utils.resize.getWindowSize();
      var _renderer = mapInstance.getRenderer();

      _renderer.autoResize = true;
      _renderer.resize(windowSize.x, windowSize.y);
      mapEvents.publish("mapResized");
      mapInstance.drawOnNextTick();
    }
  }
})();<commit_msg>Added support for giving orders in mobile, by using continuous press<commit_after>(function () {
  'use strict';

  /*-----------------------
  --------- IMPORT --------
  -----------------------*/
  var eventListeners = window.flatworld.eventListeners;
  var mapStates = window.flatworld.mapStates;
  var mapEvents = window.flatworld.mapEvents;
  var utils = window.flatworld.utils;
  var Hammer = window.flatworld_libraries.Hammer;
  var Hamster = window.flatworld_libraries.Hamster;

  /*-----------------------
  ---------- API ----------
  -----------------------*/
  window.flatworld.extensions.baseEventlisteners = baseEventlistenersModule();

  /*-----------------------
  -------- PUBLIC ---------
  -----------------------*/
  /**
   * Core plugin. Houses the default eventlisteners used in the map. When plugins are added to the map this class can be used for the
   * eventlistener management. Creates window.flatworld.setFullScreen function to be used when switching to fullscreen.
   *
   * @namespace flatworld
   * @class extensions.baseEventlisteners
   * @requires Hammer.js                    (for touch events)
   * @requires Hamster.js                   (for good cross-browser mousewheel events)
   * @param {HTMLElement} canvasElement     The canvas element we listen events from. Will try to search the first canvas in the DOM,
   * if none is provided
   */
  function baseEventlistenersModule() {
    var hammer, hamster, mapInstance;

    /*---------------------------
    ----------- API -------------
    ---------------------------*/
    return {
      init,
      toggleFullSize,
      toggleFullscreen,
      toggleZoom,
      toggleDrag,
      toggleSelect,
      toggleOrder,
      toggleMouseTextSelection,

      /**
       * Plugins name
       *
       * @attribute pluginName
       * @type {String}
       */
      pluginName: "baseEventlisteners"
    };

    /**
     * Initialize plugin
     *
     * @method init
     */
    function init(map) {
      const orderToggle = toggleOrder();
      const selectToggle = toggleSelect();

      mapInstance = map;
      hammer = new Hammer.Manager(map.canvas);
      hamster = new Hamster(map.canvas);

      eventListeners.setDetector("fullSize", toggleFullSize().on, toggleFullSize().off);
      eventListeners.setDetector("fullscreen", toggleFullscreen().on, toggleFullscreen().off);
      eventListeners.setDetector("zoom", toggleZoom().on, toggleZoom().off);
      eventListeners.setDetector("drag", toggleDrag().on, toggleDrag().off);
      eventListeners.setDetector("select", selectToggle.on, selectToggle.off);
      eventListeners.setDetector("order", orderToggle.on, orderToggle.off);

      eventListeners.on("fullSize", _resizeCanvas);
      map.setPrototype("setFullScreen", () => {
        eventListeners.on("fullscreen", _setFullScreen);
      });
    }

    /**
     * Sets the canvas to fullsize as in the same size of the window / content area. But not fullscreen. Note that
     *
     * @method toggleFullSize
     */
    function toggleFullSize() {
      var activeCB;

      return {
        on: (cb) => {
          activeCB = cb;

          window.addEventListener("resize", activeCB);
        },
        off: () => {
          window.removeEventListener("resize", activeCB);
        }
      };
    }
    /**
     * Sets the browser in fullscreen mode.
     *
     * @method toggleFullscreen
     * @param {Function} cb     Callback that fires when this event activates
     * @return {Boolean}        Return the state of this event
     */
    function toggleFullscreen() {
      var activeCB;

      return {
        on: (cb) => {
          activeCB = cb;

          window.addEventListener("fullscreen", activeCB);
        },
        off: () => {
          window.removeEventListener("fullscreen", activeCB);
        }
      };
    }
    /**
     * Zoom the map. Mousewheel (desktop) and pinch (mobile)
     *
     * @method toggleZoom
     * @param {Function} cb         Callback that fires when this event activates
     * @return {Boolean}            Return the state of this event
     */
    function toggleZoom() {
      var activeCB;

      return {
        on: (cb) => {
          var pinch = new Hammer.Pinch();
          activeCB = cb;

          hammer.add(pinch);
          hammer.on("pinch", activeCB);
          /* Hamster handles wheel events really nicely */
          hamster.wheel(activeCB);
        },
        off: () => {
          hammer.on("pinch", activeCB);
          hamster.unwheel(activeCB);
        }
      };
    }
    /**
     * DragListener (normally used for moving the map)
     *
     * @method toggleDrag
     * @param {Function} cb     Callback that fires when this event activates
     * @return {Boolean}        Return the state of this event
     */
    function toggleDrag() {
      var activeCB;

      return {
        on: (cb) => {
          var pan = new Hammer.Pan({
            pointers: 1,
            threshold: 5,
            direction:  Hammer.DIRECTION_ALL });
          activeCB = cb;

          hammer.add(pan);
          hammer.on("pan", activeCB);
        },
        off: () => {
          hammer.off("pan", activeCB);
        }
      };
    }
    /**
     * Selecting something from the map
     *
     * @method toggleSelect
     * @param {Function} cb     Callback that fires when this event activates
     * @return {Boolean}        Return the state of this event
     */
    function toggleSelect() {
      var activeCB;

      return {
        on: (cb) => {
          var tap = new Hammer.Tap();
          activeCB = cb;

          hammer.add(tap);
          hammer.on("tap", activeCB);
        },
        off: () => {
          hammer.off("tap", activeCB);
        }
      };
    }
    /**
     * Selecting something from the map. With mouse you can use the default right click and in touch devices you can use continuous press
     * event (keeping the finger pressed on the screen for a preset time).
     *
     * @method toggleOrder
     * @param {Function} cb     Callback that fires when this event activates
     * @return {Boolean}        Return the state of this event
     */
    function toggleOrder() {
      var activeCB;

      return {
        on: (cb) => {
          activeCB = cb;

          var press = new Hammer.Press();

          hammer.add(press);
          hammer.on("press", clickListener);
          mapInstance.canvas.addEventListener("contextmenu", clickListener, true);
        },
        off: () => {
          hammer.off("press", clickListener);
          mapInstance.canvas.removeEventListener("contextmenu", clickListener, true);
        }
      };

      function clickListener(e) {
        /* Prevent right clicks default menu */
        e.preventDefault();

        /* Check that finite state is correct and that if desktop, the user clicked right button */
        if (! mapStates.can("objectOrder") && ( mapInstance.isSupportedTouch || utils.mouse.isRightClick(e))) {
          return false;
        }

        activeCB(e);
      }
    }
    /**
     * Deactivate the selection of text, by dragging
     *
     * @method toggleMouseTextSelection
     */
    function toggleMouseTextSelection() {
      var bodyStyles = document.getElementsByTagName("body")[0].style;

      bodyStyles.webkitTouchCallout = "none";
      bodyStyles.webkitUserSelect = "none";
      bodyStyles.khtmlUserSelect = "none";
      bodyStyles.mozUserSelect = "none";
      bodyStyles.msUserSelect = "none";
      bodyStyles.userSelect = "none";
    }

    /**
     * Activate the browsers fullScreen mode and expand the canvas to fullsize
     *
     * @private
     * @method _setFullScreen
     */
    function _setFullScreen() {
      utils.resize.toggleFullScreen();
      mapEvents.publish("mapResized");
      _resizeCanvas();
    }
    /**
     * Resizes the canvas to the current most wide and high element status. Basically canvas size === window size.
     *
     * @private
     * @method _resizeCanvas
     */
    function _resizeCanvas() {
      var windowSize = utils.resize.getWindowSize();
      var _renderer = mapInstance.getRenderer();

      _renderer.autoResize = true;
      _renderer.resize(windowSize.x, windowSize.y);
      mapEvents.publish("mapResized");
      mapInstance.drawOnNextTick();
    }
  }
})();<|endoftext|>


<commit_before>'use strict';
var app = angular.module('angularApp')
  .controller('IndSampleCtrl', function (sampleService,navigationService, groupService, $rootScope,$scope,$http,$filter,$location,$routeParams) {
    $rootScope.tabs = navigationService.query();
    $rootScope.groups = groupService.get();
    $scope.sampleData = sampleService.sampleData;
    // $scope.sampleData = {"sampleId":27131906,"sampleName":"UMC_HSTVAMC_NCL_NB-NChandaNNBM2010-01","pointOfContact":null,"composition":null,"functions":null,"characterizations":null,"dataAvailability":null,"createdDate":1275683278000,"keywords":"BIOCOMPATIBILITY
GOLD
GUM ARABIC","pointOfContactMap":{"organizationDisplayName":["UMC_RadiolD
Department of Radiology
University of Missouri-Columbia
Columbia MO 65212 USA","UMC_RadiolD
Department of Radiology
University of Missouri-Columbia
Columbia MO 65212 USA"],"primaryContact":["true","false"],"role":["investigator","investigator"],"contactPerson":["Raghuraman Kannan
kannanr@health.missouri.edu","Kattesh V Katti
kattik@health.missouri.edu"]},"pocBeanDomainId":27066372,"availableEntityNames":null,"caNanoLabScore":null,"mincharScore":null,"chemicalAssocs":null,"physicoChars":null,"invitroChars":null,"invivoChars":null,"caNano2MINChar":null,"caNanoMINChar":null};
    $scope.sampleId = sampleService.sampleId;
    // Displays left hand nav for samples section. navTree shows nav and navDetail is page index //
    $rootScope.navTree = true;
    $rootScope.navDetail = 0;

    $scope.goBack = function() {
      $location.path("/sampleResults").replace();
      $location.search('sampleId', null);
    };

    if ($routeParams.sampleId) {
      $scope.sampleId.data = $routeParams.sampleId;
    }

    $scope.returnUserReadableBoolean = function(val) {
      if (val=='true') {
        return "Yes";
      }
      return "No";
    }

    $scope.$on('$viewContentLoaded', function(){
     $http({method: 'GET', url: '/caNanoLab/rest/sample/view?sampleId=' + $scope.sampleId.data}).
     success(function(data, status, headers, config) {
      $scope.sampleData = data;
        }).
        error(function(data, status, headers, config) {
          // called asynchronously if an error occurs
          // or server returns response with an error status.
        $scope.message = data;

         });
    });

  });
<commit_msg>Changed viewSample to include loader<commit_after>'use strict';
var app = angular.module('angularApp')
  .controller('IndSampleCtrl', function (sampleService,navigationService, groupService, $rootScope,$scope,$http,$filter,$location,$routeParams) {
    $rootScope.tabs = navigationService.query();
    $rootScope.groups = groupService.get();
    $scope.sampleData = sampleService.sampleData;
    // $scope.sampleData = {"sampleId":27131906,"sampleName":"UMC_HSTVAMC_NCL_NB-NChandaNNBM2010-01","pointOfContact":null,"composition":null,"functions":null,"characterizations":null,"dataAvailability":null,"createdDate":1275683278000,"keywords":"BIOCOMPATIBILITY
GOLD
GUM ARABIC","pointOfContactMap":{"organizationDisplayName":["UMC_RadiolD
Department of Radiology
University of Missouri-Columbia
Columbia MO 65212 USA","UMC_RadiolD
Department of Radiology
University of Missouri-Columbia
Columbia MO 65212 USA"],"primaryContact":["true","false"],"role":["investigator","investigator"],"contactPerson":["Raghuraman Kannan
kannanr@health.missouri.edu","Kattesh V Katti
kattik@health.missouri.edu"]},"pocBeanDomainId":27066372,"availableEntityNames":null,"caNanoLabScore":null,"mincharScore":null,"chemicalAssocs":null,"physicoChars":null,"invitroChars":null,"invivoChars":null,"caNano2MINChar":null,"caNanoMINChar":null};
    $scope.sampleId = sampleService.sampleId;
    // Displays left hand nav for samples section. navTree shows nav and navDetail is page index //
    $rootScope.navTree = true;
    $rootScope.navDetail = 0;

    $scope.goBack = function() {
      $location.path("/sampleResults").replace();
      $location.search('sampleId', null);
    };

    if ($routeParams.sampleId) {
      $scope.sampleId.data = $routeParams.sampleId;
    }

    $scope.returnUserReadableBoolean = function(val) {
      if (val=='true') {
        return "Yes";
      }
      return "No";
    }

    $scope.$on('$viewContentLoaded', function(){
      $scope.loader = true;
     $http({method: 'GET', url: '/caNanoLab/rest/sample/view?sampleId=' + $scope.sampleId.data}).
     success(function(data, status, headers, config) {
      $scope.sampleData = data;
      $scope.loader=false;

        }).
        error(function(data, status, headers, config) {
          // called asynchronously if an error occurs
          // or server returns response with an error status.
        $scope.message = data;
      $scope.loader=false;

         });
    });

  });
<|endoftext|>