<commit_before>import { LightningElement, track, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'
import { registerListener, unregisterAllListeners, fireEvent} from 'c/pubsubNoPageRef';
import getFieldMappingSetName
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getFieldMappingSetName';
import getFieldMappingsByObjectAndFieldSetNames
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getFieldMappingsByObjectAndFieldSetNames';
import createDataImportFieldMapping
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.createDataImportFieldMapping';
import getObjectFieldDescribes
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getObjectFieldDescribes';
import getDataImportObjectName
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getDataImportObjectName';
import getMappedDISourceFields
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getMappedDISourceFields';

// Import custom labels
import bdiFieldMappingsLabel from '@salesforce/label/c.bdiFieldMappings';
import bdiFMUIBackToMapGroup from '@salesforce/label/c.bdiFMUIBackToMapGroup';
import bdiFMUIDescription1 from '@salesforce/label/c.bdiFMUIDescription1';
import bdiFMUIDescription2 from '@salesforce/label/c.bdiFMUIDescription2';
import bdiFMUILongDeployment from '@salesforce/label/c.bdiFMUILongDeployment';
import bdiFMUILongDeploymentLink from '@salesforce/label/c.bdiFMUILongDeploymentLink';
import bdiFMUILongDeploymentMessage from '@salesforce/label/c.bdiFMUILongDeploymentMessage';
import bdiFMUINewFieldMapping from '@salesforce/label/c.bdiFMUINewFieldMapping';
import bdiFMUINoFieldMappings from '@salesforce/label/c.bdiFMUINoFieldMappings';
import bdiFMUISourceObject from '@salesforce/label/c.bdiFMUISourceObject';
import bdiFMUISuccessful from '@salesforce/label/c.bdiFMUISuccessful';
import bdiFMUITarget from '@salesforce/label/c.bdiFMUITarget';
import bdiFMUITryAgain from '@salesforce/label/c.bdiFMUITryAgain';
import bdiFMUIUnsuccessful from '@salesforce/label/c.bdiFMUIUnsuccessful';
import bdiFMUIUpdate from '@salesforce/label/c.bdiFMUIUpdate';
import stgHelpAdvancedMapping3 from '@salesforce/label/c.stgHelpAdvancedMapping3';
import stgLabelObject from '@salesforce/label/c.stgLabelObject';
import stgUnknownError from '@salesforce/label/c.stgUnknownError';

// Import custom labels for datatable
import bdiFMUIDatatableMapsTo from '@salesforce/label/c.bdiFMUIDatatableMapsTo';
import bdiFMUIDataType from '@salesforce/label/c.bdiFMUIDataType';
import bdiFMUIFieldAPIName from '@salesforce/label/c.bdiFMUIFieldAPIName';
import bdiFMUIFieldLabel from '@salesforce/label/c.bdiFMUIFieldLabel';
import bgeActionDelete from '@salesforce/label/c.bgeActionDelete';
import stgBtnEdit from '@salesforce/label/c.stgBtnEdit';

const actions = [
    { label: stgBtnEdit, name: 'edit' },
    { label: bgeActionDelete, name: 'delete' },
];

const columns = [
    { label: bdiFMUIFieldLabel, fieldName: 'Source_Field_Label', type: 'text', sortable: true },
    { label: bdiFMUIFieldAPIName, fieldName: 'Source_Field_API_Name', type: 'text', sortable: true},
    { label: bdiFMUIDataType, fieldName: 'Source_Field_Display_Type_Label', type: 'text', initialWidth: 125, sortable: true },
        {
            label: bdiFMUIDatatableMapsTo, fieldName: '', type: 'text', fixedWidth: 95,
            cellAttributes: { alignment: 'center', iconName: { fieldName: 'Maps_To_Icon' } }
        },
    { label: bdiFMUIFieldLabel, fieldName: 'Target_Field_Label', type: 'text', sortable: true },
    { label: bdiFMUIFieldAPIName, fieldName: 'Target_Field_API_Name', type: 'text', sortable: true },
    { label: bdiFMUIDataType, fieldName: 'Target_Field_Display_Type_Label', type: 'text', initialWidth: 125, sortable: true },
    { type: 'action', typeAttributes: { rowActions: actions } }
];

export default class bdiFieldMappings extends LightningElement {

    customLabels = {
        bdiFieldMappingsLabel,
        bdiFMUIBackToMapGroup,
        bdiFMUIDescription1,
        bdiFMUIDescription2,
        bdiFMUINewFieldMapping,
        bdiFMUINoFieldMappings,
        bdiFMUISourceObject,
        bdiFMUITarget,
        stgHelpAdvancedMapping3,
        stgLabelObject,
    }

    @api objectMapping;
    @api diFieldDescribes;
    @api mappedDiFieldDescribes;
    @api targetObjectFieldDescribes;

    @track displayFieldMappings = false;
    @track isLoading = true;
    @track columns = columns;
    @track sortedBy;
    @track sortDirection;
    @track fieldMappingSetName;
    @track fieldMappings;

    deploymentTimer;
    deploymentTimeout = 10000;

    _dataImportApiName;

    @api
    get noFieldMappings() {
        return !this.fieldMappings || this.fieldMappings.length === 0;
    }

    @api
    refresh() {
        if (this.displayFieldMappings) {
            this.init();
        }
    }

    handleNavButton() {
        fireEvent(this.pageRef, 'showobjectmappings');
    }

    connectedCallback() {
        registerListener('showobjectmappings', this.handleShowObjectMappings, this);
        registerListener('showfieldmappings', this.handleShowFieldMappings, this);
        registerListener('deploymentResponse', this.handleDeploymentResponse, this);
        registerListener('startDeploymentTimeout', this.handleDeploymentTimeout, this);
        registerListener('refresh', this.refresh, this);
    }

    disconnectedCallback() {
        unregisterAllListeners(this);
    }

    /*******************************************************************************
    * @description Group up various get data calls to apex
    */
    init = async() => {
        try {
            this.isLoading = true;

            this._dataImportApiName = await getDataImportObjectName();

            // Get all the data import field describes
            if (!this.diFieldDescribes) {
                this.diFieldDescribes =
                    await getObjectFieldDescribes({objectName: this._dataImportApiName});
            }

            // Get mapped data import field describes
            this.mappedDiFieldDescribes =
                await getMappedDISourceFields();

            // Get all the target object field describes based on the currently
            // selected object mapping
            let objectAPIName =
                this.objectMapping.Object_API_Name || this.objectMapping.npsp__Object_API_Name;
            this.targetObjectFieldDescribes =
                await getObjectFieldDescribes({objectName: objectAPIName});

            // Get the field mapping set name from the data import custom settings
            this.fieldMappingSetName =
                await getFieldMappingSetName();

            // Get all the field mappings for the currently selected object mapping
            this.fieldMappings =
                await getFieldMappingsByObjectAndFieldSetNames({
                    objectName: this.objectMapping.DeveloperName,
                    fieldMappingSetname: this.fieldMappingSetName
                });

            if (this.fieldMappings && this.fieldMappings.length > 0) {
                this.fieldMappings = this.sortData(
                    this.fieldMappings,
                    'Source_Field_Label',
                    'asc');
            }

            this.isLoading = false;

        } catch(error) {
            this.handleError(error);
        }
    }

    /*******************************************************************************
    * @description Handles the timeout toast of deployments whenever a deployment
    * that's registered with platformEventListener takes 10 seconds or longer to
    * send out a response.
    */
    handleDeploymentTimeout(event) {
        if (this.displayFieldMappings) {
            let that = this;
            this.deploymentTimer = setTimeout(function() {
                that.isLoading = false;
                fireEvent(this.pageRef, 'closeModal', {});

                let url =
                    '/lightning/setup/DeployStatus/page?' +
                    'address=%2Fchangemgmt%2FmonitorDeploymentsDetails.apexp%3FasyncId%3D' +
                    event.deploymentId +
                    '%26retURL%3D%252Fchangemgmt%252FmonitorDeployment.apexp';

                that.showToast(
                    bdiFMUILongDeployment,
                    bdiFMUILongDeploymentMessage + ' {0}',
                    'warning',
                    'sticky',
                    [{url, label: bdiFMUILongDeploymentLink}]);
            }, this.deploymentTimeout, that);
        }
    }

    /*******************************************************************************
    * @description Listens for an event from the platformEventListener component.
    * Upon receiving an event refreshes the field mappings records, closes the modal,
    * and creates a toast.
    *
    * @param {object} platformEvent: Object containing the platform event payload
    */
    handleDeploymentResponse(platformEvent) {
        if (this.displayFieldMappings) {
            clearTimeout(this.deploymentTimer);
            fireEvent(this.pageRef, 'refresh', {});
            fireEvent(this.pageRef, 'closeModal', {});

            const payload = platformEvent.response.data.payload;
            const status = payload.Status__c || payload.npsp__Status__c;
            // TODO: Update toasts when we're able to determine create, edit, delete from the platform event
            const successful = bdiFMUISuccessful.charAt(0).toUpperCase() + bdiFMUISuccessful.slice(1);
            const unsuccessful = bdiFMUIUnsuccessful.charAt(0).toUpperCase() + bdiFMUIUnsuccessful.slice(1);
            const successMessage = `${successful} ${bdiFieldMappingsLabel} ${bdiFMUIUpdate}.`;
            const failMessage = `${unsuccessful} ${bdiFieldMappingsLabel} ${bdiFMUIUpdate}. ${bdiFMUITryAgain}.`;
            const succeeded = status === 'Succeeded';

            this.showToast(
                `${succeeded ? successMessage : failMessage}`,
                '',
                succeeded ? 'success' : 'error');
        }
    }

    /*******************************************************************************
    * @description Hides the field mappings component and fires an event to the parent
    * object mappings component to show itself.
    */
    handleShowObjectMappings() {
        this.displayFieldMappings = false;
    }

    /*******************************************************************************
    * @description Shows the field mappings component and forces a refresh to get
    * fresh field mapping records for the currently selected object mapping.
    */
    handleShowFieldMappings(event) {
        this.objectMapping = event.objectMapping;
        this.displayFieldMappings = true;
        this.init();
    }

    /*******************************************************************************
    * @description Opens the field mapping modal passing in relevant details on
    * the currently selected object and child field mappings.
    */
    handleOpenModal() {
        fireEvent(this.pageRef, 'openModal', {
            objectMapping: this.objectMapping,
            row: undefined,
            fieldMappings: this.fieldMappings,
            diFieldDescribes: this.diFieldDescribes,
            mappedDiFieldDescribes: this.mappedDiFieldDescribes,
            targetObjectFieldDescribes: this.targetObjectFieldDescribes,
        });
    }

    /*******************************************************************************
    * @description Action handler for datatable row actions (i.e. edit, delete)
    *
    * @param {object} event: Event containing row details of the action
    */
    handleRowAction(event) {
        const actionName = event.detail.action.name;
        const row = event.detail.row;

        switch (actionName) {

            case 'delete':
                this.isLoading = true;
                row.Is_Deleted = true;

                createDataImportFieldMapping({fieldMappingString: JSON.stringify(row)})
                    .then((deploymentId) => {
                        this.handleDeleteDeploymentId(deploymentId);
                    })
                    .catch((error) => {
                        this.handleError(error);
                    });
                break;

            case 'edit':
                fireEvent(this.pageRef, 'openModal', {
                    objectMapping: this.objectMapping,
                    row: row,
                    fieldMappings: this.fieldMappings,
                    diFieldDescribes: this.diFieldDescribes,
                    mappedDiFieldDescribes: this.mappedDiFieldDescribes,
                    targetObjectFieldDescribes: this.targetObjectFieldDescribes,
                });
                break;

            default:
        }
    }

    /*******************************************************************************
    * @description Creates and dispatches a CustomEvent 'deployment' for deletion
    * letting the platformEventListener know that we have an id to register and monitor.
    * After dispatching the CustomEvent, start the deployment timeout.
    *
    * @param {string} deploymentId: Custom Metadata Deployment Id
    */
    handleDeleteDeploymentId(deploymentId) {
        if (this.displayFieldMappings) {
            const deploymentEvent = new CustomEvent('deployment', {
                bubbles: true,
                composed: true,
                detail: {deploymentId}
            });
            this.dispatchEvent(deploymentEvent);

            this.handleDeploymentTimeout({ deploymentId: deploymentId });
        }
    }

    /*******************************************************************************
    * @description Handles the onsort event from the lightning:datatable
    *
    * @param {object} event: Event holding column details of the action
    */
    handleColumnSorting(event) {
        this.sortedBy = event.detail.fieldName;
        this.sortedDirection = event.detail.sortDirection;
        this.fieldMappings = this.sortData(this.fieldMappings, this.sortedBy, this.sortedDirection);
    }

    /*******************************************************************************
    * @description Sorts the given list by field name and direction
    *
    * @param {array} list: List to be sorted
    * @param {string} fieldName: Property to sort by
    * @param {string} sortDirection: Direction to sort by (i.e. 'asc' or 'desc')
    */
    sortData(list, fieldName, sortDirection) {
        const data = JSON.parse(JSON.stringify(list));
        const key =(a) => a[fieldName];
        const reverse = sortDirection === 'asc' ? 1 : -1;

        data.sort((a,b) => {
            let valueA = key(a) ? key(a).toLowerCase() : '';
            let valueB = key(b) ? key(b).toLowerCase() : '';
            return reverse * ((valueA > valueB) - (valueB > valueA));
        });

        return data;
    }

    /*******************************************************************************
    * @description Creates and dispatches a ShowToastEvent
    *
    * @param {string} title: Title of the toast, dispalyed as a heading.
    * @param {string} message: Message of the toast. It can contain placeholders in
    * the form of {0} ... {N}. The placeholders are replaced with the links from
    * messageData param
    * @param {string} mode: Mode of the toast
    * @param {array} messageData: List of values that replace the {index} placeholders
    * in the message param
    */
    showToast(title, message, variant, mode, messageData) {
        const event = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
            mode: mode,
            messageData: messageData
        });
        this.dispatchEvent(event);
    }

    /*******************************************************************************
    * @description Creates and dispatches an error toast
    *
    * @param {object} error: Event holding error details
    */
    handleError(error) {
        if (error && error.status && error.body) {
            this.showToast(`${error.status} ${error.statusText}`, error.body.message, 'error', 'sticky');
        } else if (error && error.name && error.message) {
            this.showToast(`${error.name}`, error.message, 'error', 'sticky');
        } else {
            this.showToast(stgUnknownError, '', 'error', 'sticky');
        }
    }
}<commit_msg>add back in check for component event cross-contamination<commit_after>import { LightningElement, track, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'
import { registerListener, unregisterAllListeners, fireEvent} from 'c/pubsubNoPageRef';
import getFieldMappingSetName
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getFieldMappingSetName';
import getFieldMappingsByObjectAndFieldSetNames
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getFieldMappingsByObjectAndFieldSetNames';
import createDataImportFieldMapping
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.createDataImportFieldMapping';
import getObjectFieldDescribes
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getObjectFieldDescribes';
import getDataImportObjectName
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getDataImportObjectName';
import getMappedDISourceFields
    from '@salesforce/apex/BDI_ManageAdvancedMappingCtrl.getMappedDISourceFields';

// Import custom labels
import bdiFieldMappingsLabel from '@salesforce/label/c.bdiFieldMappings';
import bdiFMUIBackToMapGroup from '@salesforce/label/c.bdiFMUIBackToMapGroup';
import bdiFMUIDescription1 from '@salesforce/label/c.bdiFMUIDescription1';
import bdiFMUIDescription2 from '@salesforce/label/c.bdiFMUIDescription2';
import bdiFMUILongDeployment from '@salesforce/label/c.bdiFMUILongDeployment';
import bdiFMUILongDeploymentLink from '@salesforce/label/c.bdiFMUILongDeploymentLink';
import bdiFMUILongDeploymentMessage from '@salesforce/label/c.bdiFMUILongDeploymentMessage';
import bdiFMUINewFieldMapping from '@salesforce/label/c.bdiFMUINewFieldMapping';
import bdiFMUINoFieldMappings from '@salesforce/label/c.bdiFMUINoFieldMappings';
import bdiFMUISourceObject from '@salesforce/label/c.bdiFMUISourceObject';
import bdiFMUISuccessful from '@salesforce/label/c.bdiFMUISuccessful';
import bdiFMUITarget from '@salesforce/label/c.bdiFMUITarget';
import bdiFMUITryAgain from '@salesforce/label/c.bdiFMUITryAgain';
import bdiFMUIUnsuccessful from '@salesforce/label/c.bdiFMUIUnsuccessful';
import bdiFMUIUpdate from '@salesforce/label/c.bdiFMUIUpdate';
import stgHelpAdvancedMapping3 from '@salesforce/label/c.stgHelpAdvancedMapping3';
import stgLabelObject from '@salesforce/label/c.stgLabelObject';
import stgUnknownError from '@salesforce/label/c.stgUnknownError';

// Import custom labels for datatable
import bdiFMUIDatatableMapsTo from '@salesforce/label/c.bdiFMUIDatatableMapsTo';
import bdiFMUIDataType from '@salesforce/label/c.bdiFMUIDataType';
import bdiFMUIFieldAPIName from '@salesforce/label/c.bdiFMUIFieldAPIName';
import bdiFMUIFieldLabel from '@salesforce/label/c.bdiFMUIFieldLabel';
import bgeActionDelete from '@salesforce/label/c.bgeActionDelete';
import stgBtnEdit from '@salesforce/label/c.stgBtnEdit';

const actions = [
    { label: stgBtnEdit, name: 'edit' },
    { label: bgeActionDelete, name: 'delete' },
];

const columns = [
    { label: bdiFMUIFieldLabel, fieldName: 'Source_Field_Label', type: 'text', sortable: true },
    { label: bdiFMUIFieldAPIName, fieldName: 'Source_Field_API_Name', type: 'text', sortable: true},
    { label: bdiFMUIDataType, fieldName: 'Source_Field_Display_Type_Label', type: 'text', initialWidth: 125, sortable: true },
        {
            label: bdiFMUIDatatableMapsTo, fieldName: '', type: 'text', fixedWidth: 95,
            cellAttributes: { alignment: 'center', iconName: { fieldName: 'Maps_To_Icon' } }
        },
    { label: bdiFMUIFieldLabel, fieldName: 'Target_Field_Label', type: 'text', sortable: true },
    { label: bdiFMUIFieldAPIName, fieldName: 'Target_Field_API_Name', type: 'text', sortable: true },
    { label: bdiFMUIDataType, fieldName: 'Target_Field_Display_Type_Label', type: 'text', initialWidth: 125, sortable: true },
    { type: 'action', typeAttributes: { rowActions: actions } }
];

export default class bdiFieldMappings extends LightningElement {

    customLabels = {
        bdiFieldMappingsLabel,
        bdiFMUIBackToMapGroup,
        bdiFMUIDescription1,
        bdiFMUIDescription2,
        bdiFMUINewFieldMapping,
        bdiFMUINoFieldMappings,
        bdiFMUISourceObject,
        bdiFMUITarget,
        stgHelpAdvancedMapping3,
        stgLabelObject,
    }

    @api objectMapping;
    @api diFieldDescribes;
    @api mappedDiFieldDescribes;
    @api targetObjectFieldDescribes;

    @track displayFieldMappings = false;
    @track isLoading = true;
    @track columns = columns;
    @track sortedBy;
    @track sortDirection;
    @track fieldMappingSetName;
    @track fieldMappings;

    deploymentTimer;
    deploymentTimeout = 10000;

    _dataImportApiName;

    @api
    get noFieldMappings() {
        return !this.fieldMappings || this.fieldMappings.length === 0;
    }

    @api
    refresh() {
        if (this.displayFieldMappings) {
            this.init();
        }
    }

    handleNavButton() {
        fireEvent(this.pageRef, 'showobjectmappings');
    }

    connectedCallback() {
        registerListener('showobjectmappings', this.handleShowObjectMappings, this);
        registerListener('showfieldmappings', this.handleShowFieldMappings, this);
        registerListener('deploymentResponse', this.handleDeploymentResponse, this);
        registerListener('startDeploymentTimeout', this.handleDeploymentTimeout, this);
        registerListener('refresh', this.refresh, this);
    }

    disconnectedCallback() {
        unregisterAllListeners(this);
    }

    /*******************************************************************************
    * @description Group up various get data calls to apex
    */
    init = async() => {
        try {
            this.isLoading = true;

            this._dataImportApiName = await getDataImportObjectName();

            // Get all the data import field describes
            if (!this.diFieldDescribes) {
                this.diFieldDescribes =
                    await getObjectFieldDescribes({objectName: this._dataImportApiName});
            }

            // Get mapped data import field describes
            this.mappedDiFieldDescribes =
                await getMappedDISourceFields();

            // Get all the target object field describes based on the currently
            // selected object mapping
            let objectAPIName =
                this.objectMapping.Object_API_Name || this.objectMapping.npsp__Object_API_Name;
            this.targetObjectFieldDescribes =
                await getObjectFieldDescribes({objectName: objectAPIName});

            // Get the field mapping set name from the data import custom settings
            this.fieldMappingSetName =
                await getFieldMappingSetName();

            // Get all the field mappings for the currently selected object mapping
            this.fieldMappings =
                await getFieldMappingsByObjectAndFieldSetNames({
                    objectName: this.objectMapping.DeveloperName,
                    fieldMappingSetname: this.fieldMappingSetName
                });

            if (this.fieldMappings && this.fieldMappings.length > 0) {
                this.fieldMappings = this.sortData(
                    this.fieldMappings,
                    'Source_Field_Label',
                    'asc');
            }

            this.isLoading = false;

        } catch(error) {
            this.handleError(error);
        }
    }

    /*******************************************************************************
    * @description Handles the timeout toast of deployments whenever a deployment
    * that's registered with platformEventListener takes 10 seconds or longer to
    * send out a response.
    */
    handleDeploymentTimeout(event) {
        if (this.displayFieldMappings) {
            let that = this;
            this.deploymentTimer = setTimeout(function() {
                that.isLoading = false;
                fireEvent(this.pageRef, 'closeModal', {});

                let url =
                    '/lightning/setup/DeployStatus/page?' +
                    'address=%2Fchangemgmt%2FmonitorDeploymentsDetails.apexp%3FasyncId%3D' +
                    event.deploymentId +
                    '%26retURL%3D%252Fchangemgmt%252FmonitorDeployment.apexp';

                that.showToast(
                    bdiFMUILongDeployment,
                    bdiFMUILongDeploymentMessage + ' {0}',
                    'warning',
                    'sticky',
                    [{url, label: bdiFMUILongDeploymentLink}]);
            }, this.deploymentTimeout, that);
        }
    }

    /*******************************************************************************
    * @description Listens for an event from the platformEventListener component.
    * Upon receiving an event refreshes the field mappings records, closes the modal,
    * and creates a toast.
    *
    * @param {object} platformEvent: Object containing the platform event payload
    */
    handleDeploymentResponse(platformEvent) {
        if (this.displayFieldMappings) {
            clearTimeout(this.deploymentTimer);
            fireEvent(this.pageRef, 'refresh', {});
            fireEvent(this.pageRef, 'closeModal', {});

            const payload = platformEvent.response.data.payload;
            const status = payload.Status__c || payload.npsp__Status__c;
            // TODO: Update toasts when we're able to determine create, edit, delete from the platform event
            const successful = bdiFMUISuccessful.charAt(0).toUpperCase() + bdiFMUISuccessful.slice(1);
            const unsuccessful = bdiFMUIUnsuccessful.charAt(0).toUpperCase() + bdiFMUIUnsuccessful.slice(1);
            const successMessage = `${successful} ${bdiFieldMappingsLabel} ${bdiFMUIUpdate}.`;
            const failMessage = `${unsuccessful} ${bdiFieldMappingsLabel} ${bdiFMUIUpdate}. ${bdiFMUITryAgain}.`;
            const succeeded = status === 'Succeeded';

            this.showToast(
                `${succeeded ? successMessage : failMessage}`,
                '',
                succeeded ? 'success' : 'error');
        }
    }

    /*******************************************************************************
    * @description Hides the field mappings component and fires an event to the parent
    * object mappings component to show itself.
    */
    handleShowObjectMappings() {
        this.displayFieldMappings = false;
    }

    /*******************************************************************************
    * @description Shows the field mappings component and forces a refresh to get
    * fresh field mapping records for the currently selected object mapping.
    */
    handleShowFieldMappings(event) {
        this.objectMapping = event.objectMapping;
        this.displayFieldMappings = true;
        this.init();
    }

    /*******************************************************************************
    * @description Opens the field mapping modal passing in relevant details on
    * the currently selected object and child field mappings.
    */
    handleOpenModal() {
        if (this.displayFieldMappings) {
            fireEvent(this.pageRef, 'openModal', {
                objectMapping: this.objectMapping,
                row: undefined,
                fieldMappings: this.fieldMappings,
                diFieldDescribes: this.diFieldDescribes,
                mappedDiFieldDescribes: this.mappedDiFieldDescribes,
                targetObjectFieldDescribes: this.targetObjectFieldDescribes,
            });
        }
    }

    /*******************************************************************************
    * @description Action handler for datatable row actions (i.e. edit, delete)
    *
    * @param {object} event: Event containing row details of the action
    */
    handleRowAction(event) {
        const actionName = event.detail.action.name;
        const row = event.detail.row;

        switch (actionName) {

            case 'delete':
                this.isLoading = true;
                row.Is_Deleted = true;

                createDataImportFieldMapping({fieldMappingString: JSON.stringify(row)})
                    .then((deploymentId) => {
                        this.handleDeleteDeploymentId(deploymentId);
                    })
                    .catch((error) => {
                        this.handleError(error);
                    });
                break;

            case 'edit':
                fireEvent(this.pageRef, 'openModal', {
                    objectMapping: this.objectMapping,
                    row: row,
                    fieldMappings: this.fieldMappings,
                    diFieldDescribes: this.diFieldDescribes,
                    mappedDiFieldDescribes: this.mappedDiFieldDescribes,
                    targetObjectFieldDescribes: this.targetObjectFieldDescribes,
                });
                break;

            default:
        }
    }

    /*******************************************************************************
    * @description Creates and dispatches a CustomEvent 'deployment' for deletion
    * letting the platformEventListener know that we have an id to register and monitor.
    * After dispatching the CustomEvent, start the deployment timeout.
    *
    * @param {string} deploymentId: Custom Metadata Deployment Id
    */
    handleDeleteDeploymentId(deploymentId) {
        if (this.displayFieldMappings) {
            const deploymentEvent = new CustomEvent('deployment', {
                bubbles: true,
                composed: true,
                detail: {deploymentId}
            });
            this.dispatchEvent(deploymentEvent);

            this.handleDeploymentTimeout({ deploymentId: deploymentId });
        }
    }

    /*******************************************************************************
    * @description Handles the onsort event from the lightning:datatable
    *
    * @param {object} event: Event holding column details of the action
    */
    handleColumnSorting(event) {
        this.sortedBy = event.detail.fieldName;
        this.sortedDirection = event.detail.sortDirection;
        this.fieldMappings = this.sortData(this.fieldMappings, this.sortedBy, this.sortedDirection);
    }

    /*******************************************************************************
    * @description Sorts the given list by field name and direction
    *
    * @param {array} list: List to be sorted
    * @param {string} fieldName: Property to sort by
    * @param {string} sortDirection: Direction to sort by (i.e. 'asc' or 'desc')
    */
    sortData(list, fieldName, sortDirection) {
        const data = JSON.parse(JSON.stringify(list));
        const key =(a) => a[fieldName];
        const reverse = sortDirection === 'asc' ? 1 : -1;

        data.sort((a,b) => {
            let valueA = key(a) ? key(a).toLowerCase() : '';
            let valueB = key(b) ? key(b).toLowerCase() : '';
            return reverse * ((valueA > valueB) - (valueB > valueA));
        });

        return data;
    }

    /*******************************************************************************
    * @description Creates and dispatches a ShowToastEvent
    *
    * @param {string} title: Title of the toast, dispalyed as a heading.
    * @param {string} message: Message of the toast. It can contain placeholders in
    * the form of {0} ... {N}. The placeholders are replaced with the links from
    * messageData param
    * @param {string} mode: Mode of the toast
    * @param {array} messageData: List of values that replace the {index} placeholders
    * in the message param
    */
    showToast(title, message, variant, mode, messageData) {
        const event = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
            mode: mode,
            messageData: messageData
        });
        this.dispatchEvent(event);
    }

    /*******************************************************************************
    * @description Creates and dispatches an error toast
    *
    * @param {object} error: Event holding error details
    */
    handleError(error) {
        if (error && error.status && error.body) {
            this.showToast(`${error.status} ${error.statusText}`, error.body.message, 'error', 'sticky');
        } else if (error && error.name && error.message) {
            this.showToast(`${error.name}`, error.message, 'error', 'sticky');
        } else {
            this.showToast(stgUnknownError, '', 'error', 'sticky');
        }
    }
}<|endoftext|>


<commit_before>// @flow
import parseToHsl from './parseToHsl'
import toColorString from './toColorString'
import curry from '../internalHelpers/_curry'

/**
 * Changes the hue of the color. Hue is a number between 0 to 360. The first
 * argument for adjustHue is the amount of degrees the color is rotated along
 * the color wheel.
 *
 * @example
 * // Styles as object usage
 * const styles = {
 *   background: adjustHue(180, '#448'),
 *   background: adjustHue('180', 'rgba(101,100,205,0.7)'),
 * }
 *
 * // styled-components usage
 * const div = styled.div`
 *   background: ${adjustHue(180, '#448')};
 *   background: ${adjustHue('180', 'rgba(101,100,205,0.7)')};
 * `
 *
 * // CSS in JS Output
 * element {
 *   background: "#888844";
 *   background: "rgba(136,136,68,0.7)";
 * }
 */
function adjustHue(degree: number | string, color: string): string {
  if (color === 'transparent') return color
  const hslColor = parseToHsl(color)
  return toColorString({
    ...hslColor,
    hue: (hslColor.hue + parseFloat(degree)) % 360,
  })
}

// prettier-ignore
const curriedAdjustHue = curry/* ::<number | string, string, string> */(adjustHue)
export default curriedAdjustHue
<commit_msg>refactor(adjustHue): remove redundant %<commit_after>// @flow
import parseToHsl from './parseToHsl'
import toColorString from './toColorString'
import curry from '../internalHelpers/_curry'

/**
 * Changes the hue of the color. Hue is a number between 0 to 360. The first
 * argument for adjustHue is the amount of degrees the color is rotated along
 * the color wheel.
 *
 * @example
 * // Styles as object usage
 * const styles = {
 *   background: adjustHue(180, '#448'),
 *   background: adjustHue('180', 'rgba(101,100,205,0.7)'),
 * }
 *
 * // styled-components usage
 * const div = styled.div`
 *   background: ${adjustHue(180, '#448')};
 *   background: ${adjustHue('180', 'rgba(101,100,205,0.7)')};
 * `
 *
 * // CSS in JS Output
 * element {
 *   background: "#888844";
 *   background: "rgba(136,136,68,0.7)";
 * }
 */
function adjustHue(degree: number | string, color: string): string {
  if (color === 'transparent') return color
  const hslColor = parseToHsl(color)
  return toColorString({
    ...hslColor,
    hue: hslColor.hue + parseFloat(degree),
  })
}

// prettier-ignore
const curriedAdjustHue = curry/* ::<number | string, string, string> */(adjustHue)
export default curriedAdjustHue
<|endoftext|>


<commit_before>Ext.define('devilry.statistics.activeperiods.Overview', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.activeperiods-overview',
    frame: false,
    border: false,
    autoScroll: true,
    //cls: 'selectable-grid',

    requires: [
        'devilry.extjshelpers.DateTime',
        'devilry.statistics.activeperiods.AggregatedPeriodModel',
        'devilry.extjshelpers.RestProxy',
        'devilry.extjshelpers.SearchField',
        'devilry.extjshelpers.GridSelectionModel'
    ],

    /**
     * @cfg
     */
    nodeRecord: undefined,

    linkTpl: Ext.create('Ext.XTemplate',
        '',
        '{data.subject_long_name} ({data.period_long_name})',
        ''
    ),

    readyForExportTpl: Ext.create('Ext.XTemplate',
        '<tpl if="qualifies_for_exam_ready_for_export">yes</tpl>',
        '<tpl if="!qualifies_for_exam_ready_for_export">no</tpl>'
    ),

    emailLinkTpl: Ext.create('Ext.XTemplate',
        'mailto:{from}?',
        'bcc={emailAddresses}'
    ),

    initComponent: function() {
        this._createStore();
        this.selModel = Ext.create('Ext.selection.CheckboxModel', {
            checkOnly: true
        });

        Ext.apply(this, {
            tbar: [
                //xtype: 'searchfield',
                //searchdelay: 30,
                //emptyText: 'Search...',
                //width: 200,
                //listeners: {
                    //scope: this,
                    //newSearchValue: this._onNewSearchValue,
                    //emptyInput: this._onEmptySearchValue
                //}
            {
                xtype: 'button',
                iconCls: 'icon-email-16',
                text: 'Send email to admin(s) on selected',
                listeners: {
                    scope: this,
                    click: this._sendEmailsToSelected
                }
            }, '->', {
                xtype: 'combobox',
                width: 350,
                valueField: 'filterfunc',
                displayField: 'label',
                forceSelection: true,
                editable: false,
                emptyText: 'Show all',
                store: Ext.create('Ext.data.Store', {
                    fields: ['filterfunc', 'label'],
                    data: [{
                        filterfunc: this._clearFilters,
                        label: 'Show all'
                    }, {
                        filterfunc: this._filterQualifiesForExamYes,
                        label: 'Show qualifies-for-exam ready for export'
                    }, {
                        filterfunc: this._filterQualifiesForExamNo,
                        label: 'Show qualifies-for-exam NOT ready for export'
                    }],
                    proxy: 'memory'
                }),
                listeners: {
                    scope: this,
                    select: function(combo, records) {
                        var record = records[0];
                        var filterfunc = record.get('filterfunc');
                        Ext.bind(filterfunc, this)();
                    }
                }
            }],
            columns: [{
                text: 'Subject',
                dataIndex: 'subject_long_name',
                flex: 30,
                renderer: function(v, m, record) {
                    return this.linkTpl.apply({
                        data: record.data,
                        DevilrySettings: DevilrySettings
                    });
                }
            },{
                text: '«Qualifies for exam» ready for export?',
                dataIndex: 'qualifies_for_exam_ready_for_export',
                width: 230,
                renderer: function(value, m, record) {
                    return this.readyForExportTpl.apply(record.data);
                }
            }],
            listeners: {
                scope: this,
                //itemmouseup: function(view, record) {
                    //var url = Ext.String.format('{0}/administrator/period/{1}?open_students=yes&students_hidesidebar=yes', DevilrySettings.DEVILRY_URLPATH_PREFIX, record.get('period_id'));
                    //window.open(url, '_blank');
                //},
                render: function() {
                    Ext.defer(function() {
                        this._createAndLoadPeriodStore();
                    }, 100, this);
                }
            }
        });
        this.callParent(arguments);
    },

    _filterQualifiesForExamYes: function() {
        this.store.clearFilter();
        this.store.filter('qualifies_for_exam_ready_for_export', true);
    },
    _filterQualifiesForExamNo: function() {
        this.store.clearFilter();
        this.store.filter('qualifies_for_exam_ready_for_export', false);
    },
    _clearFilters: function() {
        this.store.clearFilter();
    },

    //_onNewSearchValue: function(value) {
        //console.log(value);
    //},

    //_onEmptySearchValue: function() {
    //},

    _createStore: function() {
        this.store = Ext.create('Ext.data.Store', {
            model: 'devilry.statistics.activeperiods.AggregatedPeriodModel',
            autoSync: false,
            proxy: 'memory'
        });
    },

    _createAndLoadPeriodStore: function() {
        this.getEl().mask('Loading overview');
        this.periodstore = Ext.create('Ext.data.Store', {
            model: 'devilry.apps.administrator.simplified.SimplifiedPeriod',
            remoteFilter: true,
            remoteSort: true,
            autoSync: true
        });

        this.periodstore.proxy.setDevilryFilters([{
            field: 'parentnode__parentnode',
            comp: 'exact',
            value: this.nodeRecord.get('id')
        }, {
            field: 'start_time',
            comp: '<',
            value: devilry.extjshelpers.DateTime.restfulNow()
        }, {
            field: 'end_time',
            comp: '>',
            value: devilry.extjshelpers.DateTime.restfulNow()
        }]);
        this.periodstore.proxy.setDevilryOrderby(['-publishing_time']);
        this.periodstore.pageSize = 100000;
        this.periodstore.load({
            scope: this,
            callback: this._onPeriodStoreLoad
        });
    },

    _onPeriodStoreLoad: function(records, op) {
        if(!op.success) {
            this._handleLoadError(op, 'Failed to load active periods.');
        } else {
            this._createAndLoadPeriodAppKeyValueStore();
        }
    },

    _createAndLoadPeriodAppKeyValueStore: function() {
        this.periodappkeyvalue_store = Ext.create('Ext.data.Store', {
            model: 'devilry.apps.administrator.simplified.SimplifiedPeriodApplicationKeyValue',
            remoteFilter: true,
            remoteSort: true,
            autoSync: true
        });

        this.periodappkeyvalue_store.proxy.setDevilryFilters([{
            field: 'period__parentnode__parentnode',
            comp: 'exact',
            value: this.nodeRecord.get('id')
        }, {
            field: 'period__start_time',
            comp: '<',
            value: devilry.extjshelpers.DateTime.restfulNow()
        }, {
            field: 'period__end_time',
            comp: '>',
            value: devilry.extjshelpers.DateTime.restfulNow()
        }, {
            field: 'application',
            comp: 'exact',
            value: 'statistics-qualifiesforexam'
        }, {
            field: 'key',
            comp: 'exact',
            value: 'ready-for-export'
        }]);
        this.periodappkeyvalue_store.pageSize = 100000;
        this.periodappkeyvalue_store.load({
            scope: this,
            callback: this._onPeriodAppKeyValueStoreLoad
        });
    },

    _onPeriodAppKeyValueStoreLoad: function(records, op) {
        if(!op.success) {
            this._handleLoadError(op, 'Failed to load ready-for-export status on active periods.');
        } else {
            this._createAndLoadSubjectStore();
        }
    },

    _createAndLoadSubjectStore: function() {
        this.subjectstore = Ext.create('Ext.data.Store', {
            model: 'devilry.apps.administrator.simplified.SimplifiedSubject',
            remoteFilter: true,
            remoteSort: true,
            autoSync: true
        });

        this.subjectstore.proxy.setDevilryFilters([{
            field: 'parentnode',
            comp: 'exact',
            value: this.nodeRecord.get('id')
        }]);
        this.subjectstore.pageSize = 100000;
        this.subjectstore.load({
            scope: this,
            callback: this._onSubjectStoreLoad
        });
    },

    _onSubjectStoreLoad: function(records, op) {
        if(!op.success) {
            this._handleLoadError(op, 'Failed to load subjects.');
        } else {
            this._populateStore();
        }
    },

    _handleLoadError: function(op, title) {
        this.getEl().unmask();
        devilry.extjshelpers.RestProxy.showErrorMessagePopup(op, title);
    },

    _populateStore: function() {
        Ext.each(this.periodstore.data.items, function(periodRecord, index) {
            this.store.add({
                'period_id': periodRecord.get('id'),
                'subject_long_name': periodRecord.get('parentnode__long_name'),
                'period_long_name': periodRecord.get('long_name'),
                'qualifies_for_exam_ready_for_export': false
            });
        }, this);

        Ext.each(this.periodappkeyvalue_store.data.items, function(appKeyValueRecord, index) {
            var period_id = appKeyValueRecord.get('period');
            var periodRecord = this.periodstore.getById(period_id);
            var storeRecord = this.store.getById(period_id);
            storeRecord.set('qualifies_for_exam_ready_for_export', true);
            storeRecord.commit(); // Removed red corner
        }, this);
        this.getEl().unmask();

        this.store.sort('subject_long_name', 'ASC');
    },

    //_sendEmailsToVisible: function() {
        //var emailAddresses = this._getAdminEmailAddressesFromRecords(this.store.data.items).join(',');
        //window.location = this.emailLinkTpl.apply({
            //emailAddresses: emailAddresses
        //});
    //},

    _sendEmailsToSelected: function() {
        var selected = this.getSelectionModel().getSelection();
        if(selected.length === 0) {
            Ext.MessageBox.alert('Error', 'Please select at least one row.');
            return;
        }
        var emailAddresses = this._getAdminEmailAddressesFromRecords(selected).join(',');
        window.location = this.emailLinkTpl.apply({
            emailAddresses: emailAddresses
        });
    },

    _getAdminEmailAddressesFromRecords: function(records) {
        var emails = [];
        Ext.each(records, function(record, index) {
            var periodRecord = this.periodstore.getById(record.get('period_id'));
            var subjectid = periodRecord.get('parentnode');
            var subjectRecord = this.subjectstore.getById(subjectid);
            emails = Ext.Array.merge(emails, periodRecord.get('admins__email'));
            emails = Ext.Array.merge(emails, subjectRecord.get('admins__email'));
        }, this);
        return emails;
    }
});
<commit_msg>apps.statistics...activeperiods.Overview: Tooltip for email sending.<commit_after>Ext.define('devilry.statistics.activeperiods.Overview', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.activeperiods-overview',
    frame: false,
    border: false,
    autoScroll: true,
    //cls: 'selectable-grid',

    requires: [
        'devilry.extjshelpers.DateTime',
        'devilry.statistics.activeperiods.AggregatedPeriodModel',
        'devilry.extjshelpers.RestProxy',
        'devilry.extjshelpers.SearchField',
        'devilry.extjshelpers.GridSelectionModel'
    ],

    /**
     * @cfg
     */
    nodeRecord: undefined,

    linkTpl: Ext.create('Ext.XTemplate',
        '',
        '{data.subject_long_name} ({data.period_long_name})',
        ''
    ),

    readyForExportTpl: Ext.create('Ext.XTemplate',
        '<tpl if="qualifies_for_exam_ready_for_export">yes</tpl>',
        '<tpl if="!qualifies_for_exam_ready_for_export">no</tpl>'
    ),

    emailLinkTpl: Ext.create('Ext.XTemplate',
        'mailto:{from}?',
        'bcc={emailAddresses}'
    ),

    emailTooltip: 'Opens your email application to send email to all admins on selected rows. Use the checkbox in the upper left corner to select all visible rows.' +
        (Ext.isIE8? '
WARNING: Your browser, Internet Explorer, can not handle email links containing many addresses. Use another browser if you encounter this problem.
': ''),

    initComponent: function() {
        this._createStore();
        this.selModel = Ext.create('Ext.selection.CheckboxModel', {
            checkOnly: true
        });

        Ext.apply(this, {
            tbar: [
                //xtype: 'searchfield',
                //searchdelay: 30,
                //emptyText: 'Search...',
                //width: 200,
                //listeners: {
                    //scope: this,
                    //newSearchValue: this._onNewSearchValue,
                    //emptyInput: this._onEmptySearchValue
                //}
            {
                xtype: 'button',
                iconCls: 'icon-email-16',
                text: 'Send email to admin(s) on selected',
                listeners: {
                    scope: this,
                    click: this._sendEmailsToSelected,
                    render: function(button) {
                        Ext.tip.QuickTipManager.register({
                            target: button.getEl(),
                            title: 'Click to send email to admins on selected',
                            text: this.emailTooltip,
                            width: 350,
                            dismissDelay: 30000 // Hide after 30 seconds hover
                        });
                    }
                }
            }, '->', {
                xtype: 'combobox',
                width: 350,
                valueField: 'filterfunc',
                displayField: 'label',
                forceSelection: true,
                editable: false,
                emptyText: 'Show all',
                store: Ext.create('Ext.data.Store', {
                    fields: ['filterfunc', 'label'],
                    data: [{
                        filterfunc: this._clearFilters,
                        label: 'Show all'
                    }, {
                        filterfunc: this._filterQualifiesForExamYes,
                        label: 'Show qualifies-for-exam ready for export'
                    }, {
                        filterfunc: this._filterQualifiesForExamNo,
                        label: 'Show qualifies-for-exam NOT ready for export'
                    }],
                    proxy: 'memory'
                }),
                listeners: {
                    scope: this,
                    select: function(combo, records) {
                        var record = records[0];
                        var filterfunc = record.get('filterfunc');
                        Ext.bind(filterfunc, this)();
                    }
                }
            }],
            columns: [{
                text: 'Subject',
                dataIndex: 'subject_long_name',
                flex: 30,
                renderer: function(v, m, record) {
                    return this.linkTpl.apply({
                        data: record.data,
                        DevilrySettings: DevilrySettings
                    });
                }
            },{
                text: '«Qualifies for exam» ready for export?',
                dataIndex: 'qualifies_for_exam_ready_for_export',
                width: 230,
                renderer: function(value, m, record) {
                    return this.readyForExportTpl.apply(record.data);
                }
            }],
            listeners: {
                scope: this,
                //itemmouseup: function(view, record) {
                    //var url = Ext.String.format('{0}/administrator/period/{1}?open_students=yes&students_hidesidebar=yes', DevilrySettings.DEVILRY_URLPATH_PREFIX, record.get('period_id'));
                    //window.open(url, '_blank');
                //},
                render: function() {
                    Ext.defer(function() {
                        this._createAndLoadPeriodStore();
                    }, 100, this);
                }
            }
        });
        this.callParent(arguments);
    },

    _filterQualifiesForExamYes: function() {
        this.store.clearFilter();
        this.store.filter('qualifies_for_exam_ready_for_export', true);
    },
    _filterQualifiesForExamNo: function() {
        this.store.clearFilter();
        this.store.filter('qualifies_for_exam_ready_for_export', false);
    },
    _clearFilters: function() {
        this.store.clearFilter();
    },

    //_onNewSearchValue: function(value) {
        //console.log(value);
    //},

    //_onEmptySearchValue: function() {
    //},

    _createStore: function() {
        this.store = Ext.create('Ext.data.Store', {
            model: 'devilry.statistics.activeperiods.AggregatedPeriodModel',
            autoSync: false,
            proxy: 'memory'
        });
    },

    _createAndLoadPeriodStore: function() {
        this.getEl().mask('Loading overview');
        this.periodstore = Ext.create('Ext.data.Store', {
            model: 'devilry.apps.administrator.simplified.SimplifiedPeriod',
            remoteFilter: true,
            remoteSort: true,
            autoSync: true
        });

        this.periodstore.proxy.setDevilryFilters([{
            field: 'parentnode__parentnode',
            comp: 'exact',
            value: this.nodeRecord.get('id')
        }, {
            field: 'start_time',
            comp: '<',
            value: devilry.extjshelpers.DateTime.restfulNow()
        }, {
            field: 'end_time',
            comp: '>',
            value: devilry.extjshelpers.DateTime.restfulNow()
        }]);
        this.periodstore.proxy.setDevilryOrderby(['-publishing_time']);
        this.periodstore.pageSize = 100000;
        this.periodstore.load({
            scope: this,
            callback: this._onPeriodStoreLoad
        });
    },

    _onPeriodStoreLoad: function(records, op) {
        if(!op.success) {
            this._handleLoadError(op, 'Failed to load active periods.');
        } else {
            this._createAndLoadPeriodAppKeyValueStore();
        }
    },

    _createAndLoadPeriodAppKeyValueStore: function() {
        this.periodappkeyvalue_store = Ext.create('Ext.data.Store', {
            model: 'devilry.apps.administrator.simplified.SimplifiedPeriodApplicationKeyValue',
            remoteFilter: true,
            remoteSort: true,
            autoSync: true
        });

        this.periodappkeyvalue_store.proxy.setDevilryFilters([{
            field: 'period__parentnode__parentnode',
            comp: 'exact',
            value: this.nodeRecord.get('id')
        }, {
            field: 'period__start_time',
            comp: '<',
            value: devilry.extjshelpers.DateTime.restfulNow()
        }, {
            field: 'period__end_time',
            comp: '>',
            value: devilry.extjshelpers.DateTime.restfulNow()
        }, {
            field: 'application',
            comp: 'exact',
            value: 'statistics-qualifiesforexam'
        }, {
            field: 'key',
            comp: 'exact',
            value: 'ready-for-export'
        }]);
        this.periodappkeyvalue_store.pageSize = 100000;
        this.periodappkeyvalue_store.load({
            scope: this,
            callback: this._onPeriodAppKeyValueStoreLoad
        });
    },

    _onPeriodAppKeyValueStoreLoad: function(records, op) {
        if(!op.success) {
            this._handleLoadError(op, 'Failed to load ready-for-export status on active periods.');
        } else {
            this._createAndLoadSubjectStore();
        }
    },

    _createAndLoadSubjectStore: function() {
        this.subjectstore = Ext.create('Ext.data.Store', {
            model: 'devilry.apps.administrator.simplified.SimplifiedSubject',
            remoteFilter: true,
            remoteSort: true,
            autoSync: true
        });

        this.subjectstore.proxy.setDevilryFilters([{
            field: 'parentnode',
            comp: 'exact',
            value: this.nodeRecord.get('id')
        }]);
        this.subjectstore.pageSize = 100000;
        this.subjectstore.load({
            scope: this,
            callback: this._onSubjectStoreLoad
        });
    },

    _onSubjectStoreLoad: function(records, op) {
        if(!op.success) {
            this._handleLoadError(op, 'Failed to load subjects.');
        } else {
            this._populateStore();
        }
    },

    _handleLoadError: function(op, title) {
        this.getEl().unmask();
        devilry.extjshelpers.RestProxy.showErrorMessagePopup(op, title);
    },

    _populateStore: function() {
        Ext.each(this.periodstore.data.items, function(periodRecord, index) {
            this.store.add({
                'period_id': periodRecord.get('id'),
                'subject_long_name': periodRecord.get('parentnode__long_name'),
                'period_long_name': periodRecord.get('long_name'),
                'qualifies_for_exam_ready_for_export': false
            });
        }, this);

        Ext.each(this.periodappkeyvalue_store.data.items, function(appKeyValueRecord, index) {
            var period_id = appKeyValueRecord.get('period');
            var periodRecord = this.periodstore.getById(period_id);
            var storeRecord = this.store.getById(period_id);
            storeRecord.set('qualifies_for_exam_ready_for_export', true);
            storeRecord.commit(); // Removed red corner
        }, this);
        this.getEl().unmask();

        this.store.sort('subject_long_name', 'ASC');
    },

    //_sendEmailsToVisible: function() {
        //var emailAddresses = this._getAdminEmailAddressesFromRecords(this.store.data.items).join(',');
        //window.location = this.emailLinkTpl.apply({
            //emailAddresses: emailAddresses
        //});
    //},

    _sendEmailsToSelected: function() {
        var selected = this.getSelectionModel().getSelection();
        if(selected.length === 0) {
            Ext.MessageBox.alert('Error', 'Please select at least one row.');
            return;
        }
        var emailAddresses = this._getAdminEmailAddressesFromRecords(selected).join(',');
        window.location = this.emailLinkTpl.apply({
            emailAddresses: emailAddresses
        });
    },

    _getAdminEmailAddressesFromRecords: function(records) {
        var emails = [];
        Ext.each(records, function(record, index) {
            var periodRecord = this.periodstore.getById(record.get('period_id'));
            var subjectid = periodRecord.get('parentnode');
            var subjectRecord = this.subjectstore.getById(subjectid);
            emails = Ext.Array.merge(emails, periodRecord.get('admins__email'));
            emails = Ext.Array.merge(emails, subjectRecord.get('admins__email'));
        }, this);
        return emails;
    }
});
<|endoftext|>


<commit_before>/**
 * The function `reverse` computes the reversal
 * of a given `String` (don't copy-pasta!)
 *
 * @param {String} S to reverse
 * @return {String}
 */
function reverse(S){
    // YOUR CODE HERE: NO INTERNET COPY-PASTA!
}

console.assert(reverse("") === ""); // Really?

console.assert(reverse("A") === "A"); // Jerk.

console.assert(reverse("cat") === "tac");

console.assert(reverse("ward") === "draw");

console.assert(reverse("books") === "skoob"); // Rokey, dokey...

console.assert(
  reverse("we don't want no trouble") === "elbuort on tnaw t'nod ew"
);

/**
 * Function `encode` accepts a `String` and produces
 * the appropriate ROT13 "encoded" version, i.e. every
 * character in `phrase` is "rotated" ahead by 13 characters.
 *
 * @see String.prototype.charCodeAt
 * @see String.prototype.fromCharCode
 * @see http://en.wikipedia.org/wiki/ROT13
 *
 * // Start with just `phrase`...
 * @param {String} phrase to encode
 * // Add `N` in part 2!
 * // @param {Number} N rotation to apply, default 13
 * @return {String} encoded with ROT13
 */
function encode(phrase/*, N */){
    // YOUR CODE HERE... EMPHASIS ON **YOUR**
}

/**
 * Function `decode` accepts a `phrase` and `N` and
 * decoded it appropriately, i.e. every _word_ character
 * in `phrase` is rotated backward by `N` characters.
 *
 * @param {String} phrase to decode
 * @param {Number} N rotation to apply, default 13
 * @return {String} decoded by ROT-N
 */
function decode(phrase, N){
    // YOUR CODE HERE
}

// Produce more examples, please...
console.assert(encode("hello") === "uryyb");
console.assert(encode("uryyb") === "hello");

console.assert(encode("hello", 2) === "jgnnq")
console.assert(decode("jgnnq", 2) === "hello")
<commit_msg>Solved reverse function<commit_after>/**
 * The function `reverse` computes the reversal
 * of a given `String` (don't copy-pasta!)
 *
 * @param {String} S to reverse
 * @return {String}
 */
function reverse(S){
    // YOUR CODE HERE: NO INTERNET COPY-PASTA!
    var revResult = '';
    for (var i = S.length - 1; i >= 0; i--) {
      revResult += S[i];
    }
    return revResult;
}

console.assert(reverse("") === ""); // Really?

console.assert(reverse("A") === "A"); // Jerk.

console.assert(reverse("cat") === "tac");

console.assert(reverse("ward") === "draw");

console.assert(reverse("books") === "skoob"); // Rokey, dokey...

console.assert(
  reverse("we don't want no trouble") === "elbuort on tnaw t'nod ew"
);

/**
 * Function `encode` accepts a `String` and produces
 * the appropriate ROT13 "encoded" version, i.e. every
 * character in `phrase` is "rotated" ahead by 13 characters.
 *
 * @see String.prototype.charCodeAt
 * @see String.prototype.fromCharCode
 * @see http://en.wikipedia.org/wiki/ROT13
 *
 * // Start with just `phrase`...
 * @param {String} phrase to encode
 * // Add `N` in part 2!
 * // @param {Number} N rotation to apply, default 13
 * @return {String} encoded with ROT13
 */
function encode(phrase/*, N */){
    // YOUR CODE HERE... EMPHASIS ON **YOUR**
}

/**
 * Function `decode` accepts a `phrase` and `N` and
 * decoded it appropriately, i.e. every _word_ character
 * in `phrase` is rotated backward by `N` characters.
 *
 * @param {String} phrase to decode
 * @param {Number} N rotation to apply, default 13
 * @return {String} decoded by ROT-N
 */
function decode(phrase, N){
    // YOUR CODE HERE
}

// Produce more examples, please...
console.assert(encode("hello") === "uryyb");
console.assert(encode("uryyb") === "hello");

console.assert(encode("hello", 2) === "jgnnq")
console.assert(decode("jgnnq", 2) === "hello")
<|endoftext|>


<commit_before>var tmonth=new Array("JAN","FEB","MAR","APR","May","JUN","JUL","AUG","SEPT","OCT","NOV","DEC");
var posterList=new Array("transparent.png");
var curImage = 0;
var messageCombined = "Welcome to Residential College 4! &nbsp";

moment.locale('en', {
    relativeTime : {
        future: "in %s",
        s:  "Arr",
        m:  "1 min",
        mm: "%d mins"
    },
    invalidDate: "-"
});

function GetClock(){
	var d=new Date();
	var nmonth=d.getMonth(),ndate=d.getDate(),nyear=d.getYear();
	if(nyear<1000) nyear+=1900;

	var d=new Date();
	var nhour=d.getHours(),nmin=d.getMinutes(),nsec=d.getSeconds();
	if(nmin<=9) nmin="0"+nmin
	if(nsec<=9) nsec="0"+nsec;

	document.getElementById('clockbox').innerHTML=""+ndate+" "+tmonth[nmonth]+"
"+nhour+":"+nmin+":"+nsec+"";
}

function loadNextImage(){
	$('#curImage').fadeOut(500, loadNextImageCompleted);
}

function loadNextImageCompleted(){
	if(++curImage >= posterList.length)
		curImage = 0;

	$('#curImage')[0].src=posterList[curImage];
	$('#curImage').fadeIn(500);
}

function getBusTiming(){
  $.ajax({
    url: 'http://cors.io/?u=https://nextbus.comfortdelgro.com.sg/eventservice.svc/Shuttleservice?busstopname=UTOWN',
    dataType: "json",
    success: function (data) {
        isb = data.ShuttleServiceResult.shuttles;
        for(var i=0; i<isb.length; i++) {
	        if(isb[i].name=="D1 (To UTown)") {
	            if(isb[i].arrivalTime != "Arr" && isb[i].arrivalTime != "-")
	            	$('#rc4_d1').html(isb[i].arrivalTime + " mins");
	            else
	            	$('#rc4_d1').html(isb[i].arrivalTime);
	        }
	        else if (isb[i].name=="D2 (To UTown)") {
	        	if(isb[i].arrivalTime != "Arr" && isb[i].arrivalTime != "-")
	            	$('#rc4_d2').html(isb[i].arrivalTime + " mins");
	            else
	            	$('#rc4_d2').html(isb[i].arrivalTime);
	        }
	    }

    }
});

  $.ajax({
    url: 'http://arrivelah.herokuapp.com/?id=19059',
    dataType: "json",
    success: function (data) {
        //console.log(JSON.stringify(data));
        lta = data.services;
        for(var i=0; i<lta.length; i++)
        {
        	var res = "-";
        	if(lta[i]["next"]["duration_ms"] == null)
        		res = "-";
        	else
        	{
			res = Math.floor((lta[i]["next"]["duration_ms"]/ 1000) / 60);
			if(res < 1)
				res = "Arr";
			else
				res = res  + " mins";
        	}

        	if(lta[i].no == "33")
            		$('#lta_33').html(res);
            	else if(lta[i].no == "196")
            		$('#lta_196').html(res);
          //lta[i]["subsequent"]["time"] = moment(lta[i]["subsequent"]["time"]).fromNow(true);
        }
    }
});

    $.ajax({
    url: 'http://arrivelah.herokuapp.com/?id=19051',
    dataType: "json",
    success: function (data) {
        //console.log(JSON.stringify(data));
        lta = data.services;
        for(var i=0; i<lta.length; i++)
        {
        	var res = "-";
        	if(lta[i]["next"]["duration_ms"] == null)
        		res = "-";
        	else
        	{
			res = Math.floor((lta[i]["next"]["duration_ms"]/ 1000) / 60);
			if(res < 1)
				res = "Arr";
			else
				res = res  + " mins";
        	}

            if(lta[i].no == "33")
            	$('#lta_opp_33').html(res);
            else if(lta[i].no == "196")
            	$('#lta_opp_196').html(res);
        }

    }
});
}

function loadImageList(){
	$.ajax({
    url: 'https://spreadsheets.google.com/feeds/list/1vwEDjW19YoJZmRTDVsRCMGysjePw81ls8CSe90NeRCA/1/public/values?alt=json',
    dataType: "jsonp",
    success: function (data) {
    	posterList = new Array();

        for(var i=0; i<data.feed.entry.length; i++)
        {
        	//Todo: Fix issues with start/expiry checks
            if(data.feed.entry[i]['gsx$approved']['$t'] == "Y" /*&&
            	(data.feed.entry[i]['gsx$startdate']['$t'] == "" || moment(data.feed.entry[0]['gsx$startdate']['$t']).isAfter()) &&
            	(data.feed.entry[i]['gsx$enddate']['$t'] == "" || moment(data.feed.entry[0]['gsx$enddate']['$t']).isBefore())*/)
            	posterList[posterList.length]=data.feed.entry[i]['gsx$posterurl']['$t'];
        }
    }

    });
}

function loadMessageList(){
	$.ajax({
    url: 'https://spreadsheets.google.com/feeds/list/1dRpDWAt6YZbJgnhYKuAb4YL2QkEZlKjiGMwEKnjolbU/1/public/values?alt=json',
    dataType: "jsonp",
    success: function (data) {

        for(var i=0; i<data.feed.entry.length; i++)
        {
        	//Todo: Fix issues with start/expiry checks
            if(data.feed.entry[i]['gsx$approved']['$t'] == "Y" /*&&
            	(data.feed.entry[i]['gsx$startdate']['$t'] == "" || moment(data.feed.entry[0]['gsx$startdate']['$t']).isAfter()) &&
            	(data.feed.entry[i]['gsx$enddate']['$t'] == "" || moment(data.feed.entry[0]['gsx$enddate']['$t']).isBefore())*/)
            {
            	if(data.feed.entry[i]['gsx$messagetype']['$t'] == "Announcement")
            		messageCombined += '&nbsp  &nbsp';
            	else if(data.feed.entry[i]['gsx$messagetype']['$t'] == "Birthday Wishes")
            		messageCombined += '&nbsp  &nbsp';

            	messageCombined+='&nbsp' + data.feed.entry[i]['gsx$message']['$t'] + '&nbsp';
            }
        }
        $('#scroller').html(messageCombined);
    }

    });
}

window.onload=function(){
	loadImageList();
	GetClock();
	getBusTiming();
	loadMessageList();
	loadNextImageCompleted();
	setInterval(GetClock,1000);
	setInterval(loadNextImage,6000);
	setInterval(getBusTiming,15000);
}
<commit_msg>Start/End Date expiry checks<commit_after>var tmonth=new Array("JAN","FEB","MAR","APR","May","JUN","JUL","AUG","SEPT","OCT","NOV","DEC");
var posterList=new Array("transparent.png");
var curImage = 0;
var messageCombined = "Welcome to Residential College 4! &nbsp";

moment.locale('en', {
    relativeTime : {
        future: "in %s",
        s:  "Arr",
        m:  "1 min",
        mm: "%d mins"
    },
    invalidDate: "-"
});

function GetClock(){
	var d=new Date();
	var nmonth=d.getMonth(),ndate=d.getDate(),nyear=d.getYear();
	if(nyear<1000) nyear+=1900;

	var d=new Date();
	var nhour=d.getHours(),nmin=d.getMinutes(),nsec=d.getSeconds();
	if(nmin<=9) nmin="0"+nmin
	if(nsec<=9) nsec="0"+nsec;

	document.getElementById('clockbox').innerHTML=""+ndate+" "+tmonth[nmonth]+"
"+nhour+":"+nmin+":"+nsec+"";
}

function loadNextImage(){
	$('#curImage').fadeOut(500, loadNextImageCompleted);
}

function loadNextImageCompleted(){
	if(++curImage >= posterList.length)
		curImage = 0;

	$('#curImage')[0].src=posterList[curImage];
	$('#curImage').fadeIn(500);
}

function getBusTiming(){
  $.ajax({
    url: 'http://cors.io/?u=https://nextbus.comfortdelgro.com.sg/eventservice.svc/Shuttleservice?busstopname=UTOWN',
    dataType: "json",
    success: function (data) {
        isb = data.ShuttleServiceResult.shuttles;
        for(var i=0; i<isb.length; i++) {
	        if(isb[i].name=="D1 (To UTown)") {
	            if(isb[i].arrivalTime != "Arr" && isb[i].arrivalTime != "-")
	            	$('#rc4_d1').html(isb[i].arrivalTime + " mins");
	            else
	            	$('#rc4_d1').html(isb[i].arrivalTime);
	        }
	        else if (isb[i].name=="D2 (To UTown)") {
	        	if(isb[i].arrivalTime != "Arr" && isb[i].arrivalTime != "-")
	            	$('#rc4_d2').html(isb[i].arrivalTime + " mins");
	            else
	            	$('#rc4_d2').html(isb[i].arrivalTime);
	        }
	    }

    }
});

  $.ajax({
    url: 'http://arrivelah.herokuapp.com/?id=19059',
    dataType: "json",
    success: function (data) {
        //console.log(JSON.stringify(data));
        lta = data.services;
        for(var i=0; i<lta.length; i++)
        {
        	var res = "-";
        	if(lta[i]["next"]["duration_ms"] == null)
        		res = "-";
        	else
        	{
			res = Math.floor((lta[i]["next"]["duration_ms"]/ 1000) / 60);
			if(res < 1)
				res = "Arr";
			else
				res = res  + " mins";
        	}

        	if(lta[i].no == "33")
            		$('#lta_33').html(res);
            	else if(lta[i].no == "196")
            		$('#lta_196').html(res);
          //lta[i]["subsequent"]["time"] = moment(lta[i]["subsequent"]["time"]).fromNow(true);
        }
    }
});

    $.ajax({
    url: 'http://arrivelah.herokuapp.com/?id=19051',
    dataType: "json",
    success: function (data) {
        //console.log(JSON.stringify(data));
        lta = data.services;
        for(var i=0; i<lta.length; i++)
        {
        	var res = "-";
        	if(lta[i]["next"]["duration_ms"] == null)
        		res = "-";
        	else
        	{
			res = Math.floor((lta[i]["next"]["duration_ms"]/ 1000) / 60);
			if(res < 1)
				res = "Arr";
			else
				res = res  + " mins";
        	}

            if(lta[i].no == "33")
            	$('#lta_opp_33').html(res);
            else if(lta[i].no == "196")
            	$('#lta_opp_196').html(res);
        }

    }
});
}

function loadImageList(){
	$.ajax({
    url: 'https://spreadsheets.google.com/feeds/list/1vwEDjW19YoJZmRTDVsRCMGysjePw81ls8CSe90NeRCA/1/public/values?alt=json',
    dataType: "jsonp",
    success: function (data) {
    	posterList = new Array();

        for(var i=0; i<data.feed.entry.length; i++)
        {
            if(data.feed.entry[i]['gsx$approved']['$t'] == "Y" &&
                (data.feed.entry[i]['gsx$startdate']['$t'] == "" || moment(data.feed.entry[i]['gsx$startdate']['$t'], "MM/DD/YYYY").isBefore()) &&
                (data.feed.entry[i]['gsx$enddate']['$t'] == "" || moment(data.feed.entry[i]['gsx$enddate']['$t']+ "23:59", "MM/DD/YYYY HH:mm").isAfter()))
                posterList[posterList.length]=data.feed.entry[i]['gsx$posterurl']['$t'];
        }
    }

    });
}

function loadMessageList(){
	$.ajax({
    url: 'https://spreadsheets.google.com/feeds/list/1dRpDWAt6YZbJgnhYKuAb4YL2QkEZlKjiGMwEKnjolbU/1/public/values?alt=json',
    dataType: "jsonp",
    success: function (data) {

        for(var i=0; i<data.feed.entry.length; i++)
        {
            if(data.feed.entry[i]['gsx$approved']['$t'] == "Y" &&
                (data.feed.entry[i]['gsx$startdate']['$t'] == "" || moment(data.feed.entry[i]['gsx$startdate']['$t'], "MM/DD/YYYY").isBefore()) &&
                (data.feed.entry[i]['gsx$enddate']['$t'] == "" || moment(data.feed.entry[i]['gsx$enddate']['$t']+ "23:59", "MM/DD/YYYY HH:mm").isAfter()))
            {
            	if(data.feed.entry[i]['gsx$messagetype']['$t'] == "Announcement")
            		messageCombined += '&nbsp  &nbsp';
            	else if(data.feed.entry[i]['gsx$messagetype']['$t'] == "Birthday Wishes")
            		messageCombined += '&nbsp  &nbsp';

            	messageCombined+='&nbsp' + data.feed.entry[i]['gsx$message']['$t'] + '&nbsp';
            }
        }
        $('#scroller').html(messageCombined);
    }

    });
}

window.onload=function(){
	loadImageList();
	GetClock();
	getBusTiming();
	loadMessageList();
	loadNextImageCompleted();
	setInterval(GetClock,1000);
	setInterval(loadNextImage,6000);
	setInterval(getBusTiming,15000);
}
<|endoftext|>


<commit_before>
describe('e2e leaderboard tests', function() {
    var delaylength = 500;
    var ptor;
    ptor = protractor.getInstance();

    var scrollIntoView = function () {
        arguments[0].scrollIntoView();
    }

    it('should be able to sign in', function() {
        browser.driver.manage().window().maximize();
        browser.get('http://localhost:3000/#!/');

        expect(ptor.getCurrentUrl()).toContain('signin');

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
        element(by.model('credentials.email')).sendKeys('demo@example.com');
        element(by.model('credentials.password')).sendKeys('password');

        element(by.css('button[type="submit"]')).click();

    });

it('should be able to select demo event',function(){
    element.all(by.css('.dropdown span[data-ng-bind="eventSelector.selectedEvent"]')).click();
    browser.waitForAngular();
    element(by.css('.dropdown.open .dropdown-menu.ng-scope[ng-if="eventSelector.admin"] li a', 'Project Demonstration')).click();
    browser.waitForAngular();
    //expect(element(by.css('.dropdown li a span[eventSelector.selectedEvent'))).toMatch(/Project Demonstration/);
});

 it('should be able to get to the leaderboards', function() {

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
        element.all(by.css('.homeButtonContainer a[href="/#!/leaderboard"]')).click();
        browser.waitForAngular();

        expect(ptor.getCurrentUrl()).toContain('leaderboard');

    });
  it('should be able to get to the leaderboard tab', function() {

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
      var link;
       // link = element.all(by.binding('Leaderboard')).click();
       link = element.all(by.css('.nav.nav-tabs li[heading="Leaderboard"]'));
       link.click();
        browser.waitForAngular();
        //browser.driver.sleep(2000);

       // expect(ptor.getCurrentUrl()).toContain('leaderboard');

        expect(link.getAttribute('class')).toMatch(/active/);

    });
  it('should be able to sort the leaderboard ranks', function(){
    element.all(by.model('mainTableFilter.displayName')).sendKeys('Alin');
    browser.waitForAngular();
    //browser.driver.sleep(2000);
        element.all(by.model('mainTableFilter.displayName')).clear();
        browser.waitForAngular();

  });

  it('should be able to get to the attending tab', function() {

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
      var link;
       // link = element.all(by.binding('Leaderboard')).click();
       link = element.all(by.css('.nav.nav-tabs li[heading="Attending"]'));
       link.click();
        browser.waitForAngular();
        //browser.driver.sleep(2000);

       // expect(ptor.getCurrentUrl()).toContain('leaderboard');

        expect(link.getAttribute('class')).toMatch(/active/);

    });

    // it('should be able to sort the attending by attendee name', function(){
    //     element.all(by.model('params.filter()[name:"Attendee Name"]')).sendKeys('A');
    //     browser.waitForAngular();
    //     //browser.driver.sleep(2000);
    //     element.all(by.model('params.filter()[name]')).clear;
    //     browser.waitForAngular();

    //     });

  it('should be able to get to the Invited tab', function() {

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
      var link;
       // link = element.all(by.binding('Leaderboard')).click();
       link = element.all(by.css('.nav.nav-tabs li[heading="Invited"]'));
       link.click();
        browser.waitForAngular();
        //browser.driver.sleep(2000);

       // expect(ptor.getCurrentUrl()).toContain('leaderboard');

        expect(link.getAttribute('class')).toMatch(/active/);

    });

  it('should be able to add a comment',function(){
    // browser.executeScript(scrollIntoView, element(by.css('.frank-comment-editor-compressed', 'Click to comment...')));
    browser.waitForAngular();

    element(by.css('.frank-comment-editor-compressed')).click();
    browser.waitForAngular();
    //browser.driver.sleep(3000);
    // browser.executeScript(scrollIntoView, element(by.css('.ta-scroll-window.ng-scope.ta-text.ta-editor.form-control div[ta-bind="ta-bind"]')));

    element(by.css('.ta-scroll-window.ng-scope.ta-text.ta-editor.form-control div[ta-bind="ta-bind"]')).sendKeys('Testing Comment Box');
        browser.waitForAngular();

    browser.waitForAngular();
  //  browser.driver.sleep(3000);
      // browser.executeScript(scrollIntoView, element(by.css('.frank-comment-editor-submit input[type="submit"]')));
    browser.waitForAngular();

    element(by.css('.frank-comment-editor-submit input[type="submit"]')).click();
    browser.waitForAngular();
  //  browser.driver.sleep(3000);

    //

});

  it('should be able to sign out',function() {
        element(by.css('.dropdown-toggle span[data-ng-bind="authentication.user.fName"]')).click();
        browser.waitForAngular();
        element(by.css('.dropdown-menu a[href="/auth/signout"]')).click();
        browser.waitForAngular();
        expect(ptor.getCurrentUrl()).toContain('signin');
    });

    it('should be able to visit the sign in page',function() {
        browser.get('http://localhost:3000/');
        browser.waitForAngular();
        expect(ptor.getCurrentUrl()).toContain('signin');
    });

});<commit_msg>leaderboard comment test now checks to make sure it is present in the message board<commit_after>
describe('e2e leaderboard tests', function() {
    var delaylength = 500;
    var ptor;
    ptor = protractor.getInstance();

    var scrollIntoView = function () {
        arguments[0].scrollIntoView();
    }

    it('should be able to sign in', function() {
        browser.driver.manage().window().maximize();
        browser.get('http://localhost:3000/#!/');

        expect(ptor.getCurrentUrl()).toContain('signin');

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
        element(by.model('credentials.email')).sendKeys('demo@example.com');
        element(by.model('credentials.password')).sendKeys('password');

        element(by.css('button[type="submit"]')).click();

    });

it('should be able to select demo event',function(){
    element.all(by.css('.dropdown span[data-ng-bind="eventSelector.selectedEvent"]')).click();
    browser.waitForAngular();
    element(by.css('.dropdown.open .dropdown-menu.ng-scope[ng-if="eventSelector.admin"] li a', 'Project Demonstration')).click();
    browser.waitForAngular();
    //expect(element(by.css('.dropdown li a span[eventSelector.selectedEvent'))).toMatch(/Project Demonstration/);
});

 it('should be able to get to the leaderboards', function() {

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
        element.all(by.css('.homeButtonContainer a[href="/#!/leaderboard"]')).click();
        browser.waitForAngular();

        expect(ptor.getCurrentUrl()).toContain('leaderboard');

    });
  it('should be able to get to the leaderboard tab', function() {

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
      var link;
       // link = element.all(by.binding('Leaderboard')).click();
       link = element.all(by.css('.nav.nav-tabs li[heading="Leaderboard"]'));
       link.click();
        browser.waitForAngular();
        //browser.driver.sleep(2000);

       // expect(ptor.getCurrentUrl()).toContain('leaderboard');

        expect(link.getAttribute('class')).toMatch(/active/);

    });
  it('should be able to sort the leaderboard ranks', function(){
    element.all(by.model('mainTableFilter.displayName')).sendKeys('Alin');
    browser.waitForAngular();
    //browser.driver.sleep(2000);
        element.all(by.model('mainTableFilter.displayName')).clear();
        browser.waitForAngular();

  });

  it('should be able to get to the attending tab', function() {

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
      var link;
       // link = element.all(by.binding('Leaderboard')).click();
       link = element.all(by.css('.nav.nav-tabs li[heading="Attending"]'));
       link.click();
        browser.waitForAngular();
        //browser.driver.sleep(2000);

       // expect(ptor.getCurrentUrl()).toContain('leaderboard');

        expect(link.getAttribute('class')).toMatch(/active/);

    });

    // it('should be able to sort the attending by attendee name', function(){
    //     element.all(by.model('params.filter()[name:"Attendee Name"]')).sendKeys('A');
    //     browser.waitForAngular();
    //     //browser.driver.sleep(2000);
    //     element.all(by.model('params.filter()[name]')).clear;
    //     browser.waitForAngular();

    //     });

  it('should be able to get to the Invited tab', function() {

        expect(browser.getTitle()).toEqual('frank Recruiter System - Development Environment');
      var link;
       // link = element.all(by.binding('Leaderboard')).click();
       link = element.all(by.css('.nav.nav-tabs li[heading="Invited"]'));
       link.click();
        browser.waitForAngular();
        //browser.driver.sleep(2000);

       // expect(ptor.getCurrentUrl()).toContain('leaderboard');

        expect(link.getAttribute('class')).toMatch(/active/);

    });

  it('should be able to add a comment',function(){
     // element(by.css('.frank-recruiter-comments.frank-recruiter-comments-expanded')).click();
     // browser.executeScript(scrollIntoView, element(by.id('frank-comment-editorDivider')));
    browser.waitForAngular();

    element(by.css('.frank-comment-editor-compressed')).click();
    browser.waitForAngular();
    //browser.driver.sleep(3000);
    // browser.executeScript(scrollIntoView, element(by.css('.ta-scroll-window.ng-scope.ta-text.ta-editor.form-control div[ta-bind="ta-bind"]')));

    element(by.css('.ta-scroll-window.ng-scope.ta-text.ta-editor.form-control div[ta-bind="ta-bind"]')).sendKeys('Testing Comment Box');
        browser.waitForAngular();

    browser.waitForAngular();
  //  browser.driver.sleep(3000);
      // browser.executeScript(scrollIntoView, element(by.css('.frank-comment-editor-submit input[type="submit"]')));
    browser.waitForAngular();

    element(by.css('.frank-comment-editor-submit input[type="submit"]')).click();
    browser.waitForAngular();

    expect(element(by.cssContainingText('.frank-comment-message .ng-binding p','Testing Comment Box')).isPresent());

  //  expect.(element(by.css('.frank-comment-message .ng-binding p','Testing Comment Box')).isPresent());
  //  browser.driver.sleep(3000);

});

  it('should be able to sign out',function() {
        element(by.css('.dropdown-toggle span[data-ng-bind="authentication.user.fName"]')).click();
        browser.waitForAngular();
        element(by.css('.dropdown-menu a[href="/auth/signout"]')).click();
        browser.waitForAngular();
        expect(ptor.getCurrentUrl()).toContain('signin');
    });

    it('should be able to visit the sign in page',function() {
        browser.get('http://localhost:3000/');
        browser.waitForAngular();
        expect(ptor.getCurrentUrl()).toContain('signin');
    });

});<|endoftext|>


<commit_before>const fs = require('fs');
const request = require('request');
const series = require('async-series');
const cheerio = require('cheerio');
const keysMethods = require('./services/keys.js');
const promise = require('bluebird');

// TODO: need recursive call for all pages; remove id
const fetchRecordUrls = function(query) {
  return new Promise(function(resolve, reject) {

    var records = [];

    // const source = keysMethods.getSource(query.source); // not sure if this works yet

    const parseUrls = function(url) {
      request.get(url, function(err, response, html) {
        if (!err) {

          const $ = cheerio.load(html);

          const urls = $('body').find('h2.job-title a'); // TODO: abstract this into source utility

          Object.keys(urls).forEach(function(listing) {
            if (urls[listing].attribs !== undefined) {

              var record = {
                id: '', // TODO: remove?
                date: query.date,
                country: query.country,
                state: query.state,
                hub: query.hub,
                source: query.source,
                term: query.term,
                url: '',
                text: ''
              };

              record.url = urls[listing].attribs.href;
              record.url = 'http://www.careerbuilder.com' + record.url.split('?')[0]; // TODO: abstract this into source utility
              record.id = Math.random() * 100000000000000000000;  // TODO: remove

              records.push(record);
            }
          });

          // TODO: recursive logic here to check for additional pages and feed them back into this fetch until all are listed

          resolve(records);

        } else {
          reject(console.log(err));
        }
      });
    };

    parseUrls(query.start);

  });
};

// TODO: this needs to be throttled, ASAP
const fetchRecordContent = function(records) {
  return new Promise(function(resolve, reject) {

    const fetches = records.map(function(record) {
      return function(done) {
        request.get(record.url, function(error, response, html) {
          if (!error) {

            const $ = cheerio.load(html);

            record.text = $('body').find('.description').text().toLowerCase(); // TODO: abstract this into source utility

            done();

          } else {
            reject(error);
          }
        });
      };
    });

    series(fetches, function(err, results) {
      if (!err) {
        resolve(records);
      } else {
        reject(err);
      }
    });

  });

};

// TODO: replace with write to DB
const storeRecords = function(records) {
  records.forEach(function(record) {
    fs.writeFile((__dirname + '/services/records/' + record.id + '.txt'), JSON.stringify(record), function(err) {
      if (err) console.log(err);
    });
  });
};

const queries = keysMethods.getQueries();

queries.forEach(function(query) {
  fetchRecordUrls(query)
    .then(fetchRecordContent)
    .then(storeRecords);
});
<commit_msg>syntax changes to scraper.js<commit_after>const fs = require('fs');
const request = require('request');
const series = require('async-series');
const cheerio = require('cheerio');
const keysMethods = require('./services/keys.js');
const promise = require('bluebird');

// TODO: need recursive call for all pages; remove id
const fetchRecordUrls = function(query) {
  return new Promise(function(resolve, reject) {

    var records = [];

    // const source = keysMethods.getSource(query.source); // not sure if this works yet

    const parseUrls = function(url) {
      request.get(url, function(err, response, html) {
        if (!err) {

          const $ = cheerio.load(html);
          const urls = $('body').find('h2.job-title a'); // TODO: abstract this into source utility

          Object.keys(urls).forEach(function(listing) {
            if (urls[listing].attribs !== undefined) {

              var record = {
                id: '', // TODO: remove?
                date: query.date,
                country: query.country,
                state: query.state,
                hub: query.hub,
                source: query.source,
                term: query.term,
                url: '',
                text: ''
              };

              record.url = urls[listing].attribs.href;
              record.url = 'http://www.careerbuilder.com' + record.url.split('?')[0]; // TODO: abstract this into source utility
              record.id = Math.random() * 100000000000000000000;  // TODO: remove

              records.push(record);
            }
          });

          // TODO: recursive logic here to check for additional pages and feed them back into this fetch until all are listed

          resolve(records);

        } else {
          reject(console.log(err));
        }
      });
    };

    parseUrls(query.start);

  });
};

// TODO: this needs to be throttled, ASAP
const fetchRecordContent = function(records) {
  return new Promise(function(resolve, reject) {

    const fetches = records.map(function(record) {
      return function(done) {
        request.get(record.url, function(error, response, html) {
          if (!error) {
            const $ = cheerio.load(html);
            record.text = $('body').find('.description').text().toLowerCase(); // TODO: abstract this into source utility
            done();
          } else {
            reject(error);
          }
        });
      };
    });

    series(fetches, function(err, results) {
      if (!err) {
        resolve(records);
      } else {
        reject(err);
      }
    });

  });
};

// TODO: replace with write to DB
const storeRecords = function(records) {
  records.forEach(function(record) {
    fs.writeFile((__dirname + '/services/records/' + record.id + '.txt'), JSON.stringify(record), function(err) {
      if (err) console.log(err);
    });
  });
};

const queries = keysMethods.getQueries();

queries.forEach(function(query) {
  fetchRecordUrls(query)
    .then(fetchRecordContent)
    .then(storeRecords);
});
<|endoftext|>