<commit_before>var fragmentVisualization = function (element, fragmentId, queryPlan) {
    drawCharts(element, fragmentId, queryPlan);

    // return variables that are needed outside this scope
    return {};
}

function drawCharts(element, fragmentId, queryPlan) {
    drawArea(element, fragmentId, queryPlan.queryId);
    drawLanes(element, fragmentId, queryPlan.queryId);
}

// Draw the area plot and the mini-brush and big-brush for it
function drawArea(element, fragmentId, queryId) {

    var workers_data = [];

    var margin = {top: 10, right: 10, bottom: 60, left:20 },
        margin2 = {top: 160, right:10, bottom: 20, left:20},
        width = parseInt(element.style('width'), 10) - margin.left - margin.right,
        height = 200 - margin.top - margin.bottom,
        height2 = 200 - margin2.top - margin2.bottom;

    var x = d3.scale.linear().range([0, width]),
        x2 = d3.scale.linear().range([0, width]),
        y = d3.scale.linear().range([height, 0]),
        y2 = d3.scale.linear().range([height2, 0]);

    var xAxis = d3.svg.axis()
        .scale(x)
        .tickFormat(customTimeFormat)
        .tickSize(-height)
        .orient("bottom");

    var xAxis2 = d3.svg.axis()
        .scale(x2)
        .tickFormat(customTimeFormat)
        .tickSize(-height2)
        .orient("bottom");

    var yAxis = d3.svg.axis()
        .scale(y)
        .tickFormat(d3.format("d"))
        .orient("left");

    var brush = d3.svg.brush()
                  .x(x2)
                  .on("brush", brushed);

    var brush2 = d3.svg.brush()
                      .x(x)
                      .on("brushend", brushend_workers);

    // Area 1 generator
    var area = d3.svg.area()
        .interpolate("step-after")
        .x(function(d) { return x(d.time); })
        .y0(height)
        .y1(function(d) { return y(d.value.length); });

    // Area 2 generator
    var area2 = d3.svg.area()
        .interpolate("step-after")
        .x(function(d) { return x2(d.time); })
        .y0(height2)
        .y1(function(d) { return y2(d.value.length); });

    // Contour line generator
    var line = d3.svg.line()
        .interpolate("step-after")
        .x(function(d) { return x(d.time); })
        .y(function(d) { return y(d.value.length); });

    // Svg element to draw the fragment utilization plot
    var svg = element.append("svg")
                     .attr("width", width + margin.left + margin.right)
                     .attr("height", height + margin.top + margin.bottom)
                     .attr("class", "line-plot")
                     .attr("id", "fragment_utilization");

    svg.append("defs").append("clipPath")
       .attr("id", "clip")
      .append("rect")
       .attr("width", width)
       .attr("height", height);

    // Place the plot/big_brush
    var plot = svg.append("g")
        .attr("class", "plot")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Place the mini-brush
    var mini_brush = svg.append("g")
        .attr("class", "context")
        .attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");

    var url = templates.urls.histogram({
        query: queryId,
        fragment: fragmentId
    });

    d3.csv(url, type, function(error, data) {
        x.domain(d3.extent(data.map(function(d) { return d.time; })));
        y.domain([0, d3.max(data.map(function(d) { return d.value.length; }))]);
        x2.domain(x.domain());
        y2.domain(y.domain());

        plot.append("path")
             .attr("clip-path", "url(#clip)")
             .datum(data)
             .attr("class", "area")
             .attr("d", area);

        plot.append("path")
             .attr("clip-path", "url(#clip)")
             .datum(data)
             .attr("class", "line")
             .attr("d", line);

        plot.append("g")
             .attr("class", "x axis")
             .attr("transform", "translate(0," + height + ")")
             .call(xAxis);

        plot.append("g")
             .attr("class", "y axis")
             .call(yAxis);

        plot.append("g")
               .attr("class", "x brush")
               .call(brush2)
               .selectAll("rect")
               .attr("y", -6)
               .attr("height", height + 7);

        mini_brush.append("path")
               .attr("clip-path", "url(#clip)")
               .datum(data)
               .attr("class", "area")
               .attr("d", area2);

        mini_brush.append("g")
               .attr("class", "x axis")
               .attr("transform", "translate(0," + height2 + ")")
               .call(xAxis2);

        mini_brush.append("g")
               .attr("class", "x brush")
               .call(brush)
               .selectAll("rect")
               .attr("y", -6)
               .attr("height", height2 + 7);
    });

    function brushed() {
        x.domain(brush.empty() ? x2.domain() : brush.extent());
        plot.select(".area").attr("d", area);
        plot.select(".plot path.line").attr("d", line);
        plot.select(".x.axis").call(xAxis);
    }

    function brushend_workers() {
        //cal brush on the other svg...
    }

    function type(d) {
        d.time = parseFloat(d.time, 10);
        d.value = JSON.parse( d.value);
        return d;
    }
}

function drawLanes(element, fragmentId, queryId) {

    /* Collect data for states at each worker */
    var url = templates.urls.profiling({
        myria: myriaConnection,
        query: queryId,
        fragment: fragmentId
    });

    d3.csv(url, type2, function(error, data) {
        var workers_data = get_workers_states(data);
        redrawLanes(element, workers_data);
    });

    function type2(d) {
        d.workerId = +d.workerId;
        d.nanoTime = parseFloat(d.nanoTime);
        d.numTuples = +d.numTuples;
        return d;
    }

    function get_workers_states(data) {
	// TODO: this parsing function assumes the following about the data
	// received:
        //   - events are sorted at each worker

        // Create a structure:
        //  workers = [{ workerId : [{opName : [{}]}] }]
        //
        // TODO: use queryPlan to put the right lane numbers?

        var workers_states = {};
        var tmp_stacks = {}; // a stack per worker keeps unfinished operator calls

        data.forEach(function(d) {
            stack = tmp_stacks[d.workerId];
            if (stack == null || stack.length === 0) {
                tmp_stacks[d.workerId] = [];
                tmp_stacks[d.workerId].push(get_state(d));
                return;
            }

	    // event on top of stack completed (we now know its endTime), add
	    // to states
	    states = workers_states[d.workerId];
            if (states == null) {
                states = [];
                workers_states[d.workerId] = states;
            }

            top_stack = stack[stack.length - 1];
            top_stack.end = d.nanoTime;
            states.push(top_stack);

            // check the event type and push unfinished event on the stack
            if (d.eventType === "call") {
                stack.push(get_state(d));
            } else if (d.eventType === "return") {
                // it's a return, update the link and replace top of stack
                // with a new, same opName event that starts from d.nanoTime
                stack.pop();
                if (stack.length > 0) {
                    top_stack = stack[stack.length - 1];
                    state = get_state(d);
                    state.name = top_stack.name; // the same call
                    top_stack.link = state; // belong together (still in the same call thread)
                    stack.pop();
                    stack.push(state);
               } //TODO: eos??
            }
        });

        function get_state(d) {
            return {
                      "link" : null,           // if it belongs together with some previous event
                      "name" : d.opName,
                      "begin": d.nanoTime,
                      "end"  : null,           // we don't know this yet ...
                      "lane" : 0
                   };
        }

        return workers_states;
    }
}

function redrawLanes(element, workers_data) {
    // Remove what was previously drawn
    d3.select("#fragment_workers").remove();

    var fullHeight =  Object.keys(workers_data).length * 50;

    var margin = {top: 10, right: 10, bottom: 20, left: 20},
        width = parseInt(element.style('width'), 10) - margin.left - margin.right,
        height = fullHeight - margin.top - margin.bottom;

    var x = d3.scale.linear().range([0, width]),
        y = d3.scale.ordinal().rangeRoundBands([height, 0], 0.2, 0.1);

    y.domain(_.keys(workers_data));

    // TODO: fix this!
    x.domain([769116, 5615629916]);

    var xAxis = d3.svg.axis()
                  .scale(x)
                  .tickFormat(customTimeFormat)
                  .tickSize(-height)
                  .orient("bottom");

    var yAxis = d3.svg.axis()
                  .scale(y)
                  .orient("left");

    // Add lanes chart
    var svg = element.append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .attr("id", "fragment_workers");
      //.append("g")
      //  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Place the lanes plot
    var lanes = svg.append("g")
           .attr("class", "plot")
           .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Place the xAxis
    lanes.append("g")
             .attr("class", "x axis")
             .attr("transform", "translate(0," + height + ")")
             .call(xAxis);

    for (worker in workers_data) {
        drawBoxes(lanes, workers_data[worker], worker, x, y);
        //return;
    }

    function brushend_workers() {

    }

}

var opColors = d3.scale.category20();
var opToColor = {};

function colorForOperator(opname) {
    if (opname in opToColor) {
        return opToColor[opname];
    }
    opToColor[opname] = opColors(Object.keys(opToColor).length);
    return opToColor[opname];
}

function drawBoxes(lanes, worker_data, lane, x, y) {

    //debug(worker_data);

    var box = lanes.selectAll("rect")
                   //TODO: is the key map function lane + d.begin  unique??
                   .data(worker_data, function(d) {return lane + d.begin;});

    box.enter().append("rect")
            //.attr("clip-path", "url(#clip)")
            .style("fill", function(d) { return colorForOperator(d.name); })
            .style("stroke", function(d) { return d3.rgb(colorForOperator(d.name)).darker(0.5); })
            .attr("class", "box");

    box.attr("x", function(d) { return x(d.begin);})
        .attr("width", function(d, i) {
            return x(d.end) - x(d.begin);
        })
        .transition()
        //.duration(animationDuration)
        .attr("y", function(d) { return y(lane);})
        .attr("height", function(d) {
            return y.rangeBand();
        });

    // TODO: replace this function
    function hashCode(str) {
        var hash = 0;
        if (str.length == 0) return hash;
        for (i = 0; i < str.length; i++) {
            char = str.charCodeAt(i);
            hash = ((hash<<5)-hash)+char;
            hash = hash & hash; // Convert to 32bit integer
        }
        return hash;
    }
}

<commit_msg>Added brushes - can we speed this up?<commit_after>var fragmentVisualization = function (element, fragmentId, queryPlan) {
    drawCharts(element, fragmentId, queryPlan);

    // return variables that are needed outside this scope
    return {};
}

function drawCharts(element, fragmentId, queryPlan) {
    var lanesChart = drawLanes(element, fragmentId, queryPlan.queryId);
    drawArea(element, fragmentId, queryPlan.queryId, lanesChart);
}

// Draw the area plot and the mini-brush and big-brush for it
function drawArea(element, fragmentId, queryId, lanesChart) {

    var margin = {top: 10, right: 10, bottom: 60, left:20 },
        margin2 = {top: 160, right:10, bottom: 20, left:20},
        width = parseInt(element.style('width'), 10) - margin.left - margin.right,
        height = 200 - margin.top - margin.bottom,
        height2 = 200 - margin2.top - margin2.bottom;

    var x = d3.scale.linear().range([0, width]),
        x2 = d3.scale.linear().range([0, width]),
        y = d3.scale.linear().range([height, 0]),
        y2 = d3.scale.linear().range([height2, 0]);

    var xAxis = d3.svg.axis()
        .scale(x)
        .tickFormat(customTimeFormat)
        .tickSize(-height)
        .orient("bottom");

    var xAxis2 = d3.svg.axis()
        .scale(x2)
        .tickFormat(customTimeFormat)
        .tickSize(-height2)
        .orient("bottom");

    var yAxis = d3.svg.axis()
        .scale(y)
        .tickFormat(d3.format("d"))
        .orient("left");

    var brush = d3.svg.brush()
                  .x(x2)
                  .on("brush", brushed);

    var brush2 = d3.svg.brush()
                      .x(x)
                      .on("brushend", brushend_workers);

    // Area 1 generator
    var area = d3.svg.area()
        .interpolate("step-after")
        .x(function(d) { return x(d.time); })
        .y0(height)
        .y1(function(d) { return y(d.value.length); });

    // Area 2 generator
    var area2 = d3.svg.area()
        .interpolate("step-after")
        .x(function(d) { return x2(d.time); })
        .y0(height2)
        .y1(function(d) { return y2(d.value.length); });

    // Contour line generator
    var line = d3.svg.line()
        .interpolate("step-after")
        .x(function(d) { return x(d.time); })
        .y(function(d) { return y(d.value.length); });

    // Svg element to draw the fragment utilization plot
    //var svg = element.append("svg")
    var svg = element.insert("svg", ":first-child")
                     .attr("width", width + margin.left + margin.right)
                     .attr("height", height + margin.top + margin.bottom)
                     .attr("class", "line-plot")
                     .attr("id", "fragment_utilization");

    svg.append("defs").append("clipPath")
       .attr("id", "clip")
      .append("rect")
       .attr("width", width)
       .attr("height", height);

    // Place the plot/big_brush
    var plot = svg.append("g")
        .attr("class", "plot")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Place the mini-brush
    var mini_brush = svg.append("g")
        .attr("class", "context")
        .attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");

    var url = templates.urls.histogram({
        query: queryId,
        fragment: fragmentId
    });

    //d3.csv('js/aggregated.data', type, function(error, data) {
    d3.csv(url, type, function(error, data) {
        x.domain(d3.extent(data.map(function(d) { return d.time; })));
        y.domain([0, d3.max(data.map(function(d) { return d.value.length; }))]);
        x2.domain(x.domain());
        y2.domain(y.domain());

        plot.append("path")
             .attr("clip-path", "url(#clip)")
             .datum(data)
             .attr("class", "area")
             .attr("d", area);

        plot.append("path")
             .attr("clip-path", "url(#clip)")
             .datum(data)
             .attr("class", "line")
             .attr("d", line);

        plot.append("g")
             .attr("class", "x axis")
             .attr("transform", "translate(0," + height + ")")
             .call(xAxis);

        plot.append("g")
             .attr("class", "y axis")
             .call(yAxis);

        plot.append("g")
               .attr("class", "x brush")
               .call(brush2)
               .selectAll("rect")
               .attr("y", -6)
               .attr("height", height + 7);

        mini_brush.append("path")
               .attr("clip-path", "url(#clip)")
               .datum(data)
               .attr("class", "area")
               .attr("d", area2);

        mini_brush.append("g")
               .attr("class", "x axis")
               .attr("transform", "translate(0," + height2 + ")")
               .call(xAxis2);

        mini_brush.append("g")
               .attr("class", "x brush")
               .call(brush)
               .selectAll("rect")
               .attr("y", -6)
               .attr("height", height2 + 7);
    });

    function brushed() {
        x.domain(brush.empty() ? x2.domain() : brush.extent());
        plot.select(".area").attr("d", area);
        plot.select(".plot path.line").attr("d", line);
        plot.select(".x.axis").call(xAxis);
    }

    function brushend_workers() {
        //called brush; modify the lanes Chart ...
        //compute the visible workers

        lanesChart.redrawLanes(element,
                               lanesChart.workers_data,
                               lanesChart.x,
                               lanesChart.y,
                               lanesChart.xAxis,
                               lanesChart.yAxis,
                               brush2.extent());

        x.domain(brush2.empty() ? x2.domain() : brush2.extent());
        plot.select(".area").attr("d", area);
        plot.select(".plot path.line").attr("d", line);
        plot.select(".x.axis").call(xAxis);

        brush.extent(brush2.extent());
        d3.select(".context .x.brush").call(brush);
        d3.select(".plot .x.brush").call(brush2.clear());
    }

    function type(d) {
        d.time = parseFloat(d.time, 10);
        d.value = JSON.parse( d.value);
        return d;
    }
}

function drawLanes(element, fragmentId, queryId) {

    //var fullHeight =  _.keys(workers_data).length * 50;
    var fullHeight = 400;

    var margin = {top: 10, right: 10, bottom: 20, left: 20},
        width = parseInt(element.style('width'), 10) - margin.left - margin.right,
        height = fullHeight - margin.top - margin.bottom;
    var x = d3.scale.linear().clamp(true).range([0, width]),
        y = d3.scale.ordinal().rangeRoundBands([height, 0], 0.2, 0.1);

    var xAxis = d3.svg.axis()
        .scale(x)
        .tickFormat(customTimeFormat)
        .tickSize(-height)
        .orient("bottom");

    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left");

    // Add lanes chart
    var svg = element.append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .attr("id", "fragment_workers");

    // Place the lanes plot
    var lanes = svg.append("g")
        .attr("class", "plot")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Place the xAxis
    lanes.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    /* Collect data for states at each worker */
    var url = templates.urls.profiling({
        myria: myriaConnection,
        query: queryId,
        fragment: fragmentId
    });

    var workers_data={};

    //d3.csv('js/worker.data', type2, function(error, data) {
    d3.csv(url, type2, function(error, data) {
        // copy the results into the workers_data
        // don't allocate another object as we return the
        // reference to this one
        var t_workers_data = get_workers_states(data);
        for (k in t_workers_data) {
             workers_data[k] = t_workers_data[k];
         }
         redrawLanes(element, workers_data, x, y, xAxis, yAxis, [0,0]);
    });

    function type2(d) {
        d.workerId = +d.workerId;
        d.nanoTime = parseFloat(d.nanoTime);
        d.numTuples = +d.numTuples;
        return d;
    }

    function get_workers_states(data) {
	// TODO: this parsing function assumes the following about the data
	// received:
        //   - events are sorted at each worker

        // Create a structure:
        //  workers = [{ workerId : [{opName : [{}]}] }]
        //
        // TODO: use queryPlan to put the right lane numbers?

        var workers_states = {};
        var tmp_stacks = {}; // a stack per worker keeps unfinished operator calls

        data.forEach(function(d) {
            stack = tmp_stacks[d.workerId];
            if (stack == null || stack.length === 0) {
                tmp_stacks[d.workerId] = [];
                tmp_stacks[d.workerId].push(get_state(d));
                return;
            }

	    // event on top of stack completed (we now know its endTime), add
	    // to states
	    states = workers_states[d.workerId];
            if (states == null) {
                states = [];
                workers_states[d.workerId] = states;
            }

            top_stack = stack[stack.length - 1];
            top_stack.end = d.nanoTime;
            states.push(top_stack);

            // check the event type and push unfinished event on the stack
            if (d.eventType === "call") {
                stack.push(get_state(d));
            } else if (d.eventType === "return") {
                // it's a return, update the link and replace top of stack
                // with a new, same opName event that starts from d.nanoTime
                stack.pop();
                if (stack.length > 0) {
                    top_stack = stack[stack.length - 1];
                    state = get_state(d);
                    state.name = top_stack.name; // the same call
                    top_stack.link = state; // belong together (still in the same call thread)
                    stack.pop();
                    stack.push(state);
               } //TODO: eos??
            }
        });

        function get_state(d) {
            return {
                      "link" : null,           // if it belongs together with some previous event
                      "name" : d.opName,
                      "begin": d.nanoTime,
                      "end"  : null,           // we don't know this yet ...
                      "lane" : 0
                   };
        }

        return workers_states;
    }

    var redrawLanes = function (element, workers_data, x, y, xAxis, yAxis, x_domain) {

        // Remove what was previously drawn
        // d3.select("#fragment_workers").remove();
        y.domain(_.keys(workers_data));
        x.domain(x_domain);

        var lanes = d3.select("#fragment_workers .plot");

        for (worker in workers_data) {
            drawBoxes(lanes, workers_data[worker], worker, x, y);
        }

        lanes.select("g.x.axis").call(xAxis);
    }

    return {
                "workers_data" : workers_data,
                "redrawLanes" : redrawLanes,
                "x" : x,
                "y" : y,
                "xAxis" : xAxis,
                "yAxis" : yAxis
           }
}

var opColors = d3.scale.category20();
var opToColor = {};

function colorForOperator(opname) {
    if (opname in opToColor) {
        return opToColor[opname];
    }
    opToColor[opname] = opColors(_.keys(opToColor).length);
    return opToColor[opname];
}

function drawBoxes(lanes, worker_data, lane, x, y) {

    var box = lanes.selectAll("rect")
                   //TODO: is the key map function lane + d.begin  unique??
                   .data(worker_data, function(d) {return lane + d.begin;});

    box.enter().append("rect")
            //.attr("clip-path", "url(#clip)")
            .style("fill", function(d) { return colorForOperator(d.name); })
            .style("stroke", function(d) { return d3.rgb(colorForOperator(d.name)).darker(0.5); })
            .attr("class", "box");

    box.attr("x", function(d) { return x(d.begin);})
        .attr("width", function(d, i) {
            return x(d.end) - x(d.begin);
        })
        .transition()
        //.duration(animationDuration)
        .attr("y", function(d) { return y(lane);})
        .attr("height", function(d) {
            return y.rangeBand();
        });

    // TODO: replace this function
    function hashCode(str) {
        var hash = 0;
        if (str.length == 0) return hash;
        for (i = 0; i < str.length; i++) {
            char = str.charCodeAt(i);
            hash = ((hash<<5)-hash)+char;
            hash = hash & hash; // Convert to 32bit integer
        }
        return hash;
    }
}

<|endoftext|>


<commit_before>"use strict";
/*
 * ooiui/static/js/views/common/TOCView.js
 * View definitions to build the table of contents
 *
 * Dependencies
 * Libs
 * - ooiui/static/lib/underscore/underscore.js
 * - ooiui/static/lib/backbone/backbone.js
 * - ooiui/static/js/ooi.js
 */

var TOCView = Backbone.View.extend({
    id: 'assetBrowser',
    events: {
        'keyup #search-filter' : 'filterToc'
    },
    initialize: function(options){
        _.bindAll(this, "render", "derender", "renderArrays", "renderAssets", "renderStreams");
        this.arrayCollection = options.arrayCollection;
        this.assetCollection = options.assetCollection;
        this.streamCollection = options.streamCollection;
        this.listenTo(vent, 'toc:derenderItems', function() {
            this.derender();
        });
    },
    template: JST['ooiui/static/js/partials/TOC.html'],
    renderArrays: function(){
        var arrayContainerView = this.arrayCollection.map(function(model) {
            return (new ArrayContainerView({ model:model })).render().el;
        });
        this.$el.find('ul.nav-list').append(arrayContainerView);
    },
    renderAssets: function(){
        // create a model for each item in the collection, and based on it's class,
        // render it in the browser as a platform or instrument.
        var filteredPlatforms = this.assetCollection.byClass('.AssetRecord');
        var filteredInstruments = this.assetCollection.byClass('.InstrumentAssetRecord');
        var log = [];
        filteredPlatforms.map(function(model) {
            try {
                // get the array code from the reference designator
                var arrayCode = model.get('ref_des').substr(0,2);
                // set the target to where this item will be inserted.
                var arrayTarget = '#array_'+ arrayCode;
                if ( document.getElementById( model.get('ref_des').substring(0,14)) == null ) {
                    // TODO: Remove this - only for 08/17/2015 demo: M@C
                    if ( !(model.get('ref_des').indexOf('-0000') > -1 ) ) {
                        var assetItemView = new AssetItemView({ model:model });
                        $( arrayTarget ).append( assetItemView.render().el );
                    }
                }
            } catch(e) {
                //log.push("Platform with invalid reference designator:" + model.get('id'));
                //console.log(e);
            }
        });

        filteredInstruments.map(function(model) {
            try {
                if ( document.getElementById(model.get('ref_des')) == null ) {
                    var platformCode = model.get('ref_des').substr(0,14);
                    // set the target to where this item will be inserted.

                    if ( document.getElementById(platformCode) == null ) {
                        platformCode = model.get('ref_des').substr(0,8);
                    }
                    var platformTarget = 'ul#'+platformCode;
                    var assetItemView = new AssetItemView({ model:model });
                    $( platformTarget ).append( assetItemView.render().el );
                }
            }catch (e) {
                //log.push("Instrument with invalid reference designator:" + model.get('id'));
                //console.log(e);
            }
        });
        if (log.length > 0) {
            var errorObj = { 'TOC error' : log };
            console.log(errorObj);
        }
    },
    renderStreams: function(e) {
        if ( this.streamCollection != undefined ) {
            this.streamCollection.map( function(model) {
                try {
                    var instrumentCode = model.get('reference_designator');
                    var streamName = model.get('stream_name');
                    /* Not all streams have physical instruments, so the asset list won't
                     * render the streams.  If there are streams that have been detached
                     * from their instrument / platform, we'll need to append a 'logical'
                     * tree for the streams to live.*/
                    if ( document.getElementById(instrumentCode) == null ){
                        var arrayCode = model.get('reference_designator').substring(0,2);
                        var platformCode = model.get('reference_designator').substring(0,14);
                        if ( document.getElementById(platformCode) == null ) {
                            //platform
                            model.attributes.asset_class = '.AssetRecord';
                            var homelessStreamItem = new HomelessStreamItemView({ model: model });
                            $.when( $('ul#array_'+arrayCode).append( homelessStreamItem.render().el ) ).done( function() {
                                //instrument
                                model.attributes.asset_class = '.InstrumentAssetRecord';
                                var homelessStreamItem = new HomelessStreamItemView({ model: model });
                                $.when($('ul#'+platformCode).append( homelessStreamItem.render().el )).done(function() {
                                    //stream
                                    var instrumentTarget = 'ul#'+instrumentCode;
                                    var streamItemView = new StreamItemView({ model:model });
                                    $(instrumentTarget).append(streamItemView.render().el);
                                });
                            });
                        } else {
                            //instrument
                            model.attributes.asset_class = '.InstrumentAssetRecord';
                            var homelessStreamItem = new HomelessStreamItemView({ model: model });
                            $.when($('ul#'+platformCode).append( homelessStreamItem.render().el )).done(function() {
                                //stream
                                var instrumentTarget = 'ul#'+instrumentCode;
                                var streamItemView = new StreamItemView({ model:model });
                                $(instrumentTarget).append(streamItemView.render().el);
                            });
                        }

                    } else {
                        //stream
                        var instrumentTarget = 'ul#'+instrumentCode;
                        var streamItemView = new StreamItemView({ model:model });
                        $(instrumentTarget).append(streamItemView.render().el);
                    }
                } catch (e) {
                    console.log(e);
                }
            });
        }
    },
    render: function(){
        this.$el.html(this.template());
        this.renderArrays();
        this.$el.find('[data-toggle="tooltip"]').tooltip();
        return this;
    },
    derender: function() {
        this.remove();
        this.unbind();
    },
});

var SearchResultView = Backbone.View.extend({
    tagName: 'ul',
    initialize: function() {
        _.bindAll(this, 'render', 'derender', 'onClick');
        this.listenTo(vent, 'toc:derenderItems', function() {
            this.derender();
        });

    },
    onClick: function() {
        ooi.trigger('toc:selectItem', this.model);
    },
    render: function(){
        var assetItemView = this.collection.map(function(model) {
            var coord = model.get('coordinates');
            if (coord) {
                return(new AssetItemView({ model:model }).render().el);
            }
        });
        this.$el.html(assetItemView);
        return this;
    },
    derender: function() {
        this.remove();
        this.unbind();
    },
});

var ArrayContainerView = Backbone.View.extend({
    tagName: 'li',
    attributes: function(){
        return {
            'style': 'width:100%'
        };
    },
    events:{
        'click .nav-header' : 'onClick',
        'click label.tree-toggler': 'collapse'
    },
    initialize: function(options) {
        _.bindAll(this, 'render', 'onClick', 'collapse');
        this.listenTo(vent, 'toc:derenderItems', function() {
            this.derender();
        });
    },
    collapse: function(e) {
        e.stopImmediatePropagation();
        this.$el.children('ul.tree').toggle(300);
    },
    onClick: function(e) {
                 ooi.trigger('toc:selectArray', this.model);
             },
    template: JST['ooiui/static/js/partials/ArrayItem.html'],
    render: function(){
        this.$el.html( this.template(this.model.toJSON()) );
        this.$el.append('

');
        return this;
    },
    derender: function() {
        this.remove();
        this.unbind();
        this.model.off;
    },
})

var AssetItemView = Backbone.View.extend({
    tagName: 'li',
    events: {
        'click label.platform': 'onClickPlatform',
        'click label.instrument': 'onClickInstrument',
        'click label.tree-toggler': 'collapse'
    },
    initialize: function(options) {
        _.bindAll(this,'render', 'onClickPlatform', 'onClickInstrument', 'collapse');
        this.listenTo(vent, 'toc:derenderItems', function() {
            this.derender();
        });
        this.listenTo(vent, 'toc:cleanUp', function() {
            // if the item doesn't have any children, grey it out.
            if ( this.$el.find('ul.tree').children().length == 0 ) {
                this.$el.addClass('no-children');
            }
        });
        this.listenTo(vent, 'toc:hideInstruments', function() {
            if(this.model.get('asset_class') == '.InstrumentAssetRecord') {
                this.$el.attr('style','display:none;');
            }
        });
    },
    onClickPlatform: function(e) {
        $(".active-toc-item").removeClass("active-toc-item");
        this.$('label:first').addClass("active-toc-item");
        ooi.trigger('toc:selectPlatform', this.model);
    },
    onClickInstrument: function() {
        $(".active-toc-item").removeClass("active-toc-item");
        this.$('label:first').addClass("active-toc-item");
        ooi.trigger('toc:selectInstrument', this.model);
    },
    collapse: function(e) {
        e.stopImmediatePropagation();
        this.$el.children('ul.tree').toggle(300);
    },
    derender: function() {
        this.remove();
        this.unbind();
        this.model.off;
    },
    render: function() {
        // If the asset class is an AssetRecord, give the view an ID of the
        // first 8 characters of the Reference Designator
        if (this.model.get('asset_class') == '.AssetRecord') {
            var platformName = this.model.get('assetInfo').name;
            var platformId = this.model.get('ref_des').substr(0,8);
            var assName = this.model.get('ref_des').substr(9,14);
            assName = (assName.length > 0) ? '-' + assName : "";
            this.$el.attr('id', platformId+assName);
            this.$el.attr('class', 'platform');
            // since this is an AssetRecord (platform / glider) lets assume
            // it'll need to have instruments attached to it...so create a container!
            var label = (platformName == undefined) ? platformId+assName : '' + platformName + ' | ' + platformId + assName +'';
            this.$el.append(''+ label + '

');
        } else if(this.model.get('asset_class') == '.InstrumentAssetRecord') {
            // otherwise, if it's an InstrumentAssetRecord then give the view an ID
            // of the entire Reference Designator
            var instrumentName = this.model.get('assetInfo').name;
            var instrumentId = this.model.get('ref_des');
            this.$el.attr('id', instrumentId);
            this.$el.attr('class', 'instrument');
            var label = (instrumentName == undefined) ? instrumentId : '' + instrumentName + '' + instrumentId.substr(9,27) + '';
            this.$el.append(''+ label + '

');
        }
        return this;
    }
});

var HomelessStreamItemView = AssetItemView.extend({
    render: function() {
        if ( this.model.get('asset_class') == '.AssetRecord' ) {
            this.model.set('ref_des', this.model.get('reference_designator'));
            var platformId = this.model.get('reference_designator').substr(0,14);
            this.$el.attr('id', platformId);
            var platformName = this.model.get('platform_name');
            this.$el.attr('class', 'platform detached');
            var label = (platformName == undefined) ? platformId : '' + platformName + ' | ' + platformId.substr(0,8)+'';
            this.$el.append(''+ label + '

');
        }
        if ( this.model.get('asset_class') == '.InstrumentAssetRecord' ) {
            this.model.set('ref_des', this.model.get('reference_designator'));
            var instrumentId = this.model.get('ref_des');
            var instrumentName = this.model.get('display_name');
            this.$el.attr('id', instrumentId);
            this.$el.attr('class', 'instrument detached');
            var label = (instrumentName == undefined) ? instrumentId : '' + instrumentName + '' + instrumentId.substr(9,27) + '';
            this.$el.append(''+ label + '

');
        }
        return this
    }
});

var StreamItemView = Backbone.View.extend({
    tagName: 'li',
    events: {
        'click a': 'onClick'
    },
    initialize: function(options) {
        _.bindAll(this, 'render', 'derender', 'onClick');
        this.listenTo(vent, 'toc:denrenderItems', function() {
            this.derender();
        });
        this.listenTo(vent, 'toc:hideStreams', function() {
            this.$el.attr('style','display:none;');
        });
    },
    onClick: function(e) {
        $(".active-toc-item").removeClass("active-toc-item");
        e.stopImmediatePropagation();
        $(e.target).addClass("active-toc-item");
        ooi.trigger('toc:selectStream', { model: this.model });
    },
    template: _.template('<%= stream_name %>'),
    derender: function() {
        this.remove();
        this.unbind();
        this.model.off;
    },
    render: function() {
        this.$el.attr('id', this.model.get('reference_designator') + '-' + this.model.get('stream_name'));
        this.$el.html( this.template(this.model.toJSON()) );
        return this;
    }
});
<commit_msg>Update the url as you click on ToC<commit_after>"use strict";
/*
 * ooiui/static/js/views/common/TOCView.js
 * View definitions to build the table of contents
 *
 * Dependencies
 * Libs
 * - ooiui/static/lib/underscore/underscore.js
 * - ooiui/static/lib/backbone/backbone.js
 * - ooiui/static/js/ooi.js
 */

var TOCView = Backbone.View.extend({
    id: 'assetBrowser',
    events: {
        'keyup #search-filter' : 'filterToc'
    },
    initialize: function(options){
        _.bindAll(this, "render", "derender", "renderArrays", "renderAssets", "renderStreams");
        this.arrayCollection = options.arrayCollection;
        this.assetCollection = options.assetCollection;
        this.streamCollection = options.streamCollection;
        this.listenTo(vent, 'toc:derenderItems', function() {
            this.derender();
        });
    },
    template: JST['ooiui/static/js/partials/TOC.html'],
    renderArrays: function(){
        var arrayContainerView = this.arrayCollection.map(function(model) {
            return (new ArrayContainerView({ model:model })).render().el;
        });
        this.$el.find('ul.nav-list').append(arrayContainerView);
    },
    renderAssets: function(){
        // create a model for each item in the collection, and based on it's class,
        // render it in the browser as a platform or instrument.
        var filteredPlatforms = this.assetCollection.byClass('.AssetRecord');
        var filteredInstruments = this.assetCollection.byClass('.InstrumentAssetRecord');
        var log = [];
        filteredPlatforms.map(function(model) {
            try {
                // get the array code from the reference designator
                var arrayCode = model.get('ref_des').substr(0,2);
                // set the target to where this item will be inserted.
                var arrayTarget = '#array_'+ arrayCode;
                if ( document.getElementById( model.get('ref_des').substring(0,14)) == null ) {
                    // TODO: Remove this - only for 08/17/2015 demo: M@C
                    if ( !(model.get('ref_des').indexOf('-0000') > -1 ) ) {
                        var assetItemView = new AssetItemView({ model:model });
                        $( arrayTarget ).append( assetItemView.render().el );
                    }
                }
            } catch(e) {
                //log.push("Platform with invalid reference designator:" + model.get('id'));
                //console.log(e);
            }
        });

        filteredInstruments.map(function(model) {
            try {
                if ( document.getElementById(model.get('ref_des')) == null ) {
                    var platformCode = model.get('ref_des').substr(0,14);
                    // set the target to where this item will be inserted.

                    if ( document.getElementById(platformCode) == null ) {
                        platformCode = model.get('ref_des').substr(0,8);
                    }
                    var platformTarget = 'ul#'+platformCode;
                    var assetItemView = new AssetItemView({ model:model });
                    $( platformTarget ).append( assetItemView.render().el );
                }
            }catch (e) {
                //log.push("Instrument with invalid reference designator:" + model.get('id'));
                //console.log(e);
            }
        });
        if (log.length > 0) {
            var errorObj = { 'TOC error' : log };
            console.log(errorObj);
        }
    },
    renderStreams: function(e) {
        if ( this.streamCollection != undefined ) {
            this.streamCollection.map( function(model) {
                try {
                    var instrumentCode = model.get('reference_designator');
                    var streamName = model.get('stream_name');
                    /* Not all streams have physical instruments, so the asset list won't
                     * render the streams.  If there are streams that have been detached
                     * from their instrument / platform, we'll need to append a 'logical'
                     * tree for the streams to live.*/
                    if ( document.getElementById(instrumentCode) == null ){
                        var arrayCode = model.get('reference_designator').substring(0,2);
                        var platformCode = model.get('reference_designator').substring(0,14);
                        if ( document.getElementById(platformCode) == null ) {
                            //platform
                            model.attributes.asset_class = '.AssetRecord';
                            var homelessStreamItem = new HomelessStreamItemView({ model: model });
                            $.when( $('ul#array_'+arrayCode).append( homelessStreamItem.render().el ) ).done( function() {
                                //instrument
                                model.attributes.asset_class = '.InstrumentAssetRecord';
                                var homelessStreamItem = new HomelessStreamItemView({ model: model });
                                $.when($('ul#'+platformCode).append( homelessStreamItem.render().el )).done(function() {
                                    //stream
                                    var instrumentTarget = 'ul#'+instrumentCode;
                                    var streamItemView = new StreamItemView({ model:model });
                                    $(instrumentTarget).append(streamItemView.render().el);
                                });
                            });
                        } else {
                            //instrument
                            model.attributes.asset_class = '.InstrumentAssetRecord';
                            var homelessStreamItem = new HomelessStreamItemView({ model: model });
                            $.when($('ul#'+platformCode).append( homelessStreamItem.render().el )).done(function() {
                                //stream
                                var instrumentTarget = 'ul#'+instrumentCode;
                                var streamItemView = new StreamItemView({ model:model });
                                $(instrumentTarget).append(streamItemView.render().el);
                            });
                        }

                    } else {
                        //stream
                        var instrumentTarget = 'ul#'+instrumentCode;
                        var streamItemView = new StreamItemView({ model:model });
                        $(instrumentTarget).append(streamItemView.render().el);
                    }
                } catch (e) {
                    console.log(e);
                }
            });
        }
    },
    render: function(){
        this.$el.html(this.template());
        this.renderArrays();
        this.$el.find('[data-toggle="tooltip"]').tooltip();
        return this;
    },
    derender: function() {
        this.remove();
        this.unbind();
    },
});

var SearchResultView = Backbone.View.extend({
    tagName: 'ul',
    initialize: function() {
        _.bindAll(this, 'render', 'derender', 'onClick');
        this.listenTo(vent, 'toc:derenderItems', function() {
            this.derender();
        });

    },
    onClick: function() {
        ooi.trigger('toc:selectItem', this.model);
    },
    render: function(){
        var assetItemView = this.collection.map(function(model) {
            var coord = model.get('coordinates');
            if (coord) {
                return(new AssetItemView({ model:model }).render().el);
            }
        });
        this.$el.html(assetItemView);
        return this;
    },
    derender: function() {
        this.remove();
        this.unbind();
    },
});

var ArrayContainerView = Backbone.View.extend({
    tagName: 'li',
    attributes: function(){
        return {
            'style': 'width:100%'
        };
    },
    events:{
        'click .nav-header' : 'onClick',
        'click label.tree-toggler': 'collapse'
    },
    initialize: function(options) {
        _.bindAll(this, 'render', 'onClick', 'collapse');
        this.listenTo(vent, 'toc:derenderItems', function() {
            this.derender();
        });
    },
    collapse: function(e) {
        e.stopImmediatePropagation();
        this.$el.children('ul.tree').toggle(300);
    },
    onClick: function(e) {
                 ooi.trigger('toc:selectArray', this.model);
             },
    template: JST['ooiui/static/js/partials/ArrayItem.html'],
    render: function(){
        this.$el.html( this.template(this.model.toJSON()) );
        this.$el.append('

');
        return this;
    },
    derender: function() {
        this.remove();
        this.unbind();
        this.model.off;
    },
})

var AssetItemView = Backbone.View.extend({
    tagName: 'li',
    events: {
        'click label.platform': 'onClickPlatform',
        'click label.instrument': 'onClickInstrument',
        'click label.tree-toggler': 'collapse'
    },
    initialize: function(options) {
        _.bindAll(this,'render', 'onClickPlatform', 'onClickInstrument', 'collapse');
        this.listenTo(vent, 'toc:derenderItems', function() {
            this.derender();
        });
        this.listenTo(vent, 'toc:cleanUp', function() {
            // if the item doesn't have any children, grey it out.
            if ( this.$el.find('ul.tree').children().length == 0 ) {
                this.$el.addClass('no-children');
            }
        });
        this.listenTo(vent, 'toc:hideInstruments', function() {
            if(this.model.get('asset_class') == '.InstrumentAssetRecord') {
                this.$el.attr('style','display:none;');
            }
        });
    },
    onClickPlatform: function(e) {
        $(".active-toc-item").removeClass("active-toc-item");
        this.$('label:first').addClass("active-toc-item");
        ooi.trigger('toc:selectPlatform', this.model);
        this.update_url();
    },
    onClickInstrument: function() {
        $(".active-toc-item").removeClass("active-toc-item");
        this.$('label:first').addClass("active-toc-item");
        ooi.trigger('toc:selectInstrument', this.model);
        this.update_url();
    },
    update_url:function(){
        var ref_des = this.model.get('ref_des');
        var newURL = '#'+ref_des;
        window.history.pushState(null, null, newURL);
    },
    collapse: function(e) {
        e.stopImmediatePropagation();
        this.$el.children('ul.tree').toggle(300);
    },
    derender: function() {
        this.remove();
        this.unbind();
        this.model.off;
    },
    render: function() {
        // If the asset class is an AssetRecord, give the view an ID of the
        // first 8 characters of the Reference Designator
        if (this.model.get('asset_class') == '.AssetRecord') {
            var platformName = this.model.get('assetInfo').name;
            var platformId = this.model.get('ref_des').substr(0,8);
            var assName = this.model.get('ref_des').substr(9,14);
            assName = (assName.length > 0) ? '-' + assName : "";
            this.$el.attr('id', platformId+assName);
            this.$el.attr('class', 'platform');
            // since this is an AssetRecord (platform / glider) lets assume
            // it'll need to have instruments attached to it...so create a container!
            var label = (platformName == undefined) ? platformId+assName : '' + platformName + ' | ' + platformId + assName +'';
            this.$el.append(''+ label + '

');
        } else if(this.model.get('asset_class') == '.InstrumentAssetRecord') {
            // otherwise, if it's an InstrumentAssetRecord then give the view an ID
            // of the entire Reference Designator
            var instrumentName = this.model.get('assetInfo').name;
            var instrumentId = this.model.get('ref_des');
            this.$el.attr('id', instrumentId);
            this.$el.attr('class', 'instrument');
            var label = (instrumentName == undefined) ? instrumentId : '' + instrumentName + '' + instrumentId.substr(9,27) + '';
            this.$el.append(''+ label + '

');
        }
        return this;
    }
});

var HomelessStreamItemView = AssetItemView.extend({
    render: function() {
        if ( this.model.get('asset_class') == '.AssetRecord' ) {
            this.model.set('ref_des', this.model.get('reference_designator'));
            var platformId = this.model.get('reference_designator').substr(0,14);
            this.$el.attr('id', platformId);
            var platformName = this.model.get('platform_name');
            this.$el.attr('class', 'platform detached');
            var label = (platformName == undefined) ? platformId : '' + platformName + ' | ' + platformId.substr(0,8)+'';
            this.$el.append(''+ label + '

');
        }
        if ( this.model.get('asset_class') == '.InstrumentAssetRecord' ) {
            this.model.set('ref_des', this.model.get('reference_designator'));
            var instrumentId = this.model.get('ref_des');
            var instrumentName = this.model.get('display_name');
            this.$el.attr('id', instrumentId);
            this.$el.attr('class', 'instrument detached');
            var label = (instrumentName == undefined) ? instrumentId : '' + instrumentName + '' + instrumentId.substr(9,27) + '';
            this.$el.append(''+ label + '

');
        }
        return this
    }
});

var StreamItemView = Backbone.View.extend({
    tagName: 'li',
    events: {
        'click a': 'onClick'
    },
    initialize: function(options) {
        _.bindAll(this, 'render', 'derender', 'onClick');
        this.listenTo(vent, 'toc:denrenderItems', function() {
            this.derender();
        });
        this.listenTo(vent, 'toc:hideStreams', function() {
            this.$el.attr('style','display:none;');
        });
    },
    onClick: function(e) {
        $(".active-toc-item").removeClass("active-toc-item");
        e.stopImmediatePropagation();
        $(e.target).addClass("active-toc-item");
        ooi.trigger('toc:selectStream', { model: this.model });
    },
    template: _.template('<%= stream_name %>'),
    derender: function() {
        this.remove();
        this.unbind();
        this.model.off;
    },
    render: function() {
        this.$el.attr('id', this.model.get('reference_designator') + '-' + this.model.get('stream_name'));
        this.$el.html( this.template(this.model.toJSON()) );
        return this;
    }
});
<|endoftext|>


<commit_before>define([
    'jquery',
    'moment',
    'underscore',
    'collections/product_collection',
    'models/course_model',
    'models/course_seats/professional_seat',
    'models/course_seats/audit_seat',
    'models/course_seats/honor_seat',
    'js-cookie'
],
    function($,
              moment,
              _,
              ProductCollection,
              Course,
              ProfessionalSeat,
              AuditSeat,
              HonorSeat,
              Cookies
    ) {
        'use strict';

        var model,
            auditSeat = {
                id: 6,
                url: 'http://ecommerce.local:8002/api/v2/products/6/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with no certificate',
                price: '0.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: false
                    }
                ],
                is_available_to_buy: true
            },
            honorSeat = {
                id: 8,
                url: 'http://ecommerce.local:8002/api/v2/products/8/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with honor certificate',
                price: '0.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'certificate_type',
                        value: 'honor'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: false
                    }
                ],
                is_available_to_buy: true
            },
            verifiedSeat = {
                id: 9,
                url: 'http://ecommerce.local:8002/api/v2/products/9/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with verified certificate (and ID verification)',
                price: '15.00',
                expires: '2015-01-01T00:00:00+00:00',
                attribute_values: [
                    {
                        name: 'certificate_type',
                        value: 'verified'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: true
                    }
                ],
                is_available_to_buy: true
            },
            creditSeat = {
                id: 10,
                url: 'http://ecommerce.local:8002/api/v2/products/10/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with credit certificate (and ID verification)',
                price: '200.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'certificate_type',
                        value: 'credit'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: true
                    },
                    {
                        name: 'credit_provider',
                        value: 'Harvard'
                    },
                    {
                        name: 'credit_hours',
                        value: 1
                    }
                ],
                is_available_to_buy: true
            },
            alternateCreditSeat = {
                id: 11,
                url: 'http://ecommerce.local:8002/api/v2/products/11/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with credit certificate (and ID verification)',
                price: '300.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'certificate_type',
                        value: 'credit'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: true
                    },
                    {
                        name: 'credit_provider',
                        value: 'MIT'
                    },
                    {
                        name: 'credit_hours',
                        value: 2
                    }
                ],
                is_available_to_buy: true
            },
            enrollmentCodeProduct = {
                id: 12,
                url: 'http://ecommerce.local:8002/api/v2/products/12/',
                structure: 'standalone',
                product_class: 'Enrollment Code',
                title: 'Enrollent Code for Professional Seat in edX Demonstration Course',
                price: '300.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'seat_type',
                        value: 'professional'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: true
                    }
                ],
                is_available_to_buy: true
            },
            data = {
                id: 'edX/DemoX/Demo_Course',
                url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/',
                name: 'edX Demonstration Course',
                verification_deadline: '2015-10-01T00:00:00Z',
                honor_mode: false,
                type: 'credit',
                products_url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/products/',
                last_edited: '2015-07-27T00:27:23Z',
                products: [
                    auditSeat,
                    honorSeat,
                    verifiedSeat,
                    creditSeat,
                    alternateCreditSeat,
                    enrollmentCodeProduct,
                    {
                        id: 7,
                        url: 'http://ecommerce.local:8002/api/v2/products/7/',
                        structure: 'parent',
                        product_class: 'Seat',
                        title: 'Seat in edX Demonstration Course',
                        price: null,
                        expires: null,
                        attribute_values: [
                            {
                                name: 'course_key',
                                value: 'edX/DemoX/Demo_Course'
                            }
                        ],
                        is_available_to_buy: false
                    }
                ]
            };

        describe('Course model', function() {
            beforeEach(function() {
                model = Course.findOrCreate(data, {parse: true});

                // Remove the non-essential products
                model.prepareProducts();
            });

            describe('prepareProducts', function() {
                it('should remove all non-essential products from the products collection', function() {
                    var products;

                    // Re-initialize the model since the beforeEach removes the products we don't need
                    model = Course.findOrCreate(data, {parse: true});

                    // Sanity check to ensure the products were properly parsed
                    products = model.get('products');
                    expect(products.length).toEqual(7);

                    // Re-remove the non-essential products
                    model.prepareProducts();

                    // Only the strong survived...
                    expect(products.length).toEqual(5);
                    expect(products.where({structure: 'child'}).length).toEqual(5);
                });
            });

            // NOTE (CCB): There is a bug preventing this from being called 'toJSON'.
            // See https://github.com/karma-runner/karma/issues/1534.
            describe('#toJSON', function() {
                it('should not modify verification_deadline if verification_deadline is empty', function() {
                    var json,
                        values = [null, ''];

                    _.each(values, function(value) {
                        model.set('verification_deadline', value);
                        json = model.toJSON();
                        expect(json.verification_deadline).toEqual(value);
                    });
                });

                it('should add a timezone to verification_deadline if verification_deadline is not empty', function() {
                    var json,
                        deadline = '2015-01-01T00:00:00';

                    model.set('verification_deadline', deadline);
                    json = model.toJSON();

                    expect(json.verification_deadline).toEqual(deadline + '+00:00');
                });
            });

            describe('save', function() {
                var expectedAjaxData = function() {
                    var products,
                        expected = {
                            id: data.id,
                            name: data.name,
                            verification_deadline: moment.utc(data.verification_deadline).format(),
                            create_or_activate_enrollment_code: false
                        };

                    products = _.filter(data.products, function(product) {
                        return product.structure === 'child';
                    });

                    expected.products = _.map(products, function(product) {
                        return product;
                    });

                    return expected;
                };

                it('should POST to the publication endpoint', function() {
                    var args,
                        cookie = 'save-test';

                    spyOn($, 'ajax');
                    Cookies.set('ecommerce_csrftoken', cookie);

                    expect(model.validate()).toBeFalsy();
                    model.save();

                    // $.ajax should have been called
                    expect($.ajax).toHaveBeenCalled();

                    // Ensure the data was POSTed to the correct endpoint
                    args = $.ajax.calls.argsFor(0)[0];
                    expect(args.type).toEqual('POST');
                    expect(args.url).toEqual('/api/v2/publication/');
                    expect(args.contentType).toEqual('application/json');
                    expect(args.headers).toEqual({'X-CSRFToken': cookie});
                    expect(JSON.parse(args.data)).toEqual(expectedAjaxData(data));
                });

                describe('when honor_mode true', function() {
                    it('adds an honor seat and removes the audit seat', function() {
                        var seats;

                        model.set('products', []);
                        model.get('products').push(new AuditSeat({}));
                        model.set('honor_mode', true);
                        model.save();

                        seats = model.seats();
                        expect(seats.length).toEqual(1);
                        expect(seats[0].attributes.certificate_type).toEqual('honor');
                    });
                });

                describe('when honor_mode false', function() {
                    it('does not add an honor seat and leaves the audit seat', function() {
                        var seats;

                        model.set('products', []);
                        model.get('products').push(new AuditSeat({}));
                        model.set('honor_mode', false);
                        model.save();

                        seats = model.seats();
                        expect(seats.length).toEqual(1);
                        expect(seats[0].attributes.certificate_type).toBeNull();
                    });
                });
            });

            // FIXME: These tests timeout when run. This is tracked by LEARNER-824.
            xdescribe('getOrCreateSeats', function() {
                it('should return existing seats', function() {
                    var mapping = {
                            audit: [auditSeat],
                            honor: [honorSeat],
                            verified: [verifiedSeat],
                            credit: [creditSeat, alternateCreditSeat]
                        },
                        seats;

                    _.each(mapping, function(expected, seatType) {
                        seats = model.getOrCreateSeats(seatType);
                        _.each(seats, function(seat) {
                            expect(expected).toContain(seat.toJSON());
                        });
                    });
                });

                it('should create a new CourseSeat if one does not exist', function() {
                    var seat;

                    // Sanity check to confirm a new seat is created later
                    expect(model.seats().length).toEqual(5);

                    // A new seat should be created
                    seat = model.getOrCreateSeats('professional')[0];
                    expect(model.seats().length).toEqual(6);

                    // The new seat's class/type should correspond to the passed in seat type
                    expect(seat).toEqual(jasmine.any(ProfessionalSeat));
                });
            });

            // FIXME: This test times out when run. This is tracked by LEARNER-824.
            xdescribe('products', function() {
                it('is a ProductCollection', function() {
                    expect(model.get('products')).toEqual(jasmine.any(ProductCollection));
                });
            });

            describe('verification deadline validation', function() {
                it('succeeds if the verification deadline is after the course seats\' expiration dates', function() {
                    var seat = model.getOrCreateSeats('verified')[0];
                    model.set('verification_deadline', '2016-01-01T00:00:00Z');
                    seat.set('expires', '2015-01-01T00:00:00Z');

                    expect(model.validate()).toBeUndefined();
                    expect(model.isValid(true)).toBeTruthy();
                });

                it('fails if the verification deadline is before the course seats\' expiration dates', function() {
                    var seat = model.getOrCreateSeats('verified')[0],
                        msg = 'The verification deadline must occur AFTER the upgrade deadline.';
                    model.set('verification_deadline', '2014-01-01T00:00:00Z');
                    seat.set('expires', '2015-01-01T00:00:00Z');

                    expect(model.validate().verification_deadline).toEqual(msg);
                    expect(model.isValid(true)).toBeFalsy();
                });
            });

            describe('products validation', function() {
                describe('with single value', function() {
                    it('should return an error message if any product is invalid', function() {
                        var msg = 'Product validation failed.',
                            products = model.get('products');

                        // Add an invalid product
                        products.push(new ProfessionalSeat({price: null}));

                        expect(model.validate().products).toEqual(msg);
                        expect(model.isValid(true)).toBeFalsy();
                    });
                });

                describe('with non-products', function() {
                    it('should have an undefined return value', function() {
                        expect(model.validation.products([])).toBeUndefined();
                    });
                });
            });

            describe('honorModeInit', function() {
                describe('with seats', function() {
                    it('sets honor_mode to true', function() {
                        model.set('honor_mode', null);
                        model.set('products', []);
                        model.get('products').push(new AuditSeat({}));
                        model.get('products').push(new HonorSeat({}));
                        model.honorModeInit();
                        expect(model.get('honor_mode')).toBeTruthy();
                    });

                    it('sets honor_mode to false', function() {
                        model.set('honor_mode', null);
                        model.set('products', []);
                        model.get('products').push(new AuditSeat({}));
                        model.honorModeInit();
                        expect(model.get('honor_mode')).toBeFalsy();
                    });
                });

                describe('without seats', function() {
                    it('should have an undefined return value', function() {
                        model.set('honor_mode', null);
                        model.set('products', []);
                        model.honorModeInit();
                        expect(model.get('honor_mode')).toBeNull();
                    });
                });
            });

            describe('honor mode validation', function() {
                describe('without an honor mode', function() {
                    beforeEach(function() {
                        model = Course.findOrCreate({
                            id: 'test/testX/testcourse',
                            name: 'Test Course',
                            verification_deadline: '2015-10-01T00:00:00Z',
                            honor_mode: null,
                            type: null
                        });
                    });

                    it('is valid for professional education courses', function() {
                        model.set('type', 'professional');
                        expect(model.isValid(true)).toBeTruthy();
                    });

                    it('is not valid for non-prof-ed courses', function() {
                        _.each(['audit', 'verified', 'credit'], function(type) {
                            model.set('type', type);
                            expect(model.isValid(true)).toBeFalsy();
                        });
                    });
                });
            });

            describe('product title validation', function() {
                it('succeeds if the product title does not contain HTML', function() {
                    model.set('name', 'edX Demonstration Course');
                    expect(model.isValid(true)).toBeTruthy();
                });

                it('fails if the product title contains HTML', function() {
                    model.set('name', 'edx Demo Course & test with html');
                    expect(model.validate().name).toEqual('The product name cannot contain HTML.');
                    expect(model.isValid(true)).toBeFalsy();
                });
            });
        });
    }
);
<commit_msg>Fixed flaky tests introduced by LEARNER-1293<commit_after>define([
    'jquery',
    'moment',
    'underscore',
    'collections/product_collection',
    'models/course_model',
    'models/course_seats/professional_seat',
    'models/course_seats/audit_seat',
    'models/course_seats/honor_seat',
    'js-cookie'
],
    function($,
              moment,
              _,
              ProductCollection,
              Course,
              ProfessionalSeat,
              AuditSeat,
              HonorSeat,
              Cookies
    ) {
        'use strict';

        var model,
            auditSeat = {
                id: 6,
                url: 'http://ecommerce.local:8002/api/v2/products/6/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with no certificate',
                price: '0.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: false
                    }
                ],
                is_available_to_buy: true
            },
            honorSeat = {
                id: 8,
                url: 'http://ecommerce.local:8002/api/v2/products/8/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with honor certificate',
                price: '0.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'certificate_type',
                        value: 'honor'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: false
                    }
                ],
                is_available_to_buy: true
            },
            verifiedSeat = {
                id: 9,
                url: 'http://ecommerce.local:8002/api/v2/products/9/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with verified certificate (and ID verification)',
                price: '15.00',
                expires: '2015-01-01T00:00:00+00:00',
                attribute_values: [
                    {
                        name: 'certificate_type',
                        value: 'verified'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: true
                    }
                ],
                is_available_to_buy: true
            },
            creditSeat = {
                id: 10,
                url: 'http://ecommerce.local:8002/api/v2/products/10/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with credit certificate (and ID verification)',
                price: '200.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'certificate_type',
                        value: 'credit'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: true
                    },
                    {
                        name: 'credit_provider',
                        value: 'Harvard'
                    },
                    {
                        name: 'credit_hours',
                        value: 1
                    }
                ],
                is_available_to_buy: true
            },
            alternateCreditSeat = {
                id: 11,
                url: 'http://ecommerce.local:8002/api/v2/products/11/',
                structure: 'child',
                product_class: 'Seat',
                title: 'Seat in edX Demonstration Course with credit certificate (and ID verification)',
                price: '300.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'certificate_type',
                        value: 'credit'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: true
                    },
                    {
                        name: 'credit_provider',
                        value: 'MIT'
                    },
                    {
                        name: 'credit_hours',
                        value: 2
                    }
                ],
                is_available_to_buy: true
            },
            enrollmentCodeProduct = {
                id: 12,
                url: 'http://ecommerce.local:8002/api/v2/products/12/',
                structure: 'standalone',
                product_class: 'Enrollment Code',
                title: 'Enrollent Code for Professional Seat in edX Demonstration Course',
                price: '300.00',
                expires: null,
                attribute_values: [
                    {
                        name: 'seat_type',
                        value: 'professional'
                    },
                    {
                        name: 'course_key',
                        value: 'edX/DemoX/Demo_Course'
                    },
                    {
                        name: 'id_verification_required',
                        value: true
                    }
                ],
                is_available_to_buy: true
            },
            data = {
                id: 'edX/DemoX/Demo_Course',
                url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/',
                name: 'edX Demonstration Course',
                verification_deadline: '2015-10-01T00:00:00Z',
                honor_mode: false,
                type: 'credit',
                products_url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/products/',
                last_edited: '2015-07-27T00:27:23Z',
                products: [
                    auditSeat,
                    honorSeat,
                    verifiedSeat,
                    creditSeat,
                    alternateCreditSeat,
                    enrollmentCodeProduct,
                    {
                        id: 7,
                        url: 'http://ecommerce.local:8002/api/v2/products/7/',
                        structure: 'parent',
                        product_class: 'Seat',
                        title: 'Seat in edX Demonstration Course',
                        price: null,
                        expires: null,
                        attribute_values: [
                            {
                                name: 'course_key',
                                value: 'edX/DemoX/Demo_Course'
                            }
                        ],
                        is_available_to_buy: false
                    }
                ]
            };

        describe('Course model', function() {
            beforeEach(function() {
                model = Course.findOrCreate(data, {parse: true});

                // Remove the non-essential products
                model.prepareProducts();
            });

            describe('prepareProducts', function() {
                it('should remove all non-essential products from the products collection', function() {
                    var products;

                    // Re-initialize the model since the beforeEach removes the products we don't need
                    model = Course.findOrCreate(data, {parse: true});

                    // Sanity check to ensure the products were properly parsed
                    products = model.get('products');
                    expect(products.length).toEqual(7);

                    // Re-remove the non-essential products
                    model.prepareProducts();

                    // Only the strong survived...
                    expect(products.length).toEqual(5);
                    expect(products.where({structure: 'child'}).length).toEqual(5);
                });
            });

            // NOTE (CCB): There is a bug preventing this from being called 'toJSON'.
            // See https://github.com/karma-runner/karma/issues/1534.
            describe('#toJSON', function() {
                it('should not modify verification_deadline if verification_deadline is empty', function() {
                    var json,
                        values = [null, ''];

                    _.each(values, function(value) {
                        model.set('verification_deadline', value);
                        json = model.toJSON();
                        expect(json.verification_deadline).toEqual(value);
                    });
                });

                it('should add a timezone to verification_deadline if verification_deadline is not empty', function() {
                    var json,
                        deadline = '2015-01-01T00:00:00';

                    model.set('verification_deadline', deadline);
                    json = model.toJSON();

                    expect(json.verification_deadline).toEqual(deadline + '+00:00');
                });
            });

            describe('save', function() {
                var expectedAjaxData = function() {
                    var products,
                        expected = {
                            id: data.id,
                            name: data.name,
                            verification_deadline: moment.utc(data.verification_deadline).format(),
                            create_or_activate_enrollment_code: false
                        };

                    products = _.filter(data.products, function(product) {
                        return product.structure === 'child';
                    });

                    expected.products = _.map(products, function(product) {
                        return product;
                    });

                    return expected;
                };

                it('should POST to the publication endpoint', function() {
                    var args,
                        cookie = 'save-test',
                        modelVerifiedSeat = model.getOrCreateSeats('verified')[0];

                    // The next line prevents flakiness.
                    spyOn(modelVerifiedSeat.validation, 'expires').and.returnValue(undefined);

                    spyOn($, 'ajax');
                    Cookies.set('ecommerce_csrftoken', cookie);

                    expect(model.validate()).toBeFalsy();
                    model.save();

                    // $.ajax should have been called
                    expect($.ajax).toHaveBeenCalled();

                    // Ensure the data was POSTed to the correct endpoint
                    args = $.ajax.calls.argsFor(0)[0];
                    expect(args.type).toEqual('POST');
                    expect(args.url).toEqual('/api/v2/publication/');
                    expect(args.contentType).toEqual('application/json');
                    expect(args.headers).toEqual({'X-CSRFToken': cookie});
                    expect(JSON.parse(args.data)).toEqual(expectedAjaxData(data));
                });

                describe('when honor_mode true', function() {
                    it('adds an honor seat and removes the audit seat', function() {
                        var seats;

                        model.set('products', []);
                        model.get('products').push(new AuditSeat({}));
                        model.set('honor_mode', true);
                        model.save();

                        seats = model.seats();
                        expect(seats.length).toEqual(1);
                        expect(seats[0].attributes.certificate_type).toEqual('honor');
                    });
                });

                describe('when honor_mode false', function() {
                    it('does not add an honor seat and leaves the audit seat', function() {
                        var seats;

                        model.set('products', []);
                        model.get('products').push(new AuditSeat({}));
                        model.set('honor_mode', false);
                        model.save();

                        seats = model.seats();
                        expect(seats.length).toEqual(1);
                        expect(seats[0].attributes.certificate_type).toBeNull();
                    });
                });
            });

            // FIXME: These tests timeout when run. This is tracked by LEARNER-824.
            xdescribe('getOrCreateSeats', function() {
                it('should return existing seats', function() {
                    var mapping = {
                            audit: [auditSeat],
                            honor: [honorSeat],
                            verified: [verifiedSeat],
                            credit: [creditSeat, alternateCreditSeat]
                        },
                        seats;

                    _.each(mapping, function(expected, seatType) {
                        seats = model.getOrCreateSeats(seatType);
                        _.each(seats, function(seat) {
                            expect(expected).toContain(seat.toJSON());
                        });
                    });
                });

                it('should create a new CourseSeat if one does not exist', function() {
                    var seat;

                    // Sanity check to confirm a new seat is created later
                    expect(model.seats().length).toEqual(5);

                    // A new seat should be created
                    seat = model.getOrCreateSeats('professional')[0];
                    expect(model.seats().length).toEqual(6);

                    // The new seat's class/type should correspond to the passed in seat type
                    expect(seat).toEqual(jasmine.any(ProfessionalSeat));
                });
            });

            // FIXME: This test times out when run. This is tracked by LEARNER-824.
            xdescribe('products', function() {
                it('is a ProductCollection', function() {
                    expect(model.get('products')).toEqual(jasmine.any(ProductCollection));
                });
            });

            describe('verification deadline validation', function() {
                it('succeeds if the verification deadline is after the course seats\' expiration dates', function() {
                    var seat = model.getOrCreateSeats('verified')[0];
                    model.set('products', seat);
                    model.set('verification_deadline', '2016-01-01T00:00:00Z');
                    seat.set('expires', '2015-01-01T00:00:00Z');

                    expect(model.validate()).toBeUndefined();
                    expect(model.isValid(true)).toBeTruthy();
                });

                it('fails if the verification deadline is before the course seats\' expiration dates', function() {
                    var seat = model.getOrCreateSeats('verified')[0],
                        msg = 'The verification deadline must occur AFTER the upgrade deadline.';
                    model.set('verification_deadline', '2014-01-01T00:00:00Z');
                    seat.set('expires', '2015-01-01T00:00:00Z');

                    expect(model.validate().verification_deadline).toEqual(msg);
                    expect(model.isValid(true)).toBeFalsy();
                });
            });

            describe('products validation', function() {
                describe('with single value', function() {
                    it('should return an error message if any product is invalid', function() {
                        var msg = 'Product validation failed.',
                            products = model.get('products');

                        // Add an invalid product
                        products.push(new ProfessionalSeat({price: null}));

                        expect(model.validate().products).toEqual(msg);
                        expect(model.isValid(true)).toBeFalsy();
                    });
                });

                describe('with non-products', function() {
                    it('should have an undefined return value', function() {
                        expect(model.validation.products([])).toBeUndefined();
                    });
                });
            });

            describe('honorModeInit', function() {
                describe('with seats', function() {
                    it('sets honor_mode to true', function() {
                        model.set('honor_mode', null);
                        model.set('products', []);
                        model.get('products').push(new AuditSeat({}));
                        model.get('products').push(new HonorSeat({}));
                        model.honorModeInit();
                        expect(model.get('honor_mode')).toBeTruthy();
                    });

                    it('sets honor_mode to false', function() {
                        model.set('honor_mode', null);
                        model.set('products', []);
                        model.get('products').push(new AuditSeat({}));
                        model.honorModeInit();
                        expect(model.get('honor_mode')).toBeFalsy();
                    });
                });

                describe('without seats', function() {
                    it('should have an undefined return value', function() {
                        model.set('honor_mode', null);
                        model.set('products', []);
                        model.honorModeInit();
                        expect(model.get('honor_mode')).toBeNull();
                    });
                });
            });

            describe('honor mode validation', function() {
                describe('without an honor mode', function() {
                    beforeEach(function() {
                        model = Course.findOrCreate({
                            id: 'test/testX/testcourse',
                            name: 'Test Course',
                            verification_deadline: '2015-10-01T00:00:00Z',
                            honor_mode: null,
                            type: null
                        });
                    });

                    it('is valid for professional education courses', function() {
                        model.set('type', 'professional');
                        expect(model.isValid(true)).toBeTruthy();
                    });

                    it('is not valid for non-prof-ed courses', function() {
                        _.each(['audit', 'verified', 'credit'], function(type) {
                            model.set('type', type);
                            expect(model.isValid(true)).toBeFalsy();
                        });
                    });
                });
            });

            describe('product title validation', function() {
                it('succeeds if the product title does not contain HTML', function() {
                    model.unset('products');
                    model.set('name', 'edX Demonstration Course');
                    expect(model.isValid(true)).toBeTruthy();
                });

                it('fails if the product title contains HTML', function() {
                    model.set('name', 'edx Demo Course & test with html');
                    expect(model.validate().name).toEqual('The product name cannot contain HTML.');
                    expect(model.isValid(true)).toBeFalsy();
                });
            });
        });
    }
);
<|endoftext|>