<commit_before>"use strict";
let express = require("express");
let router = express.Router(); // eslint-disable-line new-cap
let { db, sql, as } = require("../helpers/db");
let log = require("winston");

const RESPONSE_LIMIT = 30;

router.get("/getAllForType", function getAllForType(req, res) {
  let objType = req.query.objType.toLowerCase();
  let page = parseInt(req.query.page || 1);
  let offset = (page - 1) * RESPONSE_LIMIT;
  if (
    objType !== "organization" && objType !== "case" && objType !== "method"
  ) {
    res.status(401).json({
      message: "Unsupported objType for getAllForType: " + objType
    });
  }
  db
    .any(sql("../sql/titles_for_" + objType + "s.sql"), {
      language: req.query.language || "en",
      limit: RESPONSE_LIMIT,
      offset: offset
    })
    .then(function(titlelist) {
      let jtitlelist = {};
      // FIXME: this is a dumb format but it is what front-end expects.
      // Switch both (and tests) to use array of {title: , id: } pairs.
      // Also, if we're going to use {OK: true, data: []} everywhere else
      // we should use it here too.
      titlelist.forEach(function(row) {
        jtitlelist[row.title] = parseInt(row[objType + "_id"]);
      });
      res.status(200).json(jtitlelist);
    })
    .catch(function(error) {
      log.error("Exception in GET /search/getAllForType", error);
      res.status(500).json({ error: error });
    });
});

function query_nouns_by_type(
  res,
  objType,
  query,
  facets,
  page,
  language,
  orderBy
) {
  console.log(
    "query_nouns_by_type: objType=%s, query=%s, facets=%s",
    objType,
    query,
    JSON.stringify(facets)
  );
  db
    .any(sql("../sql/search_" + objType + "s.sql"), {
      query: query,
      facets: format_facet_string(facets, objType),
      order_by: orderBy,
      language: language,
      limit: RESPONSE_LIMIT,
      offset: (page - 1) * RESPONSE_LIMIT
    })
    .then(function(objList) {
      res.status(200).json({ results: [{ type: objType, hits: objList }] });
    })
    .catch(function(error) {
      log.error("Exception in GET /search/getAllForType", error);
      res.status(500).json({ error: error });
    });
}

function query_all_nouns(res, query, facets, page, language, orderBy) {
  console.log(
    "query_all_nouns: query=%s, facets=%s",
    query,
    JSON.stringify(facets)
  );
  db
    .task(t => {
      let dbqueries = ["case", "method", "organization"].map(objType => {
        return t.any(sql("../sql/search_" + objType + "s.sql"), {
          query: query,
          facets: format_facet_string(facets, objType),
          language: language,
          limit: RESPONSE_LIMIT,
          offset: (page - 1) * RESPONSE_LIMIT,
          order_by: orderBy
        });
      });
      return t.batch(dbqueries);
    })
    .then(function(objLists) {
      res.status(200).json({
        results: [
          {
            type: "case",
            hits: objLists[0]
          },
          {
            type: "method",
            hits: objLists[1]
          },
          {
            type: "organization",
            hits: objLists[2]
          }
        ]
      });
    })
    .catch(function(error) {
      log.error("Exception in GET /search/getAllForType", error);
      res.status(500).json({ error: error });
    });
}

function get_nouns_by_type(res, objType, facets, page, language, orderBy) {
  console.log(
    "get_nouns_by_type: objType=%s, facets=%s",
    objType,
    JSON.stringify(facets)
  );
  db
    .any(sql("../sql/list_" + objType + "s.sql"), {
      facets: format_facet_string(facets, objType),
      language: language,
      limit: RESPONSE_LIMIT,
      offset: (page - 1) * RESPONSE_LIMIT,
      order_by: orderBy
    })
    .then(function(objList) {
      res.status(200).json({ results: [{ type: objType, hits: objList }] });
    })
    .catch(function(error) {
      log.error("Exception in GET /search/getAllForType", error);
      res.status(500).json({ error: error });
    });
}

function get_all_nouns(res, facets, page, language, orderBy) {
  console.log(
    "get_all_nouns: query=%s, facets=%s",
    query,
    JSON.stringify(facets)
  );
  db
    .task(t => {
      let query = ["case", "method", "organization"].map(objType => {
        return t.any(sql("../sql/list_" + objType + "s.sql"), {
          facets: format_facet_string(facets, objType),
          language: language,
          limit: RESPONSE_LIMIT,
          offset: (page - 1) * RESPONSE_LIMIT,
          order_by: orderBy
        });
      });
      return t.batch(query);
    })
    .then(function(objLists) {
      res.status(200).json({
        results: [
          {
            type: "case",
            hits: objLists[0]
          },
          {
            type: "method",
            hits: objLists[1]
          },
          {
            type: "organization",
            hits: objLists[2]
          }
        ]
      });
    })
    .catch(function(error) {
      log.error("Exception in GET /search/getAllForType", error);
      res.status(500).json({ error: error });
    });
}

function format_facet_string(facets, type) {
  // super-simple for now
  if (facets["location.country"]) {
    let country = as.text(facets["location.country"]);
    return `(${type}s).location.country = ${country} AND`;
  } else if (facets.tag) {
    let tag = as.text(facets.tag);
    return `(${type}s.tags @> array[${tag}]) AND`;
  } else {
    return "";
  }
}

/**
 * @api {get} /search Search through the cases
 * @apiGroup Search
 * @apiVersion 0.1.0
 * @apiName search
 *
 * @apiParam  {String} query query term
 * @apiParam  {String} sortingMethod ('chronological' or 'alphabetical' or 'featured')
 * @apiParam  {String} selectedCategory ('All' or 'Case' or 'Method' or 'Organization' or 'News')
 *
 * @apiSuccess {Boolean} OK true if call was successful
 * @apiSuccess {String[]} errors List of error strings (when `OK` is false)
 * @apiSuccess {Object} data Mapping of country names to counts (when `OK` is true)
 *
 * @apiSuccessExample Success-Response:
 *     HTTP/1.1 200 OK
 *     {
 *       "OK": true,
 *       "data": {
 *          ... (records) ...
 *       }
 *     }
 *
 */

// Should not return things that aren't displayable as SearchHits (i.e. Users...)

router.get("/", function(req, res) {
  let query = req.query.query;
  let facets = {};
  let sortingMethod = req.query.sortingMethod || "chronological";
  let selectedCategory = req.query.selectedCategory || "All";
  let language = req.query.language || "en";
  let page = parseInt(req.query.page || 1);

  // handle faceted queries
  // currently only faceted query is "geo_country" and tags
  // for more facets, and mixing facets with query terms
  // we'll need a more capable query parser
  if (query) {
    if (query.indexOf("geo_country") > -1) {
      facets["location.country"] = query.split(":")[1];
      query = "";
    }
    if (query.indexOf("tag") > -1) {
      facets["tag"] = query.split(":")[1];
      query = "";
    }
  }
  let orderBy = {
    alphabetical: "ORDER BY title",
    chronological: "ORDER BY updated_date DESC",
    featured: "ORDER BY featured, id"
  }[sortingMethod];
  if (query) {
    switch (selectedCategory) {
      case "Cases":
        query_nouns_by_type(
          res,
          "case",
          query,
          facets,
          page,
          language,
          orderBy
        );
        break;
      case "Organizations":
        query_nouns_by_type(
          res,
          "organization",
          query,
          facets,
          page,
          language,
          orderBy
        );
        break;
      case "Methods":
        query_nouns_by_type(
          res,
          "method",
          query,
          facets,
          page,
          language,
          orderBy
        );
        break;
      default:
        query_all_nouns(res, query, facets, page, language, orderBy);
        break;
    }
  } else {
    switch (selectedCategory) {
      case "Cases":
        get_nouns_by_type(res, "case", facets, page, language, orderBy);
        break;
      case "Methods":
        get_nouns_by_type(res, "method", facets, page, language, orderBy);
        break;
      case "Organizations":
        get_nouns_by_type(res, "organization", facets, page, language, orderBy);
        break;
      default:
        get_all_nouns(res, facets, page, language, orderBy);
        break;
    }
  }
});

module.exports = router;
<commit_msg>clean up search, use async/await<commit_after>"use strict";
let express = require("express");
let router = express.Router(); // eslint-disable-line new-cap
let { db, sql, as } = require("../helpers/db");
let log = require("winston");

const RESPONSE_LIMIT = 30;

router.get("/getAllForType", function getAllForType(req, res) {
  let objType = req.query.objType.toLowerCase();
  let page = parseInt(req.query.page || 1);
  let offset = (page - 1) * RESPONSE_LIMIT;
  if (
    objType !== "organization" && objType !== "case" && objType !== "method"
  ) {
    res.status(401).json({
      message: "Unsupported objType for getAllForType: " + objType
    });
  }
  db
    .any(sql("../sql/titles_for_" + objType + "s.sql"), {
      language: req.query.language || "en",
      limit: RESPONSE_LIMIT,
      offset: offset
    })
    .then(function(titlelist) {
      let jtitlelist = {};
      // FIXME: this is a dumb format but it is what front-end expects.
      // Switch both (and tests) to use array of {title: , id: } pairs.
      // Also, if we're going to use {OK: true, data: []} everywhere else
      // we should use it here too.
      titlelist.forEach(function(row) {
        jtitlelist[row.title] = parseInt(row[objType + "_id"]);
      });
      res.status(200).json(jtitlelist);
    })
    .catch(function(error) {
      log.error("Exception in GET /search/getAllForType", error);
      res.status(500).json({ error: error });
    });
});

const query_nouns_by_type = async (
  res,
  objType,
  query,
  facets,
  page,
  language,
  orderBy
) => {
  try {
    const objList = await db.any(sql("../sql/search_" + objType + "s.sql"), {
      query: query,
      facets: format_facet_string(facets, objType),
      order_by: orderBy,
      language: language,
      limit: RESPONSE_LIMIT,
      offset: (page - 1) * RESPONSE_LIMIT
    });
    res.status(200).json({ results: [{ type: objType, hits: objList }] });
  } catch (error) {
    log.error("Exception in GET /search/getAllForType", error);
    res.status(500).json({ error: error });
  }
};

const query_all_nouns = async (res, query, facets, page, language, orderBy) => {
  try {
    const objLists = await db.task(t => {
      let dbqueries = ["case", "method", "organization"].map(objType => {
        return t.any(sql("../sql/search_" + objType + "s.sql"), {
          query: query,
          facets: format_facet_string(facets, objType),
          language: language,
          limit: RESPONSE_LIMIT,
          offset: (page - 1) * RESPONSE_LIMIT,
          order_by: orderBy
        });
      });
      return t.batch(dbqueries);
    });
    res.status(200).json({
      results: [
        {
          type: "case",
          hits: objLists[0]
        },
        {
          type: "method",
          hits: objLists[1]
        },
        {
          type: "organization",
          hits: objLists[2]
        }
      ]
    });
  } catch (error) {
    log.error("Exception in GET /search/getAllForType", error);
    res.status(500).json({ error: error });
  }
};

const get_nouns_by_type = async (
  res,
  objType,
  facets,
  page,
  language,
  orderBy
) => {
  try {
    const objList = await db.any(sql("../sql/list_" + objType + "s.sql"), {
      facets: format_facet_string(facets, objType),
      language: language,
      limit: RESPONSE_LIMIT,
      offset: (page - 1) * RESPONSE_LIMIT,
      order_by: orderBy
    });
    res.status(200).json({ results: [{ type: objType, hits: objList }] });
  } catch (error) {
    log.error("Exception in GET /search/getAllForType", error);
    res.status(500).json({ error: error });
  }
};

const get_all_nouns = async (res, facets, page, language, orderBy) => {
  try {
    const objLists = await db.task(t => {
      let query = ["case", "method", "organization"].map(objType => {
        return t.any(sql("../sql/list_" + objType + "s.sql"), {
          facets: format_facet_string(facets, objType),
          language: language,
          limit: RESPONSE_LIMIT,
          offset: (page - 1) * RESPONSE_LIMIT,
          order_by: orderBy
        });
      });
      return t.batch(query);
    });
    res.status(200).json({
      results: [
        {
          type: "case",
          hits: objLists[0]
        },
        {
          type: "method",
          hits: objLists[1]
        },
        {
          type: "organization",
          hits: objLists[2]
        }
      ]
    });
  } catch (error) {
    log.error("Exception in GET /search/getAllForType", error);
    res.status(500).json({ error: error });
  }
};

function format_facet_string(facets, type) {
  // super-simple for now
  if (facets["location.country"]) {
    let country = as.text(facets["location.country"]);
    return `(${type}s).location.country = ${country} AND`;
  } else if (facets.tag) {
    let tag = as.text(facets.tag);
    return `(${type}s.tags @> array[${tag}]) AND`;
  } else {
    return "";
  }
}

// strip off final character (assumed to be "s")
const singularLowerCase = name => name.slice(0, -1).toLowerCase();

function parseSearchReq(req) {
  let query = req.query.query;
  let facets = {};
  const sortingMethod = req.query.sortingMethod || "chronological";
  const category = singularLowerCase(req.query.selectedCategory || "Alls");
  const language = as.text(req.query.language || "en");
  const page = as.number(req.query.page || 1);
  // handle faceted queries
  // currently only faceted query is "geo_country" and tags
  // for more facets, and mixing facets with query terms
  // we'll need a more capable query parser

  if (query) {
    if (query.indexOf("geo_country") > -1) {
      facets["location.country"] = query.split(":")[1];
      query = "";
    }
    if (query.indexOf("tag") > -1) {
      facets["tag"] = query.split(":")[1];
      query = "";
    }
  }

  const orderBy = {
    alphabetical: "ORDER BY title",
    chronological: "ORDER BY updated_date DESC",
    featured: "ORDER BY featured, id"
  }[sortingMethod];

  return { query, facets, orderBy, category, language, page };
}

/**
 * @api {get} /search Search through the cases
 * @apiGroup Search
 * @apiVersion 0.1.0
 * @apiName search
 *
 * @apiParam  {String} query query term
 * @apiParam  {String} sortingMethod ('chronological' or 'alphabetical' or 'featured')
 * @apiParam  {String} selectedCategory ('All' or 'Case' or 'Method' or 'Organization' or 'News')
 *
 * @apiSuccess {Boolean} OK true if call was successful
 * @apiSuccess {String[]} errors List of error strings (when `OK` is false)
 * @apiSuccess {Object} data Mapping of country names to counts (when `OK` is true)
 *
 * @apiSuccessExample Success-Response:
 *     HTTP/1.1 200 OK
 *     {
 *       "OK": true,
 *       "data": {
 *          ... (records) ...
 *       }
 *     }
 *
 */

// Should not return things that aren't displayable as SearchHits (i.e. Users...)

router.get("/", function(req, res) {
  const { query, facets, orderBy, category, lang, page } = parseSearchReq(req);

  if (query) {
    if (["case", "method", "organization"].includes(category)) {
      query_nouns_by_type(res, category, query, facets, page, lang, orderBy);
    } else {
      // no category selected, search across all categories
      query_all_nouns(res, query, facets, page, lang, orderBy);
    }
  } else {
    // no query
    if (["case", "method", "organization"].includes(category)) {
      get_nouns_by_type(res, category, facets, page, lang, orderBy);
    } else {
      // no category selected, get across all categories
      get_all_nouns(res, facets, page, lang, orderBy);
    }
  }
});

module.exports = router;
<|endoftext|>


<commit_before>/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2013 Red Hat, Inc.
 *
 * Cockpit is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Cockpit is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
 */

function cockpit_setup_plot (graph_id, resmon, data, user_options,
                             store_samples)
{
    var options = {
        colors: [ "#0099d3" ],
        legend: { show: false },
        series: { shadowSize: 0,
                  lines: { lineWidth: 0.0,
                           fill: 1.0
                         }
                },
        xaxis: { tickFormatter: function() { return ""; } },
        yaxis: { tickFormatter: function() { return ""; } },
        // The point radius influences
        // the margin around the grid
        // even if no points are plotted.
        // We don't want any margin, so
        // we set the radius to zero.
        points: { radius: 0 },
        grid: { borderWidth: 1,
                aboveData: true,
                color: "black",
                borderColor: $.color.parse("black").scale('a', 0.22).toString(),
                labelMargin: 0
              }
    };

    var num_series = data.length;
    var num_points = resmon.NumSamples;
    var got_historical_data = false;
    var plot;
    var running = false;

    $.extend(true, options, user_options);

    // We put the plot inside its own div so that we can give that div
    // a fixed size which only changes when we can also immediately
    // call plot.resize().  Otherwise, the labels and legends briefly
    // get out of sync during resizing.

    var outer_div = $(graph_id);
    var inner_div = $('<div/>');
    outer_div.empty();
    outer_div.append(inner_div);

    // Initialize series
    for (var n = 0; n < num_series; n++) {
        var series = [];
        for (var m = 0; m < num_points; m++) {
            series[m] = [m, 0];
        }
        data[n].data = series;
    }

    function sync_divs ()
    {
        inner_div.width(outer_div.width());
        inner_div.height(outer_div.height());
    }

    function start ()
    {
        running = true;
        if (!plot) {
            sync_divs ();
            plot = $.plot(inner_div, data, options);
        } else
            resize();
    }

    function stop ()
    {
        running = false;
    }

    function refresh ()
    {
        if (running) {
            plot.setData(data);
            plot.setupGrid();
            plot.draw();
        }
    }

    function resize ()
    {
        if (running) {
            sync_divs ();
            plot.resize();
            refresh();
        }
    }

    function new_samples (samples)
    {
        var series;
        var i, n, m, floor;

        for (n = 0; n < data.length; n++) {
            series = data[n].data;
            for (m = 0; m < series.length-1; m++) {
                series[m][1] = series[m+1][1];
                series[m][2] = series[m+1][2];
            }
        }

        store_samples (samples, data[0].data.length-1);
    }

    function got_historical_samples (history)
    {
        var n, offset;

        offset = data[0].data.length - history.length;
        for (n = 0; n < history.length; n++) {
            store_samples (history[n][1], n+offset);
        }
    }

    $(resmon).on("NewSample", function (event, timestampUsec, samples) {
        if (got_historical_data) {
            new_samples (samples);
            refresh ();
        }
    });

    resmon.call("GetSamples", {},
                function(error, result) {
                    if (!error) {
                        got_historical_samples (result);
                        got_historical_data = true;
                        refresh ();
                    }
                });

    $(window).on('resize', resize);

    return { start: start, stop: stop, resize: resize, element: inner_div[0] };
}

function cockpit_setup_complicated_plot (graph_id, resmon, data, options)
{
    var i;

    function store_samples (samples, index)
    {
        var value, series;
        var n, i, floor;
        for (n = 0; n < data.length; n++) {
            series = data[n].data;
            value = samples[n];

            if (options["x_rh_stack_graphs"]) {
                floor = 0;
                for (i = n + 1; i < samples.length; i++) {
                    floor += samples[i];
                }
                series[index][1] = value + floor;
                series[index][2] = floor;
            } else {
                series[index][1] = value;
            }
        }
    }

    for (i = 0; i < data.length; i++)
        data[i].label = resmon.Legends[i];

    return cockpit_setup_plot (graph_id, resmon, data, options,
                               store_samples);
}

// ----------------------------------------------------------------------------------------------------

function cockpit_setup_simple_plot (plot_id,
                                    text_id,
                                    resmon,
                                    options,
                                    series_combine_func,
                                    series_text_func)
{
    var data = [ {} ];

    function store_samples (samples, index)
    {
        var series = data[0].data;
        series[index][1] = series_combine_func(samples);
        if (index == series.length-1)
            $(text_id).html(series_text_func(samples));
    }

    return cockpit_setup_plot (plot_id, resmon, data, options,
                               store_samples);
}
<commit_msg>web: Add plot.destroy method.<commit_after>/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2013 Red Hat, Inc.
 *
 * Cockpit is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Cockpit is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
 */

function cockpit_setup_plot (graph_id, resmon, data, user_options,
                             store_samples)
{
    var options = {
        colors: [ "#0099d3" ],
        legend: { show: false },
        series: { shadowSize: 0,
                  lines: { lineWidth: 0.0,
                           fill: 1.0
                         }
                },
        xaxis: { tickFormatter: function() { return ""; } },
        yaxis: { tickFormatter: function() { return ""; } },
        // The point radius influences
        // the margin around the grid
        // even if no points are plotted.
        // We don't want any margin, so
        // we set the radius to zero.
        points: { radius: 0 },
        grid: { borderWidth: 1,
                aboveData: true,
                color: "black",
                borderColor: $.color.parse("black").scale('a', 0.22).toString(),
                labelMargin: 0
              }
    };

    var num_series = data.length;
    var num_points = resmon.NumSamples;
    var got_historical_data = false;
    var plot;
    var running = false;

    $.extend(true, options, user_options);

    // We put the plot inside its own div so that we can give that div
    // a fixed size which only changes when we can also immediately
    // call plot.resize().  Otherwise, the labels and legends briefly
    // get out of sync during resizing.

    var outer_div = $(graph_id);
    var inner_div = $('<div/>');
    outer_div.empty();
    outer_div.append(inner_div);

    // Initialize series
    for (var n = 0; n < num_series; n++) {
        var series = [];
        for (var m = 0; m < num_points; m++) {
            series[m] = [m, 0];
        }
        data[n].data = series;
    }

    function sync_divs ()
    {
        inner_div.width(outer_div.width());
        inner_div.height(outer_div.height());
    }

    function start ()
    {
        running = true;
        if (!plot) {
            sync_divs ();
            plot = $.plot(inner_div, data, options);
        } else
            resize();
    }

    function stop ()
    {
        running = false;
    }

    function refresh ()
    {
        if (plot && running) {
            plot.setData(data);
            plot.setupGrid();
            plot.draw();
        }
    }

    function resize ()
    {
        if (running) {
            sync_divs ();
            plot.resize();
            refresh();
        }
    }

    function new_samples (samples)
    {
        var series;
        var i, n, m, floor;

        for (n = 0; n < data.length; n++) {
            series = data[n].data;
            for (m = 0; m < series.length-1; m++) {
                series[m][1] = series[m+1][1];
                series[m][2] = series[m+1][2];
            }
        }

        store_samples (samples, data[0].data.length-1);
    }

    function got_historical_samples (history)
    {
        var n, offset;

        offset = data[0].data.length - history.length;
        for (n = 0; n < history.length; n++) {
            store_samples (history[n][1], n+offset);
        }
    }

    function new_sample_handler (event, timestampUsec, samples) {
        if (got_historical_data) {
            new_samples (samples);
            refresh ();
        }
    }

    function destroy () {
        $(resmon).off("NewSample", new_sample_handler);
        $(window).off('resize', resize);
        $(outer_div).empty();
        plot = null;
    }

    $(resmon).on("NewSample", new_sample_handler);

    resmon.call("GetSamples", {},
                function(error, result) {
                    if (!error) {
                        got_historical_samples (result);
                        got_historical_data = true;
                        refresh ();
                    }
                });

    $(window).on('resize', resize);

    return { start: start, stop: stop,
             resize: resize, element: inner_div[0],
             destroy: destroy
           };
}

function cockpit_setup_complicated_plot (graph_id, resmon, data, options)
{
    var i;

    function store_samples (samples, index)
    {
        var value, series;
        var n, i, floor;
        for (n = 0; n < data.length; n++) {
            series = data[n].data;
            value = samples[n];

            if (options["x_rh_stack_graphs"]) {
                floor = 0;
                for (i = n + 1; i < samples.length; i++) {
                    floor += samples[i];
                }
                series[index][1] = value + floor;
                series[index][2] = floor;
            } else {
                series[index][1] = value;
            }
        }
    }

    for (i = 0; i < data.length; i++)
        data[i].label = resmon.Legends[i];

    return cockpit_setup_plot (graph_id, resmon, data, options,
                               store_samples);
}

// ----------------------------------------------------------------------------------------------------

function cockpit_setup_simple_plot (plot_id,
                                    text_id,
                                    resmon,
                                    options,
                                    series_combine_func,
                                    series_text_func)
{
    var data = [ {} ];

    function store_samples (samples, index)
    {
        var series = data[0].data;
        series[index][1] = series_combine_func(samples);
        if (index == series.length-1)
            $(text_id).html(series_text_func(samples));
    }

    return cockpit_setup_plot (plot_id, resmon, data, options,
                               store_samples);
}
<|endoftext|>


<commit_before>/**
 * @name AccountConfirmController
 * @desc Controller for the accounts confirm views */

(function() {
    'use strict';

    angular.module('cometApp')
           .controller('AccountConfirmController', AccountConfirmController);

        AccountConfirmController.$inject = ['$state', '$stateParams', 'accountService'];

        function AccountConfirmController ($state, $stateParams, accountService) {

          var vm = this;
          vm.confirm = confirm;
          vm.resendConfirmation = resendConfirmation;
          vm.token = $stateParams.token;
          vm.confirmedAccount = false;
          vm.confirmationError = false;
          vm.resend = false;
          vm.resendSuccess = false;
          vm.resendError = false;

          activate();

          /**
           * @name activate
           * @desc controller startup logic
           */
          function activate() {
            confirm();
          }

          /**
           * @name confirm
           * @desc calls the backend endpoint to confirm a user
           */
          function confirm () {
            accountService.confirm(vm.token).error(function(data) {
                vm.confirmationError = true;
                console.log(data);
            }).then(function() {
                vm.confirmedAccount = true;
                console.log("Cuenta confirmada");
            });
          }

          /**
           * @name confirm
           * @desc calls the backend endpoint to resend the confirmation email
           */
          function resendConfirmation () {
            vm.resend = false;

            accountService.resendConfirmation('asd').error(function(data) {
              vm.resend = true;
              vm.resendError = true;
              console.log("error:" + data);
            }).then(function() {
                vm.resend = true;
                vm.resendSuccess = true;
                console.log("confirmation resend");
            });
          }
      }
})();
<commit_msg>Editar perfile - Arreglo bug de post fantasma a /confirm<commit_after>/**
 * @name AccountConfirmController
 * @desc Controller for the accounts confirm views */

(function() {
    'use strict';

    angular.module('cometApp')
           .controller('AccountConfirmController', AccountConfirmController);

        AccountConfirmController.$inject = ['$state', '$stateParams', 'accountService'];

        function AccountConfirmController ($state, $stateParams, accountService) {

          var vm = this;
          vm.confirm = confirm;
          vm.resendConfirmation = resendConfirmation;
          vm.token = $stateParams.token;
          vm.confirmedAccount = false;
          vm.confirmationError = false;
          vm.resend = false;
          vm.resendSuccess = false;
          vm.resendError = false;

          activate();

          /**
           * @name activate
           * @desc controller startup logic
           */
          function activate() {
            if ($state.current.name !== 'dashboard.profile') {
              confirm();
            }
          }

          /**
           * @name confirm
           * @desc calls the backend endpoint to confirm a user
           */
          function confirm () {
            accountService.confirm(vm.token).error(function(data) {
                vm.confirmationError = true;
                console.log(data);
            }).then(function() {
                vm.confirmedAccount = true;
                console.log("Cuenta confirmada");
            });
          }

          /**
           * @name confirm
           * @desc calls the backend endpoint to resend the confirmation email
           */
          function resendConfirmation () {
            vm.resend = false;

            accountService.resendConfirmation('asd').error(function(data) {
              vm.resend = true;
              vm.resendError = true;
              console.log("error:" + data);
            }).then(function() {
                vm.resend = true;
                vm.resendSuccess = true;
                console.log("confirmation resend");
            });
          }
      }
})();
<|endoftext|>


<commit_before>/**
 * @file XML XPath Parser
 * @since 1.0.1
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GPF_START*/ // 0
/*global _gpfArraySlice*/
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfRegExpTokenize*/ // _gpfRegExpForEach with token #
/*global _GpfXmlXPathConcat*/
/*global _GpfXmlXPathDeep*/
/*global _GpfXmlXPathMatch*/
/*global _GpfXmlXPathSub*/
/*exported _gpfXmlXPathParse*/ // gpf.xml.Parser
/*#endif*/

_gpfErrorDeclare("xml/xpath/parser", {
    /**
     * ### Summary
     *
     * Invalid XPath syntax
     *
     * ### Description
     *
     * This error is used when the parser can't process an XPath
     * @since 1.0.1
     */
    invalidXPathSyntax: "Invalid XPath syntax"
});

var _GPF_XML_XPATH_TOKEN = {};

(function (tokens) {
    var index = _GPF_START,
        regexpSource = [];
    tokens.forEach(function (token) {
        if (token.includes("\t")) {
            var split = token.split("\t"),
                tokenName = split.pop();
            _GPF_XML_XPATH_TOKEN[tokenName] = ++index;
            regexpSource.push("(" + split[0] + ")");
        } else {
            regexpSource.push(token);
        }
    });
    _GPF_XML_XPATH_TOKEN.regexp = new RegExp(regexpSource.join("|"), "g");
}([
    "\\s+",
    "\\|		CONCAT",
    "\\/\\/		DEPTH",
    "\\/		SUB",
    "\\.		CURRENT",
    "@			ATTRIBUTE",
    "\\*		ANY",
    "\\w+:		NAMESPACE_PREFIX",
    "\\w+		NAME"
]));

// <start> -> <level> (CONCAT <level>)?
// <level> -> CURRENT? (SUB|DEPTH) <match>
// <match> -> ATTRIBUTE? NAMESPACE_PREFIX? (NAME|ANY)

/**
 * Parse the XPath expression
 *
 * @param {String} xpathExpression XPath expression to evaluate
 * @return {Object} Parsed expression
 * @since 1.0.1
 */
function _gpfXmlXPathParse (xpathExpression) {
    var tokens = _gpfRegExpTokenize(_GPF_XML_XPATH_TOKEN.regexp, xpathExpression);

    function consumeIfTokenMatch () {
        var expected = _gpfArraySlice(arguments),
            current = tokens[_GPF_START];
        if (expected.include(current.token)) {
            tokens.shift();
            return current;
        }
    }

    function checkAndConsumeIfTokenMatch () {
        var token = consumeIfTokenMatch.apply(null, arguments);
        if (!token) {
            gpf.Error.invalidXPathSyntax();
        }
    }

    function match () {
        var isAttribute = consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.ATTRIBUTE),
            namespacePrefix = consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.NAMESPACE_PREFIX),
            any = consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.ATTRIBUTE),
            name;
        if (!any) {
            name = checkAndConsumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.NAME)[_GPF_XML_XPATH_TOKEN.NAME];
        } else {
            name = "";
        }
        if (namespacePrefix) {
            namespacePrefix = namespacePrefix[_GPF_XML_XPATH_TOKEN.NAMESPACE_PREFIX];
            namespacePrefix = namespacePrefix.substring(0, namespacePrefix.length - 1);
        } else {
            namespacePrefix = "";
        }
        return _GpfXmlXPathMatch(!!isAttribute, namespacePrefix, name);
    }

    var levelClasses = {};
    levelClasses[_GPF_XML_XPATH_TOKEN.SUB] = _GpfXmlXPathSub;
    levelClasses[_GPF_XML_XPATH_TOKEN.DEEP] = _GpfXmlXPathDeep;

    function level () {
        var current = consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.CURRENT),
            level = checkAndConsumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.SUB, _GPF_XML_XPATH_TOKEN.DEEP),
            Operator = levelClasses[level.token];
        var operator = new Operator();
        operator.addChild(match());
        return operator;
    }

    function start () {
        var concat = new _GpfXmlXPathConcat();
        concat.addChild(level());
        while (consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.CONCAT)) {
            concat.addChild(level());
        }
    }

    return start();
}
<commit_msg>Fixes implementation (#326)<commit_after>/**
 * @file XML XPath Parser
 * @since 1.0.1
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GPF_START*/ // 0
/*global _gpfArraySlice*/
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfRegExpTokenize*/ // _gpfRegExpForEach with token #
/*global _GpfXmlXPathConcat*/
/*global _GpfXmlXPathDeep*/
/*global _GpfXmlXPathMatch*/
/*global _GpfXmlXPathSub*/
/*exported _gpfXmlXPathParse*/ // gpf.xml.Parser
/*#endif*/

_gpfErrorDeclare("xml/xpath/parser", {
    /**
     * ### Summary
     *
     * Invalid XPath syntax
     *
     * ### Description
     *
     * This error is used when the parser can't process an XPath
     * @since 1.0.1
     */
    invalidXPathSyntax: "Invalid XPath syntax"
});

var _GPF_XML_XPATH_TOKEN = {};

(function (tokens) {
    var index = _GPF_START,
        regexpSource = [];
    tokens.forEach(function (token) {
        if (token.includes("\t")) {
            var split = token.split("\t"),
                tokenName = split.pop();
            _GPF_XML_XPATH_TOKEN[tokenName] = ++index;
            regexpSource.push("(" + split[0] + ")");
        } else {
            regexpSource.push(token);
        }
    });
    _GPF_XML_XPATH_TOKEN.regexp = new RegExp(regexpSource.join("|"), "g");
}([
    "\\s+",
    "\\|		CONCAT",
    "\\/\\/		DEEP",
    "\\/		SUB",
    "\\.		CURRENT",
    "@			ATTRIBUTE",
    "\\*		ANY",
    "\\w+:		NAMESPACE_PREFIX",
    "\\w+		NAME"
]));

// <start> -> <level> (CONCAT <level>)?
// <level> -> CURRENT? (SUB|DEEP) <match>
// <match> -> ATTRIBUTE? NAMESPACE_PREFIX? (NAME|ANY)

/**
 * Parse the XPath expression
 *
 * @param {String} xpathExpression XPath expression to evaluate
 * @return {Object} Parsed expression
 * @since 1.0.1
 */
function _gpfXmlXPathParse (xpathExpression) {
    var tokens = _gpfRegExpTokenize(_GPF_XML_XPATH_TOKEN.regexp, xpathExpression);

    function consumeIfTokenMatch () {
        var expected = _gpfArraySlice(arguments),
            current = tokens[_GPF_START];
        if (current && expected.includes(current.token)) {
            tokens.shift();
            return current;
        }
    }

    function checkAndConsumeIfTokenMatch () {
        var token = consumeIfTokenMatch.apply(null, arguments);
        if (!token) {
            gpf.Error.invalidXPathSyntax();
        }
        return token;
    }

    function match () {
        var isAttribute = consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.ATTRIBUTE),
            namespacePrefix = consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.NAMESPACE_PREFIX),
            any = consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.ANY),
            name;
        if (!any) {
            name = checkAndConsumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.NAME)[_GPF_XML_XPATH_TOKEN.NAME];
        } else {
            name = "";
        }
        if (namespacePrefix) {
            namespacePrefix = namespacePrefix[_GPF_XML_XPATH_TOKEN.NAMESPACE_PREFIX];
            namespacePrefix = namespacePrefix.substring(0, namespacePrefix.length - 1);
        } else {
            namespacePrefix = "";
        }
        return new _GpfXmlXPathMatch(!!isAttribute, namespacePrefix, name);
    }

    var levelClasses = {};
    levelClasses[_GPF_XML_XPATH_TOKEN.SUB] = _GpfXmlXPathSub;
    levelClasses[_GPF_XML_XPATH_TOKEN.DEEP] = _GpfXmlXPathDeep;

    function level () {
        var current = consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.CURRENT),
            level = checkAndConsumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.SUB, _GPF_XML_XPATH_TOKEN.DEEP),
            Operator = levelClasses[level.token];
        var operator = new Operator();
        operator.addChild(match());
        return operator;
    }

    function start () {
        var concat = new _GpfXmlXPathConcat();
        concat.addChild(level());
        while (consumeIfTokenMatch(_GPF_XML_XPATH_TOKEN.CONCAT)) {
            concat.addChild(level());
        }
        return concat;
    }

    return start();
}
<|endoftext|>


<commit_before>"use strict";
const Docker = require('dockerode');

const docker = new Docker({
  socketPath: '/var/run/docker.sock'
});

exports.listVolumes = (req, res) => {
  docker.listVolumes((err, data) => {
    res.status(200).json({
      volumes: data
    })
  });
}

exports.listSpecificVolume = (req, res) => {
  let id = req.params.id;
  let volume = docker.getVolume(id);

  volume.inspect((err, data) => {

    if (data === null) {
      res.status(404).json({
        volume: "Volume not found",
        error: err
      })
    } else {
      res.status(200).json({
        volume: data
      })
    }
  });
}

exports.removeVolume = (req, res) => {
  const id = req.params.id;
  const volume = docker.getVolume(id);

  volume.remove((err, data) => {
    if (data === null) {
      res.status(409).json({
        message: 'Volume cannot be removed',
        error: err
      });
    } else {
      res.status(200).json({
        message: 'Volume removed successfully'
      });
    }
  });
};
<commit_msg>testing ssh key change<commit_after>"use strict";
const Docker = require('dockerode');

const docker = new Docker({
  socketPath: '/var/run/docker.sock'
});

// exports.createVolume = (req, res) => {
//   docker.createVolume((err, data) => {
//     if (data === null) {
//       res.status(409).json({
//         message: 'Volume cannot be removed',
//         error: err
//       });
//     } else {
//       res.status(200).json({
//         message: 'Volume removed successfully'
//       });
//     }
//   });
// };

exports.listVolumes = (req, res) => {
  docker.listVolumes((err, data) => {
    res.status(200).json({
      volumes: data
    })
  });
}

exports.listSpecificVolume = (req, res) => {
  let id = req.params.id;
  let volume = docker.getVolume(id);

  volume.inspect((err, data) => {

    if (data === null) {
      res.status(404).json({
        volume: "Volume not found",
        error: err
      })
    } else {
      res.status(200).json({
        volume: data
      })
    }
  });
}

exports.removeVolume = (req, res) => {
  const id = req.params.id;
  const volume = docker.getVolume(id);

  volume.remove((err, data) => {
    if (data === null) {
      res.status(409).json({
        message: 'Volume cannot be removed',
        error: err
      });
    } else {
      res.status(200).json({
        message: 'Volume removed successfully'
      });
    }
  });
};
<|endoftext|>


<commit_before>(function (angular) {
    "use strict";
    angular.module("ng.nuget", [])
        .directive('nuget', [function () {
            return {
                restrict: "E",
                transclude: true,
                link: function (scope, elm, attrs, ctrl, transclude) {
                    scope.nugets=scope.nugets||[];
                    scope.nugets.push({
                        name: attrs.name,
                        data: (attrs.data||"").split(','),
                        transclude: transclude
                    });
                }
            };
        }]).directive('nuget', [function () {
            return {
                restrict: "A",
                link: function (scope, elm, attrs) {
                    var nuget = (scope.nugets||[]).filter(function (nugetI) {
                        return nugetI.name == attrs.nuget;
                    })[0];

                    var newScope = scope.$new();

                    newScope.nugetData = nuget.data.reduce(function (map, attr) {
                        var value;
                        if (attr[0] == '*') {
                            attr = attr.slice(1);
                            value = scope.$eval(attrs[attr]);
                        }
                        else {
                            value = attrs[attr];
                        }
                        map[attr] = value;
                        return map;
                    }, {});

                    var transclude = angular.element(nuget.transclude(newScope, function () {}));
                    angular.forEach(transclude.find("nuget-transclude"),function (trn) {
                        trn=angular.element(trn);
                        var name=trn.attr("transclude-id");
                        var replacement=elm.find("[transclude-id='"+name+"']").first();
                        trn.replaceWith(replacement);
                    });

                    elm.replaceWith(transclude);
                }
            };
        }]);
})(angular);
<commit_msg>another issue with JQ light<commit_after>(function (angular) {
    "use strict";
    angular.module("ng.nuget", [])
        .directive('nuget', [function () {
            return {
                restrict: "E",
                transclude: true,
                link: function (scope, elm, attrs, ctrl, transclude) {
                    scope.nugets=scope.nugets||[];
                    scope.nugets.push({
                        name: attrs.name,
                        data: (attrs.data||"").split(','),
                        transclude: transclude
                    });
                }
            };
        }]).directive('nuget', [function () {
            return {
                restrict: "A",
                link: function (scope, elm, attrs) {
                    var nuget = (scope.nugets||[]).filter(function (nugetI) {
                        return nugetI.name == attrs.nuget;
                    })[0];

                    var newScope = scope.$new();

                    newScope.nugetData = nuget.data.reduce(function (map, attr) {
                        var value;
                        if (attr[0] == '*') {
                            attr = attr.slice(1);
                            value = scope.$eval(attrs[attr]);
                        }
                        else {
                            value = attrs[attr];
                        }
                        map[attr] = value;
                        return map;
                    }, {});

                    var transclude = angular.element(nuget.transclude(newScope, function () {}));
                    angular.forEach(transclude.find("nuget-transclude"),function (trn) {
                        trn=angular.element(trn);
                        var name=trn.attr("transclude-id");
                        var replacement=elm.find("[transclude-id='"+name+"']");
                        trn.replaceWith(replacement);
                    });

                    elm.replaceWith(transclude);
                }
            };
        }]);
})(angular);
<|endoftext|>


<commit_before>import { createRequestHandler, makeMutations, makeQueries } from './index'
import '../../test/setup'
import factories from '../../test/setup/factories'
import { some, sortBy } from 'lodash/fp'
import { updateNetworkMemberships } from '../models/post/util'

describe('graphql request handler', () => {
  var handler,
    req, res,
    user, user2,
    community, network,
    post, post2, comment, media

  before(() => {
    handler = createRequestHandler()

    user = factories.user()
    user2 = factories.user()
    community = factories.community()
    network = factories.network()
    post = factories.post({type: Post.Type.DISCUSSION})
    post2 = factories.post({type: Post.Type.REQUEST})
    comment = factories.comment()
    media = factories.media()
    return network.save()
    .then(() => community.save({network_id: network.id}))
    .then(() => user.save())
    .then(() => user2.save())
    .then(() => post.save({user_id: user.id}))
    .then(() => post2.save())
    .then(() => comment.save({post_id: post.id}))
    .then(() => media.save({comment_id: comment.id}))
    .then(() => Promise.all([
      community.posts().attach(post),
      community.posts().attach(post2),
      community.users().attach({
        user_id: user.id,
        active: true,
        created_at: new Date(new Date().getTime() - 86400000)}),
      community.users().attach({user_id: user2.id, active: true})
    ]))
    .then(() => Promise.all([
      updateNetworkMemberships(post),
      updateNetworkMemberships(post2)
    ]))
  })

  beforeEach(() => {
    req = factories.mock.request()
    req.method = 'POST'
    req.session = {userId: user.id}
    res = factories.mock.response()
  })

  describe('with a simple query', () => {
    beforeEach(() => {
      req.body = {
        query: `{
          me {
            name
            memberships {
              community {
                name
              }
            }
            posts {
              title
              communities {
                name
              }
            }
          }
        }`
      }
    })

    it('responds as expected', () => {
      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            me: {
              name: user.get('name'),
              memberships: [
                {
                  community: {
                    name: community.get('name')
                  }
                }
              ],
              posts: [
                {
                  title: post.get('name'),
                  communities: [
                    {
                      name: community.get('name')
                    }
                  ]
                }
              ]
            }
          }
        })
      })
    })
  })

  describe('with a complex query', () => {
    var thread, message

    before(() => {
      thread = factories.post({type: Post.Type.THREAD})

      return thread.save()
      .then(() => {
        message = factories.comment({post_id: thread.id, user_id: user2.id})
        return Promise.all([
          comment.save({user_id: user2.id}),
          message.save(),
          post.followers().attach(user2),
          thread.followers().attach(user)
        ])
        .then(() => thread.followers().attach(user2))
      })
    })

    beforeEach(() => {
      req.body = {
        query: `{
          me {
            name
            memberships {
              community {
                name
              }
            }
            posts {
              title
              communities {
                name
              }
              comments {
                items {
                  text
                  creator {
                    name
                  }
                }
              }
              followers {
                name
              }
              followersTotal
            }
            messageThreads {
              total
              hasMore
              items {
                id
                messages {
                  items {
                    text
                    creator {
                      name
                    }
                  }
                }
                participants {
                  name
                }
                participantsTotal
              }
            }
          }
        }`
      }
    })

    it('responds as expected', () => {
      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            me: {
              name: user.get('name'),
              memberships: [
                {
                  community: {
                    name: community.get('name')
                  }
                }
              ],
              posts: [
                {
                  title: post.get('name'),
                  communities: [
                    {
                      name: community.get('name')
                    }
                  ],
                  comments: {
                    items: [
                      {
                        text: comment.get('text'),
                        creator: {
                          name: user2.get('name')
                        }
                      }
                    ]
                  },
                  followers: [
                    {
                      name: user2.get('name')
                    }
                  ],
                  followersTotal: 1
                }
              ],
              messageThreads: {
                hasMore: false,
                total: 1,
                items: [
                  {
                    id: thread.id,
                    messages: {
                      items: [
                        {
                          text: message.get('text'),
                          creator: {
                            name: user2.get('name')
                          }
                        }
                      ]
                    },
                    participants: [
                      {
                        name: user.get('name')
                      },
                      {
                        name: user2.get('name')
                      }
                    ],
                    participantsTotal: 2
                  }
                ]
              }
            }
          }
        })
      })
    })
  })

  describe('querying Comment attachments', () => {
    beforeEach(() => {
      req.body = {
        query: `{
          post (id: ${post.id}) {
            comments {
              items {
                text
                attachments {
                  id
                  type
                  position
                  url
                }
              }
            }
          }
        }`
      }
    })

    it('responds as expected', () => {
      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            post: {
              comments: {
                items: [
                  {
                    text: comment.get('text'),
                    attachments: [
                      {
                        id: media.id,
                        type: media.get('type'),
                        position: media.get('position'),
                        url: media.get('url')
                      }
                    ]
                  }
                ]
              }
            }
          }
        })
      })
    })
  })

  describe('without a logged-in user', () => {
    beforeEach(() => {
      req.session = {}
    })

    it('shows "not logged in" errors for most queries', () => {
      req.body = {
        query: `{
          me {
            name
          }
          community(id: 9) {
            name
          }
        }`
      }

      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            me: null,
            community: null
          },
          errors: [
            {
              locations: [
                {column: 11, line: 2}
              ],
              message: 'not logged in',
              path: ['me']
            },
            {
              locations: [
                {column: 11, line: 5}
              ],
              message: 'not logged in',
              path: ['community']
            }
          ]
        })
      })
    })

    it('allows checkInvitation', () => {
      req.body = {
        query: `{
          checkInvitation(invitationToken: "foo") {
            valid
          }
        }`
      }
      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            checkInvitation: {
              valid: false
            }
          }
        })
      })
    })
  })

  describe('querying community data', () => {
    it('works as expected', () => {
      req.body = {
        query: `{
          community(id: "${community.id}") {
            slug
            members(first: 2, sortBy: "join") {
              items {
                name
              }
            }
            posts(first: 1, filter: "${Post.Type.REQUEST}") {
              items {
                title
              }
            }
          }
        }`
      }

      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            community: {
              slug: community.get('slug'),
              members: {
                items: [
                  {name: user2.get('name')},
                  {name: user.get('name')}
                ]
              },
              posts: {
                items: [
                  {title: post2.get('name')}
                ]
              }
            }
          }
        })
      })
    })

    describe('with an invalid sort option', () => {
      it('shows an error', () => {
        req.body = {
          query: `{
            community(id: "${community.id}") {
              members(first: 2, sortBy: "height") {
                items {
                  name
                }
              }
            }
          }`
        }

        return handler(req, res).then(() => {
          expectJSON(res, {
            data: {
              community: {
                members: null
              }
            },
            errors: [
              {
                locations: [
                  {column: 15, line: 3}
                ],
                message: 'Cannot sort by "height"',
                path: ['community', 'members']
              }
            ]
          })
        })
      })
    })
  })

  describe('querying network data', () => {
    it('works as expected', () => {
      req.body = {
        query: `{
          network(id: "${network.id}") {
            slug
            members(first: 2, sortBy: "name") {
              items {
                name
              }
            }
            posts(first: 1, filter: "${Post.Type.REQUEST}") {
              items {
                title
              }
            }
          }
        }`
      }

      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            network: {
              slug: network.get('slug'),
              members: {
                items: sortBy('name', [
                  {name: user2.get('name')},
                  {name: user.get('name')}
                ])
              },
              posts: {
                items: [
                  {title: post2.get('name')}
                ]
              }
            }
          }
        })
      })
    })
  })

  describe('search', () => {
    beforeEach(() => {
      return FullTextSearch.dropView().catch(() => {})
      .then(() => FullTextSearch.createView())
    })

    it('works', () => {
      req.body = {
        query: `{
          search(term: "${post.get('name').substring(0, 4)}") {
            items {
              content {
                __typename
                ... on Post {
                  title
                }
              }
            }
          }
        }`
      }

      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            search: {
              items: [
                {
                  content: {
                    __typename: 'Post',
                    title: post.get('name')
                  }
                }
              ]
            }
          }
        })
      })
    })
  })

  describe('removeSkill', () => {
    var skill1, skill2

    before(() => {
      skill1 = factories.skill()
      skill2 = factories.skill()
      return Promise.join(skill1.save(), skill2.save())
    })

    beforeEach(() => {
      return Promise.join(
        user.skills().detach(skill1),
        user.skills().detach(skill2)
      ).then(() => Promise.join(
        user.skills().attach(skill1),
        user.skills().attach(skill2)
      ))
    })

    it('removes a skill with an id', () => {
      req.body = {
        query: `mutation {
          removeSkill(id: ${skill1.id}) {
            success
          }
        }`
      }
      return handler(req, res)
      .then(() => user.load('skills'))
      .then(() => {
        expectJSON(res, {
          data: {
            removeSkill: {
              success: true
            }
          }
        })
        expect(user.relations.skills.length).to.equal(1)
        expect(user.relations.skills.first().id).to.equal(skill2.id)
      })
    })

    it('removes a skill with a name', () => {
      req.body = {
        query: `mutation {
          removeSkill(name: "${skill2.get('name')}") {
            success
          }
        }`
      }
      return handler(req, res)
      .then(() => user.load('skills'))
      .then(() => {
        expectJSON(res, {
          data: {
            removeSkill: {
              success: true
            }
          }
        })
        expect(user.relations.skills.length).to.equal(1)
        expect(user.relations.skills.first().id).to.equal(skill1.id)
      })
    })
  })
})

describe('makeMutations', () => {
  it('imports mutation functions correctly', () => {
    // this test does not check the correctness of the functions used in
    // mutations; it only checks that they are actually functions (i.e. it fails
    // if there are any broken imports)

    const mutations = makeMutations(11)
    const root = {}
    const args = {}

    return Promise.each(Object.keys(mutations), key => {
      const fn = mutations[key]
      return Promise.resolve()
      .then(() => fn(root, args))
      .catch(err => {
        if (some(pattern => err.message.match(pattern), [
          /is not a function/,
          /is not defined/
        ])) {
          expect.fail(null, null, `Mutation "${key}" is not imported correctly: ${err.message}`)
        }

        // FIXME: the console.log below shows a number of places where we need
        // more validation and/or are exposing SQL errors to the end-user
        // console.log(`${key}: ${err.message}`)
      })
    })
  })
})

describe('makeQueries', () => {
  let queries

  before(() => {
    queries = makeQueries(10)
  })

  describe('communityExists', () => {
    it('throws an error if slug is invalid', () => {
      expect(() => {
        queries.communityExists(null, {slug: 'a b'})
      }).to.throw()
    })
  })
})

function expectJSON (res, expected) {
  expect(res.body).to.exist
  return expect(JSON.parse(res.body)).to.deep.equal(expected)
}
<commit_msg>add another test<commit_after>import { createRequestHandler, makeMutations, makeQueries } from './index'
import '../../test/setup'
import factories from '../../test/setup/factories'
import { some, sortBy } from 'lodash/fp'
import { updateNetworkMemberships } from '../models/post/util'

describe('graphql request handler', () => {
  var handler,
    req, res,
    user, user2,
    community, network,
    post, post2, comment, media

  before(() => {
    handler = createRequestHandler()

    user = factories.user()
    user2 = factories.user()
    community = factories.community()
    network = factories.network()
    post = factories.post({type: Post.Type.DISCUSSION})
    post2 = factories.post({type: Post.Type.REQUEST})
    comment = factories.comment()
    media = factories.media()
    return network.save()
    .then(() => community.save({network_id: network.id}))
    .then(() => user.save())
    .then(() => user2.save())
    .then(() => post.save({user_id: user.id}))
    .then(() => post2.save())
    .then(() => comment.save({post_id: post.id}))
    .then(() => media.save({comment_id: comment.id}))
    .then(() => Promise.all([
      community.posts().attach(post),
      community.posts().attach(post2),
      community.users().attach({
        user_id: user.id,
        active: true,
        created_at: new Date(new Date().getTime() - 86400000)}),
      community.users().attach({user_id: user2.id, active: true})
    ]))
    .then(() => Promise.all([
      updateNetworkMemberships(post),
      updateNetworkMemberships(post2)
    ]))
  })

  beforeEach(() => {
    req = factories.mock.request()
    req.method = 'POST'
    req.session = {userId: user.id}
    res = factories.mock.response()
  })

  describe('with a simple query', () => {
    beforeEach(() => {
      req.body = {
        query: `{
          me {
            name
            memberships {
              community {
                name
              }
            }
            posts {
              title
              communities {
                name
              }
            }
          }
        }`
      }
    })

    it('responds as expected', () => {
      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            me: {
              name: user.get('name'),
              memberships: [
                {
                  community: {
                    name: community.get('name')
                  }
                }
              ],
              posts: [
                {
                  title: post.get('name'),
                  communities: [
                    {
                      name: community.get('name')
                    }
                  ]
                }
              ]
            }
          }
        })
      })
    })
  })

  describe('with a complex query', () => {
    var thread, message

    before(() => {
      thread = factories.post({type: Post.Type.THREAD})

      return thread.save()
      .then(() => {
        message = factories.comment({post_id: thread.id, user_id: user2.id})
        return Promise.all([
          comment.save({user_id: user2.id}),
          message.save(),
          post.followers().attach(user2),
          thread.followers().attach(user)
        ])
        .then(() => thread.followers().attach(user2))
      })
    })

    beforeEach(() => {
      req.body = {
        query: `{
          me {
            name
            memberships {
              community {
                name
              }
            }
            posts {
              title
              communities {
                name
              }
              comments {
                items {
                  text
                  creator {
                    name
                  }
                }
              }
              followers {
                name
              }
              followersTotal
            }
            messageThreads {
              total
              hasMore
              items {
                id
                messages {
                  items {
                    text
                    creator {
                      name
                    }
                  }
                }
                participants {
                  name
                }
                participantsTotal
              }
            }
          }
        }`
      }
    })

    it('responds as expected', () => {
      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            me: {
              name: user.get('name'),
              memberships: [
                {
                  community: {
                    name: community.get('name')
                  }
                }
              ],
              posts: [
                {
                  title: post.get('name'),
                  communities: [
                    {
                      name: community.get('name')
                    }
                  ],
                  comments: {
                    items: [
                      {
                        text: comment.get('text'),
                        creator: {
                          name: user2.get('name')
                        }
                      }
                    ]
                  },
                  followers: [
                    {
                      name: user2.get('name')
                    }
                  ],
                  followersTotal: 1
                }
              ],
              messageThreads: {
                hasMore: false,
                total: 1,
                items: [
                  {
                    id: thread.id,
                    messages: {
                      items: [
                        {
                          text: message.get('text'),
                          creator: {
                            name: user2.get('name')
                          }
                        }
                      ]
                    },
                    participants: [
                      {
                        name: user.get('name')
                      },
                      {
                        name: user2.get('name')
                      }
                    ],
                    participantsTotal: 2
                  }
                ]
              }
            }
          }
        })
      })
    })
  })

  describe('querying Comment attachments', () => {
    beforeEach(() => {
      req.body = {
        query: `{
          post (id: ${post.id}) {
            comments {
              items {
                text
                attachments {
                  id
                  type
                  position
                  url
                }
              }
            }
          }
        }`
      }
    })

    it('responds as expected', () => {
      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            post: {
              comments: {
                items: [
                  {
                    text: comment.get('text'),
                    attachments: [
                      {
                        id: media.id,
                        type: media.get('type'),
                        position: media.get('position'),
                        url: media.get('url')
                      }
                    ]
                  }
                ]
              }
            }
          }
        })
      })
    })
  })

  describe('without a logged-in user', () => {
    beforeEach(() => {
      req.session = {}
    })

    it('shows "not logged in" errors for most queries', () => {
      req.body = {
        query: `{
          me {
            name
          }
          community(id: 9) {
            name
          }
        }`
      }

      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            me: null,
            community: null
          },
          errors: [
            {
              locations: [
                {column: 11, line: 2}
              ],
              message: 'not logged in',
              path: ['me']
            },
            {
              locations: [
                {column: 11, line: 5}
              ],
              message: 'not logged in',
              path: ['community']
            }
          ]
        })
      })
    })

    it('allows checkInvitation', () => {
      req.body = {
        query: `{
          checkInvitation(invitationToken: "foo") {
            valid
          }
        }`
      }
      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            checkInvitation: {
              valid: false
            }
          }
        })
      })
    })
  })

  describe('querying community data', () => {
    it('works as expected', () => {
      req.body = {
        query: `{
          community(id: "${community.id}") {
            slug
            members(first: 2, sortBy: "join") {
              items {
                name
              }
            }
            posts(first: 1, filter: "${Post.Type.REQUEST}") {
              items {
                title
              }
            }
          }
        }`
      }

      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            community: {
              slug: community.get('slug'),
              members: {
                items: [
                  {name: user2.get('name')},
                  {name: user.get('name')}
                ]
              },
              posts: {
                items: [
                  {title: post2.get('name')}
                ]
              }
            }
          }
        })
      })
    })

    describe('with an invalid sort option', () => {
      it('shows an error', () => {
        req.body = {
          query: `{
            community(id: "${community.id}") {
              members(first: 2, sortBy: "height") {
                items {
                  name
                }
              }
            }
          }`
        }

        return handler(req, res).then(() => {
          expectJSON(res, {
            data: {
              community: {
                members: null
              }
            },
            errors: [
              {
                locations: [
                  {column: 15, line: 3}
                ],
                message: 'Cannot sort by "height"',
                path: ['community', 'members']
              }
            ]
          })
        })
      })
    })
  })

  describe('querying network data', () => {
    it('works as expected', () => {
      req.body = {
        query: `{
          network(id: "${network.id}") {
            slug
            members(first: 2, sortBy: "name") {
              items {
                name
              }
            }
            posts(first: 1, filter: "${Post.Type.REQUEST}") {
              items {
                title
              }
            }
          }
        }`
      }

      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            network: {
              slug: network.get('slug'),
              members: {
                items: sortBy('name', [
                  {name: user2.get('name')},
                  {name: user.get('name')}
                ])
              },
              posts: {
                items: [
                  {title: post2.get('name')}
                ]
              }
            }
          }
        })
      })
    })
  })

  describe('search', () => {
    beforeEach(() => {
      return FullTextSearch.dropView().catch(() => {})
      .then(() => FullTextSearch.createView())
    })

    it('works', () => {
      req.body = {
        query: `{
          search(term: "${post.get('name').substring(0, 4)}") {
            items {
              content {
                __typename
                ... on Post {
                  title
                }
              }
            }
          }
        }`
      }

      return handler(req, res).then(() => {
        expectJSON(res, {
          data: {
            search: {
              items: [
                {
                  content: {
                    __typename: 'Post',
                    title: post.get('name')
                  }
                }
              ]
            }
          }
        })
      })
    })
  })

  describe('removeSkill', () => {
    var skill1, skill2

    before(() => {
      skill1 = factories.skill()
      skill2 = factories.skill()
      return Promise.join(skill1.save(), skill2.save())
    })

    beforeEach(() => {
      return Promise.join(
        user.skills().detach(skill1),
        user.skills().detach(skill2)
      ).then(() => Promise.join(
        user.skills().attach(skill1),
        user.skills().attach(skill2)
      ))
    })

    it('removes a skill with an id', () => {
      req.body = {
        query: `mutation {
          removeSkill(id: ${skill1.id}) {
            success
          }
        }`
      }
      return handler(req, res)
      .then(() => user.load('skills'))
      .then(() => {
        expectJSON(res, {
          data: {
            removeSkill: {
              success: true
            }
          }
        })
        expect(user.relations.skills.length).to.equal(1)
        expect(user.relations.skills.first().id).to.equal(skill2.id)
      })
    })

    it('removes a skill with a name', () => {
      req.body = {
        query: `mutation {
          removeSkill(name: "${skill2.get('name')}") {
            success
          }
        }`
      }
      return handler(req, res)
      .then(() => user.load('skills'))
      .then(() => {
        expectJSON(res, {
          data: {
            removeSkill: {
              success: true
            }
          }
        })
        expect(user.relations.skills.length).to.equal(1)
        expect(user.relations.skills.first().id).to.equal(skill1.id)
      })
    })
  })
})

describe('makeMutations', () => {
  it('imports mutation functions correctly', () => {
    // this test does not check the correctness of the functions used in
    // mutations; it only checks that they are actually functions (i.e. it fails
    // if there are any broken imports)

    const mutations = makeMutations(11)
    const root = {}
    const args = {}

    return Promise.each(Object.keys(mutations), key => {
      const fn = mutations[key]
      return Promise.resolve()
      .then(() => fn(root, args))
      .catch(err => {
        if (some(pattern => err.message.match(pattern), [
          /is not a function/,
          /is not defined/
        ])) {
          expect.fail(null, null, `Mutation "${key}" is not imported correctly: ${err.message}`)
        }

        // FIXME: the console.log below shows a number of places where we need
        // more validation and/or are exposing SQL errors to the end-user
        // console.log(`${key}: ${err.message}`)
      })
    })
  })
})

describe('makeQueries', () => {
  let queries

  before(() => {
    queries = makeQueries(10)
  })

  describe('communityExists', () => {
    it('throws an error if slug is invalid', () => {
      expect(() => {
        queries.communityExists(null, {slug: 'a b'})
      }).to.throw()
    })

    it('returns true if the slug is in use', () => {
      const community = factories.community()
      return community.save()
      .then(() => queries.communityExists(null, {slug: community.get('slug')}))
      .then(result => expect(result.exists).to.be.true)
    })
  })
})

function expectJSON (res, expected) {
  expect(res.body).to.exist
  return expect(JSON.parse(res.body)).to.deep.equal(expected)
}
<|endoftext|>


<commit_before>import { Class } from '../../core/Core.js';
import { REQUIRED } from '../../datastore/record/ValidationError.js';
import { create as el } from '../../dom/Element.js';
import { AbstractControlView } from './AbstractControlView.js';

/* { on, observes } from */
import '../../foundation/Decorators.js';

/**
    Class: O.AbstractInputView

    Extends: O.View

    The superclass for most DOM-control view classes. This is an abstract class
    and should not be instantiated directly; it is only intended to be
    subclassed.
*/
const AbstractInputView = Class({
    Name: 'AbstractInputView',

    Extends: AbstractControlView,

    /**
        Property: O.AbstractInputView#label
        Type: String|Element|null
        Default: ''

        A label for the control, to be displayed next to it.
    */
    label: null,

    /**
        Property: O.AbstractInputView#label
        Type: String|Element|null
        Default: ''

        A description for the control, to be displayed next to it.
    */
    description: null,

    /**
        Property: O.AbstractInputView#name
        Type: String|undefined
        Default: undefined

        If set, this will be the name attribute of the control.
    */
    name: undefined,

    /**
        Property: O.AbstractInputView#value
        Type: *
        Default: false

        The value represented by this control, for example true/false if a
        checkbox is checked/unchecked, or the text input into a textarea.
    */
    value: false,

    /**
        Property: O.AbstractInputView#error
        Type: ValidationError|null
        Default: null

        A validation error for this input.
    */
    error: null,

    /**
        Property: O.AbstractInputView#isValid
        Type: Boolean
        Default: true

        If false, an `is-invalid' class will be added to the view's class name.
    */
    isValid: function (isValid) {
        if (isValid !== undefined) {
            return isValid;
        }
        const error = this.get('error');
        if (error && error.type !== REQUIRED) {
            return this.get('isFocused');
        }
        return true;
    }.property('error', 'isFocused'),

    /**
        Property: O.AbstractInputView#inputAttributes
        Type: Object

        Extra attributes to add to the text view. Examples include:

        - maxLength: Number
        - autocomplete: 'on' or 'off'
        - autocapitalize: 'on' or 'off'
        - autocorrect: 'on' or 'off'
        - pattern: String (regexp)
    */
    inputAttributes: {
        autocomplete: 'off',
    },

    /**
        Property: O.AbstractInputView#className
        Type: String
        Default: baseClassName

        Overrides default in <O.View#className>.
    */
    className: function () {
        const type = this.get('type');
        return (
            this.get('baseClassName') +
            (this.get('isDisabled') ? ' is-disabled' : '') +
            (this.get('isFocused') ? ' is-focused' : '') +
            (this.get('isValid') ? '' : ' is-invalid') +
            (type ? ' ' + type : '')
        );
    }.property('baseClassName', 'isDisabled', 'isFocused', 'isValid', 'type'),

    /**
        Method: O.AbstractInputView#drawControl

        Overridden to set properties and add label. See <O.View#draw>.
    */
    drawControl() {
        throw new Error('drawControl() must be overriden in subclass');
    },

    drawLabel(label) {
        const control = this._domControl;
        return el('label', { for: control && control.id }, [label]);
    },

    drawDescription(description) {
        return el('p', [description]);
    },

    /**
        Method: O.AbstractInputView#draw

        Overridden to set properties and add label. See <O.View#draw>.
    */
    draw(layer) {
        const control = this.drawControl();

        let label = this.get('label');
        if (label) {
            label = this.drawLabel(label);
        }

        let description = this.get('description');
        if (description) {
            description = this.drawDescription(description);
        }

        this.redrawInputAttributes(layer);
        this.redrawTabIndex(layer);

        return [label, control, description];
    },

    // --- Keep render in sync with state ---

    abstractInputNeedsRedraw: function (self, property, oldValue) {
        return this.propertyNeedsRedraw(self, property, oldValue);
    }.observes('inputAttributes', 'name', 'value'),

    /**
        Method: O.AbstractInputView#redrawInputAttributes

        Updates any other properties of the `<input>` element.
    */
    redrawInputAttributes() {
        const inputAttributes = this.get('inputAttributes');
        const control = this._domControl;
        for (const property in inputAttributes) {
            control.set(property, inputAttributes[property]);
        }
    },

    /**
        Method: O.AbstractInputView#redrawName

        Updates the name attribute on the DOM control to match the name
        property of the view.
    */
    redrawName() {
        this._domControl.name = this.get('name');
    },

    /**
        Method: O.AbstractInputView#redrawValue

        Updates the content of the input to match the <#value> property.
    */
    redrawValue() {
        this._domControl.value = this.get('value');
    },

    // --- Focus ---

    /**
        Method: O.AbstractInputView#focus

        Focusses the control.

        Returns:
            {O.AbstractInputView} Returns self.
    */
    focus() {
        if (this.get('isInDocument')) {
            this._domControl.focus({
                preventScroll: true,
            });
            // Fire event synchronously.
            if (!this.get('isFocused')) {
                this.fire('focus', {
                    target: this._domControl,
                    targetView: this,
                });
            }
        }
        return this;
    },

    /**
        Method: O.AbstractInputView#blur

        Removes focus from the control.

        Returns:
            {O.AbstractInputView} Returns self.
    */
    blur() {
        if (this.get('isInDocument')) {
            this._domControl.blur();
            // Fire event synchronously.
            if (this.get('isFocused')) {
                this.fire('blur', {
                    target: this._domControl,
                    targetView: this,
                });
            }
        }
        return this;
    },

    /**
        Method (private): O.AbstractInputView#_updateIsFocused

        Updates the <#isFocused> property.

        Parameters:
            event - {Event} The focus event.
    */
    _updateIsFocused: function (event) {
        this.set(
            'isFocused',
            event.type === 'focus' && event.target === this._domControl,
        );
    }.on('focus', 'blur'),

    // --- Input ---

    userDidInput(value) {
        this.set('value', value);
    },
});

export { AbstractInputView };
<commit_msg>Improve AbstractInputView#draw<commit_after>import { Class } from '../../core/Core.js';
import { REQUIRED } from '../../datastore/record/ValidationError.js';
import { create as el } from '../../dom/Element.js';
import { AbstractControlView } from './AbstractControlView.js';

/* { on, observes } from */
import '../../foundation/Decorators.js';

/**
    Class: O.AbstractInputView

    Extends: O.View

    The superclass for most DOM-control view classes. This is an abstract class
    and should not be instantiated directly; it is only intended to be
    subclassed.
*/
const AbstractInputView = Class({
    Name: 'AbstractInputView',

    Extends: AbstractControlView,

    /**
        Property: O.AbstractInputView#label
        Type: String|Element|null
        Default: ''

        A label for the control, to be displayed next to it.
    */
    label: null,

    /**
        Property: O.AbstractInputView#label
        Type: String|Element|null
        Default: ''

        A description for the control, to be displayed next to it.
    */
    description: null,

    /**
        Property: O.AbstractInputView#name
        Type: String|undefined
        Default: undefined

        If set, this will be the name attribute of the control.
    */
    name: undefined,

    /**
        Property: O.AbstractInputView#value
        Type: *
        Default: false

        The value represented by this control, for example true/false if a
        checkbox is checked/unchecked, or the text input into a textarea.
    */
    value: false,

    /**
        Property: O.AbstractInputView#error
        Type: ValidationError|null
        Default: null

        A validation error for this input.
    */
    error: null,

    /**
        Property: O.AbstractInputView#isValid
        Type: Boolean
        Default: true

        If false, an `is-invalid' class will be added to the view's class name.
    */
    isValid: function (isValid) {
        if (isValid !== undefined) {
            return isValid;
        }
        const error = this.get('error');
        if (error && error.type !== REQUIRED) {
            return this.get('isFocused');
        }
        return true;
    }.property('error', 'isFocused'),

    /**
        Property: O.AbstractInputView#inputAttributes
        Type: Object

        Extra attributes to add to the text view. Examples include:

        - maxLength: Number
        - autocomplete: 'on' or 'off'
        - autocapitalize: 'on' or 'off'
        - autocorrect: 'on' or 'off'
        - pattern: String (regexp)
    */
    inputAttributes: {
        autocomplete: 'off',
    },

    /**
        Property: O.AbstractInputView#className
        Type: String
        Default: baseClassName

        Overrides default in <O.View#className>.
    */
    className: function () {
        const type = this.get('type');
        return (
            this.get('baseClassName') +
            (this.get('isDisabled') ? ' is-disabled' : '') +
            (this.get('isFocused') ? ' is-focused' : '') +
            (this.get('isValid') ? '' : ' is-invalid') +
            (type ? ' ' + type : '')
        );
    }.property('baseClassName', 'isDisabled', 'isFocused', 'isValid', 'type'),

    /**
        Method: O.AbstractInputView#drawControl

        Overridden to set properties and add label. See <O.View#draw>.
    */
    drawControl() {
        throw new Error('drawControl() must be overriden in subclass');
    },

    drawLabel(label) {
        const control = this._domControl;
        return el('label', { for: control && control.id }, [label]);
    },

    drawDescription(description) {
        return el('p', [description]);
    },

    drawHelp() {
        const description = this.get('description');
        return description ? this.drawDescription(description) : null;
    },

    draw(layer) {
        const controlEl = this.drawControl();
        const label = this.get('label');
        const labelEl = label ? this.drawLabel(label) : null;
        const descriptionEl = this.drawHelp();

        this.redrawInputAttributes(layer);
        this.redrawTabIndex(layer);

        return [labelEl, controlEl, descriptionEl];
    },

    // --- Keep render in sync with state ---

    abstractInputNeedsRedraw: function (self, property, oldValue) {
        return this.propertyNeedsRedraw(self, property, oldValue);
    }.observes('inputAttributes', 'name', 'value'),

    /**
        Method: O.AbstractInputView#redrawInputAttributes

        Updates any other properties of the `<input>` element.
    */
    redrawInputAttributes() {
        const inputAttributes = this.get('inputAttributes');
        const control = this._domControl;
        for (const property in inputAttributes) {
            control.set(property, inputAttributes[property]);
        }
    },

    /**
        Method: O.AbstractInputView#redrawName

        Updates the name attribute on the DOM control to match the name
        property of the view.
    */
    redrawName() {
        this._domControl.name = this.get('name');
    },

    /**
        Method: O.AbstractInputView#redrawValue

        Updates the content of the input to match the <#value> property.
    */
    redrawValue() {
        this._domControl.value = this.get('value');
    },

    // --- Focus ---

    /**
        Method: O.AbstractInputView#focus

        Focusses the control.

        Returns:
            {O.AbstractInputView} Returns self.
    */
    focus() {
        if (this.get('isInDocument')) {
            this._domControl.focus({
                preventScroll: true,
            });
            // Fire event synchronously.
            if (!this.get('isFocused')) {
                this.fire('focus', {
                    target: this._domControl,
                    targetView: this,
                });
            }
        }
        return this;
    },

    /**
        Method: O.AbstractInputView#blur

        Removes focus from the control.

        Returns:
            {O.AbstractInputView} Returns self.
    */
    blur() {
        if (this.get('isInDocument')) {
            this._domControl.blur();
            // Fire event synchronously.
            if (this.get('isFocused')) {
                this.fire('blur', {
                    target: this._domControl,
                    targetView: this,
                });
            }
        }
        return this;
    },

    /**
        Method (private): O.AbstractInputView#_updateIsFocused

        Updates the <#isFocused> property.

        Parameters:
            event - {Event} The focus event.
    */
    _updateIsFocused: function (event) {
        this.set(
            'isFocused',
            event.type === 'focus' && event.target === this._domControl,
        );
    }.on('focus', 'blur'),

    // --- Input ---

    userDidInput(value) {
        this.set('value', value);
    },
});

export { AbstractInputView };
<|endoftext|>