text:
[![Build Status](https://travis-ci.org/nathanjh/coffee-grader-api.svg?branch=development)](https://travis-ci.org/nathanjh/coffee-grader-api)
[![codecov](https://codecov.io/gh/nathanjh/coffee-grader-api/branch/development/graph/badge.svg)](https://codecov.io/gh/nathanjh/coffee-grader-api)
# coffee-grader-api
back-end api for 'coffee-grader' coffee cupping app

:coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee: :coffee:

###### Built on RAILS 5
    bundle install
    rails db:migrate
    rails db:seed
    rails server
###### Run Tests
    bundle exec rspec

## Table of Contents

* [Authentication](#authentication)
* [Cuppings](#cuppings)
* [Cupped-Coffees](#cupped-coffees)
* [Invites](#invites)
* [Scores](#scores)
* [Users](#users)
* [Coffees and Roasters](#coffees-and-roasters)

## Authentication
Coffee-Grader-API uses token-based authentication (leveraging the [DeviseTokenAuth](https://github.com/lynndylanhurley/devise_token_auth) gem).  The client should include the following headers in each request ([RFC 6750 Bearer Token](http://tools.ietf.org/html/rfc6750) format).
###### Example Authentication Headers:
    "access-token": "wwwwww",
    "token-type":   "Bearer",
    "client":       "xxxxxx",
    "expiry":       "yyyyyy",
    "uid":          "zzzzzz"
**Note**: Each response will include the required headers for the following request, as the access-token (for example) is reset on each authenticated request.
##### To Sign Up
`POST` to `/auth` with the following parameters:

Field | Description
------|------------
**`name`** | User's full name
**`username`** | User's nickname
**`email`** | A valid email address
**`password`** | A password (at least 8 characters)
**`password_confirmation`** | to confirm the above

##### To Sign In
`POST` to `/auth/sign_in` with a valid `email` and `password`

## Cuppings
The `cupping` resource manages more or less all aspects of the coffee grading process; samples are to be scored, graders are invited, etc.  A cupping must be created before any samples can be scored, so a `POST` request to `/cuppings` is a sensible first step.

###### The Cupping Object
Field | Description
------|------------
**`location`** | Location of cupping event as string (required)
**`cup_date`** | Date of cupping event as datetime; UTC formatted string works great (required)
**`host_id`** | Foreign key of user hosting the cupping (required)
**`cups_per_sample`** | Integer number of cups per sample (required)
**`open`** | Boolean value indicating whether the cupping has occurred; default value is `true` (for more, see [cupping-open](#open))

###### Cupping Endpoints
    GET /cuppings
    GET /cuppings/:id
    POST /cuppings
    PATCH /cuppings/:id
    DELETE /cuppings/:id
**Note**: Only a cupping's host has access to `PATCH /cuppings/:id` and `DELETE /cuppings/:id`
###### Sample Response from GET /cuppings/:id
```JSON
{
    "cupping": {
        "id": 6,
        "location": "2571 Ritchie Plain",
        "cup_date": "2017-07-16T22:52:24.155Z",
        "cups_per_sample": 5,
        "host_id": 2,
        "open": true,
        "cupped_coffees": [
            {
                "id": 1,
                "roast_date": "2017-07-16T22:52:23.155Z",
                "coffee_alias": "6-181",
                "coffee_id": 2,
                "roaster_id": 9,
                "cupping_id": 6
            },
            {
                "id": 2,
                "roast_date": "2017-07-16T22:52:23.155Z",
                "coffee_alias": "6-241",
                "coffee_id": 19,
                "roaster_id": 3,
                "cupping_id": 6
            },
            {
                "id": 3,
                "roast_date": "2017-07-16T22:52:23.155Z",
                "coffee_alias": "6-760",
                "coffee_id": 2,
                "roaster_id": 3,
                "cupping_id": 6
            }
        ],
        "scores": [],
        "invites": [
            {
                "id": 1,
                "status": "pending",
                "cupping_id": 6,
                "grader_id": 7,
                "grader_email": null,
                "invite_token": null
            },
            {
                "id": 2,
                "status": "pending",
                "cupping_id": 6,
                "grader_id": 6,
                "grader_email": null,
                "invite_token": null
            },
            {
                "id": 3,
                "status": "pending",
                "cupping_id": 6,
                "grader_id": 5,
                "grader_email": null,
                "invite_token": null
            }
        ]
    }
}
```
As you can see, a cupping includes `invites`, `scores`, and `cupped_coffees`(AKA 'samples')
###### Open?
* A cupping's status is determined by its `open` property.  When `open: true`, a cupping can receive new scores, cupped_coffees, and invites; conversely, `open: false` indicates that the cupping *occurred in the past* and is therefore closed to new scores, invites, etc.
* **The client should set the `open` property to `false` after all scores have been submitted.**

## Cupped Coffees
The Cupped Coffee object represents a unique roasted coffee sample, bringing together a green `coffee` and `roaster`, to be added to a `cupping`.
###### The CuppedCoffee Object
Field | Description
------|------------
**`roast_date`** | Date of coffee roast as datetime; UTC formatted string works great (required)
**`coffee_alias`** | Alternate identifier for coffee as string
**`coffee_id`** | Foreign key of the associated coffee (required)
**`roaster_id`** | Foreign key of the associated roaster (required)
**`cupping_id`** | Foreign key of the associated roaster (required)

###### Cupped Coffee Endpoints
    GET /cuppings/:cupping_id/cupped_coffees
    GET /cuppings/:cupping_id/cupped_coffees/:id
    POST /cuppings/:cupping_id/cupped_coffees
    PATCH /cuppings/:cupping_id/cupped_coffees/:id
    DELETE /cuppings/:cupping_id/cupped_coffees/:id
**Note**: Only a cupping's host (the user who owns the cupping referred to by `/cuppings/:cupping_id`) has access to the following:
* `POST /cuppings/:cupping_id/cupped_coffees`
* `PATCH /cuppings/:cupping_id/cupped_coffees/:id`
* `DELETE /cuppings/:cupping_id/cupped_coffees/:id`

A cupped_coffee includes its associated `roaster`, `coffee`, and `scores`.
###### Sample Response from GET /cuppings/:cupping_id/cupped_coffees/:id
```JSON
{
  "cupped_coffee": {
    "id": 2,
    "roast_date": "2017-07-16T22:52:23.155Z",
    "coffee_alias": "6-241",
    "coffee_id": 19,
    "roaster_id": 3,
    "cupping_id": 6,
    "coffee": {
      "id": 19,
      "name": "Heart Pie",
      "origin": "Mount Elgon, Uganda",
      "producer": "Marjorie Cormier",
      "variety": "Obata"
    },
    "roaster": {
      "id": 3,
      "name": "Salvia Roastery",
      "location": "Carolview",
      "website": "http://rennerromaguera.biz/leslie_fay"
    },
    "scores": []
  }
}
```

## Invites
The Invite object--like a Cupped Coffee--is dependent on a `cupping`, and may be used to associate `users` (as `graders`) with a specific `cupping` event.  Simply send a `POST` request to `cuppings/:cupping_id/invites` with the invitee's `grader_id`(alias for `user_id`) or email address (as `grader_email`).

###### The Invite Object
Field | Description
------|------------
**`cupping_id`** | Foreign key of the associated cupping event (required)
**`grader_id`** | Foreign key of the user being invited (required in the absence of `grader_email`)
**`grader_email`** | Email address of a prospective grader; to be used when no user account exists for grader, see [below](#on-invite-creation) (required in the absence of `grader_id`)
**`status`** | Indication of whether invitee has accepted invite; possible values include: `'pending'`(default), `'accepted'`, `'declined'`, or `'maybe'`

###### Invites Endpoints
    GET /cuppings/:cupping_id/invites
    GET /cuppings/:cupping_id/invites/:id
    POST /cuppings/:cupping_id/invites
    PATCH /cuppings/:cupping_id/invites/:id
    DELETE /cuppings/:cupping_id/invites/:id

###### On Invite Creation
When sending `POST` requests to `cuppings/:id/invites`, the typical request params
need only include an `invite` object with a valid `grader_id`.  A notification email
will be sent to the corresponding user.
- Want to invite a friend without an account?  No problem!  Just send along any valid email address eg.
`
{
  "invite": {
    "grader_email": "your@coffee.friend"
  }
}
`
and we'll send an email to the provided address containing a custom link to sign-up,
which passes along (as a query string) a unique invite token which needs to be
`POST`ed, along with required account registration params, to `/auth`.  We take it
from there, and update your invite with the new user.

###### Sample Response from GET /cuppings/:cupping_id/invites
```JSON
{
    "invites": [
        {
            "id": 13,
            "status": "pending",
            "cupping_id": 10,
            "grader_id": 6,
            "grader_email": null,
            "invite_token": null,
            "grader": {
                "id": 6,
                "name": "Jeramy Lang",
                "username": "jimmie",
                "email": "luciano@sanford.com"
            }
        },
        {
            "id": 14,
            "status": "pending",
            "cupping_id": 10,
            "grader_id": 4,
            "grader_email": null,
            "invite_token": null,
            "grader": {
                "id": 4,
                "name": "Miss Kim Heaney",
                "username": "murray",
                "email": "adriel.spinka@rempel.io"
            }
        },
        {
            "id": 15,
            "status": "pending",
            "cupping_id": 10,
            "grader_id": 3,
            "grader_email": null,
            "invite_token": null,
            "grader": {
                "id": 3,
                "name": "Earline Lemke",
                "username": "mireille.kunde",
                "email": "guido@jacobs.io"
            }
        }
    ]
}
```

## Scores
The `score` resource refers to a specific evaluation of a `cupped_coffee`/sample by a grader, storing a handful of parameters where characteristics of a coffee are assigned numeric values.  The scoring format and characteristics in question conform to current [SCAA scoring guidelines](http://www.scaa.org/?page=resources&d=cupping-protocols).
- Given that the associated `cupping`'s `open` attribute is `true`, a `score` may be created by `POST`ing to `/scores` with valid attributes.

###### The Score Object

Field | Description
------|------------
**`roast_level`** | Integer indicating roast level, determined by grader
**`aroma`** | Decimal indicating aroma, determined by grader
**`flavor`** | Decimal indicating flavor, determined by grader
**`aftertaste`** | Decimal indicating aftertaste, determined by grader
**`acidity`** | Decimal indicating acidity, determined by grader
**`body`** | Decimal indicating body, determined by grader
**`balance`** | Decimal indicating balance, determined by grader
**`sweetness`** | Decimal indicating sweetness, determined by grader
**`clean_cup`** | Decimal indicating level of cup cleanliness, determined by grader
**`uniformity`** | Decimal indicating uniformity, determined by grader
**`overall`** | Decimal indicating overall impression, determined by grader
**`total_score`** | Decimal indicating sum of all scores
**`defects`** | Integer indicating defect score, determined by grader
**`final_score`** | Decimal indicating final score attributed to cupped coffee
**`notes`** | Text notes regarding cupped coffee, entered by grader
**`cupped_coffee_id`** | Foreign key of associated cupped coffee
**`cupping_id`** | Foreign key of associated cupping event
**`grader_id`** | Foreign key of associated grader

###### Scores Endpoints
    GET /scores
    GET /scores/:id
    POST /scores
    POST /scores/submit_scores
    PATCH /scores/:id
    DELETE /scores/:id

###### Sample Response from GET /scores/:id
```JSON
{
  "score": {
    "id": 20,
    "grader_id": 7,
    "cupping_id": 3,
    "cupped_coffee_id": 23,
    "aroma": "8.25",
    "aftertaste": "6.25",
    "acidity": "8.0",
    "body": "7.0",
    "uniformity": "9.0",
    "flavor": "6.25",
    "balance": "7.5",
    "clean_cup": "6.25",
    "sweetness": "6.5",
    "overall": "9.0",
    "defects": 4,
    "total_score": "74.0",
    "final_score": "70.0",
    "notes": "bright, full, clementine, molasses, cream"
  }
}
```

###### Batch create for scores
`POST` `/scores/submit_scores` allows for the addition of multiple scores with a single call.
```JSON
Example POST data (note that score objects have been truncated in this example to save space)
{
  "scores": [
    {
      "roast_level": "3",
      "aroma": "8.5",
      "aftertaste": "9.25",
      "acidity": "9.0",
      "body": "8.75",
      "cupped_coffee_id": "123"
    },
    {
      "roast_level": "4",
      "aroma": "7.25",
      "aftertaste": "8.25",
      "acidity": "7.5",
      "body": "8.0",
      "cupped_coffee_id": "456"
    }
  ]
}

```
Please note that **any errors in a single score will prevent the entire batch of scores from processing**.
The server returns a 204 No Content response on success, and a 400 Bad Request response--along with an error message--on failure.

## Users
  The `user` resource exposes an endpoint for accessing a specific `user`, as well as a `users/search` route intended for use when implementing autocompletion in the client.

###### Users Endpoints
    GET /users/:id
    GET /users/search

###### Users search
Queries for users by both `email` and `username`. Please send search term with
key `term`, eg. `{ "term": "your_search" }`.  Also supports optional pagination
parameters: `limit`, the number of records per page, and `page`, which calculates
offset for records returned (relative to `limit`).

## Coffees and Roasters
The `coffee` and `roaster` resources represent specific coffees and roasteries.  Typically, these will be referenced when creating a `cupped_coffee`, but may also be created when new green coffees or roasters need to be added to the database.

### Coffees
The `coffee` resource represents green coffee (i.e. not yet roasted), such that a single `coffee` resource may be referenced in many `cupped_coffee`s.

###### The Coffee Object
Field | Description
------|------------
**`name`** | Name of coffee as string (required)
**`origin`** | Origin of coffee as string (required)
**`producer`** | Coffee producer as string (required)
**`variety`** | Coffee variety as string

###### Coffees Endpoints
    GET /coffees
    GET /coffees/:id
    GET /coffees/search
    POST /coffees

###### Coffees search
Queries for coffees by  `name`, `producer` and `origin`. Please see [notes on *users search* for further information](#users-search).

### Roasters
The `roaster` resource represents a specific roastery or roaster, such that a single `roaster` resource may be referenced in many `cupped_coffee`s.

###### The Roaster Object
Field | Description
------|------------
**`name`** | Name of the roaster/roastery as string (required)
**`location`** | Roastery location as string (required)
**`website`** | Roastery website

###### Roasters Endpoints
    GET /roasters
    GET /roasters/:id
    GET /roasters/search
    POST /roasters

###### Roasters search
Queries for roasters by `name`and `location`. Please see [notes on *users search* for further information](#users-search).


text:
"use strict";
var $ = require("jquery"), EventEmitter = require("events").EventEmitter, utils = require("yasgui-utils");
console = console || {
  log: function() {}
}; //make sure any console statements don't break in IE

require("./jquery/extendJquery.js");

/**
 * Main YASR constructor
 *
 * @constructor
 * @param {DOM-Element} parent element to append editor to.
 * @param {object} settings
 * @class YASR
 * @return {doc} YASR document
 */
var YASR = function(parent, options, queryResults) {
  EventEmitter.call(this);
  var yasr = this;
  // console.log(EventEmitter.call(this));

  // var yasr = {};
  // EventEmitter.call(yasr);
  yasr.options = $.extend(true, {}, module.exports.defaults, options);
  //the recursive copy does merge (overwrite) array values how we want it to. Do this manually
  if (options && options.outputPlugins) yasr.options.outputPlugins = options.outputPlugins;

  yasr.container = $("

").appendTo(parent);
  yasr.header = $("

").appendTo(yasr.container);
  yasr.resultsContainer = $("

").appendTo(yasr.container);
  yasr.storage = utils.storage;

  var prefix = null;
  yasr.getPersistencyId = function(postfix) {
    if (prefix === null) {
      //instantiate prefix
      if (yasr.options.persistency && yasr.options.persistency.prefix) {
        prefix = typeof yasr.options.persistency.prefix == "string"
          ? yasr.options.persistency.prefix
          : yasr.options.persistency.prefix(yasr);
      } else {
        prefix = false;
      }
    }
    if (prefix && postfix != null) {
      return prefix + (typeof postfix == "string" ? postfix : postfix(yasr));
    } else {
      return null;
    }
  };

  if (yasr.options.useGoogleCharts) {
    //pre-load google-loader
    require("./gChartLoader.js")
      .once("initError", function() {
        yasr.options.useGoogleCharts = false;
      })
      .init();
  }

  //first initialize plugins
  yasr.plugins = {};
  for (var pluginName in module.exports.plugins) {
    if (!yasr.options.useGoogleCharts && pluginName == "gchart") continue;
    yasr.plugins[pluginName] = new module.exports.plugins[pluginName](yasr);
  }

  yasr.updateHeader = function() {
    var downloadIcon = yasr.header.find(".yasr_downloadIcon").removeAttr("title"); //and remove previous titles
    var embedButton = yasr.header.find(".yasr_embedBtn");
    var outputPlugin = yasr.plugins[yasr.options.output];
    if (outputPlugin) {
      //Manage download link
      var info = outputPlugin.getDownloadInfo ? outputPlugin.getDownloadInfo() : null;
      if (info) {
        if (info.buttonTitle) downloadIcon.attr("title", info.buttonTitle);
        downloadIcon.prop("disabled", false);
        downloadIcon.find("path").each(function() {
          this.style.fill = "black";
        });
      } else {
        downloadIcon.prop("disabled", true).prop("title", "Download not supported for this result representation");
        downloadIcon.find("path").each(function() {
          this.style.fill = "gray";
        });
      }

      //Manage embed button
      var link = null;
      if (outputPlugin.getEmbedHtml) link = outputPlugin.getEmbedHtml();
      if (link && link.length > 0) {
        embedButton.show();
      } else {
        embedButton.hide();
      }
    }
  };
  yasr.draw = function(output) {
    if (!yasr.results) return false;
    if (!output) output = yasr.options.output;

    //ah, our default output does not take our current results. Try to autodetect
    var selectedOutput = null;
    var selectedOutputPriority = -1;
    var unsupportedOutputs = [];
    for (var tryOutput in yasr.plugins) {
      if (yasr.plugins[tryOutput].canHandleResults(yasr)) {
        var priority = yasr.plugins[tryOutput].getPriority;
        if (typeof priority == "function") priority = priority(yasr);
        if (priority != null && priority != undefined && priority > selectedOutputPriority) {
          selectedOutputPriority = priority;
          selectedOutput = tryOutput;
        }
      } else {
        unsupportedOutputs.push(tryOutput);
      }
    }
    disableOutputs(unsupportedOutputs);
    var outputToDraw = null;
    if (output in yasr.plugins && yasr.plugins[output].canHandleResults(yasr)) {
      outputToDraw = output;
    } else if (selectedOutput) {
      outputToDraw = selectedOutput;
    }

    if (outputToDraw) {
      if (yasr.options.output !== outputToDraw) {
        yasr.options.output = outputToDraw;
        updateOutputSelectorButtons();
      }

      $(yasr.resultsContainer).empty();
      yasr.emit("draw", yasr, yasr.plugins[outputToDraw]);
      yasr.plugins[outputToDraw].draw(yasr.options.pluginSettings[outputToDraw] || {});
      yasr.emit("drawn", yasr, yasr.plugins[outputToDraw]);
      yasr.updateHeader();
      return true;
    } else {
      yasr.updateHeader();
      return false;
    }
  };

  var updateOutputSelectorButtons = function() {
      var buttons = yasr.header.find(".yasr_btnGroup .yasr_btn").removeClass("selected");
      buttons.filter(".select_" + yasr.options.output).addClass("selected");
  };

  var disableOutputs = function(outputs) {
    //first enable everything.
    yasr.header.find(".yasr_btnGroup .yasr_btn").removeClass("disabled").removeAttr("disabled");

    //now disable the outputs passed as param
    outputs.forEach(function(outputName) {
      var disabledTitle, disabledMsg;
      if (module.exports.plugins[outputName] && module.exports.plugins[outputName].defaults) {
        disabledTitle = module.exports.plugins[outputName].defaults.disabledTitle;
      }
      yasr.header.find(".yasr_btnGroup .select_" + outputName).addClass("disabled").attr("title", disabledTitle || "")
        .attr("disabled", true);
    });
  };
  yasr.somethingDrawn = function() {
    return !yasr.resultsContainer.is(":empty");
  };

  yasr.setResponse = function(dataOrJqXhr, textStatus, jqXhrOrErrorString) {
    try {
      yasr.results = require("./parsers/wrapper.js")(dataOrJqXhr, textStatus, jqXhrOrErrorString);
    } catch (exception) {
      console.log(exception);
      yasr.results = {
        getException: function() {
          return exception;
        }
      };
    }
    yasr.draw();

    //store if needed
    var resultsId = yasr.getPersistencyId(yasr.options.persistency.results.key);
    if (resultsId) {
      if (
        !yasr.results.getException() &&
        yasr.results.getOriginalResponseAsString &&
        yasr.results.getOriginalResponseAsString().length < yasr.options.persistency.results.maxSize
      ) {
        utils.storage.set(resultsId, yasr.results.getAsStoreObject(), "month", yasr.options.onQuotaExceeded);
      } else {
        //remove old string
        utils.storage.remove(resultsId);
      }
    }
  };
  var $toggableWarning = null;
  var $toggableWarningClose = null;
  var $toggableWarningMsg = null;
  yasr.warn = function(warning) {
    if (!$toggableWarning) {
      //first time instantiation
      $toggableWarning = $("
", {
        class: "toggableWarning"
      })
        .prependTo(yasr.container)
        .hide();
      $toggableWarningClose = $("", {
        class: "toggleWarning"
      })
        .html("×")
        .click(function() {
          $toggableWarning.hide(400);
        })
        .appendTo($toggableWarning);
      $toggableWarningMsg = $("", {
        class: "toggableMsg"
      }).appendTo($toggableWarning);
    }
    $toggableWarningMsg.empty();
    if (warning instanceof $) {
      $toggableWarningMsg.append(warning);
    } else {
      $toggableWarningMsg.html(warning);
    }
    $toggableWarning.show(400);
  };

  var blobDownloadSupported = null;
  var checkBlobDownloadSupported = function() {
    if (blobDownloadSupported === null) {
      var windowUrl = window.URL || window.webkitURL || window.mozURL || window.msURL;
      blobDownloadSupported = windowUrl && Blob;
    }
    return blobDownloadSupported;
  };
  var embedBtn = null;
  var drawHeader = function(yasr) {
    var drawOutputSelector = function() {
      var btnGroup = $('

');
      $.each(yasr.options.outputPlugins, function(i, pluginName) {
        var plugin = yasr.plugins[pluginName];
        if (!plugin) return; //plugin not loaded

        if (plugin.hideFromSelection) return;
        var name = plugin.name || pluginName;
        var button = $("<button type='button' class='yasr_btn'></button>")
          .text(name)
          .addClass("select_" + pluginName)
          .click(function() {
            //update buttons
            btnGroup.find("button.selected").removeClass("selected");
            $(this).addClass("selected");
            //set and draw output
            yasr.options.output = pluginName;

            //store if needed
            yasr.store();

            //close warning if there is any
            if ($toggableWarning) $toggableWarning.hide(400);

            yasr.draw();
          })
          .appendTo(btnGroup);
        if (yasr.options.output == pluginName) button.addClass("selected");
      });

      if (btnGroup.children().length > 1) yasr.header.append(btnGroup);
    };
    var drawDownloadIcon = function() {
      var stringToUrl = function(string, contentType) {
        var url = null;
        var windowUrl = window.URL || window.webkitURL || window.mozURL || window.msURL;
        if (windowUrl && Blob) {
          var blob = new Blob([string], {
            type: contentType
          });
          url = windowUrl.createObjectURL(blob);
        }
        return url;
      };
      var button = $("<button type='button' class='yasr_btn yasr_downloadIcon btn_icon'></button>")
        .append(require("yasgui-utils").svg.getElement(require("./imgs.js").download))
        .click(function() {
          var currentPlugin = yasr.plugins[yasr.options.output];
          if (currentPlugin && currentPlugin.getDownloadInfo) {
            var downloadInfo = currentPlugin.getDownloadInfo();
            var downloadUrl = stringToUrl(
              downloadInfo.getContent(),
              downloadInfo.contentType ? downloadInfo.contentType : "text/plain"
            );
            var downloadMockLink = $("", {
              href: downloadUrl,
              download: downloadInfo.filename
            });
            require("./utils.js").fireClick(downloadMockLink);
            //						downloadMockLink[0].click();
          }
        });
      yasr.header.append(button);
    };
    var drawFullscreenButton = function() {
      var button = $("<button type='button' class='yasr_btn btn_fullscreen btn_icon'></button>")
        .append(require("yasgui-utils").svg.getElement(require("./imgs.js").fullscreen))
        .click(function() {
          yasr.container.addClass("yasr_fullscreen");
          //draw, as yasr dimensions have changed (needed for e.g. leaflet)
          yasr.draw();
          //useful to emit this, as then we can e.g. update the yasgui style when this happens
          //needed this to fix z-index issues with leaflet, as the yasgui el has a relative pos
          yasr.emit('fullscreen-enter', yasr);

        });
      yasr.header.append(button);
    };
    var drawSmallscreenButton = function() {
      var button = $("<button type='button' class='yasr_btn btn_smallscreen btn_icon'></button>")
        .append(require("yasgui-utils").svg.getElement(require("./imgs.js").smallscreen))
        .click(function() {
          yasr.container.removeClass("yasr_fullscreen");
          //draw, as yasr dimensions have changed (needed for e.g. leaflet)
          yasr.draw();
          yasr.emit('fullscreen-leave', yasr);
        });
      yasr.header.append(button);
    };
    var drawEmbedButton = function() {
      embedBtn = $("<button type='button'>", {
        class: "yasr_btn yasr_embedBtn",
        title: "Get HTML snippet to embed results on a web page"
      })
        .text("</>")
        .click(function(event) {
          var currentPlugin = yasr.plugins[yasr.options.output];
          if (currentPlugin && currentPlugin.getEmbedHtml) {
            var embedLink = currentPlugin.getEmbedHtml();

            event.stopPropagation();
            var popup = $("

").appendTo(yasr.header);
            $("html").click(function() {
              if (popup) popup.remove();
            });

            popup.click(function(event) {
              event.stopPropagation();
              //dont close when clicking on popup
            });
            var prePopup = $("<textarea>").val(embedLink);
            prePopup.focus(function() {
              var $this = $(this);
              $this.select();

              // Work around Chrome's little problem
              $this.mouseup(function() {
                // Prevent further mouseup intervention
                $this.unbind("mouseup");
                return false;
              });
            });

            popup.empty().append(prePopup);
            var positions = embedBtn.position();
            var top = positions.top + embedBtn.outerHeight() + "px";
            var left = Math.max(positions.left + embedBtn.outerWidth() - popup.outerWidth(), 0) + "px";

            popup.css("top", top).css("left", left);
          }
        });
      yasr.header.append(embedBtn);
    };
    drawFullscreenButton();
    drawSmallscreenButton();
    if (yasr.options.drawOutputSelector) drawOutputSelector();
    if (yasr.options.drawDownloadIcon && checkBlobDownloadSupported()) drawDownloadIcon(); //only draw when it's supported
    drawEmbedButton();
  };

  var persistentId = null;
  //store persistent options (not results though. store these separately, as they are too large)
  yasr.store = function() {
    if (!persistentId) persistentId = yasr.getPersistencyId("main");
    if (persistentId) {
      utils.storage.set(persistentId, yasr.getPersistentSettings(), null, yasr.options.onQuotaExceeded);
    }
  };

  yasr.load = function() {
    if (!persistentId) persistentId = yasr.getPersistencyId("main");
    yasr.setPersistentSettings(utils.storage.get(persistentId));
  };

  yasr.setPersistentSettings = function(settings) {
    if (settings) {
      if (settings.output) {
        yasr.options.output = settings.output;
      }
      for (var pluginName in settings.plugins) {
        if (yasr.plugins[pluginName] && yasr.plugins[pluginName].setPersistentSettings) {
          yasr.plugins[pluginName].setPersistentSettings(settings.plugins[pluginName]);
        }
      }
    }
  };

  yasr.getPersistentSettings = function() {
    var settings = {
      output: yasr.options.output,
      plugins: {}
    };
    for (var pluginName in yasr.plugins) {
      if (yasr.plugins[pluginName].getPersistentSettings) {
        settings.plugins[pluginName] = yasr.plugins[pluginName].getPersistentSettings();
      }
    }
    return settings;
  };

  /**
	 * postprocess
	 */
  yasr.load();
  drawHeader(yasr);
  if (!queryResults && yasr.options.persistency && yasr.options.persistency.results) {
    var resultsId = yasr.getPersistencyId(yasr.options.persistency.results.key);
    var fromStorage;
    if (resultsId) {
      fromStorage = utils.storage.get(resultsId);
    }

    if (!fromStorage && yasr.options.persistency.results.id) {
      //deprecated! But keep for backwards compatability
      //if results are stored under old ID. Fetch the results, and delete that key (results can be large, and clutter space)
      //setting the results, will automatically store it under the new key, so we don't have to worry about that here
      var deprId = typeof yasr.options.persistency.results.id == "string"
        ? yasr.options.persistency.results.id
        : yasr.options.persistency.results.id(yasr);
      if (deprId) {
        fromStorage = utils.storage.get(deprId);
        if (fromStorage) utils.storage.remove(deprId);
      }
    }
    if (fromStorage) {
      if ($.isArray(fromStorage)) {
        yasr.setResponse.apply(this, fromStorage);
      } else {
        yasr.setResponse(fromStorage);
      }
    }
  }

  if (queryResults) {
    yasr.setResponse(queryResults);
  }
  yasr.updateHeader();

  return yasr;
};

YASR.prototype = new EventEmitter();
module.exports = function(parent, options, queryResults) {
  return new YASR(parent, options, queryResults);
};

module.exports.plugins = {};
module.exports.registerOutput = function(name, constructor) {
  module.exports.plugins[name] = constructor;
};

/**
 * The default options of YASR. Either change the default options by setting YASR.defaults, or by
 * passing your own options as second argument to the YASR constructor
 *
 * @attribute YASR.defaults
 */
module.exports.defaults = require("./defaults.js");
module.exports.version = {
  YASR: require("../package.json").version,
  jquery: $.fn.jquery,
  "yasgui-utils": require("yasgui-utils").version
};
module.exports.$ = $;

//put these in a try-catch. When using the unbundled version, and when some dependencies are missing, then YASR as a whole will still function
try {
  module.exports.registerOutput("boolean", require("./boolean.js"));
} catch (e) {
  console.warn(e);
}
try {
  module.exports.registerOutput("rawResponse", require("./rawResponse.js"));
} catch (e) {
  console.warn(e);
}
try {
  module.exports.registerOutput("leaflet", require("./leaflet.js"));
} catch (e) {
  console.warn(e);
}
try {
  module.exports.registerOutput("table", require("./table.js"));
} catch (e) {
  console.warn(e);
}
try {
  module.exports.registerOutput("error", require("./error.js"));
} catch (e) {
  console.warn(e);
}
try {
  module.exports.registerOutput("pivot", require("./pivot.js"));
} catch (e) {
  console.warn(e);
}
try {
  module.exports.registerOutput("gchart", require("./gchart.js"));
} catch (e) {
  console.warn(e);
}

redpajama_set_name: RedPajamaGithub


text:
%% VK_KERNEL_AUGMENT Augment the kernel with 'additional variable' data.
%
% SYNOPSIS
%   Where a project has specified additional variables, these are evaluated for
%   each point in the kernel.
%
% USAGE
%   % Augment the kernel, store the result in V, using data from p:
%   V = vk_kernel_augment(p);
%
%   % Using the viability kernel V, and taking other options from p:
%   V = vk_kernel_augment(p, V);
%
% See also: vk_kernel_augment_constraints

%%
%  Copyright 2011 Jacek B. Krawczyk and Alastair Pharo
%
%  Licensed under the Apache License, Version 2.0 (the "License");
%  you may not use this file except in compliance with the License.
%  You may obtain a copy of the License at
%
%      http://www.apache.org/licenses/LICENSE-2.0
%`
%  Unless required by applicable law or agreed to in writing, software
%  distributed under the License is distributed on an "AS IS" BASIS,
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%  See the License for the specific language governing permissions and
%  limitations under the License.
function V = vk_kernel_augment(project, varargin)

    %% V can optionally be specified.
    if (nargin > 1)
        V = varargin{1};
    else
        V = project.V;
    end

    %% Augment V with zeros to begin with:
    V = [V, zeros(size(V, 1), project.numaddnvars)];

    %% For each non-ignored index, fill in the gaps:
    for i = transpose(find(project.addnignore == false))
        fn = inline(project.addneqns{i}, project.symbols{:});
        for row = 1:size(V, 1)
            args = num2cell(transpose(V(row,1:project.numvars)));
            V(row, project.numvars+i) = fn(args{:});
        end
    end

    %% Then, for each ignored variable, remove the column:
    for i = sort(transpose(find(project.addnignore))+project.numvars, 'descend')
        V = [V(:,1:i-1), V(:,i+1:end)];
    end
end


text:
import React from 'react';
import { mount } from 'enzyme';
import { Button } from '@talend/react-bootstrap';

import ItemEdit from './ItemEdit.component';

const item = {
	id: 1,
	values: ['toto'],
	index: 0,
	itemProps: {
		key: 'values',
		onSubmitItem: jest.fn(), // provided click callback
		onAbortItem: jest.fn(), // provided click callback
		actions: [{
			label: 'Validate',
			id: 'validate',
			onClick: jest.fn(), // provided click callback
		}, {
			label: 'Cancel',
			id: 'cancel',
			onClick: jest.fn(), // provided click callback
		}],
	},
};

describe('Item', () => {
	it('should display value with two buttons and trigger callback on button title click', () => {
		// given
		const props = {
			item,
			currentEdit: {
				validate: {
					disabled: false,
				},
			},
		};
		const itemInstance = <ItemEdit {...props} />;

		// when
		const wrapper = mount(itemInstance);
		const buttons = wrapper.find(Button);

		buttons.at(1).simulate('click', { stopPropagation: () => {} });

		// then
		expect(buttons.length).toBe(2);
		expect(props.item.itemProps.actions[1].onClick).toBeCalled();
	});

	it('should trigger callback on input title ENTER', () => {
		// given
		const props = {
			item,
			currentEdit: {
				validate: {
					disabled: false,
				},
			},
			itemProps: {
				key: 'values',
				onSubmitItem: jest.fn(), // provided click callback
				onAbortItem: jest.fn(), // provided click callback
				actions: [{
					label: 'Validate',
					id: 'validate',
					onClick: jest.fn(), // provided click callback
				}, {
					label: 'Cancel',
					id: 'cancel',
					onClick: jest.fn(), // provided click callback
				}],
			},
		};

		const itemEditInstance = <ItemEdit {...props} />;

		// when
		const wrapper = mount(itemEditInstance);
		wrapper.find('input').simulate('keyDown', { keyCode: 13, target: { value: 'my new title' } });

		// then
		expect(props.item.itemProps.onSubmitItem).toBeCalled();
		const callArgs = props.item.itemProps.onSubmitItem.mock.calls[0];
		expect(callArgs[1]).toEqual({ value: 'my new title', model: props.item, index: 0 });
	});

	it('should trigger callback on input title ESC', () => {
		// given
		const props = {
			item,
			currentEdit: {
				validate: {
					disabled: false,
				},
			},
			itemProps: {
				key: 'values',
				onSubmitItem: jest.fn(), // provided click callback
				onAbortItem: jest.fn(), // provided click callback
				actions: [{
					label: 'Validate',
					id: 'validate',
					onClick: jest.fn(), // provided click callback
				}, {
					label: 'Cancel',
					id: 'cancel',
					onClick: jest.fn(), // provided click callback
				}],
			},
		};

		const itemEditInstance = <ItemEdit {...props} />;

		// when
		const wrapper = mount(itemEditInstance);
		wrapper.find('input').simulate('keyDown', { keyCode: 27 });

		// then
		expect(props.item.itemProps.onAbortItem).toBeCalled();
		const callArgs = props.item.itemProps.onAbortItem.mock.calls[0];
		expect(callArgs[1]).toEqual({ value: 'toto', model: props.item, index: 0 });
	});
});


text:
<?php

namespace TrafficCophp\ByteBuffer;

abstract class AbstractBuffer implements ReadableBuffer, WriteableBuffer {
	abstract public function __construct($length);
	abstract public function __toString();
	abstract public function length();
}


text:
layout: post
title: ""
categories: purdue-themen
author: Jonah Blank
---
## Studies on event selection criteria for the non-resonant {% latex %}HH\rightarrow b\bar{b}b\bar{b}{% endlatex %} decay channel

In this thesis, methods of event selection for non-resonant Di-Higgs production in proton-proton collisions are discussed and a comparison of the different trigger paths for the Compact Muon Solenoid (CMS) and ATLAS experiment is made.

An event selection based on selection criteria used by ATLAS experiment is applied to Monte Carlo simulated data for the CMS experiment, with a center-of-mass energy of
{% latex %}\sqrt{s}={13}\,\mathrm{TeV}{% endlatex %}
and an integrated luminosity of
{% latex %}36\,\mathrm{fb^{-1}}{% endlatex %}
, that only contains Di-Higgs events that decay exclusively via the dominant channel
{% latex %}H\!H\rightarrow b\bar{b}b\bar{b}{% endlatex %}
. To determine the efficiency and purity, selection criteria are applied on the Monte Carlo simulated data sample, and the result is compared with the generator level information of the simulation.

Furthermore, the background rejection provided these criteria, namely the dominant QCD-multijet background and the sub-dominant
{% latex %}t\bar{t}{% endlatex %}
 background, are studied. Based on the expected signal and background rates, experimental upper limits for the non-resonant Di-Higgs cross section are studied.


text:
package org.elasticsearch.search.internal;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.*;
import org.elasticsearch.common.lucene.MinimumScoreCollector;
import org.elasticsearch.common.lucene.MultiCollector;
import org.elasticsearch.common.lucene.search.AndFilter;
import org.elasticsearch.common.lucene.search.FilteredCollector;
import org.elasticsearch.index.engine.Engine;
import org.elasticsearch.search.dfs.CachedDfSource;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 *
 */
public class ContextIndexSearcher extends IndexSearcher {

    public static final class Scopes {
        public static final String MAIN = "_main_";
        public static final String GLOBAL = "_global_";
        public static final String NA = "_na_";
    }

    private final SearchContext searchContext;

    private final IndexReader reader;

    private CachedDfSource dfSource;

    private Map<String, List<Collector>> scopeCollectors;

    private String processingScope;

    public ContextIndexSearcher(SearchContext searchContext, Engine.Searcher searcher) {
        super(searcher.reader());
        this.searchContext = searchContext;
        this.reader = searcher.reader();
        setSimilarity(searcher.searcher().getSimilarity());
    }

    public void dfSource(CachedDfSource dfSource) {
        this.dfSource = dfSource;
    }

    public void addCollector(String scope, Collector collector) {
        if (scopeCollectors == null) {
            scopeCollectors = Maps.newHashMap();
        }
        List<Collector> collectors = scopeCollectors.get(scope);
        if (collectors == null) {
            collectors = Lists.newArrayList();
            scopeCollectors.put(scope, collectors);
        }
        collectors.add(collector);
    }

    public List<Collector> removeCollectors(String scope) {
        if (scopeCollectors == null) {
            return null;
        }
        return scopeCollectors.remove(scope);
    }

    public boolean hasCollectors(String scope) {
        if (scopeCollectors == null) {
            return false;
        }
        if (!scopeCollectors.containsKey(scope)) {
            return false;
        }
        return !scopeCollectors.get(scope).isEmpty();
    }

    public void processingScope(String scope) {
        this.processingScope = scope;
    }

    public void processedScope() {
        // clean the current scope (we processed it, also handles scrolling since we don't want to
        // do it again)
        if (scopeCollectors != null) {
            scopeCollectors.remove(processingScope);
        }
        this.processingScope = Scopes.NA;
    }

    @Override
    public Query rewrite(Query original) throws IOException {
        if (original == searchContext.query() || original == searchContext.parsedQuery().query()) {
            // optimize in case its the top level search query and we already rewrote it...
            if (searchContext.queryRewritten()) {
                return searchContext.query();
            }
            Query rewriteQuery = super.rewrite(original);
            searchContext.updateRewriteQuery(rewriteQuery);
            return rewriteQuery;
        } else {
            return super.rewrite(original);
        }
    }

    @Override
    public Weight createNormalizedWeight(Query query) throws IOException {
        // if its the main query, use we have dfs data, only then do it
        if (dfSource != null && (query == searchContext.query() || query == searchContext.parsedQuery().query())) {
            return dfSource.createNormalizedWeight(query);
        }
        return super.createNormalizedWeight(query);
    }

    private Filter combinedFilter(Filter filter) {
        Filter combinedFilter;
        if (filter == null) {
            combinedFilter = searchContext.aliasFilter();
        } else {
            if (searchContext.aliasFilter() != null) {
                combinedFilter = new AndFilter(ImmutableList.of(filter, searchContext.aliasFilter()));
            } else {
                combinedFilter = filter;
            }
        }
        return combinedFilter;
    }

    @Override
    public void search(Query query, Collector results) throws IOException {
        Filter filter = combinedFilter(null);
        if (filter != null) {
            super.search(wrapFilter(query, filter), results);
        } else {
            super.search(query, results);
        }
    }

    @Override
    public TopDocs search(Query query, Filter filter, int n) throws IOException {
        return super.search(query, combinedFilter(filter), n);
    }

    @Override
    public void search(List<AtomicReaderContext> leaves, Weight weight, Collector collector) throws IOException {
        if (searchContext.parsedFilter() != null && Scopes.MAIN.equals(processingScope)) {
            // this will only get applied to the actual search collector and not
            // to any scoped collectors, also, it will only be applied to the main collector
            // since that is where the filter should only work
            collector = new FilteredCollector(collector, searchContext.parsedFilter());
        }
        if (searchContext.timeoutInMillis() != -1) {
            // TODO: change to use our own counter that uses the scheduler in ThreadPool
            collector = new TimeLimitingCollector(collector, TimeLimitingCollector.getGlobalCounter(), searchContext.timeoutInMillis());
        }
        if (scopeCollectors != null) {
            List<Collector> collectors = scopeCollectors.get(processingScope);
            if (collectors != null && !collectors.isEmpty()) {
                collector = new MultiCollector(collector, collectors.toArray(new Collector[collectors.size()]));
            }
        }
        // apply the minimum score after multi collector so we filter facets as well
        if (searchContext.minimumScore() != null) {
            collector = new MinimumScoreCollector(collector, searchContext.minimumScore());
        }

        // we only compute the doc id set once since within a context, we execute the same query always...
        if (searchContext.timeoutInMillis() != -1) {
            try {
                super.search(leaves, weight, collector);
            } catch (TimeLimitingCollector.TimeExceededException e) {
                searchContext.queryResult().searchTimedOut(true);
            }
        } else {
            super.search(leaves, weight, collector);
        }
    }

    @Override
    public Explanation explain(Query query, int doc) throws IOException {
        if (searchContext.aliasFilter() == null) {
            return super.explain(query, doc);
        }

        FilteredQuery filteredQuery = new FilteredQuery(query, searchContext.aliasFilter());
        return super.explain(filteredQuery, doc);
    }
}


text:
package com.waltz3d.museum;

import java.io.File;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Environment;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Toast;

public class Util {

	public static boolean net_type_changed = false;

	public static void showToast(Context mContext, String string, int duration) {
		Toast.makeText(mContext, string, duration).show();
	}

	public static String getSDCardDir(Context mContext) {
		final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ? Environment.getExternalStorageDirectory().getPath() : mContext.getCacheDir().getPath();
		String path = cachePath + File.separator;
		if (BuildConfig.DEBUG) {
			Log.d("getSDCardDir", "getSDCardDir=" + path);
		}
		return path;
	}

	public static boolean ensureDir(String path) {
		if (null == path) {
			return false;
		}
		boolean ret = false;
		File file = new File(path);
		if (!file.exists() || !file.isDirectory()) {
			try {
				ret = file.mkdirs();
			} catch (SecurityException se) {
				se.printStackTrace();
			}
		}

		return ret;
	}

	public static boolean showDialog(ProgressDialog dialog, String msg) {
		if (dialog == null) {
			return false;
		}
		dismissDialog(dialog);

		dialog.setMessage(msg);
		dialog.setCanceledOnTouchOutside(false);
		dialog.setCancelable(true);
		try {
			dialog.show();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return true;
	}

    public static boolean isMobileNet(final Context context) {
        boolean bRet = false;
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo wifiInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
        if (wifiInfo != null && wifiInfo.isConnectedOrConnecting()) {
            bRet = true;
        }
        return bRet;
    }

	public static void dismissDialog(ProgressDialog dialog) {
		if (null != dialog) {
			try {
				dialog.dismiss();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			}
			// dialog = null;
		}
	}

	public static boolean isWifiNet(Context context) {
		boolean bRet = false;
		ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo wifiInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
		if (wifiInfo.isConnectedOrConnecting()) {
			bRet = true;
		}
		return bRet;
	}
}


text:
using System;
using System.Collections.Generic;
using System.Text;
using Rs317.Sharp;

namespace Rs317
{
	public static class ChatModeTypeExtensions
	{
		/// <summary>
		/// Indicates if the mode is enabled such as:
		/// On or Friends Only.
		/// </summary>
		/// <param name="mode">The mode to check.</param>
		/// <returns></returns>
		public static bool isEnabled(this ChatModeType mode)
		{
			return mode < ChatModeType.Off;
		}
	}
}


text:
class DailyCalendarController {

    constructor ($scope, $rootScope, $state, CalendarService, $stateParams, toasterService) {

        const dateFormat = 'DD/MM/YYYY HH:mm:ss';

        var calculateDuration = () => {
            $scope.dailyData.Appointments.forEach((daily) => {
                var difference = moment(moment(daily.End), dateFormat).diff(moment(moment(daily.Start), dateFormat));
                daily.Duration = moment.duration(difference).asHours();
            });
        };

        var retrieveDayInformation = (year, month, day) => {
            $rootScope.loading = true;

            CalendarService.getDayInformation(year, month, day)
                .then((response) => {
                    $scope.dailyData = response.data;
                    $scope.dailyData.Today = new Date($scope.dailyData.Year, $scope.dailyData.Month - 1, $scope.dailyData.Day);
                    calculateDuration();
                })
                .catch((error) => {
                    toasterService.showServerError(error);
                })
                .finally(() => {
                    $rootScope.loading = false;
                });
        };

        $scope.goBack = () => {
            $state.go('calendar.monthly');
        };

        retrieveDayInformation($stateParams.year, $stateParams.month, $stateParams.day);
    }
}

DailyCalendarController.$inject = ['$scope', '$rootScope', '$state', 'calendarService', '$stateParams', 'toasterService'];
export default DailyCalendarController;


text:
import {Injectable} from '@angular/core';

import { Http, Headers, Response } from '@angular/http';

import { Observable } from 'rxjs/Rx';

import {ApiUrl} from '../../../../shared/constants/apiUrl';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Injectable()
export class EventsTabsService {
  private timeUrl = ApiUrl.API_URL + "DashBoardDateTimeWeather04";
  private latestUrl = ApiUrl.API_URL + "DashBoardRealTime02";
  private allUrl = ApiUrl.API_URL + "DashBoardRealTime02History";

  constructor(
    private http: Http) {
  }

  //Used for busy/loading indicator
  getConnectionLatest(): any {
    return this.http.get(this.latestUrl)
        .map(this.extractData)
        .catch(this.handleError);
  }

  //Used for busy/loading indicator
  getConnectionAll(): any {
    return this.http.get(this.allUrl)
        .map(this.extractData)
        .catch(this.handleError);
  }

  getTimeData(): any {
    return this.http.get(this.timeUrl)
      .map(this.extractData)
      .catch(this.handleError);
  }

  getLatestData(): any {
    //WITH AUTOMATIC REFRESH
    return Observable.interval(60000) //Automatic data refresh
    .startWith(0)
    .flatMap(() =>
      this.http.get(this.latestUrl)
    )
    .map(this.extractData)
    .catch(this.handleError);

    //W/O AUTOMATIC REFRESH
    // return this.http.get(this.summaryUrl + date)
    //   .map(this.extractData)
    //   .catch(this.handleError);
  }

  getAllData(): any {
    //WITH AUTOMATIC REFRESH (TOO SLOW)
    // return Observable.interval(60000) //Automatic data refresh
    // .startWith(0)
    // .flatMap(() =>
    //   this.http.get(this.detailUrl + date)
    // )
    // .map(this.extractData)
    // .catch(this.handleError);

    //W/O AUTOMATIC REFRESH
    return this.http.get(this.allUrl)
      .map(this.extractData)
      .catch(this.handleError);
  }

  private extractData(res: Response) {
    let body = res.json();
    return body || { };
  }

  // handleError method as per the Angular.io website
  private handleError (error: Response | any) {
    // In a real world app, we might use a remote logging infrastructure
    let errMsg: string;
    if (error instanceof Response) {
      const body = error.json() || '';
      const err = body.error || JSON.stringify(body);
      errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }
    console.error(errMsg);
    return Observable.throw(errMsg);
  }
}