<commit_before>/**
 * SprintController
 *
 * @module      ::  Controller
 * @description ::  Contains logic for handling requests.
 */
var async = require("async");

module.exports = {
    /**
     * Sprint add action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    add: function(req, res) {
        var projectId = parseInt(req.param("projectId"), 10);

        res.view({
            layout: req.isAjax ? "layout_ajax" : "layout",
            projectId: projectId
        });
    },

    /**
     * Sprint edit action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    edit: function(req, res) {
        var sprintId = parseInt(req.param("id"), 10);

        // Fetch sprint data for edit action
        async.parallel(
            {
                // Fetch single sprint data
                sprint: function(callback) {
                    DataService.getSprint(sprintId, callback);
                },

                // Determine user role in this sprint
                role: function(callback) {
                    AuthService.hasSprintAccess(req.user, sprintId, callback, true);
                }
            },

            /**
             * Callback function which is called after all specified parallel jobs are done.
             *
             * @param   {Error} error   Error object
             * @param   {{}}    data    Object that contains sprint and role data
             */
            function (error, data) {
                if (error) {
                    res.send(error.status ? error.status : 500, error);
                } else {
                    data.layout = req.isAjax ? "layout_ajax" : "layout";

                    res.view(data);
                }
            }
        );
    },

    /**
     * Sprint backlog action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    backlog: function(req, res) {
        var sprintId = parseInt(req.param("id"), 10);

        // Initialize view data object
        var data = {
            layout: req.isAjax ? "layout_ajax" : "layout",
            stories: false,
            role: 0,
            sprint: {
                data: false,
                progressStory: 0,
                progressTask: 0,
                cntStoryDone: 0,
                cntStoryNotDone: 0,
                cntStoryTotal: 0,
                cntTaskDone: 0,
                cntTaskNotDone: 0,
                cntTaskTotal: 0
            }
        };

        // Fetch basic data for backlog action parallel
        async.parallel(
            {
                // Fetch sprint data.
                sprint: function(callback) {
                    DataService.getSprint(sprintId, callback);
                },

                // Fetch sprint stories data.
                stories: function(callback) {
                    DataService.getStories({sprintId: sprintId}, callback);
                },

                // Determine user role in this sprint
                role: function(callback) {
                    AuthService.hasSprintAccess(req.user, sprintId, callback, true);
                }
            },

            /**
             * Callback function which is been called after all parallel jobs are processed.
             *
             * @param   {Error} error   Error object
             * @param   {{}}    result  Result object that contains 'sprint', 'stories' and 'role' data
             */
            function(error, result) {
                if (error) {
                    res.send(error.status ? error.status : 500, error);
                } else {
                    data.sprint.data = result.sprint;
                    data.stories = result.stories;
                    data.role = result.role;

                    // Fetch task data
                    fetchTaskData();
                }
            }
        );

        /**
         * Private function to fetch task data for each story in specified sprint.
         *
         * @return  void
         */
        function fetchTaskData() {
            // Calculate story specified statistics
            data.sprint.cntStoryTotal = data.stories.length;
            data.sprint.cntStoryDone = _.reduce(data.stories, function(memo, story) { return (story.isDone) ? memo + 1 : memo; }, 0);
            data.sprint.cntStoryNotDone = data.sprint.cntStoryTotal - data.sprint.cntStoryDone;

            if (data.sprint.cntStoryDone > 0) {
                data.sprint.progressStory = Math.round(data.sprint.cntStoryDone / data.sprint.cntStoryTotal * 100);
            } else {
                data.sprint.progressStory = 0;
            }

            // Map each sprint story and fetch task data of those
            async.map(
                data.stories,

                /**
                 * Iterator function which will fetch all tasks that are attached to current
                 * story and calculates some statistics data for story and sprint data.
                 *
                 * @param   {sails.model.story} story       Story object
                 * @param   {Function}          callback    Callback function to call after job is finished
                 */
                function(story, callback) {
                    Task
                        .find()
                        .where({
                            storyId: story.id
                        })
                        .sort("title ASC")
                        .done(function(error, tasks) {
                            if (error) {
                                callback(error, false);
                            } else {
                                // Add tasks to story data
                                story.tasks = tasks;
                                story.doneTasks = _.reduce(tasks, function(memo, task) { return (task.isDone) ? memo + 1 : memo; }, 0);

                                if (story.doneTasks > 0) {
                                    story.progress = Math.round(story.doneTasks / tasks.length * 100);
                                } else {
                                    story.progress = 0;
                                }

                                // Add task counts to sprint
                                data.sprint.cntTaskTotal += story.tasks.length;
                                data.sprint.cntTaskDone += story.doneTasks;

                                callback(null, true);
                            }
                        });
                },

                /**
                 * Callback function which is been called after all stories are processed.
                 *
                 * @param   {Error}     error   Error object
                 * @param   {Boolean}   result
                 */
                function(error, result) {
                    if (error) {
                        res.send(error.status ? error.status : 500, error);
                    } else {
                        data.sprint.cntTaskNotDone = data.sprint.cntTaskTotal - data.sprint.cntTaskDone;

                        if (data.sprint.cntTaskDone > 0) {
                            data.sprint.progressTask = Math.round(data.sprint.cntTaskDone / data.sprint.cntTaskTotal * 100);
                        } else {
                            data.sprint.progressTask = 0;
                        }

                        res.view(data);
                    }
                }
            )
        }
    },

    /**
     * Sprint charts action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    charts: function(req, res) {
        var sprintId = parseInt(req.param("id"), 10);

        var data = {
            layout: req.isAjax ? "layout_ajax" : "layout"
        };

        // Get sprint and attached stories data
        async.parallel(
            {
                // Fetch sprint data
                sprint: function(callback) {
                    DataService.getSprint(sprintId, callback);

                },

                // Fetch stories that are attached to this sprint
                stories: function(callback) {
                    DataService.getStories({sprintId: sprintId}, callback);
                }
            },
            function(error, results) {
                if (error) {
                    res.send(error.status ? error.status : 500, error);
                } else {
                    // Store results to data
                    data.sprint = results.sprint;
                    data.stories = results.stories;

                    getTasks();
                }
            }
        );

        /**
         * Private function to fetch all task data that are attached specified sprint.
         */
        function getTasks() {
            // Determine story id values for task search.
            var storyIds = _.map(data.stories, function(story) { return {storyId: story.id}; });

            // Fetch stories tasks
            DataService.getTasks({or: storyIds}, function(error, tasks) {
                if (error) {
                    res.send(error.status ? error.status : 500, error);
                } else {
                    data.tasks = tasks;

                    res.view(data);
                }
            });
        }
    },

    /**
     * Sprint chartDataTask action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    chartDataTasks: function(req, res) {
        var sprintId = parseInt(req.param("sprintId"), 10);

        var data = {};

        // Get sprint and attached stories data
        async.parallel(
            {
                // Fetch sprint data
                sprint: function(callback) {
                    DataService.getSprint(sprintId, callback);

                },

                // Fetch stories that are attached to this sprint
                stories: function(callback) {
                    DataService.getStories({sprintId: sprintId}, callback);
                }
            },
            function(error, results) {
                if (error) {
                    res.send(500, error);
                } else {
                    // Store results to data
                    data.sprint = results.sprint;
                    data.stories = results.stories;

                    // Determine story id values for task search.
                    var storyIds = _.map(data.stories, function(story) { return {storyId: story.id}; });

                    // Fetch story tasks
                    DataService.getTasks({or: storyIds}, function(error, tasks) {
                        if (error) {
                            res.send(500, error);
                        } else {
                            data.tasks = _.sortBy(tasks, function(task) { return task.timeEnd; } );
                            data.tasksDone = _.filter(data.tasks, function(task) { return task.isDone; } );

                            parseData();
                        }
                    });
                }
            }
        );

        /**
         * Private function to parse actual data.
         */
        function parseData() {
            var initTasks = 0;
            var storyTasks = 0;
            var tasksOver = [];

            // Iterate each stories and determine initial and "over" tasks
            _.each(data.stories, function(story) {
                storyTasks = 0;

                // Story has not yet started, so all of its tasks are in "init" data
                if (!(story.timeStart && story.timeStart != '0000-00-00 00:00:00')) {
                    storyTasks = _.size(_.filter(data.tasks, function(task) {
                        return task.storyId === story.id;
                    }));
                } else { // Otherwise story is either in process or done
                    // calculate task count that are created before story start time
                    storyTasks = _.size(_.filter(data.tasks, function(task) {
                        return (task.storyId === story.id && task.createdAt <= story.timeStart);
                    }));

                    // Determine tasks that are added after story start
                    tasksOver = tasksOver.concat(_.filter(data.tasks, function(task) {
                        return (task.storyId === story.id && task.createdAt > story.timeStart);
                    }));
                }

                // Add current story init task count to main task count
                initTasks = initTasks + storyTasks;
            });

            data.tasksOver = _.sortBy(tasksOver, function(task) { return task.createdAt; } );
            data.initTasks = initTasks;
            data.chartData = [];

            var pointStart = Date.UTC(
                data.sprint.dateStartObject().year(),
                data.sprint.dateStartObject().month(),
                data.sprint.dateStartObject().date()
            );

            data.pointStart = pointStart;

            // Add 'Ideal task remaining' data
            data.chartData.push({
                name: "Ideal tasks remaining",
                nameShort: "Ideal",
                type: "spline",
                color: "#3276b1",
                dashStyle: "dash",
                marker: {
                    enabled: false,
                    radius: 3
                },
                lineWidth: 1,
                zIndex: 10,
                pointStart: pointStart,
                data: getIdealData()
            });

            // Add 'Actual task remaining' data
            data.chartData.push({
                name: "Actual tasks remaining",
                nameShort: "Actual",
                type: "spline",
                color: "#c9302c",
                shadow: true,
                marker: {
                    enabled: false,
                    radius: 3
                },
                zIndex: 20,
                pointStart: pointStart,
                data: getActualData()
            });

            // Add 'Done tasks' data
            data.chartData.push({
                name: "Done tasks",
                nameShort: "Done",
                type: "column",
                color: "#47a447",
                zIndex: 5,
                pointStart: pointStart,
                data: getDoneData()
            });

            // Add 'Added tasks' data
            data.chartData.push({
                name: "Added tasks",
                nameShort: "Added",
                type: "column",
                color: "#ec971f",
                zIndex: 0,
                pointStart: pointStart,
                data: getAddedData()
            });

            res.json(data);
        }

        /**
         * Private function to determine ideal data for charts. This is basically linear line where
         * start point is at sprint start and value for that point is sprint initial task count. End
         * point is at sprint end and value is zero.
         *
         * Output is an array of UTC date times and actual values.
         *
         * @returns {Array}
         */
        function getIdealData() {
            var output = [];
            var tasks = data.initTasks;
            var startTime = data.sprint.dateStartObject();
            var endTime = data.sprint.dateEndObject();
            var tasksPerDay = data.initTasks / (data.sprint.durationDays() - 1);

            for (var i = 0; i < (data.sprint.durationDays() - 1); i++) {
                var date = startTime.add("days", i === 0 ? 0 : 1);

                output.push([Date.UTC(date.year(), date.month(), date.date()), (tasks - i * tasksPerDay)]);
            }

            output.push([Date.UTC(endTime.year(), endTime.month(), endTime.date()), 0]);

            return output;
        }

        /**
         * Private function to get actual task data.
         *
         * @returns {Array}
         */
        function getActualData() {
            var output = [];
            var tasks = data.initTasks;
            var startTime = data.sprint.dateStartObject();
            var endTime = data.sprint.dateEndObject();
            var tasksDone = 0;
            var tasksOver = 0;

            // Add first point of data
            output.push([Date.UTC(startTime.year(), startTime.month(), startTime.date()), tasks]);

            var currentDate = startTime.add("days", 1);

            console.log(data.tasks);

            // Loops days
            while (endTime.diff(currentDate, "days") >= 0) {
                // Get reference date, this is used to determine actual tasks that are done or added
                var referenceDate = currentDate.clone().subtract("days", 1);

                // We are only interested days that are before current day
                if (referenceDate.isBefore(moment().add("days", 1), "day")) {
                    // Calculate done task count
                    tasksDone = _.size(_.filter(data.tasks, function(task) {
                        return task.isDone && task.timeEndObject().isSame(referenceDate, "day");
                    }));

                    // Calculate added task count
                    tasksOver = _.size(_.filter(data.tasksOver, function(task) {
                        return task.createdAtObject().isSame(referenceDate, "day");
                    }));

                    // Calculate new task count based to previous tasks, done and added tasks count
                    tasks = tasks - tasksDone + tasksOver;

                    output.push([Date.UTC(currentDate.year(), currentDate.month(), currentDate.date()), tasks]);
                }

                // Go to next date
                currentDate.add("days", 1);
            }

            return output;
        }

        /**
         * Private function to get possible added task data for current sprint.
         *
         * @returns {Array}
         */
        function getAddedData() {
            var output = [];

            _.each(_.groupBy(data.tasksOver, function(task) { return task.createdAtObject().format("YYYY-MM-DD"); }),
                function(tasks) {
                    var date = tasks[0].createdAtObject();

                    output.push([Date.UTC(date.year(), date.month(), date.date()), _.size(tasks)]);
                }
            );

            return output;
        }

        /**
         * Private function to get done data for current sprint.
         *
         * @returns {Array}
         */
        function getDoneData() {
            var output = [];

            _.each(_.groupBy(data.tasksDone, function(task) { return task.timeEndObject().format("YYYY-MM-DD"); }),
                function(tasks) {
                    var date = tasks[0].timeEndObject();

                    output.push([Date.UTC(date.year(), date.month(), date.date()), _.size(tasks)]);
                }
            );

            return output;
        }
    }
};
<commit_msg>Refactored rest of controller.<commit_after>/**
 * SprintController
 *
 * @module      ::  Controller
 * @description ::  Contains logic for handling requests.
 */
var async = require("async");

module.exports = {
    /**
     * Sprint add action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    add: function(req, res) {
        var projectId = parseInt(req.param("projectId"), 10);

        res.view({
            layout: req.isAjax ? "layout_ajax" : "layout",
            projectId: projectId
        });
    },

    /**
     * Sprint edit action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    edit: function(req, res) {
        var sprintId = parseInt(req.param("id"), 10);

        // Fetch sprint data for edit action
        async.parallel(
            {
                // Fetch single sprint data
                sprint: function(callback) {
                    DataService.getSprint(sprintId, callback);
                },

                // Determine user role in this sprint
                role: function(callback) {
                    AuthService.hasSprintAccess(req.user, sprintId, callback, true);
                }
            },

            /**
             * Callback function which is called after all specified parallel jobs are done.
             *
             * @param   {Error} error   Error object
             * @param   {{}}    data    Object that contains sprint and role data
             */
            function (error, data) {
                if (error) {
                    res.send(error.status ? error.status : 500, error);
                } else {
                    data.layout = req.isAjax ? "layout_ajax" : "layout";

                    res.view(data);
                }
            }
        );
    },

    /**
     * Sprint backlog action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    backlog: function(req, res) {
        var sprintId = parseInt(req.param("id"), 10);

        // Initialize view data object
        var data = {
            layout: req.isAjax ? "layout_ajax" : "layout",
            stories: false,
            role: 0,
            sprint: {
                data: false,
                progressStory: 0,
                progressTask: 0,
                cntStoryDone: 0,
                cntStoryNotDone: 0,
                cntStoryTotal: 0,
                cntTaskDone: 0,
                cntTaskNotDone: 0,
                cntTaskTotal: 0
            }
        };

        // Fetch basic data for backlog action parallel
        async.parallel(
            {
                // Fetch sprint data.
                sprint: function(callback) {
                    DataService.getSprint(sprintId, callback);
                },

                // Fetch sprint stories data.
                stories: function(callback) {
                    DataService.getStories({sprintId: sprintId}, callback);
                },

                // Determine user role in this sprint
                role: function(callback) {
                    AuthService.hasSprintAccess(req.user, sprintId, callback, true);
                }
            },

            /**
             * Callback function which is been called after all parallel jobs are processed.
             *
             * @param   {Error} error   Error object
             * @param   {{}}    result  Result object that contains 'sprint', 'stories' and 'role' data
             */
            function(error, result) {
                if (error) {
                    res.send(error.status ? error.status : 500, error);
                } else {
                    data.sprint.data = result.sprint;
                    data.stories = result.stories;
                    data.role = result.role;

                    // Fetch task data
                    fetchTaskData();
                }
            }
        );

        /**
         * Private function to fetch task data for each story in specified sprint.
         *
         * @return  void
         */
        function fetchTaskData() {
            // Calculate story specified statistics
            data.sprint.cntStoryTotal = data.stories.length;
            data.sprint.cntStoryDone = _.reduce(data.stories, function(memo, story) { return (story.isDone) ? memo + 1 : memo; }, 0);
            data.sprint.cntStoryNotDone = data.sprint.cntStoryTotal - data.sprint.cntStoryDone;

            if (data.sprint.cntStoryDone > 0) {
                data.sprint.progressStory = Math.round(data.sprint.cntStoryDone / data.sprint.cntStoryTotal * 100);
            } else {
                data.sprint.progressStory = 0;
            }

            // Map each sprint story and fetch task data of those
            async.map(
                data.stories,

                /**
                 * Iterator function which will fetch all tasks that are attached to current
                 * story and calculates some statistics data for story and sprint data.
                 *
                 * @param   {sails.model.story} story       Story object
                 * @param   {Function}          callback    Callback function to call after job is finished
                 */
                function(story, callback) {
                    Task
                        .find()
                        .where({
                            storyId: story.id
                        })
                        .sort("title ASC")
                        .done(function(error, tasks) {
                            if (error) {
                                callback(error, false);
                            } else {
                                // Add tasks to story data
                                story.tasks = tasks;
                                story.doneTasks = _.reduce(tasks, function(memo, task) { return (task.isDone) ? memo + 1 : memo; }, 0);

                                if (story.doneTasks > 0) {
                                    story.progress = Math.round(story.doneTasks / tasks.length * 100);
                                } else {
                                    story.progress = 0;
                                }

                                // Add task counts to sprint
                                data.sprint.cntTaskTotal += story.tasks.length;
                                data.sprint.cntTaskDone += story.doneTasks;

                                callback(null, true);
                            }
                        });
                },

                /**
                 * Callback function which is been called after all stories are processed.
                 *
                 * @param   {Error}     error   Error object
                 * @param   {Boolean}   result
                 */
                function(error, result) {
                    if (error) {
                        res.send(error.status ? error.status : 500, error);
                    } else {
                        data.sprint.cntTaskNotDone = data.sprint.cntTaskTotal - data.sprint.cntTaskDone;

                        if (data.sprint.cntTaskDone > 0) {
                            data.sprint.progressTask = Math.round(data.sprint.cntTaskDone / data.sprint.cntTaskTotal * 100);
                        } else {
                            data.sprint.progressTask = 0;
                        }

                        res.view(data);
                    }
                }
            )
        }
    },

    /**
     * Sprint charts action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    charts: function(req, res) {
        var sprintId = parseInt(req.param("id"), 10);

        var data = {
            layout: req.isAjax ? "layout_ajax" : "layout"
        };

        // Get sprint and attached stories data
        async.parallel(
            {
                // Fetch sprint data
                sprint: function(callback) {
                    DataService.getSprint(sprintId, callback);

                },

                // Fetch stories that are attached to this sprint
                stories: function(callback) {
                    DataService.getStories({sprintId: sprintId}, callback);
                }
            },

            /**
             * Callback function which is been called after all parallel jobs are processed.
             *
             * @param   {Error} error   Error object
             * @param   {{}}    results Result data as an object that contains 'sprint' and 'stories'
             */
            function(error, results) {
                if (error) {
                    res.send(error.status ? error.status : 500, error);
                } else {
                    // Store results to data
                    data.sprint = results.sprint;
                    data.stories = results.stories;

                    getTasks();
                }
            }
        );

        /**
         * Private function to fetch all task data that are attached specified sprint.
         */
        function getTasks() {
            // Determine story id values for task search.
            var storyIds = _.map(data.stories, function(story) { return {storyId: story.id}; });

            // Fetch stories tasks
            DataService.getTasks({or: storyIds}, function(error, tasks) {
                if (error) {
                    res.send(error.status ? error.status : 500, error);
                } else {
                    data.tasks = tasks;

                    res.view(data);
                }
            });
        }
    },

    /**
     * Sprint chartDataTask action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    chartDataTasks: function(req, res) {
        var sprintId = parseInt(req.param("sprintId"), 10);

        var data = {};

        // Get sprint and attached stories data
        async.parallel(
            {
                // Fetch sprint data
                sprint: function(callback) {
                    DataService.getSprint(sprintId, callback);

                },

                // Fetch stories that are attached to this sprint
                stories: function(callback) {
                    DataService.getStories({sprintId: sprintId}, callback);
                }
            },

            /**
             * Callback function which is been called after all parallel jobs are processed.
             *
             * @param   {Error} error   Error object
             * @param   {{}}    results Result data as an object that contains 'sprint' and 'stories'
             */
            function(error, results) {
                if (error) {
                    res.send(500, error);
                } else {
                    // Store results to data
                    data.sprint = results.sprint;
                    data.stories = results.stories;

                    // Determine story id values for task search.
                    var storyIds = _.map(data.stories, function(story) { return {storyId: story.id}; });

                    // Fetch story tasks
                    DataService.getTasks({or: storyIds}, function(error, tasks) {
                        if (error) {
                            res.send(error.status ? error.status : 500, error);
                        } else {
                            data.tasks = _.sortBy(tasks, function(task) { return task.timeEnd; } );
                            data.tasksDone = _.filter(data.tasks, function(task) { return task.isDone; } );

                            parseData();
                        }
                    });
                }
            }
        );

        /**
         * Private function to parse actual data for chart.
         */
        function parseData() {
            var initTasks = 0;
            var storyTasks = 0;
            var tasksOver = [];

            // Iterate each stories and determine initial and "over" tasks
            _.each(data.stories, function(story) {
                storyTasks = 0;

                // Story has not yet started, so all of its tasks are in "init" data
                if (!(story.timeStart && story.timeStart != '0000-00-00 00:00:00')) {
                    storyTasks = _.size(_.filter(data.tasks, function(task) {
                        return task.storyId === story.id;
                    }));
                } else { // Otherwise story is either in process or done
                    // calculate task count that are created before story start time
                    storyTasks = _.size(_.filter(data.tasks, function(task) {
                        return (task.storyId === story.id && task.createdAt <= story.timeStart);
                    }));

                    // Determine tasks that are added after story start
                    tasksOver = tasksOver.concat(_.filter(data.tasks, function(task) {
                        return (task.storyId === story.id && task.createdAt > story.timeStart);
                    }));
                }

                // Add current story init task count to main task count
                initTasks = initTasks + storyTasks;
            });

            data.tasksOver = _.sortBy(tasksOver, function(task) { return task.createdAt; } );
            data.initTasks = initTasks;
            data.chartData = [];

            var pointStart = Date.UTC(
                data.sprint.dateStartObject().year(),
                data.sprint.dateStartObject().month(),
                data.sprint.dateStartObject().date()
            );

            data.pointStart = pointStart;

            // Add 'Ideal task remaining' data
            data.chartData.push({
                name: "Ideal tasks remaining",
                nameShort: "Ideal",
                type: "spline",
                color: "#3276b1",
                dashStyle: "dash",
                marker: {
                    enabled: false,
                    radius: 3
                },
                lineWidth: 1,
                zIndex: 10,
                pointStart: pointStart,
                data: getIdealData()
            });

            // Add 'Actual task remaining' data
            data.chartData.push({
                name: "Actual tasks remaining",
                nameShort: "Actual",
                type: "spline",
                color: "#c9302c",
                shadow: true,
                marker: {
                    enabled: false,
                    radius: 3
                },
                zIndex: 20,
                pointStart: pointStart,
                data: getActualData()
            });

            // Add 'Done tasks' data
            data.chartData.push({
                name: "Done tasks",
                nameShort: "Done",
                type: "column",
                color: "#47a447",
                zIndex: 5,
                pointStart: pointStart,
                data: getDoneData()
            });

            // Add 'Added tasks' data
            data.chartData.push({
                name: "Added tasks",
                nameShort: "Added",
                type: "column",
                color: "#ec971f",
                zIndex: 0,
                pointStart: pointStart,
                data: getAddedData()
            });

            res.json(data);
        }

        /**
         * Private function to determine ideal data for charts. This is basically linear line where
         * start point is at sprint start and value for that point is sprint initial task count. End
         * point is at sprint end and value is zero.
         *
         * Output is an array of UTC date times and actual values.
         *
         * @returns {Array}
         */
        function getIdealData() {
            var output = [];
            var tasks = data.initTasks;
            var startTime = data.sprint.dateStartObject();
            var endTime = data.sprint.dateEndObject();
            var tasksPerDay = data.initTasks / (data.sprint.durationDays() - 1);

            for (var i = 0; i < (data.sprint.durationDays() - 1); i++) {
                var date = startTime.add("days", i === 0 ? 0 : 1);

                output.push([Date.UTC(date.year(), date.month(), date.date()), (tasks - i * tasksPerDay)]);
            }

            output.push([Date.UTC(endTime.year(), endTime.month(), endTime.date()), 0]);

            return output;
        }

        /**
         * Private function to get actual task data.
         *
         * @returns {Array}
         */
        function getActualData() {
            var output = [];
            var tasks = data.initTasks;
            var startTime = data.sprint.dateStartObject();
            var endTime = data.sprint.dateEndObject();
            var tasksDone = 0;
            var tasksOver = 0;

            // Add first point of data
            output.push([Date.UTC(startTime.year(), startTime.month(), startTime.date()), tasks]);

            var currentDate = startTime.add("days", 1);

            // Loops days
            while (endTime.diff(currentDate, "days") >= 0) {
                // Get reference date, this is used to determine actual tasks that are done or added
                var referenceDate = currentDate.clone().subtract("days", 1);

                // We are only interested days that are before current day
                if (referenceDate.isBefore(moment().add("days", 1), "day")) {
                    // Calculate done task count
                    tasksDone = _.size(_.filter(data.tasks, function(task) {
                        return task.isDone && task.timeEndObject().isSame(referenceDate, "day");
                    }));

                    // Calculate added task count
                    tasksOver = _.size(_.filter(data.tasksOver, function(task) {
                        return task.createdAtObject().isSame(referenceDate, "day");
                    }));

                    // Calculate new task count based to previous tasks, done and added tasks count
                    tasks = tasks - tasksDone + tasksOver;

                    output.push([Date.UTC(currentDate.year(), currentDate.month(), currentDate.date()), tasks]);
                }

                // Go to next date
                currentDate.add("days", 1);
            }

            return output;
        }

        /**
         * Private function to get possible added task data for current sprint.
         *
         * @returns {Array}
         */
        function getAddedData() {
            var output = [];

            // Group task data by create date and iterate grouped data
            _.each(_.groupBy(data.tasksOver, function(task) { return task.createdAtObject().format("YYYY-MM-DD"); } ),
                function(tasks) {
                    var date = tasks[0].createdAtObject();

                    output.push([Date.UTC(date.year(), date.month(), date.date()), _.size(tasks)]);
                }
            );

            return output;
        }

        /**
         * Private function to get done data for current sprint.
         *
         * @returns {Array}
         */
        function getDoneData() {
            var output = [];

            // Group task data by done date and iterate grouped data
            _.each(_.groupBy(data.tasksDone, function(task) { return task.timeEndObject().format("YYYY-MM-DD"); }),
                function(tasks) {
                    var date = tasks[0].timeEndObject();

                    output.push([Date.UTC(date.year(), date.month(), date.date()), _.size(tasks)]);
                }
            );

            return output;
        }
    }
};
<|endoftext|>


<commit_before>"use strict";

define(['logManager',
    'clientUtil',
    'js/Constants',
    'js/NodePropertyNames',
    'js/Utils/GMEConcepts',
    './MetaRelations',
    'js/DragDrop/DragHelper'], function (logManager,
                                        util,
                                        CONSTANTS,
                                        nodePropertyNames,
                                        GMEConcepts,
                                        MetaRelations,
                                        DragHelper) {

    var MetaEditorControlDiagramDesignerWidgetEventHandlers,
        DRAG_PARAMS_META_CONTAINER_ID = 'metaContainerID';

    MetaEditorControlDiagramDesignerWidgetEventHandlers = function () {
    };

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype.attachDiagramDesignerWidgetEventHandlers = function () {
        var self = this;

        /*OVERRIDE DESIGNER CANVAS METHODS*/
        this.diagramDesigner.onDesignerItemsMove = function (repositionDesc) {
            self._onDesignerItemsMove(repositionDesc);
        };

        this.diagramDesigner.onCreateNewConnection = function (params) {
            self._onCreateNewConnection(params);
        };

        this.diagramDesigner.onSelectionDelete = function (idList) {
            self._onSelectionDelete(idList);
        };

        this.diagramDesigner.onBackgroundDroppableAccept = function (event, dragInfo) {
            return self._onBackgroundDroppableAccept(event, dragInfo);
        };

        this.diagramDesigner.onBackgroundDrop = function (event, dragInfo, position) {
            self._onBackgroundDrop(event, dragInfo, position);
        };

        this.diagramDesigner.onCheckChanged = function (value, isChecked) {
            self._onConnectionTypeFilterCheckChanged(value, isChecked);
        };

        this.diagramDesigner.onConnectionDstTextChanged = function (connId, oldValue, newValue) {
            self._onConnectionDstTextChanged(connId, oldValue, newValue);
        };

        this._oGetDragParams = this.diagramDesigner.getDragParams;
        this.diagramDesigner.getDragParams = function (selectedElements, event) {
            return self._getDragParams(selectedElements, event);
        };

        this.diagramDesigner.getDragItems = function (selectedElements) {
            return self._getDragItems(selectedElements);
        };

        this.diagramDesigner.onSelectionChanged = function (selectedIds) {
            self._onSelectionChanged(selectedIds);
        };

        this.diagramDesigner.onSetConnectionProperty = function (params) {
            self._onSetConnectionProperty(params);
        };

        this.logger.debug("attachDesignerCanvasEventHandlers finished");
    };

    /**********************************************************/
    /*  HANDLE OBJECT DRAG & DROP ACCEPTANCE                  */
    /**********************************************************/
    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onBackgroundDroppableAccept = function (event, dragInfo) {
        var gmeIDList = DragHelper.getDragItems(dragInfo),
            params = DragHelper.getDragParams(dragInfo),
            i,
            accept = false;

        //accept is self reposition OR dragging from somewhere else and the items are not on the sheet yet
        if (params && params.hasOwnProperty(DRAG_PARAMS_META_CONTAINER_ID)) {
            if (gmeIDList.length === 0) {
                accept = true;
            }
        } else {
            //return true if there is at least one item among the dragged ones that is not on the sheet yet
            if (gmeIDList.length > 0) {
                for (i = 0; i < gmeIDList.length; i+= 1) {
                    if (this._GMENodes.indexOf(gmeIDList[i]) === -1 ) {
                        accept = true;
                        break;
                    }
                }
            }
        }

        return accept;
    };
    /**********************************************************/
    /*  END OF --- HANDLE OBJECT DRAG & DROP ACCEPTANCE       */
    /**********************************************************/

    /**********************************************************/
    /*  HANDLE OBJECT DRAG & DROP TO SHEET                    */
    /**********************************************************/
    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onBackgroundDrop = function (event, dragInfo, position) {
        var gmeIDList = DragHelper.getDragItems(dragInfo),
            params = DragHelper.getDragParams(dragInfo),
            cNode = this._client.getNode(this.currentNodeInfo.id),
            registry = cNode.getEditableRegistry(this._META_EDITOR_REGISTRY_KEY) || this._emptyMetaEditorRegistry(),
            i,
            addMember,
            repositionMember,
            selectedIDs = [],
            componentID;

        addMember = function (gmeID, position) {
            var added = false;

            if (registry.Members.indexOf(gmeID) === -1) {
                registry.Members.push(gmeID);
                registry.MemberCoord[gmeID] = { "x": position.x,
                    "y": position.y};

                added = true;
            }

            return added;
        };

        repositionMember = function (gmeID, position) {
            if (registry.Members.indexOf(gmeID) !== -1) {
                registry.MemberCoord[gmeID] = { "x": position.x,
                    "y": position.y};
            }
        };

        //check to see it self drop and reposition or dropping fro somewhere else
        if (params && params.hasOwnProperty(DRAG_PARAMS_META_CONTAINER_ID) && params[DRAG_PARAMS_META_CONTAINER_ID] === this.currentNodeInfo.id) {
            if (gmeIDList.length === 0) {
                //params.position holds the old coordinates of the items being dragged
                //update UI
                this.diagramDesigner.beginUpdate();

                for (i in params.positions) {
                    if (params.positions.hasOwnProperty(i)) {
                        repositionMember(i, {'x': position.x + params.positions[i].x,
                                             'y': position.y + params.positions[i].y});

                        componentID = this._GMEID2ComponentID[i];

                        selectedIDs.push(componentID);
                        this.diagramDesigner.updateDesignerItem(componentID, { "position": {"x": position.x + params.positions[i].x, "y": position.y + params.positions[i].y }});
                    }
                }

                this.diagramDesigner.endUpdate();
                this.diagramDesigner.select(selectedIDs);
            }
        } else {
            //return true if there is at least one item among the dragged ones that is not on the sheet yet
            if (gmeIDList.length > 0) {
                for (i = 0; i < gmeIDList.length; i += 1) {
                    if (addMember(gmeIDList[i], position)) {
                        position.x += 20;
                        position.y += 20;
                    }
                }
            }
        }

        this._client.setRegistry(this.currentNodeInfo.id, this._META_EDITOR_REGISTRY_KEY, registry);
    };
    /**********************************************************/
    /*  END OF --- HANDLE OBJECT DRAG & DROP TO SHEET         */
    /**********************************************************/

    /**********************************************************/
    /*  HANDLE OBJECT REPOSITION IN THE ASPECT ASPECT         */
    /**********************************************************/
    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onDesignerItemsMove = function (repositionDesc) {
        var cNode = this._client.getNode(this.currentNodeInfo.id),
            registry = cNode.getEditableRegistry(this._META_EDITOR_REGISTRY_KEY) || this._emptyMetaEditorRegistry(),
            id,
            gmeID;

        for (id in repositionDesc) {
            if (repositionDesc.hasOwnProperty(id)) {
                gmeID = this._ComponentID2GMEID[id];
                if (registry.Members.indexOf(gmeID) !== -1) {
                    registry.MemberCoord[gmeID] = { "x": repositionDesc[id].x,
                        "y": repositionDesc[id].y};
                }
            }
        }

        this._client.setRegistry(this.currentNodeInfo.id, this._META_EDITOR_REGISTRY_KEY, registry);
    };
    /************************************************************/
    /* END OF --- HANDLE OBJECT REPOSITION IN THE ASPECT ASPECT */
    /************************************************************/

    /*************************************************************/
    /*  HANDLE OBJECT / CONNECTION DELETION IN THE ASPECT ASPECT */
    /*************************************************************/
    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onSelectionDelete = function (idList) {
        var cNode = this._client.getNode(this.currentNodeInfo.id),
            registry = cNode.getEditableRegistry(this._META_EDITOR_REGISTRY_KEY) || this._emptyMetaEditorRegistry(),
            len = idList.length,
            gmeID,
            idx,
            getAssociatedConnections,
            connectionListBySrcGMEID = this._connectionListBySrcGMEID,
            connectionListByDstGMEID = this._connectionListByDstGMEID,
            aConnections,
            deleteConnection,
            i,
            self = this;

        getAssociatedConnections = function (objectID) {
            var associatedConnectionIDs = [],
                otherID,
                connType,
                len,
                cID,
                checkConnections;

            checkConnections = function (cList) {
                //check objectID as source
                if (cList.hasOwnProperty(objectID)) {
                    for (otherID in cList[objectID]) {
                        if (cList[objectID].hasOwnProperty(otherID)) {
                            for (connType in cList[objectID][otherID]) {
                                if (cList[objectID][otherID].hasOwnProperty(connType)) {
                                    len = cList[objectID][otherID][connType].length;
                                    while (len--) {
                                        cID = cList[objectID][otherID][connType][len];
                                        if (associatedConnectionIDs.indexOf(cID) === -1) {
                                            associatedConnectionIDs.push(cID);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            };

            checkConnections(connectionListBySrcGMEID);
            checkConnections(connectionListByDstGMEID);

            return associatedConnectionIDs;
        };

        deleteConnection = function (connectionID) {
            var connDesc = self._connectionListByID[connectionID];

            if (connDesc.type === MetaRelations.META_RELATIONS.CONTAINMENT) {
                self._deleteContainmentRelationship(connDesc.GMESrcId, connDesc.GMEDstId);
            } else if (connDesc.type === MetaRelations.META_RELATIONS.POINTER) {
                self._deletePointerRelationship(connDesc.GMESrcId, connDesc.GMEDstId, connDesc.name);
            } else if (connDesc.type === MetaRelations.META_RELATIONS.INHERITANCE) {
                self._deleteInheritanceRelationship(connDesc.GMESrcId, connDesc.GMEDstId);
            }
            //TODO: PointerList deletion not yet handled
        };

        this._client.startTransaction();

        while (len--) {
            gmeID = this._ComponentID2GMEID[idList[len]];
            idx = registry.Members.indexOf(gmeID);
            if ( idx !== -1) {
                //entity is a box --> delete GME object from the sheet member's list and delete relationship definitions as well

                //handle associated connections
                //if GMEObject is a source of a connection (Containment / Pointer / PointerList relationship)
                // --> DELETE these relationship definitions from the node
                //if GMEObject is a destination of a connection (Inheritance)
                // --> inheritance is stored on the 'other' end, need to delete from the 'other' node
                aConnections = getAssociatedConnections(gmeID);
                i = aConnections.length;
                while (i--) {
                    deleteConnection(aConnections[i]);
                }

                //finally remove from members list
                registry.Members.splice(idx, 1);
                delete registry.MemberCoord[gmeID];
            } else if (this._connectionListByID.hasOwnProperty(idList[len])) {
                //entity is a connection, just simply delete it
                deleteConnection(idList[len]);
            }
        }

        this._client.setRegistry(this.currentNodeInfo.id, this._META_EDITOR_REGISTRY_KEY, registry);

        this._client.completeTransaction();
    };
    /************************************************************************/
    /*  END OF --- HANDLE OBJECT / CONNECTION DELETION IN THE ASPECT ASPECT */
    /************************************************************************/

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._getDragParams = function (selectedElements, event) {
        var oParams = this._oGetDragParams.call(this.diagramDesigner, selectedElements, event),
            params = { 'positions': {} },
            i;

        params[DRAG_PARAMS_META_CONTAINER_ID] = this.currentNodeInfo.id;

        for (i in oParams.positions) {
            if (oParams.positions.hasOwnProperty(i)) {
                params.positions[this._ComponentID2GMEID[i]] = oParams.positions[i];
            }
        }

        return params;
    };

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._getDragItems = function (selectedElements) {
        return [];
    };

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onSelectionChanged = function (selectedIds) {
        var gmeIDs = [],
            len = selectedIds.length,
            id,
            onlyConnectionTypeSelected = selectedIds.length > 0;

        while (len--) {
            id = this._ComponentID2GMEID[selectedIds[len]];
            if (id &&
                this.diagramDesigner.itemIds.indexOf(selectedIds[len]) !== -1) {
                gmeIDs.push(id);

                onlyConnectionTypeSelected = onlyConnectionTypeSelected && GMEConcepts.isConnectionType(id);
            } else {
                onlyConnectionTypeSelected = false;
            }
        }

        this.diagramDesigner.toolbarItems.ddbtnConnectionArrowStart.enabled(onlyConnectionTypeSelected);
        this.diagramDesigner.toolbarItems.ddbtnConnectionPattern.enabled(onlyConnectionTypeSelected);
        this.diagramDesigner.toolbarItems.ddbtnConnectionArrowEnd.enabled(onlyConnectionTypeSelected);

        //nobody is selected on the canvas
        //set the active selection to the opened guy
        if (gmeIDs.length === 0 && this.currentNodeInfo.id) {
            gmeIDs.push(this.currentNodeInfo.id);
        }

        if (gmeIDs.length !== 0) {
            this._client.setPropertyEditorIdList(gmeIDs);
        }
    };

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onSetConnectionProperty = function (params) {
        var items = params.items,
            visualParams = params.params,
            gmeIDs = [],
            len = items.length,
            id,
            connRegLineStyle;

        this._client.startTransaction();

        while (len--) {
            id = this._ComponentID2GMEID[items[len]];
            if (id && GMEConcepts.isConnectionType(id)) {
                connRegLineStyle = this._client.getNode(id).getEditableRegistry(nodePropertyNames.Registry.lineStyle);
                if (connRegLineStyle && !_.isEmpty(connRegLineStyle)) {
                    _.extend(connRegLineStyle, visualParams);
                    this._client.setRegistry(id, nodePropertyNames.Registry.lineStyle, connRegLineStyle);
                }
            }
        }

        this._client.completeTransaction();
    };

    return MetaEditorControlDiagramDesignerWidgetEventHandlers;
});
<commit_msg>enable dragging component from PArtBrowser on METAEditor<commit_after>"use strict";

define(['logManager',
    'clientUtil',
    'js/Constants',
    'js/NodePropertyNames',
    'js/Utils/GMEConcepts',
    './MetaRelations',
    'js/DragDrop/DragHelper'], function (logManager,
                                        util,
                                        CONSTANTS,
                                        nodePropertyNames,
                                        GMEConcepts,
                                        MetaRelations,
                                        DragHelper) {

    var MetaEditorControlDiagramDesignerWidgetEventHandlers,
        DRAG_PARAMS_META_CONTAINER_ID = 'metaContainerID';

    MetaEditorControlDiagramDesignerWidgetEventHandlers = function () {
    };

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype.attachDiagramDesignerWidgetEventHandlers = function () {
        var self = this;

        /*OVERRIDE DESIGNER CANVAS METHODS*/
        this.diagramDesigner.onDesignerItemsMove = function (repositionDesc) {
            self._onDesignerItemsMove(repositionDesc);
        };

        this.diagramDesigner.onCreateNewConnection = function (params) {
            self._onCreateNewConnection(params);
        };

        this.diagramDesigner.onSelectionDelete = function (idList) {
            self._onSelectionDelete(idList);
        };

        this.diagramDesigner.onBackgroundDroppableAccept = function (event, dragInfo) {
            return self._onBackgroundDroppableAccept(event, dragInfo);
        };

        this.diagramDesigner.onBackgroundDrop = function (event, dragInfo, position) {
            self._onBackgroundDrop(event, dragInfo, position);
        };

        this.diagramDesigner.onCheckChanged = function (value, isChecked) {
            self._onConnectionTypeFilterCheckChanged(value, isChecked);
        };

        this.diagramDesigner.onConnectionDstTextChanged = function (connId, oldValue, newValue) {
            self._onConnectionDstTextChanged(connId, oldValue, newValue);
        };

        this._oGetDragParams = this.diagramDesigner.getDragParams;
        this.diagramDesigner.getDragParams = function (selectedElements, event) {
            return self._getDragParams(selectedElements, event);
        };

        this.diagramDesigner.getDragItems = function (selectedElements) {
            return self._getDragItems(selectedElements);
        };

        this.diagramDesigner.onSelectionChanged = function (selectedIds) {
            self._onSelectionChanged(selectedIds);
        };

        this.diagramDesigner.onSetConnectionProperty = function (params) {
            self._onSetConnectionProperty(params);
        };

        this.logger.debug("attachDesignerCanvasEventHandlers finished");
    };

    /**********************************************************/
    /*  HANDLE OBJECT DRAG & DROP ACCEPTANCE                  */
    /**********************************************************/
    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onBackgroundDroppableAccept = function (event, dragInfo) {
        var gmeIDList = DragHelper.getDragItems(dragInfo),
            params = DragHelper.getDragParams(dragInfo),
            dragEffects = DragHelper.getDragEffects(dragInfo),
            i,
            accept = false,
            PROJECT_META_ID = this._client.getNode(this.currentNodeInfo.id).getRegistry(nodePropertyNames.Registry.ProjectRegistry)[CONSTANTS.PROJECT_META_ID];

        //accept is self reposition OR dragging from somewhere else and the items are not on the sheet yet
        if (params && params.hasOwnProperty(DRAG_PARAMS_META_CONTAINER_ID)) {
            if (gmeIDList.length === 0) {
                accept = true;
            }
        } else {
            if (gmeIDList.length === 1 &&
                dragEffects.length === 1 &&
                dragEffects[0] === DragHelper.DRAG_EFFECTS.DRAG_CREATE_INSTANCE) {
                //dragging from PartBrowser
                //if the dragged item can be a valid children, let it drop
                if (GMEConcepts.canCreateChild(PROJECT_META_ID, gmeIDList[0])) {
                    accept = true;
                }
            } else {
                //return true if there is at least one item among the dragged ones that is not on the sheet yet
                if (gmeIDList.length > 0) {
                    for (i = 0; i < gmeIDList.length; i+= 1) {
                        if (this._GMENodes.indexOf(gmeIDList[i]) === -1 ) {
                            accept = true;
                            break;
                        }
                    }
                }
            }
        }

        return accept;
    };
    /**********************************************************/
    /*  END OF --- HANDLE OBJECT DRAG & DROP ACCEPTANCE       */
    /**********************************************************/

    /**********************************************************/
    /*  HANDLE OBJECT DRAG & DROP TO SHEET                    */
    /**********************************************************/
    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onBackgroundDrop = function (event, dragInfo, position) {
        var gmeIDList = DragHelper.getDragItems(dragInfo),
            params = DragHelper.getDragParams(dragInfo),
            cNode = this._client.getNode(this.currentNodeInfo.id),
            registry = cNode.getEditableRegistry(this._META_EDITOR_REGISTRY_KEY) || this._emptyMetaEditorRegistry(),
            i,
            addMember,
            repositionMember,
            selectedIDs = [],
            componentID,
            dragEffects = DragHelper.getDragEffects(dragInfo),
            PROJECT_META_ID = this._client.getNode(this.currentNodeInfo.id).getRegistry(nodePropertyNames.Registry.ProjectRegistry)[CONSTANTS.PROJECT_META_ID],
            createChildParams,
            newID;

        addMember = function (gmeID, position) {
            var added = false;

            if (registry.Members.indexOf(gmeID) === -1) {
                registry.Members.push(gmeID);
                registry.MemberCoord[gmeID] = { "x": position.x,
                    "y": position.y};

                added = true;
            }

            return added;
        };

        repositionMember = function (gmeID, position) {
            if (registry.Members.indexOf(gmeID) !== -1) {
                registry.MemberCoord[gmeID] = { "x": position.x,
                    "y": position.y};
            }
        };

        this._client.startTransaction();

        //check to see it self drop and reposition or dropping fro somewhere else
        if (params && params.hasOwnProperty(DRAG_PARAMS_META_CONTAINER_ID) && params[DRAG_PARAMS_META_CONTAINER_ID] === this.currentNodeInfo.id) {
            if (gmeIDList.length === 0) {
                //params.position holds the old coordinates of the items being dragged
                //update UI
                this.diagramDesigner.beginUpdate();

                for (i in params.positions) {
                    if (params.positions.hasOwnProperty(i)) {
                        repositionMember(i, {'x': position.x + params.positions[i].x,
                                             'y': position.y + params.positions[i].y});

                        componentID = this._GMEID2ComponentID[i];

                        selectedIDs.push(componentID);
                        this.diagramDesigner.updateDesignerItem(componentID, { "position": {"x": position.x + params.positions[i].x, "y": position.y + params.positions[i].y }});
                    }
                }

                this.diagramDesigner.endUpdate();
                this.diagramDesigner.select(selectedIDs);
            }
        } else {
            if (gmeIDList.length === 1 &&
                dragEffects.length === 1 &&
                dragEffects[0] === DragHelper.DRAG_EFFECTS.DRAG_CREATE_INSTANCE) {
                //dragging from PartBrowser
                //if the dragged item can be a valid children, let it drop
                if (GMEConcepts.canCreateChild(PROJECT_META_ID, gmeIDList[0])) {
                    createChildParams = { "parentId": PROJECT_META_ID,
                        "baseId": gmeIDList[0]};

                    newID = this._client.createChild(createChildParams);

                    if (newID) {
                        addMember(newID, position);
                            //store new position
                        this._client.setRegistry(newID, nodePropertyNames.Registry.position, {'x': position.x,
                                'y': position.y});
                    }
                }
            } else {
                //return true if there is at least one item among the dragged ones that is not on the sheet yet
                if (gmeIDList.length > 0) {
                    for (i = 0; i < gmeIDList.length; i += 1) {
                        if (addMember(gmeIDList[i], position)) {
                            position.x += 20;
                            position.y += 20;
                        }
                    }
                }
            }
        }

        this._client.setRegistry(this.currentNodeInfo.id, this._META_EDITOR_REGISTRY_KEY, registry);

        this._client.completeTransaction();
    };
    /**********************************************************/
    /*  END OF --- HANDLE OBJECT DRAG & DROP TO SHEET         */
    /**********************************************************/

    /**********************************************************/
    /*  HANDLE OBJECT REPOSITION IN THE ASPECT ASPECT         */
    /**********************************************************/
    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onDesignerItemsMove = function (repositionDesc) {
        var cNode = this._client.getNode(this.currentNodeInfo.id),
            registry = cNode.getEditableRegistry(this._META_EDITOR_REGISTRY_KEY) || this._emptyMetaEditorRegistry(),
            id,
            gmeID;

        for (id in repositionDesc) {
            if (repositionDesc.hasOwnProperty(id)) {
                gmeID = this._ComponentID2GMEID[id];
                if (registry.Members.indexOf(gmeID) !== -1) {
                    registry.MemberCoord[gmeID] = { "x": repositionDesc[id].x,
                        "y": repositionDesc[id].y};
                }
            }
        }

        this._client.setRegistry(this.currentNodeInfo.id, this._META_EDITOR_REGISTRY_KEY, registry);
    };
    /************************************************************/
    /* END OF --- HANDLE OBJECT REPOSITION IN THE ASPECT ASPECT */
    /************************************************************/

    /*************************************************************/
    /*  HANDLE OBJECT / CONNECTION DELETION IN THE ASPECT ASPECT */
    /*************************************************************/
    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onSelectionDelete = function (idList) {
        var cNode = this._client.getNode(this.currentNodeInfo.id),
            registry = cNode.getEditableRegistry(this._META_EDITOR_REGISTRY_KEY) || this._emptyMetaEditorRegistry(),
            len = idList.length,
            gmeID,
            idx,
            getAssociatedConnections,
            connectionListBySrcGMEID = this._connectionListBySrcGMEID,
            connectionListByDstGMEID = this._connectionListByDstGMEID,
            aConnections,
            deleteConnection,
            i,
            self = this;

        getAssociatedConnections = function (objectID) {
            var associatedConnectionIDs = [],
                otherID,
                connType,
                len,
                cID,
                checkConnections;

            checkConnections = function (cList) {
                //check objectID as source
                if (cList.hasOwnProperty(objectID)) {
                    for (otherID in cList[objectID]) {
                        if (cList[objectID].hasOwnProperty(otherID)) {
                            for (connType in cList[objectID][otherID]) {
                                if (cList[objectID][otherID].hasOwnProperty(connType)) {
                                    len = cList[objectID][otherID][connType].length;
                                    while (len--) {
                                        cID = cList[objectID][otherID][connType][len];
                                        if (associatedConnectionIDs.indexOf(cID) === -1) {
                                            associatedConnectionIDs.push(cID);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            };

            checkConnections(connectionListBySrcGMEID);
            checkConnections(connectionListByDstGMEID);

            return associatedConnectionIDs;
        };

        deleteConnection = function (connectionID) {
            var connDesc = self._connectionListByID[connectionID];

            if (connDesc.type === MetaRelations.META_RELATIONS.CONTAINMENT) {
                self._deleteContainmentRelationship(connDesc.GMESrcId, connDesc.GMEDstId);
            } else if (connDesc.type === MetaRelations.META_RELATIONS.POINTER) {
                self._deletePointerRelationship(connDesc.GMESrcId, connDesc.GMEDstId, connDesc.name);
            } else if (connDesc.type === MetaRelations.META_RELATIONS.INHERITANCE) {
                self._deleteInheritanceRelationship(connDesc.GMESrcId, connDesc.GMEDstId);
            }
            //TODO: PointerList deletion not yet handled
        };

        this._client.startTransaction();

        while (len--) {
            gmeID = this._ComponentID2GMEID[idList[len]];
            idx = registry.Members.indexOf(gmeID);
            if ( idx !== -1) {
                //entity is a box --> delete GME object from the sheet member's list and delete relationship definitions as well

                //handle associated connections
                //if GMEObject is a source of a connection (Containment / Pointer / PointerList relationship)
                // --> DELETE these relationship definitions from the node
                //if GMEObject is a destination of a connection (Inheritance)
                // --> inheritance is stored on the 'other' end, need to delete from the 'other' node
                aConnections = getAssociatedConnections(gmeID);
                i = aConnections.length;
                while (i--) {
                    deleteConnection(aConnections[i]);
                }

                //finally remove from members list
                registry.Members.splice(idx, 1);
                delete registry.MemberCoord[gmeID];
            } else if (this._connectionListByID.hasOwnProperty(idList[len])) {
                //entity is a connection, just simply delete it
                deleteConnection(idList[len]);
            }
        }

        this._client.setRegistry(this.currentNodeInfo.id, this._META_EDITOR_REGISTRY_KEY, registry);

        this._client.completeTransaction();
    };
    /************************************************************************/
    /*  END OF --- HANDLE OBJECT / CONNECTION DELETION IN THE ASPECT ASPECT */
    /************************************************************************/

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._getDragParams = function (selectedElements, event) {
        var oParams = this._oGetDragParams.call(this.diagramDesigner, selectedElements, event),
            params = { 'positions': {} },
            i;

        params[DRAG_PARAMS_META_CONTAINER_ID] = this.currentNodeInfo.id;

        for (i in oParams.positions) {
            if (oParams.positions.hasOwnProperty(i)) {
                params.positions[this._ComponentID2GMEID[i]] = oParams.positions[i];
            }
        }

        return params;
    };

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._getDragItems = function (selectedElements) {
        return [];
    };

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onSelectionChanged = function (selectedIds) {
        var gmeIDs = [],
            len = selectedIds.length,
            id,
            onlyConnectionTypeSelected = selectedIds.length > 0;

        while (len--) {
            id = this._ComponentID2GMEID[selectedIds[len]];
            if (id &&
                this.diagramDesigner.itemIds.indexOf(selectedIds[len]) !== -1) {
                gmeIDs.push(id);

                onlyConnectionTypeSelected = onlyConnectionTypeSelected && GMEConcepts.isConnectionType(id);
            } else {
                onlyConnectionTypeSelected = false;
            }
        }

        this.diagramDesigner.toolbarItems.ddbtnConnectionArrowStart.enabled(onlyConnectionTypeSelected);
        this.diagramDesigner.toolbarItems.ddbtnConnectionPattern.enabled(onlyConnectionTypeSelected);
        this.diagramDesigner.toolbarItems.ddbtnConnectionArrowEnd.enabled(onlyConnectionTypeSelected);

        //nobody is selected on the canvas
        //set the active selection to the opened guy
        if (gmeIDs.length === 0 && this.currentNodeInfo.id) {
            gmeIDs.push(this.currentNodeInfo.id);
        }

        if (gmeIDs.length !== 0) {
            this._client.setPropertyEditorIdList(gmeIDs);
        }
    };

    MetaEditorControlDiagramDesignerWidgetEventHandlers.prototype._onSetConnectionProperty = function (params) {
        var items = params.items,
            visualParams = params.params,
            gmeIDs = [],
            len = items.length,
            id,
            connRegLineStyle;

        this._client.startTransaction();

        while (len--) {
            id = this._ComponentID2GMEID[items[len]];
            if (id && GMEConcepts.isConnectionType(id)) {
                connRegLineStyle = this._client.getNode(id).getEditableRegistry(nodePropertyNames.Registry.lineStyle);
                if (connRegLineStyle && !_.isEmpty(connRegLineStyle)) {
                    _.extend(connRegLineStyle, visualParams);
                    this._client.setRegistry(id, nodePropertyNames.Registry.lineStyle, connRegLineStyle);
                }
            }
        }

        this._client.completeTransaction();
    };

    return MetaEditorControlDiagramDesignerWidgetEventHandlers;
});
<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import React, { Component } from 'react'
import classnames from 'classnames'
import { isValidEmail } from '~client/utils/validation-helper'
import AnalyticsEvents from '~client/mobilizations/widgets/utils/analytics-events'

if (require('exenv').canUseDOM) require('./index.scss')

// TODO: Reusable Input
const controlClassname = 'px3 py1'
const inputReset = {
  border: 'none',
  padding: '0',
  height: '2rem',
  outline: 'none'
}

class PressureForm extends Component {
  constructor (props) {
    super(props)
    this.state = {
      email: '',
      name: '',
      lastname: '',
      city: '',
      subject: props.subject,
      body: props.body
    }
  }

  validate () {
    const { targetList, widget: { settings: { show_city: showCity } } } = this.props
    const requiredMsg = 'Preenchimento obrigatório'
    const errors = { valid: true }

    if (!this.state.email) {
      errors.email = requiredMsg
    } else if (!isValidEmail(this.state.email)) {
      errors.email = 'E-mail inválido'
    } else if (targetList.some(target => target.match(`<${this.state.email}>`))) {
      errors.email = 'O email que você está tentando usar é de um dos alvos da mobilização.'
    }
    if (!this.state.name) {
      errors.name = requiredMsg
    }
    if (!this.state.lastname) {
      errors.lastname = requiredMsg
    }
    if (showCity === 'city-true' && !this.state.city) {
      errors.city = requiredMsg
    }
    if (!this.state.subject) {
      errors.subject = requiredMsg
    }
    if (!this.state.body) {
      errors.body = requiredMsg
    }

    if (Object.keys(errors).length > 1) {
      errors.valid = false
    }
    return errors
  }

  handleSubmit (e) {
    e.preventDefault()
    const { onSubmit } = this.props
    const errors = this.validate()
    if (!errors.valid) {
      this.setState({ errors })
    } else {
      onSubmit && onSubmit(this.state)
    }
  }

  render () {
    const { buttonColor, buttonText, children, widget } = this.props
    const { email, name, lastname, city, subject, body, errors } = this.state
    return (
      <form className='pressure-form' onSubmit={::this.handleSubmit}>

                E-mail
                {(errors && errors['email'] && {errors['email']})}

              <input
                id='pressure-sender-email-id'
                className='col-12'
                style={inputReset}
                onBlur={::AnalyticsEvents.pressureIsFilled}
                type='email'
                placeholder='Insira seu e-mail'
                value={email}
                onChange={e => this.setState({ email: e.target.value })}
              />

                Nome
                {(errors && errors['name'] && {errors['name']})}

              <input
                className='col-12'
                style={inputReset}
                type='text'
                placeholder='Insira seu nome'
                value={name}
                onChange={e => this.setState({ name: e.target.value })}
              />

                Sobrenome
                {(errors && errors['lastname'] && {errors['lastname']})}

              <input
                className='col-12'
                style={inputReset}
                type='text'
                placeholder='Insira seu sobrenome'
                value={lastname}
                onChange={e => this.setState({ lastname: e.target.value })}
              />

            {
              !widget.settings.show_city || widget.settings.show_city !== 'city-true' ? null : (

                    Cidade
                    {(errors && errors['city'] && {errors['city']})}

                  <input
                    className='col-12'
                    style={inputReset}
                    type='text'
                    placeholder='Insira sua cidade'
                    value={city}
                    onChange={e => this.setState({ city: e.target.value })}
                  />

              )
            }

                Assunto
                {(errors && errors['subject'] && {errors['subject']})}

              <input
                id='pressure-subject-id'
                className='col-12'
                style={inputReset}
                type='text'
                value={subject}
                onChange={e => this.setState({ subject: e.target.value })}
              />

                Corpo do e-mail
                {(errors && errors['body'] && {errors['body']})}

              <textarea
                id='pressure-body-id'
                className='col-12 mt1'
                style={{...inputReset, height: '7rem'}}
                value={body}
                onChange={e => this.setState({ body: e.target.value })}
              />

            <button
              type='submit'
              onClick={::this.handleSubmit}
              className='btn caps white col-12 py2 rounded'
              style={{ backgroundColor: buttonColor }}
            >
              {buttonText}
            </button>

        {children}
      </form>
    )
  }
}

PressureForm.propTypes = {
  onSubmit: PropTypes.func,
  buttonColor: PropTypes.string,
  buttonText: PropTypes.string,
  subject: PropTypes.string,
  body: PropTypes.string,
  widget: PropTypes.object
}

PressureForm.defaultProps = {
  subject: '',
  body: ''
}

export default PressureForm
<commit_msg>chore(pressure-widget): check if targetList is defined fix #728<commit_after>import PropTypes from 'prop-types'
import React, { Component } from 'react'
import classnames from 'classnames'
import { isValidEmail } from '~client/utils/validation-helper'
import AnalyticsEvents from '~client/mobilizations/widgets/utils/analytics-events'

if (require('exenv').canUseDOM) require('./index.scss')

// TODO: Reusable Input
const controlClassname = 'px3 py1'
const inputReset = {
  border: 'none',
  padding: '0',
  height: '2rem',
  outline: 'none'
}

class PressureForm extends Component {
  constructor (props) {
    super(props)
    this.state = {
      email: '',
      name: '',
      lastname: '',
      city: '',
      subject: props.subject,
      body: props.body
    }
  }

  validate () {
    const { targetList, widget: { settings: { show_city: showCity } } } = this.props
    const requiredMsg = 'Preenchimento obrigatório'
    const errors = { valid: true }

    if (!this.state.email) {
      errors.email = requiredMsg
    } else if (!isValidEmail(this.state.email)) {
      errors.email = 'E-mail inválido'
    } else if (targetList && targetList.some(target => target.match(`<${this.state.email}>`))) {
      errors.email = 'O email que você está tentando usar é de um dos alvos da mobilização.'
    }
    if (!this.state.name) {
      errors.name = requiredMsg
    }
    if (!this.state.lastname) {
      errors.lastname = requiredMsg
    }
    if (showCity === 'city-true' && !this.state.city) {
      errors.city = requiredMsg
    }
    if (!this.state.subject) {
      errors.subject = requiredMsg
    }
    if (!this.state.body) {
      errors.body = requiredMsg
    }

    if (Object.keys(errors).length > 1) {
      errors.valid = false
    }
    return errors
  }

  handleSubmit (e) {
    e.preventDefault()
    const { onSubmit } = this.props
    const errors = this.validate()
    if (!errors.valid) {
      this.setState({ errors })
    } else {
      onSubmit && onSubmit(this.state)
    }
  }

  render () {
    const { buttonColor, buttonText, children, widget } = this.props
    const { email, name, lastname, city, subject, body, errors } = this.state
    return (
      <form className='pressure-form' onSubmit={::this.handleSubmit}>

                E-mail
                {(errors && errors['email'] && {errors['email']})}

              <input
                id='pressure-sender-email-id'
                className='col-12'
                style={inputReset}
                onBlur={::AnalyticsEvents.pressureIsFilled}
                type='email'
                placeholder='Insira seu e-mail'
                value={email}
                onChange={e => this.setState({ email: e.target.value })}
              />

                Nome
                {(errors && errors['name'] && {errors['name']})}

              <input
                className='col-12'
                style={inputReset}
                type='text'
                placeholder='Insira seu nome'
                value={name}
                onChange={e => this.setState({ name: e.target.value })}
              />

                Sobrenome
                {(errors && errors['lastname'] && {errors['lastname']})}

              <input
                className='col-12'
                style={inputReset}
                type='text'
                placeholder='Insira seu sobrenome'
                value={lastname}
                onChange={e => this.setState({ lastname: e.target.value })}
              />

            {
              !widget.settings.show_city || widget.settings.show_city !== 'city-true' ? null : (

                    Cidade
                    {(errors && errors['city'] && {errors['city']})}

                  <input
                    className='col-12'
                    style={inputReset}
                    type='text'
                    placeholder='Insira sua cidade'
                    value={city}
                    onChange={e => this.setState({ city: e.target.value })}
                  />

              )
            }

                Assunto
                {(errors && errors['subject'] && {errors['subject']})}

              <input
                id='pressure-subject-id'
                className='col-12'
                style={inputReset}
                type='text'
                value={subject}
                onChange={e => this.setState({ subject: e.target.value })}
              />

                Corpo do e-mail
                {(errors && errors['body'] && {errors['body']})}

              <textarea
                id='pressure-body-id'
                className='col-12 mt1'
                style={{...inputReset, height: '7rem'}}
                value={body}
                onChange={e => this.setState({ body: e.target.value })}
              />

            <button
              type='submit'
              onClick={::this.handleSubmit}
              className='btn caps white col-12 py2 rounded'
              style={{ backgroundColor: buttonColor }}
            >
              {buttonText}
            </button>

        {children}
      </form>
    )
  }
}

PressureForm.propTypes = {
  onSubmit: PropTypes.func,
  buttonColor: PropTypes.string,
  buttonText: PropTypes.string,
  subject: PropTypes.string,
  body: PropTypes.string,
  widget: PropTypes.object
}

PressureForm.defaultProps = {
  subject: '',
  body: ''
}

export default PressureForm
<|endoftext|>


<commit_before>
window.onload = function () {
    // Display knowledge network Stats.
    fetchStats();
};

/*
 * Function to read .tab file containing stats about the knowledge network & its mappings & to update
 * them in the release.html webpage.
 */
function fetchStats() {
    var fileUrl = api_url + "/latestNetworkStats";
//console.log("Fetching Network stats from: "+ fileUrl);
    $.get(fileUrl).done(function (data) {
        var resp = data.stats.split("\n");
        var totalGenes = fetchValue(resp[1]);
        var totalConcepts = fetchValue(resp[2]);
        var totalRelations = fetchValue(resp[3]);
        var geneEvidenceConcepts = fetchValue(resp[4]);
        var minValues = fetchValue(resp[6]);
        var maxValues = fetchValue(resp[7]);
        var avgValues = fetchValue(resp[8]);
        // calculate concept occurrence percentage.
        var conceptPercentage = (geneEvidenceConcepts / (totalConcepts - totalGenes)) * 100;
        conceptPercentage = conceptPercentage.toFixed(2);

        // Display stats data.
        var statsText = "

Total number of genes: " + totalGenes + "
" +
                "
Total concepts: " + totalConcepts + "
" +
                "
Total relations: " + totalRelations + "
" +
                "
Concept2Gene #mappings: " + geneEvidenceConcepts + " (" + conceptPercentage + "%)
" +
                "
Size of the gene-evidence networks:" +
                "

Min.: " + minValues + "
" +
                "
Max.: " + maxValues + "
" +
                "
Average: " + avgValues + "

" +
                "

";

        // Tabular breakdown of all conceptTypes and their count.
        var cc_table = "Detailed breakdown:

" +
                "
";

        var ccArray = [];
        var cc_bool = false, rel_bool = false, ccEvi_bool = false; // Define first Bools
        var loop_count = 0;

        for (var i = 11; i < resp.length - 2; i++) {

            if (resp[i].toString().includes("<cc_count>")) {
                if (cc_bool === false) {
                    cc_table += "Type	" +
                            "#Total
"
                    cc_bool = true;
                }
                var cc = fetchValue(resp[i]).split("=");
                ccArray.push(cc[1]);
                cc_table += "
" + cc[0] + "	" +
                        "" + cc[1] + "
";
            }

            if (resp[i].toString().includes("<ccEvi>")) {
                if (ccEvi_bool === false) {
                    cc_table += "

#Linked	" +
                            "Linked (%)
"
                    ccEvi_bool = true;
                }
                var conEvi = fetchValue(resp[i]).split("=>");
                var conEviPercentage = (parseFloat(conEvi[1]) / parseFloat(ccArray[loop_count])) * 100;
                conEviPercentage = conEviPercentage.toFixed(2);
                cc_table += "
" + conEvi[1] + "	" +
                        "" + conEviPercentage + "
";
                loop_count++;
            }

            if (resp[i].toString().includes("<hubiness>")) {
                if (rel_bool === false) {
                    cc_table += "

#Total/#Relations (%)
";
                    rel_bool = true;
                }
                var rel = fetchValue(resp[i]).split("->");
                cc_table += "
" + rel[1] + "
";
            }
        }

        cc_table += "

";

        $("#network_stats").append(statsText + cc_table);
    }
    ).fail(function () {
        console.log("Error occurred while retrieving Network details");
    });
}

function fetchValue(valText) {
    var start = valText.indexOf(">");
    var end = valText.indexOf("</");
    var val = valText.substring(start + 1, end);
    return val;
}<commit_msg>Improved network stats output<commit_after>
window.onload = function () {
    // Display knowledge network Stats.
    fetchStats();
};

/*
 * Function to read .tab file containing stats about the knowledge network & its mappings & to update
 * them in the release.html webpage.
 */
function fetchStats() {
    var fileUrl = api_url + "/latestNetworkStats";
//console.log("Fetching Network stats from: "+ fileUrl);
    $.get(fileUrl).done(function (data) {
        var resp = data.stats.split("\n");
        var totalGenes = fetchValue(resp[1]);
        var totalConcepts = fetchValue(resp[2]);
        var totalRelations = fetchValue(resp[3]);
        var geneEvidenceConcepts = fetchValue(resp[4]);
        var minValues = fetchValue(resp[6]);
        var maxValues = fetchValue(resp[7]);
        var avgValues = fetchValue(resp[8]);
        // calculate concept occurrence percentage.
        var conceptPercentage = (geneEvidenceConcepts / (totalConcepts - totalGenes)) * 100;
        conceptPercentage = conceptPercentage.toFixed(2);

        // Display stats data.
        var statsText = "

Total number of genes: " + totalGenes + "
" +
                "
Total concepts: " + totalConcepts + "
" +
                "
Total relations: " + totalRelations + "
" +
                "
Concept2Gene #mappings: " + geneEvidenceConcepts + " (" + conceptPercentage + "%)
" +
                "
Size of the gene-evidence networks:" +
                "

Min.: " + minValues + "
" +
                "
Max.: " + maxValues + "
" +
                "
Average: " + avgValues + "

" +
                "

";

        // Tabular breakdown of all conceptTypes and their count.
        var cc_table = "Detailed breakdown:

" +
                "
"+
                "Type	" +
                "#Total
";

        var ccArray = [];
        var rel_bool = false, ccEvi_bool = false; // Define first Bools
        var loop_count = 0;

        for (var i = 11; i < resp.length - 2; i++) {

            if (resp[i].toString().includes("<cc_count>")) {
                var cc = fetchValue(resp[i]).split("=");
                ccArray.push(cc[1]);
                cc_table += "
" + cc[0] + "	" +
                        "" + cc[1] + "
";
            }

            if (resp[i].toString().includes("<ccEvi>")) {
                if (ccEvi_bool === false) {
                    cc_table += "

#Linked	" +
                            "%Linked
"
                    ccEvi_bool = true;
                }
                var conEvi = fetchValue(resp[i]).split("=>");
                var conEviPercentage = (parseFloat(conEvi[1]) / parseFloat(ccArray[loop_count])) * 100;
                conEviPercentage = conEviPercentage.toFixed(2);
                cc_table += "
" + conEvi[1] + "	" +
                        "" + conEviPercentage + "
";
                loop_count++;
            }

            if (resp[i].toString().includes("<hubiness>")) {
                if (rel_bool === false) {
                    cc_table += "

Avg.degree
";
                    rel_bool = true;
                }
                var rel = fetchValue(resp[i]).split("->");
                cc_table += "
" + rel[1] + "
";
            }
        }

        cc_table += "

";

        $("#network_stats").append(statsText + cc_table);
        $("#network_stats th:contains('Avg.degree')").attr('title', "The Avg.degree calculates the average degree of inwards and outwards connections to a node, given as (for each concept) the total number of unique relationships divided by the total number of concepts present in the graph");
    }
    ).fail(function () {
        console.log("Error occurred while retrieving Network details");
    });
}

function fetchValue(valText) {
    var start = valText.indexOf(">");
    var end = valText.indexOf("</");
    var val = valText.substring(start + 1, end);
    return val;
}<|endoftext|>


<commit_before>/*
*
* Write a program to calculate the credit card balance after one year
* if a person only pays the minimum monthly payment required by the credit
* card company each month.
*
* Use raw_input() to ask for the following three floating point numbers:
*
*   1. the outstanding balance on the credit card
*   2. annual interest rate
*   3. minimum monthly payment rate
*
* For each month, print the minimum monthly payment, remaining balance,
*   principle paid in the format shown in the test cases below.
* All numbers should be rounded to the nearest penny. Finally, print the result,
*   which should include the total amount paid that year and the remaining balance.
*
*   - Minimum monthly payment = Minimum monthly payment rate x Balance
*       (Minimum monthly payment gets split into interest paid and principal paid)
*   - Interest Paid = Annual interest rate / 12 months x Balance
*   - Principal paid = Minimum monthly payment – Interest paid
*   - Remaining balance = Balance – Principal paid
*
* http://bit.ly/1S6Tdys
*
*/
var prompt = require( 'prompt' );

prompt.start();
prompt.get([
  {
    name : 'balance',
    description : 'Enter the outstanding balance on your credit card'
  },
  {
    name : 'interest',
    description : 'Enter the annual credit card interest rate as a decimal'
  },
  {
    name : 'minimum',
    description : 'Enter the minimum monthly payment rate as a decimal'
  }
], function( err, results ) {

  console.log( results );

  // constants
  var PERIOD            = 12;

  // input variables
  var initialBalance    = parseFloat( results.balance, 10 );
  var interest          = parseFloat( results.interest, 10 );
  var minPayment        = parseFloat( results.minimum, 10 );

  console.log( initialBalance );
  console.log( interest );
  console.log( minPayment );

  // output variables
  var month             = 1;
  var minMonthlyPayment = 0;
  var principalPaid     = 0;
  var actualBalance     = initialBalance;

  // helper variables
  var actualMinPayment  = 0;
  var actualInterest    = 0;
  var totalPaid         = 0;

  while( month <= PERIOD ) {

    actualMinPayment = minPayment * actualBalance;
    actualInterest   = ( interest / 12 ) * actualBalance;
    principalPaid    = actualMinPayment - actualInterest;
    actualBalance    -= principalPaid;
    totalPaid        += actualMinPayment;

    console.log( 'Month:', month );
    console.log( 'Minimum monthly payment:', actualMinPayment.toFixed( 2 ));
    console.log( 'Principal paid:', principalPaid.toFixed( 2 ));
    console.log( 'Remaining balance', actualBalance.toFixed( 2 ), '\n' );

    month += 1;

  }

  console.log( '======= RESULT =======');
  console.log( 'Total amount paid:', totalPaid.toFixed( 2 ));
  console.log( 'Remaining balance', actualBalance.toFixed( 2 ), '\n' );

});
<commit_msg>Intro to CS - problem set 01.01 - add info problem set 01.01<commit_after>/*
*
* Author: Eric Douglas
* Finished: 20/06/2015
*
* Write a program to calculate the credit card balance after one year
* if a person only pays the minimum monthly payment required by the credit
* card company each month.
*
* Use raw_input() to ask for the following three floating point numbers:
*
*   1. the outstanding balance on the credit card
*   2. annual interest rate
*   3. minimum monthly payment rate
*
* For each month, print the minimum monthly payment, remaining balance,
*   principle paid in the format shown in the test cases below.
* All numbers should be rounded to the nearest penny. Finally, print the result,
*   which should include the total amount paid that year and the remaining balance.
*
*   - Minimum monthly payment = Minimum monthly payment rate x Balance
*       (Minimum monthly payment gets split into interest paid and principal paid)
*   - Interest Paid = Annual interest rate / 12 months x Balance
*   - Principal paid = Minimum monthly payment – Interest paid
*   - Remaining balance = Balance – Principal paid
*
* http://bit.ly/1S6Tdys
*
*/
var prompt = require( 'prompt' );

prompt.start();
prompt.get([
  {
    name : 'balance',
    description : 'Enter the outstanding balance on your credit card'
  },
  {
    name : 'interest',
    description : 'Enter the annual credit card interest rate as a decimal'
  },
  {
    name : 'minimum',
    description : 'Enter the minimum monthly payment rate as a decimal'
  }
], function( err, results ) {

  console.log( results );

  // constants
  var PERIOD            = 12;

  // input variables
  var initialBalance    = parseFloat( results.balance, 10 );
  var interest          = parseFloat( results.interest, 10 );
  var minPayment        = parseFloat( results.minimum, 10 );

  console.log( initialBalance );
  console.log( interest );
  console.log( minPayment );

  // output variables
  var month             = 1;
  var minMonthlyPayment = 0;
  var principalPaid     = 0;
  var actualBalance     = initialBalance;

  // helper variables
  var actualMinPayment  = 0;
  var actualInterest    = 0;
  var totalPaid         = 0;

  while( month <= PERIOD ) {

    actualMinPayment = minPayment * actualBalance;
    actualInterest   = ( interest / 12 ) * actualBalance;
    principalPaid    = actualMinPayment - actualInterest;
    actualBalance    -= principalPaid;
    totalPaid        += actualMinPayment;

    console.log( 'Month:', month );
    console.log( 'Minimum monthly payment:', actualMinPayment.toFixed( 2 ));
    console.log( 'Principal paid:', principalPaid.toFixed( 2 ));
    console.log( 'Remaining balance', actualBalance.toFixed( 2 ), '\n' );

    month += 1;

  }

  console.log( '======= RESULT =======');
  console.log( 'Total amount paid:', totalPaid.toFixed( 2 ));
  console.log( 'Remaining balance', actualBalance.toFixed( 2 ), '\n' );

});
<|endoftext|>