Model click to hide

Controller click to hide

var transform = function (data, callback) { // Insert your code here. Transform the `data` object into anything // you like. var transformedData = []; // Create a chart to show top signatures. var signatures = []; var counts = []; for (var j = 0; j < data.facets.signature.length && j < 20; j++) { var term = data.facets.signature[j]; var shortSig = term.term; if (shortSig.length > 20) { shortSig = shortSig.substr(0, 17) + '…'; } signatures.push(shortSig); counts.push(term.count); } transformedData.push({ name: 'Top signatures', type: 'chart', data: { labels: signatures, datasets: [{ fillColor: "rgba(151,187,205,0.2)", strokeColor: "rgba(151,187,205,1)", pointColor: "rgba(151,187,205,1)", pointStrokeColor: "#fff", pointHighlightFill: "#fff", pointHighlightStroke: "rgba(151,187,205,1)", data: counts }] }, options: { bezierCurve: false } }); // Create a table to show reports. var hits = []; for (var i = 0; i < data.hits.length; i++) { var hit = data.hits[i]; hits.push({ 'Crash id': hit.uuid, 'Product': hit.product, 'Version': hit.version, 'Build id': hit.build_id, 'Platform': hit.platform, }); } transformedData.push({ name: 'Reports', type: 'table', headers: [ {name: 'Crash id', type: "text", width: 300}, {name: 'Product', type: "text", width: 150}, {name: 'Version', type: "text", width: 68}, {name: 'Build id', type: "number", width: 150}, {name: 'Platform', type: "text", width: 150} ], data: hits }); // Always keep this callback, its parameter is the transformed data. callback(transformedData); }; // Exports the api to the application environment. Do not remove. application.setInterface({ transform: transform });

View

No data to show yet.

Purpose

When working on crash analysis, it often happens that one needs to perform some specific logic on a data set to find the source of a problem or to understand it better. It is possible to solve this by querying the public API and writing a script that will run locally. But that has some limitations: it requires working on the rendering of the data, it's hard to share the logic (and you might have written it with your favorite scripting language that others don't master as well), and it's hard to share the results.

Spectateur is a tool that tries to solve those problems. It lets you query our public API as you want, and then run any kind of logic on the data set you get. Spectateur also handles most of the rendering for you, all you need to do is to correctly format your data, and express what kind of display you want (table, line chart, bar chart... ). You can then very easily save your custom report and share it with others via a URL.

Spectateur's interface is split in 3 parts: the Model lets you interact with the public API and give you data, the Controller gives you the power of JavaScript to manipulate your data, and the View takes that data and makes it pretty.

Model

In its current form, Spectateur "only" allows you to query the SuperSearch endpoint. That means access to all the non-restricted data we have in its purest form. If you have ever used crash-stats' Super Search page, you will find this familiar: it works about the same. To filter the data, pick a field, choose an operator, and set a value. If you need to aggregate the data (what used to be called "facets"), set the fields name in the Aggregations input box.

The data returned by the Model, that you will get to manipulate in the Controller, is the same that the public API exposes. You can get an idea of what it is in this example request: https://crash-stats.mozilla.com/api/SuperSearch/?product=Firefox&_results_number=1. Here is an incomplete schema of the data structure:

{
    "hits": [
        {
            "uuid": "xxyyzz",
            "product": "Firefox",
            "version": "4.0",
            "and": ["so", "on", "..."]
        }
    ],
    "facets": {
        "signature": [
            { "term": "something", "count": 12 }
        ]
    },
    "total": 42
}

Example - See it live

Controller

The controller's code is executed in a protected sandbox, and it has some strong restrictions. For example, you cannot do any HTTP request. You also do not have access to the main page's scope. This is all for the sake of security.

In your code, you only need to keep this structure:

var transform = function (data, callback) {
    // Your code goes here!
    // ...

    callback(transformedData);
};

application.setInterface({
    transform: transform
});

In the transform function, you will get 2 parameters. data is an object containing what was returned by the model. callback is the function that the main page runs to store your data once you are done transforming it. application.setInterface is how you make your code accessible from the main page.

The transformedData object you will generate must follow those rules:

Each element of the array contains one dataset that will have one representation. The name key contains the name of the set (and will be used as the name of the tab in the view), data holds the dataset, and type controls the rendering you want to have for that data (see the chapter about the View for more).

Example - See it live

View

The view supports several display modes. At the moment, you can show your data in a table or as a chart.

Table

{
    "name": "A table! A table! My data for a table!"
    "type": "table",
    "data": [
        {'Foo': 'bar', 'Oof': 41},
        {'Foo': 'bor', 'Oof': 42},
    ],
    "headers": [
        {name: 'Foo', type: 'text'},
        {name: 'Oof', type: 'number'},
    ]
}

Tables are generated using the jsGrid library (documentation).

Example - See it live

Chart

{
    "name": "Hold the line!"
    "type": "chart",
    "chartType": "line",
    "data": {
        "labels":  ["Foo", "Bar"],
        "datasets": [
            {
                "data": [41, 42]
            }
        ]
    }
}

Charts are generated using the Chart.js library (documentation). It provides 6 types of charts, all accessible using the chartType key:

Note that for some of those, the expected data format changes quite a bit.

Example - See it live

Controls

The "Run" button starts the process of 1. fetching the data 2. processing the data and 3. rendering the data. There is no caching for the moment, so it re-downloads the data set every time. The "Save" button will save the model and controller you have set in a database, and attribute this custom report you created a unique URL. Once the data is saved, the URL will be updated with a UUID, and you will be able to share that URL with anyone to show your work.