merge field popularity fix

This commit is contained in:
Rashid Khan 2014-09-19 13:58:59 -07:00
commit eff974dd19
71 changed files with 2169 additions and 432 deletions

6
.esvmrc Normal file
View file

@ -0,0 +1,6 @@
{
"defaults": {
"branch": "1.x",
"plugins": ["elasticsearch/marvel/latest"]
}
}

View file

@ -13,10 +13,10 @@ notifications:
hipchat:
rooms:
secure: a2FERvICecrUAR62vP4vrUCTG3haRzf6kSzDDzGu6SICEXWLRrK0xeNQDpdwDAfzFmaIJ6txpkmInvEFeNPYNngTgEDyfhqdIa/lW0Ermdg+1hL0dK6QJiVmT1V6LDB2mgtaTTmfontxJqq7P2tmr0zz8ny4Eqq3lUnwPxYFNNo=
template:
- ! '%{repository}/%{branch} #%{build_number} by %{author}: %{message} (<a href="%{build_url}">open</a>)'
format: html
on_success: change
template:
- ! '%{repository_slug}/%{branch} by %{author}: %{commit_message} (<a href="%{build_url}">open</a>)'
env:
global:
secure: AX9xidE0quyS07ZfOcecxEGjlNDT9YlM+fvtQHqOaODBII2jg5rgz0SyyxmTPSG68aqUNk8ML9slbRE4h0iPqNkB6fbDE2Dc6oTrRE7XFGDBjw66OHV2ZbsobdORf4UtWO06JBgLUEU2pzRYphe/B14dyA+ZO6p+bAgBmcdLd8k=

View file

@ -1,5 +1,4 @@
module.exports = function (grunt) {
// set the config once before calling load-grunt-config
// and once durring so that we have access to it via
// grunt.config.get() within the config files
@ -38,5 +37,5 @@ module.exports = function (grunt) {
});
// load task definitions
grunt.loadTasks('tasks');
grunt.task.loadTasks('tasks');
};

318
README.md
View file

@ -1,3 +1,317 @@
# Kibana 4
<!-- render {"template":"# Kibana <%= pkg.version %>"} -->
# Kibana 4.0.0-BETA1
<!-- /render -->
[![Build Status](https://magnum.travis-ci.com/elasticsearch/kibana4.svg?token=tsFxSKHtVKG8EZavSjXY&branch=master)](https://magnum.travis-ci.com/elasticsearch/kibana4)
[![Build Status](https://magnum.travis-ci.com/elasticsearch/kibana4.svg?token=tsFxSKHtVKG8EZavSjXY&branch=master)](https://magnum.travis-ci.com/elasticsearch/kibana4)
Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elasticsearch.
## Contents
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Discover](#discover)
- [Visualize](#visualize)
- [Dashboard](#dashboard)
- [Settings](#settings)
## Installation
* Download: [http://www.elasticsearch.org/overview/kibana/installation/](http://www.elasticsearch.org/overview/kibana/installation/)
* Run `bin/kibana` on unix, or `bin/kibana.bat` on Windows.
* Visit [http://localhost:5601](http://localhost:5601)
<!-- include {"path":"docs/quick_start.md"} -->
## Quick Start
You're up and running! Fantastic! Kibana is now running on port 5601, so point your browser at http://YOURDOMAIN.com:5601.
The first screen you arrive at will ask you to configure an **index pattern**. An index pattern describes to kibana how to access your data. We make the guess that you're working with log data, and we hope (because it's awesome) that you're working with Logstash. By default, we fill in `logstash-*` as your index pattern, thus the only thing you need to do is select which field contains the timestamp you'd like to use. Kibana reads your Elasticsearch mapping to find your time fields - select one from the list and hit *Create*.
**Tip:** there's an optimization in the way of the *Use event times to create index names* option. Since Logstash creates an index every day, Kibana uses that fact to only search indices that could possibly contain data in your selected time range.
Congratulations, you have an index pattern! You should now be looking at a paginated list of the fields in your index or indices, as well as some informative data about them. Kibana has automatically set this new index pattern as your default index pattern. If you'd like to know more about index patterns, pop into to the [Settings](#settings) section of the documentation.
**Did you know:** Both *indices* and *indexes* are acceptable plural forms of the word *index*. Knowledge is power.
Now that you've configured an index pattern, you're ready to hop over to the [Discover](#discover) screen and try out a few searches. Click on **Discover** in the navigation bar at the top of the screen.
<!-- /include -->
<!-- include {"path":"docs/discover.md"} -->
## Discover
Discover is your first step on the road to information enlightenment. From this interface you have access to every document, in every index that matches your configured index pattern. For the purpose of this documentation, we will assume you have selected a time field. If you didn't, ignore anything that mentions time.
You should see a few things:
- A list of documents
- A list of fields
- A time chart
If you don't see any documents, it is possible that:
- You don't **have** any documents
- Your time range is too narrow
By default Kibana shows the last 15 minutes of data. You might want to expand this by clicking the time in the top right of the screen and selecting a broader range.
### Document list
Once you see some documents, you can begin to explore Discover. In the document list, Kibana will show you the localized version of the time field you specified in your index pattern, as well as the `_source` of the elasticsearch document.
**Tip:** By default the table contains 500 of the most recent documents. You can increase the number of documents in the table from the advanced settings screen. See the [Setting section](#advanced) of the documentation.
Click on the expand button to the left of the time. Kibana will read the fields from the document and present them in a table. The + and - buttons allow you to quickly filter for documents that share common traits with the one you're looking at. Click the JSON tab at the top of the table to see the full, pretty printed, original document.
Click the expand button again to collapse the detailed view of the document.
### Field list
The field list has several powerful functions. The first being the ability to add columns to the document list. If no fields are selected `_source` will be automatically selected and shown in the table. Mouse over a field name and click the **add** button that appears. Now, instead of seeing `_source` in the document list, you have the extracted value of the selected field. In addition, the field name has moved up to the **Selected** section of the field list. Add a few more fields. Sweet!
Now, instead of clicking the **add** button, click the name of the field itself. You will see a break down of the 5 most popular values for the field, as well as a count of how many records in the document list the field is present in.
In addition, the Visualize button will pop you over to the **Visualize** application and run a more detailed aggregation on the field. For more information about visualization, see the [Visualize section](#visualize) of the docs.
### Filters
When you expand a document in the document list you will see two magnifying glasses next to indexed terms, one with a plus sign and one with a minus sign. If you click on the magnifying glass with the plus sign it will add a filter to the query for that term. If you click on the magnifying glass with the minus sign, it will add a negative filter (which will remove any documents containing the term). Both filters will appear in the filter bar underneath the **search bar**. When you hover over the filters in the filter bar you will see an option to toggle or remove them. There is also a link to remove all the filters.
### Sorting
You may have noticed that documents appear in reverse chronological order by default, meaning the newest documents are shown first. You can change this by clicking on the **Time** column header. In fact, any column can be sorted in this manner as long as it is indexed in Elasticsearch. Note that some fields are not indexed by default, such as `_id`, and that other may have indexing disabled in the Elasticsearch mapping. See the [Settings > Index Patterns](#indices) section of the docs for more details.
You can also reorder columns by placing your mouse over the column header and clicking the left and right arrows that appear.
### The Time Chart
The time chart runs an Elasticsearch aggregation to show the time stamps associated with documents in the table. Hover over a bar in the chart to see the count of documents contained with in it. Clicking on the bar will narrow the selected time range to match the time range of that bar. If you hover over the background of the chart (not a bar) the cursor will become a crosshair. In this mode you can click-and-drag to select a new time range.
### Searching
See the [Querying section](#querying) of the documentation.
### Saving and reloading searches.
Click the save button to save your search for later, or to reuse in other screens, such as Visualize. Saved searches can be loaded via the folder icon.
### Querying
The search bar at the top allows Kibana uses Elasticsearch's support for Lucene Query String syntax. Let's say we're searching web server logs that have been parsed into a few fields.
We can of course do free text search. Find requests that contain the number 200, in any field.
```
200
```
Or we can search in a specific field. Find 200 in the status field:
```
status:200
```
Find all from 400-499 status codes:
```
status:[400 TO 499]
```
Find status codes 400-499 with the extension php:
```
status:[400 TO 499] AND extension:PHP
```
Or HTML
```
status:[400 TO 499] AND (extension:php OR extension:html)
```
While lucene query syntax is simple and very powerful, Kibana also supports the full elasticsearch, JSON based, query DSL. See the [Elasticsearch documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax) for usage and examples.
<!-- /include -->
<!-- include {"path":"docs/visualize.md"} -->
## Visualize
The **Visualize** app is used to design and create saved visualizations that can be used on their own, or added to a dashboard. The data source for a visualization can be based on three types: a new interactive search, a saved search, or an existing saved visualization. Visualizations are based on the aggregation feature introduced in Elasticsearch 1.x. Aggregations are highly performant, but may require significant memory from Elasticsearch.
### Getting Started
To create a new visualization either click on the visualize tab at the top of the screen or the new document button in the toolbar panel to the right of the search bar. This will start the *New Visualization Wizard*.
- **Step 1:** Choose the data source for the new visualization - You have 3 options here:
- *"New search"* : Pick an index pattern and search as you create your visualization
- *"From a saved search"* : Pick a Saved Search and create a visualization from it. If you later save the visualization it will be tied to this search. This means if you edit the search later, say in Discover, any visualization that uses it will also be updated automatically.
- *"From an existing visualization"* Pick an existing visualization and make changes to it.
- **Step 2:** Choose a visualization type from the list of currently available visualizations.
Once the visualization wizard is complete you will be presented with the *visualization editor*.
### Visualization Editor
The visualization editor is where you will configure and edit your visualization. There are three parts to the visualization editor:
1. [Toolbar Panel](#toolbar-panel)
1. [Aggregation Builder](#aggregation-builder)
1. [Preview Canvas](#preview-canvas)
#### Toolbar Panel
The toolbar panel is used for interactive searching of data as well as saving and loading visualizations. When you choose *New search* in the wizard, you will be presented with a search bar where you can add your search terms. For visualizations based on saved searches, the search bar will be disabled, but you can double click the grayed out saved search link to convert it to an interactive search.
To the right of the search box there are a row of icons for creating new visualizations, saving the current visualization, loading an existing visualization, sharing or embedding the visualization, and refreshing the data for the current visualization.
#### Aggregation Builder
The aggregation builder on the left of the screen is used for configuring the [metric](http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-aggregations.html#_metrics_aggregations) and [bucket](http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-aggregations.html#_bucket_aggregations) aggregations used to create a visualization. (If you are coming from the SQL world, buckets are similar to group-bys. Check out the [elasticsearch docs](http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-aggregations.html) for more info) For a bar chart or line chart the *metric* is used for the y-axis and the *buckets* are used for the x-axis, segment bar colors, and row/column splits. For pie charts the "metric" is used for the size of the slice and the *bucket* is used for the number of slices. Other visualizations may use these in new and different ways.
For the remainder of this documentation we are going to use the bar chart as our example when discussing the features of the aggregation panel. The same concepts apply to the other visualizations but the bar chart is the workhorse of the visualization world.
The aggregation builder allows you to choose which *metric* aggregation you would like to use for the x-axis. Examples include: count, average, sum, min, max, and unique count (cardinality). The *bucket* aggregations are used for the x-axis, color slices, and row/columns splits. Example *bucket* aggregations include date histogram, range, terms, filters, and significant terms.
You can also change the execution order of the buckets. In Elasticsearch the first aggregation determines the data set for the subsequent aggregations. For example, lets say you want to create a date bar chart of the hits for top 5 extensions. If you want to have the same extension across all of the hits then you will need to set the order as follows:
1. **Color:** Terms aggregation of extensions
1. **X-Axis:** Date bar chart of `@timestamp`
Inside Elasticsearch, it collects the records for the top 5 extensions then creates a date bar chart for each of them. Let say you now want the top 5 extensions for each hour then you would use the following order:
1. **X-Axis:** Date bar chart of `@timestamp` (with 1 hour interval)
1. **Color:** Terms aggregation of extensions
For these requests, Elasticsearch will create a date bar chart from all the records then group the top five extensions inside each bucket (or hour since we specified an hour interval). Just remember each subsequent bucket slices the data from the previous bucket.
#### Preview Canvas
The preview canvas will render the visualization once you click the apply button below the buckets in the aggregation builder.
You can refresh this preview by clicking the refresh button on the far-right of the toolbar.
<!-- /include -->
<!-- include {"path":"docs/dashboard.md"} -->
## Dashboard
The dashboard is used to group and display any Visualizations you've created. Once you have a collection of visualizations that you like, you can save it as a custom dashboard to share or reload later.
### Getting Started
Using the dashboard requires that you have at least one [saved visualization](#visualize).
#### Creating a New Dashboard
The first time you open the Dashboard, it will be ready for you to build a new dashboard. You can create a new dashboard by clicking the left-most icon in the toolbar panel.
#### Adding Visualizations to a Dashboard
To add a visualization to the dashboard, click the plus button in the toolbar panel. A menu of your saved visualizations will appear.
From the menu, click on the visualization you want to include in your dashboard. If you have more then 5 saved visualizations, the list will paginate. You can also filter the list from the **Visualization Filter** at the top of the list.
Once you've clicked on the visualization, you will see it appear in a *container* in the dashboard below.
**NOTE:** You may see a message saying that the height and/or width of the container is too small. If you see this message, you can fix it by making the container larger - described below.
#### Saving Dashboards
Click on the save button in the toolbar panel to save the dashboard to Elasticsearch. Clicking the save icon will show you a menu below the toolbar panel where you can enter a name for your dashboard. After giving it a name, click the *Save* button.
#### Loading a Saved Dashboard
To load an existing dashboard, click on the *Open* icon in the toolbar menu. This will present you with a list of existing dashboard you can load. If you have more then 5 dashboards, you can use the filter input at the top to search for the dashboard you want to load or click the page links at the bottom of the loader panel.
#### Sharing Dashboards
To obtain the code needed to embed a dashboard in other applications, click the right-most icon in the toolbar menu. It will present you with menu containing two links.
##### Embedding Dashboards
Dashboards can be embedded in other web apps by using the embed code. Simply copy the embed code from the *Share* menu and paste it in your external web application. Note that anyone that views an embedded dashboard must also have access to Kibana.
##### Sharing Dashboards
Dashboards can also be shared with anyone that has access to Kibana. Simply copy the share link from the *Share* menu and share it with others via email or other means.
### Customizing Your Dashboard
The dashboard can be customized in a number of ways to suit your needs.
#### Moving Containers
To move containers around, drag the container by clicking and holding the header and moving it where you want it. Other containers may shift around to make room for the container you are moving. When you are happy with the location of the container, release the mouse button.
#### Resizing Containers
As you move the mouse cursor to the bottom right corner of the container, a small move icon will appear. Once your cursor changes the move icon, you can click and drag the container to make it the size you need. When you let go of the mouse button, the visualization inside the container will adjust to the new container size.
#### Removing Containers
Containers can be removed from your dashboard by clicking on the close icon located in the top right of corner the container. This will not delete the saved visualization, it will remove it from the current Dashboard.
### Viewing Detailed Information
It may sometimes be useful to view the data that is being used to create the visualization. You can view this information by clicking on the bar at the bottom of the container. Doing so will hide the visualization and show the raw data it's using. There are four tabs at the top of this view that break down the data in various ways.
#### Table
This is a representation of all the underlying data, presented as a paginated data grid. The items in this table can be sorted by clicking on the table headers at the top of each column.
#### Request
This is the raw request used to query the server, presented as prettified JSON text.
#### Response
This is the raw response from the server, presented as prettified JSON text.
#### Statistics
This is a summary of the statistics related to the request and the response, presented as a data grid. It includes information such as the query duration, the request duration, the total number of records found on the server and the index pattern used to make the query.
### Changing the Visualization
To change a visualization, click on the *Edit* icon at the top right of the visualization container. This will open that visualization in the *Visualize* app. Refer to the [Visualize docs](#visualize) for usage instructions.
<!-- /include -->
<!-- include {"path":"docs/settings.md"} -->
## Settings
The settings application is broken up into three pages: Indices, Advanced, and Object.
### Indices
The Indices page manages Index Patterns. Before you can do anything in Kibana you will need to create an Index Pattern to use in other parts of the application. Index Patterns represent one or more indices in Elasticsearch and track associated meta-data, like field types and pattern interval.
#### Creating an Index Pattern
If this is your first time in Kibana you'll be prompted to create your first index pattern. For more information on index pattern creation see the **Getting Started** section of the documentation.
### Advanced
Please, **use caution** on this page, because the advanced editor will let you break things.
The Advanced page allows modification of individual configuration parameters. Each of these parameters can be tweaked to customize the entire Kibana installation. This means that your changes will apply to all users. This could prevent the application from loading if used incorrectly.
#### Edit
Clicking on the edit button for any line will cause the *Value* column on that line to become an input, allowing you change the value.
Click the *Save* button to save your changes.
#### Reset
Clicking on the *Reset* button will undo any changes you made and restore the value back to its default.
### Objects
Please, **use caution** on this page. No support is available for changes made here.
The Objects page manages all of the objects created by Kibana (except Index Patterns which are handled by the Indices page). Most apps give you all the tools needed to manage objects they create, but if/when they fall short, you can come here to tweak the specifics.
#### View
Clicking on the *View* action loads that item in the associated applications. Refer to the documentation for the associated applications if you need help using them.
#### Edit
Clicking *Edit* will allow you to change the title, description and other settings of the saved object. You can also edit the schema of the stored object.
*Note:* this operation is for advanced users only - making changes here can break large portions of the application.
<!-- /include -->

862
STYLEGUIDE.md Normal file
View file

@ -0,0 +1,862 @@
This is a collection of style guides for Kibana projects. The include guides for the following:
- [JavaScript](#javascript-style-guide)
- [Kibana Project](#kibana-style-guide)
# JavaScript Style Guide
## 2 Spaces for indention
Use 2 spaces for indenting your code and swear an oath to never mix tabs and
spaces - a special kind of hell is awaiting you otherwise.
## Newlines
Use UNIX-style newlines (`\n`), and a newline character as the last character
of a file. Windows-style newlines (`\r\n`) are forbidden inside any repository.
## No trailing whitespace
Just like you brush your teeth after every meal, you clean up any trailing
whitespace in your JS files before committing. Otherwise the rotten smell of
careless neglect will eventually drive away contributors and/or co-workers.
## Use Semicolons
According to [scientific research][hnsemicolons], the usage of semicolons is
a core value of our community. Consider the points of [the opposition][], but
be a traditionalist when it comes to abusing error correction mechanisms for
cheap syntactic pleasures.
[the opposition]: http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding
[hnsemicolons]: http://news.ycombinator.com/item?id=1547647
## 120 characters per line
Try to limit your lines to 80 characters. If it feels right, you can go up to 120 characters.
## Use single quotes
Use single quotes, unless you are writing JSON.
*Right:*
```js
var foo = 'bar';
```
*Wrong:*
```js
var foo = "bar";
```
## Opening braces go on the same line
Your opening braces go on the same line as the statement.
*Right:*
```js
if (true) {
console.log('winning');
}
```
*Wrong:*
```js
if (true)
{
console.log('losing');
}
```
Also, notice the use of whitespace before and after the condition statement.
## Always use braces for multi-line code
*Right:*
```js
if (err) {
return cb(err);
}
```
*Wrong:*
```js
if (err)
return cb(err);
```
## Prefer multi-line conditionals
But single-line conditionals are allowed for short lines
*Preferred:*
```js
if (err) {
return cb(err);
}
```
*Allowed:*
```js
if (err) return cb(err);
```
## Declare one variable per var statement
Declare one variable per var statement, it makes it easier to re-order the
lines. However, ignore [Crockford][crockfordconvention] when it comes to
declaring variables deeper inside a function, just put the declarations wherever
they make sense.
*Right:*
```js
var keys = ['foo', 'bar'];
var values = [23, 42];
var object = {};
while (keys.length) {
var key = keys.pop();
object[key] = values.pop();
}
```
*Wrong:*
```js
var keys = ['foo', 'bar'],
values = [23, 42],
object = {},
key;
while (keys.length) {
key = keys.pop();
object[key] = values.pop();
}
```
[crockfordconvention]: http://javascript.crockford.com/code.html
## Use lowerCamelCase for variables, properties and function names
Variables, properties and function names should use `lowerCamelCase`. They
should also be descriptive. Single character variables and uncommon
abbreviations should generally be avoided.
*Right:*
```js
var adminUser = db.query('SELECT * FROM users ...');
```
*Wrong:*
```js
var admin_user = db.query('SELECT * FROM users ...');
```
## Use UpperCamelCase for class names
Class names should be capitalized using `UpperCamelCase`.
*Right:*
```js
function BankAccount() {
}
```
*Wrong:*
```js
function bank_Account() {
}
```
## Use UPPERCASE for Constants
Constants should be declared as regular variables or static class properties,
using all uppercase letters.
Node.js / V8 actually supports mozilla's [const][const] extension, but
unfortunately that cannot be applied to class members, nor is it part of any
ECMA standard.
*Right:*
```js
var SECOND = 1 * 1000;
function File() {
}
File.FULL_PERMISSIONS = 0777;
```
*Wrong:*
```js
const SECOND = 1 * 1000;
function File() {
}
File.fullPermissions = 0777;
```
[const]: https://developer.mozilla.org/en/JavaScript/Reference/Statements/const
## Magic numbers
These are numbers (or other values) simply used in line in your code. **Do not use these**, give them a variable name so they can be understood and changed easily.
*Right:*
```js
var minWidth = 300;
if (width < minWidth) {
...
}
```
*Wrong:*
```js
if (width < 300) {
...
}
```
## Global definitions
Don't do this. Everything should be wrapped in a module that can be depended on by other modules. Even things as simple as a single value should be a module.
## Function definitions
Prefer the use of function declarations over function expressions. Function expressions are allowed, but should usually be avoided.
Also, keep function definitions above other code instead of relying on function hoising.
*Preferred:*
```js
function myFunc() {
...
}
```
*Allowed:*
```js
var myFunc = function () {
...
};
```
## Object / Array creation
Use trailing commas and put *short* declarations on a single line. Only quote
keys when your interpreter complains:
*Right:*
```js
var a = ['hello', 'world'];
var b = {
good: 'code',
'is generally': 'pretty'
};
```
*Wrong:*
```js
var a = [
'hello', 'world'
];
var b = {"good": 'code'
, is generally: 'pretty'
};
```
## Object / Array iterations, transformations and operations
Use native ES5 methods to iterate and transform arrays and objects where possible. Do not use `for` and `while` loops.
Use descriptive variable names in the closures.
Use a utility library as needed and where it will make code more comprehensible.
*Right:*
```js
var userNames = users.map(function (user) {
return user.name;
});
// examples where lodash makes the code more readable
var userNames = _.pluck(users, 'name');
```
*Wrong:*
```js
var userNames = [];
for (var i = 0; i < users.length; i++) {
userNames.push(users[i].name);
}
```
## Use the === operator
Programming is not about remembering [stupid rules][comparisonoperators]. Use
the triple equality operator as it will work just as expected.
*Right:*
```js
var a = 0;
if (a !== '') {
console.log('winning');
}
```
*Wrong:*
```js
var a = 0;
if (a == '') {
console.log('losing');
}
```
[comparisonoperators]: https://developer.mozilla.org/en/JavaScript/Reference/Operators/Comparison_Operators
## Only use ternary operators for small, simple code
And **never** use multiple ternaries together
*Right:*
```js
var foo = (a === b) ? 1 : 2;
```
*Wrong:*
```js
var foo = (a === b) ? 1 : (a === c) ? 2 : 3;
```
## Do not extend built-in prototypes
Do not extend the prototype of native JavaScript objects. Your future self will
be forever grateful.
*Right:*
```js
var a = [];
if (!a.length) {
console.log('winning');
}
```
*Wrong:*
```js
Array.prototype.empty = function() {
return !this.length;
}
var a = [];
if (a.empty()) {
console.log('losing');
}
```
## Use descriptive conditions
Any non-trivial conditions should be assigned to a descriptively named variables, broken into
several names variables, or converted to be a function:
*Right:*
```js
var thing = ...;
var isShape = thing instanceof Shape;
var notSquare = !(thing instanceof Square);
var largerThan10 = isShape && thing.size > 10;
if (isShape && notSquare && largerThan10) {
console.log('some big polygon');
}
```
*Wrong:*
```js
if (
thing instanceof Shape
&& !(thing instanceof Square)
&& thing.size > 10
) {
console.log('bigger than ten?? Woah!');
}
```
## Name regular expressions
*Right:*
```js
var validPasswordRE = /^(?=.*\d).{4,}$/;
if (password.length >= 4 && validPasswordRE.test(password)) {
console.log('password is valid');
}
```
*Wrong:*
```js
if (password.length >= 4 && /^(?=.*\d).{4,}$/.test(password)) {
console.log('losing');
}
```
## Write small functions
Keep your functions short. A good function fits on a slide that the people in
the last row of a big room can comfortably read. So don't count on them having
perfect vision and limit yourself to ~15 lines of code per function.
## Return early from functions
To avoid deep nesting of if-statements, always return a function's value as early
as possible.
*Right:*
```js
function isPercentage(val) {
if (val < 0) return false;
if (val > 100) return false;
return true;
}
```
*Wrong:*
```js
function isPercentage(val) {
if (val >= 0) {
if (val < 100) {
return true;
} else {
return false;
}
} else {
return false;
}
}
```
Or for this particular example it may also be fine to shorten things even
further:
```js
function isPercentage(val) {
var isInRange = (val >= 0 && val <= 100);
return isInRange;
}
```
## Chaining operations
When using a chaining syntax (jquery or promises, for example), do not indent the subsequent chained operations, unless there is a logical grouping in them.
Also, if the chain is long, each method should be on a new line.
*Right:*
```js
$('.someClass')
.addClass('another-class')
.append(someElement)
```
```js
d3.selectAll('g.bar')
.enter()
.append('thing')
.data(anything)
.exit()
.each(function() ... )
```
*Wrong:*
```js
$('.someClass')
.addClass('another-class')
.append(someElement)
```
```js
d3.selectAll('g.bar')
.enter().append('thing').data(anything).exit()
.each(function() ... )
```
## Name your closures
Feel free to give your closures a descriptive name. It shows that you care about them, and
will produce better stack traces, heap and cpu profiles.
*Right:*
```js
req.on('end', function onEnd() {
console.log('winning');
});
```
*Wrong:*
```js
req.on('end', function() {
console.log('losing');
});
```
## No nested closures
Use closures, but don't nest them. Otherwise your code will become a mess.
*Right:*
```js
setTimeout(function() {
client.connect(afterConnect);
}, 1000);
function afterConnect() {
console.log('winning');
}
```
*Wrong:*
```js
setTimeout(function() {
client.connect(function() {
console.log('losing');
});
}, 1000);
```
## Use slashes for comments
Use slashes for both single line and multi line comments. Try to write
comments that explain higher level mechanisms or clarify difficult
segments of your code. **Don't use comments to restate trivial things**.
***Exception:*** Comment blocks describing a function and it's arguments (docblock) should start with `/**`, contain a single `*` at the begining of each line, and end with `*/`.
*Right:*
```js
// 'ID_SOMETHING=VALUE' -> ['ID_SOMETHING=VALUE', 'SOMETHING', 'VALUE']
var matches = item.match(/ID_([^\n]+)=([^\n]+)/));
/**
* Fetches a user from...
* @param {string} id - id of the user
* @return {Promise}
*/
function loadUser(id) {
// This function has a nasty side effect where a failure to increment a
// redis counter used for statistics will cause an exception. This needs
// to be fixed in a later iteration.
...
}
var isSessionValid = (session.expires < Date.now());
if (isSessionValid) {
...
}
```
*Wrong:*
```js
// Execute a regex
var matches = item.match(/ID_([^\n]+)=([^\n]+)/));
// Usage: loadUser(5, function() { ... })
function loadUser(id, cb) {
// ...
}
// Check if the session is valid
var isSessionValid = (session.expires < Date.now());
// If the session is valid
if (isSessionValid) {
// ...
}
```
## Do not comment out code
We use a version management system. If a line of code is no longer needed, remove it, don't simply comment it out.
## Classes/Constructors and Inheritance
While JavaScript it is not always considered an object-oriented language, it does have the building blocks for writing object oriented code. Of course, as with all things JavaScript, there are many ways this can be accomplished. Generally, we try to err on the side of readability.
### Capitalized function definition as Constructors
When Defining a Class/Constructor, use the function definition syntax.
*Right:*
```js
function ClassName() {
}
```
*Wrong:*
```js
var ClassName = function () {};
```
### Inhertiance should be done with a utility
While you can do it with pure JS, a utility will remove a lot of boilerplate, and be more readable and functional.
*Right:*
```js
// uses a lodash inherits mixin
// inheritance is defined first - it's easier to read and the function will be hoisted
_(Square).inherits(Shape);
function Square(width, height) {
Square.Super.call(this);
}
```
*Wrong:*
```js
function Square(width, height) {
this.width = width;
this.height = height;
}
Square.prototype = Object.create(Shape);
```
### Keep Constructors Small
It is often the case that there are properties that can't be defined on the prototype, or work that needs to be done to completely create an object (like call it's Super class). This is all that should be done within constructors.
Try to follow the [Write small functions](#write-small-functions) rule here too.
### Use the prototype
If a method/property *can* go on the prototype, it probably should.
```js
function Square() {
...
}
/**
* method does stuff
* @return {undefined}
*/
Square.prototype.method = function () {
...
}
```
### Handling scope and aliasing `this`
When creating a prototyped class, each method should almost always start with:
`var self = this;`
With the exception of very short methods (roughly 3 lines or less), `self` should always be used in place of `this`.
Avoid the use of `bind`
*Right:*
```js
Square.prototype.doFancyThings = function () {
var self = this;
somePromiseUtil()
.then(function (result) {
self.prop = result.prop;
});
}
```
*Wrong:*
```js
Square.prototype.doFancyThings = function () {
somePromiseUtil()
.then(function (result) {
this.prop = result.prop;
}).bind(this);
}
```
*Allowed:*
```js
Square.prototype.area = function () {
return this.width * this.height;
}
```
## Object.freeze, Object.preventExtensions, Object.seal, with, eval
Crazy shit that you will probably never need. Stay away from it.
## Getters and Setters
Feel free to use getters that are free from [side effects][sideeffect], like
providing a length property for a collection class.
Do not use setters, they cause more problems for people who try to use your
software than they can solve.
[sideeffect]: http://en.wikipedia.org/wiki/Side_effect_(computer_science)
# Kibana Style Guide
Things listed here are specific to Kibana and likely only apply to this project
## Share common utilities as lodash mixins
When creating a utility function, attach it as a lodash mixin.
Several already exist, and can be found in `src/kibana/utils/_mixins.js`
## Modules
Kibana uses AMD modules to organize code, and require.js to load those modules.
Even Angular code is loaded this way.
### Module paths
Paths to modules should not be relative (ie. no dot notation). Instead, they should be loaded from one of the defined paths in the require config.
*Right:*
```js
require('some/base/path/my_module');
require('another/path/another_module');
```
*Wrong:*
```js
require('../my_module');
require('./path/another_module');
```
### CommonJS Syntax
Module dependencies should be loaded via the CommonJS syntax:
*Right:*
```js
define(function (require) {
var _ = require('lodash');
...
});
```
*Wrong:*
```js
define(['lodash'], function (_) {
...
});
```
## Angular Usage
Kibana is written in Angular, and uses several utility methods to make using Angular easier.
### Defining modules
Angular modules are defined using a custom require module named `module`. It is used as follows:
```js
var app = require('modules').get('app/namespace');
```
`app` above is a reference to an Angular module, and can be used to define controllers, providers and anything else used in Angular.
### Private modules
A service called `Private` is available to load any function as an angular module without needing to define it as such. It is used as follows:
```js
app.controller('myController', function($scope, otherDeps, Private) {
var ExternalClass = Private(require('path/to/some/class'));
...
});
```
### Promises
A more robust version of Angular's `$q` service is available as `Promise`. It can be used in the same way as `$q`, but it comes packaged with several utility methods that provide many of the same useful utilities as Bluebird.
```js
app.service('CustomService', function(Promise, otherDeps) {
new Promise(function (resolve, reject) {
...
});
var promisedFunc = Promise.cast(someFunc);
return Promise.resolve('value');
});
```
### Routes
Angular routes are defined using a custom require modules named `routes` that remove much of the required boilerplate.
```js
require('routes')
.when('/my/object/route/:id?', {
// angular route code goes here
});
```
# Attribution
This Javascript guide forked from the [node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) and is
licensed under the [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/)
license.

View file

@ -446,6 +446,7 @@
- **[src/kibana/components/vislib/vis.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/vislib/vis.js)**
- need to come up with a solution for resizing when no data is available
- **[src/kibana/components/vislib/visualizations/column_chart.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/vislib/visualizations/column_chart.js)**
- Replace the following code with something more robust for finding the field
- refactor so that this is called from the data module
- **[src/kibana/components/visualize/visualize.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/visualize/visualize.js)**
- we need to have some way to clean up result requests

81
docs/dashboard.md Normal file
View file

@ -0,0 +1,81 @@
## Dashboard
The dashboard is used to group and display any Visualizations you've created. Once you have a collection of visualizations that you like, you can save it as a custom dashboard to share or reload later.
### Getting Started
Using the dashboard requires that you have at least one [saved visualization](#visualize).
#### Creating a New Dashboard
The first time you open the Dashboard, it will be ready for you to build a new dashboard. You can create a new dashboard by clicking the left-most icon in the toolbar panel.
#### Adding Visualizations to a Dashboard
To add a visualization to the dashboard, click the plus button in the toolbar panel. A menu of your saved visualizations will appear.
From the menu, click on the visualization you want to include in your dashboard. If you have more then 5 saved visualizations, the list will paginate. You can also filter the list from the **Visualization Filter** at the top of the list.
Once you've clicked on the visualization, you will see it appear in a *container* in the dashboard below.
**NOTE:** You may see a message saying that the height and/or width of the container is too small. If you see this message, you can fix it by making the container larger - described below.
#### Saving Dashboards
Click on the save button in the toolbar panel to save the dashboard to Elasticsearch. Clicking the save icon will show you a menu below the toolbar panel where you can enter a name for your dashboard. After giving it a name, click the *Save* button.
#### Loading a Saved Dashboard
To load an existing dashboard, click on the *Open* icon in the toolbar menu. This will present you with a list of existing dashboard you can load. If you have more then 5 dashboards, you can use the filter input at the top to search for the dashboard you want to load or click the page links at the bottom of the loader panel.
#### Sharing Dashboards
To obtain the code needed to embed a dashboard in other applications, click the right-most icon in the toolbar menu. It will present you with menu containing two links.
##### Embedding Dashboards
Dashboards can be embedded in other web apps by using the embed code. Simply copy the embed code from the *Share* menu and paste it in your external web application. Note that anyone that views an embedded dashboard must also have access to Kibana.
##### Sharing Dashboards
Dashboards can also be shared with anyone that has access to Kibana. Simply copy the share link from the *Share* menu and share it with others via email or other means.
### Customizing Your Dashboard
The dashboard can be customized in a number of ways to suit your needs.
#### Moving Containers
To move containers around, drag the container by clicking and holding the header and moving it where you want it. Other containers may shift around to make room for the container you are moving. When you are happy with the location of the container, release the mouse button.
#### Resizing Containers
As you move the mouse cursor to the bottom right corner of the container, a small move icon will appear. Once your cursor changes the move icon, you can click and drag the container to make it the size you need. When you let go of the mouse button, the visualization inside the container will adjust to the new container size.
#### Removing Containers
Containers can be removed from your dashboard by clicking on the close icon located in the top right of corner the container. This will not delete the saved visualization, it will remove it from the current Dashboard.
### Viewing Detailed Information
It may sometimes be useful to view the data that is being used to create the visualization. You can view this information by clicking on the bar at the bottom of the container. Doing so will hide the visualization and show the raw data it's using. There are four tabs at the top of this view that break down the data in various ways.
#### Table
This is a representation of all the underlying data, presented as a paginated data grid. The items in this table can be sorted by clicking on the table headers at the top of each column.
#### Request
This is the raw request used to query the server, presented as prettified JSON text.
#### Response
This is the raw response from the server, presented as prettified JSON text.
#### Statistics
This is a summary of the statistics related to the request and the response, presented as a data grid. It includes information such as the query duration, the request duration, the total number of records found on the server and the index pattern used to make the query.
### Changing the Visualization
To change a visualization, click on the *Edit* icon at the top right of the visualization container. This will open that visualization in the *Visualize* app. Refer to the [Visualize docs](#visualize) for usage instructions.

93
docs/discover.md Normal file
View file

@ -0,0 +1,93 @@
## Discover
Discover is your first step on the road to information enlightenment. From this interface you have access to every document, in every index that matches your configured index pattern. For the purpose of this documentation, we will assume you have selected a time field. If you didn't, ignore anything that mentions time.
You should see a few things:
- A list of documents
- A list of fields
- A time chart
If you don't see any documents, it is possible that:
- You don't **have** any documents
- Your time range is too narrow
By default Kibana shows the last 15 minutes of data. You might want to expand this by clicking the time in the top right of the screen and selecting a broader range.
### Document list
Once you see some documents, you can begin to explore Discover. In the document list, Kibana will show you the localized version of the time field you specified in your index pattern, as well as the `_source` of the elasticsearch document.
**Tip:** By default the table contains 500 of the most recent documents. You can increase the number of documents in the table from the advanced settings screen. See the [Setting section](#advanced) of the documentation.
Click on the expand button to the left of the time. Kibana will read the fields from the document and present them in a table. The + and - buttons allow you to quickly filter for documents that share common traits with the one you're looking at. Click the JSON tab at the top of the table to see the full, pretty printed, original document.
Click the expand button again to collapse the detailed view of the document.
### Field list
The field list has several powerful functions. The first being the ability to add columns to the document list. If no fields are selected `_source` will be automatically selected and shown in the table. Mouse over a field name and click the **add** button that appears. Now, instead of seeing `_source` in the document list, you have the extracted value of the selected field. In addition, the field name has moved up to the **Selected** section of the field list. Add a few more fields. Sweet!
Now, instead of clicking the **add** button, click the name of the field itself. You will see a break down of the 5 most popular values for the field, as well as a count of how many records in the document list the field is present in.
In addition, the Visualize button will pop you over to the **Visualize** application and run a more detailed aggregation on the field. For more information about visualization, see the [Visualize section](#visualize) of the docs.
### Filters
When you expand a document in the document list you will see two magnifying glasses next to indexed terms, one with a plus sign and one with a minus sign. If you click on the magnifying glass with the plus sign it will add a filter to the query for that term. If you click on the magnifying glass with the minus sign, it will add a negative filter (which will remove any documents containing the term). Both filters will appear in the filter bar underneath the **search bar**. When you hover over the filters in the filter bar you will see an option to toggle or remove them. There is also a link to remove all the filters.
### Sorting
You may have noticed that documents appear in reverse chronological order by default, meaning the newest documents are shown first. You can change this by clicking on the **Time** column header. In fact, any column can be sorted in this manner as long as it is indexed in Elasticsearch. Note that some fields are not indexed by default, such as `_id`, and that other may have indexing disabled in the Elasticsearch mapping. See the [Settings > Index Patterns](#indices) section of the docs for more details.
You can also reorder columns by placing your mouse over the column header and clicking the left and right arrows that appear.
### The Time Chart
The time chart runs an Elasticsearch aggregation to show the time stamps associated with documents in the table. Hover over a bar in the chart to see the count of documents contained with in it. Clicking on the bar will narrow the selected time range to match the time range of that bar. If you hover over the background of the chart (not a bar) the cursor will become a crosshair. In this mode you can click-and-drag to select a new time range.
### Searching
See the [Querying section](#querying) of the documentation.
### Saving and reloading searches.
Click the save button to save your search for later, or to reuse in other screens, such as Visualize. Saved searches can be loaded via the folder icon.
### Querying
The search bar at the top allows Kibana uses Elasticsearch's support for Lucene Query String syntax. Let's say we're searching web server logs that have been parsed into a few fields.
We can of course do free text search. Find requests that contain the number 200, in any field.
```
200
```
Or we can search in a specific field. Find 200 in the status field:
```
status:200
```
Find all from 400-499 status codes:
```
status:[400 TO 499]
```
Find status codes 400-499 with the extension php:
```
status:[400 TO 499] AND extension:PHP
```
Or HTML
```
status:[400 TO 499] AND (extension:php OR extension:html)
```
While lucene query syntax is simple and very powerful, Kibana also supports the full elasticsearch, JSON based, query DSL. See the [Elasticsearch documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax) for usage and examples.

13
docs/quick_start.md Normal file
View file

@ -0,0 +1,13 @@
## Quick Start
You're up and running! Fantastic! Kibana is now running on port 5601, so point your browser at http://YOURDOMAIN.com:5601.
The first screen you arrive at will ask you to configure an **index pattern**. An index pattern describes to kibana how to access your data. We make the guess that you're working with log data, and we hope (because it's awesome) that you're working with Logstash. By default, we fill in `logstash-*` as your index pattern, thus the only thing you need to do is select which field contains the timestamp you'd like to use. Kibana reads your Elasticsearch mapping to find your time fields - select one from the list and hit *Create*.
**Tip:** there's an optimization in the way of the *Use event times to create index names* option. Since Logstash creates an index every day, Kibana uses that fact to only search indices that could possibly contain data in your selected time range.
Congratulations, you have an index pattern! You should now be looking at a paginated list of the fields in your index or indices, as well as some informative data about them. Kibana has automatically set this new index pattern as your default index pattern. If you'd like to know more about index patterns, pop into to the [Settings](#settings) section of the documentation.
**Did you know:** Both *indices* and *indexes* are acceptable plural forms of the word *index*. Knowledge is power.
Now that you've configured an index pattern, you're ready to hop over to the [Discover](#discover) screen and try out a few searches. Click on **Discover** in the navigation bar at the top of the screen.

43
docs/settings.md Normal file
View file

@ -0,0 +1,43 @@
## Settings
The settings application is broken up into three pages: Indices, Advanced, and Object.
### Indices
The Indices page manages Index Patterns. Before you can do anything in Kibana you will need to create an Index Pattern to use in other parts of the application. Index Patterns represent one or more indices in Elasticsearch and track associated meta-data, like field types and pattern interval.
#### Creating an Index Pattern
If this is your first time in Kibana you'll be prompted to create your first index pattern. For more information on index pattern creation see the **Getting Started** section of the documentation.
### Advanced
Please, **use caution** on this page, because the advanced editor will let you break things.
The Advanced page allows modification of individual configuration parameters. Each of these parameters can be tweaked to customize the entire Kibana installation. This means that your changes will apply to all users. This could prevent the application from loading if used incorrectly.
#### Edit
Clicking on the edit button for any line will cause the *Value* column on that line to become an input, allowing you change the value.
Click the *Save* button to save your changes.
#### Reset
Clicking on the *Reset* button will undo any changes you made and restore the value back to its default.
### Objects
Please, **use caution** on this page. No support is available for changes made here.
The Objects page manages all of the objects created by Kibana (except Index Patterns which are handled by the Indices page). Most apps give you all the tools needed to manage objects they create, but if/when they fall short, you can come here to tweak the specifics.
#### View
Clicking on the *View* action loads that item in the associated applications. Refer to the documentation for the associated applications if you need help using them.
#### Edit
Clicking *Edit* will allow you to change the title, description and other settings of the saved object. You can also edit the schema of the stored object.
*Note:* this operation is for advanced users only - making changes here can break large portions of the application.

55
docs/visualize.md Normal file
View file

@ -0,0 +1,55 @@
## Visualize
The **Visualize** app is used to design and create saved visualizations that can be used on their own, or added to a dashboard. The data source for a visualization can be based on three types: a new interactive search, a saved search, or an existing saved visualization. Visualizations are based on the aggregation feature introduced in Elasticsearch 1.x. Aggregations are highly performant, but may require significant memory from Elasticsearch.
### Getting Started
To create a new visualization either click on the visualize tab at the top of the screen or the new document button in the toolbar panel to the right of the search bar. This will start the *New Visualization Wizard*.
- **Step 1:** Choose the data source for the new visualization - You have 3 options here:
- *"New search"* : Pick an index pattern and search as you create your visualization
- *"From a saved search"* : Pick a Saved Search and create a visualization from it. If you later save the visualization it will be tied to this search. This means if you edit the search later, say in Discover, any visualization that uses it will also be updated automatically.
- *"From an existing visualization"* Pick an existing visualization and make changes to it.
- **Step 2:** Choose a visualization type from the list of currently available visualizations.
Once the visualization wizard is complete you will be presented with the *visualization editor*.
### Visualization Editor
The visualization editor is where you will configure and edit your visualization. There are three parts to the visualization editor:
1. [Toolbar Panel](#toolbar-panel)
1. [Aggregation Builder](#aggregation-builder)
1. [Preview Canvas](#preview-canvas)
#### Toolbar Panel
The toolbar panel is used for interactive searching of data as well as saving and loading visualizations. When you choose *New search* in the wizard, you will be presented with a search bar where you can add your search terms. For visualizations based on saved searches, the search bar will be disabled, but you can double click the grayed out saved search link to convert it to an interactive search.
To the right of the search box there are a row of icons for creating new visualizations, saving the current visualization, loading an existing visualization, sharing or embedding the visualization, and refreshing the data for the current visualization.
#### Aggregation Builder
The aggregation builder on the left of the screen is used for configuring the [metric](http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-aggregations.html#_metrics_aggregations) and [bucket](http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-aggregations.html#_bucket_aggregations) aggregations used to create a visualization. (If you are coming from the SQL world, buckets are similar to group-bys. Check out the [elasticsearch docs](http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-aggregations.html) for more info) For a bar chart or line chart the *metric* is used for the y-axis and the *buckets* are used for the x-axis, segment bar colors, and row/column splits. For pie charts the "metric" is used for the size of the slice and the *bucket* is used for the number of slices. Other visualizations may use these in new and different ways.
For the remainder of this documentation we are going to use the bar chart as our example when discussing the features of the aggregation panel. The same concepts apply to the other visualizations but the bar chart is the workhorse of the visualization world.
The aggregation builder allows you to choose which *metric* aggregation you would like to use for the x-axis. Examples include: count, average, sum, min, max, and unique count (cardinality). The *bucket* aggregations are used for the x-axis, color slices, and row/columns splits. Example *bucket* aggregations include date histogram, range, terms, filters, and significant terms.
You can also change the execution order of the buckets. In Elasticsearch the first aggregation determines the data set for the subsequent aggregations. For example, lets say you want to create a date bar chart of the hits for top 5 extensions. If you want to have the same extension across all of the hits then you will need to set the order as follows:
1. **Color:** Terms aggregation of extensions
1. **X-Axis:** Date bar chart of `@timestamp`
Inside Elasticsearch, it collects the records for the top 5 extensions then creates a date bar chart for each of them. Let say you now want the top 5 extensions for each hour then you would use the following order:
1. **X-Axis:** Date bar chart of `@timestamp` (with 1 hour interval)
1. **Color:** Terms aggregation of extensions
For these requests, Elasticsearch will create a date bar chart from all the records then group the top five extensions inside each bucket (or hour since we specified an hour interval). Just remember each subsequent bucket slices the data from the previous bucket.
#### Preview Canvas
The preview canvas will render the visualization once you click the apply button below the buckets in the aggregation builder.
You can refresh this preview by clicking the refresh button on the far-right of the toolbar.

View file

@ -41,7 +41,7 @@
"scripts": {
"test": "grunt test --use-jruby",
"server": "grunt server",
"precommit": "grunt jshint todos"
"precommit": "grunt jshint todos render_readme"
},
"repository": {
"type": "git",

View file

@ -59,7 +59,7 @@ define(function (require) {
;
},
popularity: function (field) {
return field.count;
return field.count > 0;
},
getActive: function () {
return _.some(filter.props, function (prop) {
@ -78,12 +78,15 @@ define(function (require) {
$scope.$watch('fields', function (newFields) {
// Find the top N most popular fields
$scope.popularFields = _.sortBy(_.filter(
_.sortBy(newFields, 'count')
.reverse()
.slice(0, config.get('fields:popularLimit')), function (field) {
return (field.count > 0);
}), 'name');
$scope.popularFields = _(newFields)
.where(function (field) {
return field.count > 0;
})
.sortBy('count')
.reverse()
.slice(0, config.get('fields:popularLimit'))
.sortBy('name')
.value();
// Find the top N most popular fields
$scope.unpopularFields = _.sortBy(_.sortBy(newFields, 'count')

View file

@ -159,11 +159,6 @@ define(function (require) {
$detailsTr.toggle($scope.open);
// Change the caret icon
var $toggleIcon = $(element.children().first().find('i')[0]);
$toggleIcon.toggleClass('fa-caret-down');
$toggleIcon.toggleClass('fa-caret-right');
if (!$scope.open) {
// close the child scope if it exists
$child.$destroy();
@ -190,15 +185,17 @@ define(function (require) {
return _.contains(validTypes, mapping.type);
};
var $childScope = _.assign($child, { row: row, showFilters: showFilters });
$compile($detailsTr)($childScope);
$child.row = row;
$child.showFilters = showFilters;
$compile($detailsTr)($child);
};
$scope.filter = function (row, field, operation) {
$scope.filtering(field, row._source[field] || row[field], operation);
};
$scope.$watch('columns', function (columns) {
$scope.$watchCollection('columns', function (columns) {
element.empty();
createSummaryRow($scope.row, $scope.row._id);
});
@ -211,7 +208,11 @@ define(function (require) {
// create a tr element that lists the value for each *column*
function createSummaryRow(row, id) {
var expandTd = $('<td>').html('<i class="fa fa-caret-right"></span>')
var expandTd = $('<td>')
.append(
$('<i class="fa"></span>')
.attr('ng-class', '{"fa-caret-right": !open, "fa-caret-down": open}')
)
.attr('ng-click', 'toggleRow()');
$compile(expandTd)($scope);
element.append(expandTd);

View file

@ -1,9 +1,20 @@
<table class="table">
<thead kbn-table-header columns="columns" mapping="mapping" sorting="sorting" timefield="timefield"></thead>
<thead
kbn-table-header
columns="columns"
mapping="mapping"
sorting="sorting"
timefield="timefield">
</thead>
<tbody>
<tr ng-repeat="row in rows |limitTo:limit track by row._index+row._id"
kbn-table-row="row"
columns="columns" mapping="mapping" sorting="sorting" timefield="timefield" max-length="maxLength" filtering="filtering"
columns="columns"
mapping="mapping"
sorting="sorting"
timefield="timefield"
max-length="maxLength"
filtering="filtering"
class="discover-table-row"></tr>
</tbody>
</table>

View file

@ -24,8 +24,8 @@
</div>
<div class="form-group">
<label>
Index Name
or Pattern &mdash;
Index name
or pattern &mdash;
<small><a href="http://momentjs.com/docs/#/displaying/format/">docs</a></small>
</label>
<input
@ -39,7 +39,7 @@
<section ng-if="index.isTimeBased && index.nameIsPattern">
<div class="form-group">
<label>
Index Pattern Interval&nbsp;
Index pattern interval&nbsp;
<kbn-info info="The interval at which index names rotate."></kbn-info>
</label>
<select
@ -53,7 +53,7 @@
<div class="alert alert-danger" ng-repeat="err in index.patternErrors">{{err}}</div>
<div class="alert alert-info" ng-if="index.samples">
Sample Index Names
Sample index names
<ul>
<li ng-repeat="sample in index.samples">{{sample}}</li>
</ul>
@ -90,7 +90,7 @@
<div class="form-group" ng-if="index.isTimeBased">
<label>
Time-Field Name
Time-field name
&nbsp;
<kbn-info info="This field will be use to filter events with the global time filter"></kbn-info>
&nbsp;

View file

@ -69,6 +69,7 @@
config-object="conf">
</config>
<filter-bar state="state"></filter-bar>
<div class="vis-editor-content">
<vis-editor-sidebar
@ -84,4 +85,4 @@
</div>
</div>
</div>
</div>

View file

@ -68,7 +68,8 @@ define(function (require) {
var savedVisState = vis.getState();
var $state = appStateFactory.create({
vis: savedVisState
vis: savedVisState,
filters: _.cloneDeep(searchSource.get('filter'))
});
if (!angular.equals($state.vis, savedVisState)) {
@ -122,6 +123,12 @@ define(function (require) {
searchSource.set('query', null);
}
if ($state.filters) {
searchSource.set('filter', $state.filters);
} else {
searchSource.set('filter', null);
}
$scope.fetch();
});
@ -136,8 +143,37 @@ define(function (require) {
$scope.$on('$destroy', function () {
savedVis.destroy();
});
if (!vis.listeners) vis.listeners = {};
vis.listeners.click = function (e) {
// This code is only inplace for the beta release this will all get refactored
// after we get the release out.
if (e.aggConfig && e.aggConfig.aggType && e.aggConfig.aggType.name === 'terms') {
var filter;
var filters = _.flatten([$state.filters || []], true);
var previous = _.find(filters, function (item) {
if (item && item.query) {
return item.query.match[e.field].query === e.label;
}
});
if (!previous) {
filter = { query: { match: {} } };
filter.query.match[e.field] = { query: e.label, type: 'phrase' };
filters.push(filter);
$state.filters = filters;
}
} else {
notify.info('Filtering is only supported for Term aggergations at the time, others are coming soon.');
}
};
}
$scope.$watch('state.filters', function (filters) {
searchSource.set('filter', filters);
$state.save();
$scope.fetch();
});
$scope.fetch = function () {
searchSource.fetch();
};

View file

@ -8,7 +8,7 @@ define(function (require) {
var getColor = (function () {
var i = 0;
var colorPool = Private(require('components/vislib/components/_functions/color/color_palette'))(100);
var colorPool = Private(require('components/vislib/components/color/color_palette'))(100);
var assigned = {};
return function (item) {
var key = item.$$hashKey;

View file

@ -1,7 +1,7 @@
<form role="form" ng-submit="conf.doSave()">
<div class="form-group">
<label for="visTitle">Title</label>
<input class="form-control" type="text" name="visTitle" ng-model="conf.savedVis.title" required>
<input class="form-control" input-focus type="text" name="visTitle" ng-model="conf.savedVis.title" required>
</div>
<div class="form-group">
<label for="visDescription">Description</label>

View file

@ -27,12 +27,12 @@ define(function (require) {
var negative = (aIndex > bIndex);
var count = aggs
.slice(aIndex, bIndex - aIndex - 1)
.slice(Math.min(aIndex, bIndex), Math.max(aIndex, bIndex))
.reduce(function (count, cfg) {
if (cfg.schema.group === 'buckets') {
return count + 1;
} else {
if (cfg === aggConfigA || cfg === aggConfigB || cfg.schema.group !== 'buckets') {
return count;
} else {
return count + 1;
}
}, 0);

View file

@ -27,7 +27,7 @@ define(function (require) {
{
name: 'min_doc_count',
default: false,
default: null,
editor: require('text!components/agg_types/controls/min_doc_count.html'),
write: function (aggConfig, output) {
if (aggConfig.params.min_doc_count) {

View file

@ -55,7 +55,19 @@ define(function (require) {
});
}
notifs.push(notif);
notif.count = (notif.count || 0) + 1;
var dup = _.find(notifs, function (item) {
return item.content === notif.content && item.lifetime === notif.lifetime;
});
if (dup) {
dup.count += 1;
dup.stacks = _.union(dup.stacks, [notif.stack]);
} else {
notif.stacks = [notif.stack];
notifs.push(notif);
}
}
function formatMsg(msg, from) {

View file

@ -1,5 +1,6 @@
define(function (require) {
var notify = require('modules').get('kibana/notify');
var _ = require('lodash');
notify.directive('kbnNotifications', function () {
return {
@ -11,4 +12,4 @@ define(function (require) {
template: require('text!components/notify/partials/toaster.html')
};
});
});
});

View file

@ -1,9 +1,14 @@
<div class="toaster-container">
<ul class="toaster">
<li ng-repeat="notif in list" kbn-toast notif="notif">
<div class="alert" ng-class="'alert-' + notif.type">
<div class="toast alert" ng-class="'alert-' + notif.type">
<div class="btn-group pull-right toaster-controls">
<span ng-show="notif.count > 1" class="badge">{{ notif.count }}</span>
<i class="fa" ng-class="'fa-' + notif.icon" tooltip="{{notif.title}}"></i>
<kbn-truncated orig="{{notif.content}}" length="250" class="toast-message" /></kbn-truncated>
<div class="btn-group pull-right toast-controls">
<button
type="button"
ng-if="notif.stack && !notif.showStack"
@ -26,14 +31,12 @@
ng-click="notif.address()"
>Fix it</button>
</div>
<i class="fa" ng-class="'fa-' + notif.icon" tooltip="{{notif.title}}"></i> <kbn-truncated orig="{{notif.content}}" length="250" /></kbn-truncated>
<div ng-if="notif.stack && notif.showStack" class="toaster-stack">
<pre ng-bind="notif.stack"></pre>
</div>
</div>
<div ng-if="notif.stack && notif.showStack" class="toast-stack alert" ng-class="'alert-' + notif.type">
<pre ng-repeat="stack in notif.stacks" ng-bind="stack"></pre>
</div>
</li>
</ul>
</div>
</div>

View file

@ -130,8 +130,12 @@ define(function (require) {
datum.y = datum.y * colX.metricScale;
}
if (hasColor) {
datum.aggConfigs = [columns[iX], columns[iColor]];
}
s.values.push(datum);
});
};
};
});
});

View file

@ -7,8 +7,8 @@ define(function (require) {
index: ['name'],
initialSet: [
Private(require('components/vis_types/histogram')),
Private(require('components/vis_types/line')),
Private(require('components/vis_types/pie'))
// Private(require('components/vis_types/line')),
// Private(require('components/vis_types/pie'))
]
});
};

View file

@ -1,23 +0,0 @@
define(function (require) {
return function ColorUtilService(Private) {
var _ = require('lodash');
var createColorPalette = Private(require('components/vislib/components/_functions/color/color_palette'));
var createColorObj = Private(require('components/vislib/components/_functions/color/color_obj'));
// Takes an array of strings or numbers
return function (arr) {
if (!_.isArray(arr)) {
throw new Error(typeof arr + ' should be an array of strings or numbers');
}
var colorObj = createColorObj(arr, createColorPalette(arr.length));
// Returns a function that accepts a value (i.e. a string or number)
// and returns a hex color from the colorObj
return function (value) {
return colorObj[value];
};
};
};
});

View file

@ -1,12 +0,0 @@
define(function (require) {
return function ColorObjUtilService() {
var _ = require('lodash');
// Accepts 2 arrays of strings or numbers
return function (arr1, arr2) {
// Returns an object with arr1 values as keys
// and arr2 values as values
return _.zipObject(arr1, arr2);
};
};
});

View file

@ -1,18 +0,0 @@
define(function () {
return function ReplaceIndexUtilService() {
/*
* Replaces an object in an array at a specific index
*
* Accepts an array of objects
* an index (num)
* and an obj
*/
return function (arr, index, obj) {
arr.splice(index, 1);
arr.splice(index, 0, obj);
// Returns an array with a replaced object
return arr;
};
};
});

View file

@ -0,0 +1,28 @@
define(function (require) {
return function ColorUtilService(Private) {
var _ = require('lodash');
var createColorPalette = Private(require('components/vislib/components/color/color_palette'));
/*
* Accepts an array of strings or numbers that are used to create a
* a lookup table that associates the values (key) with a hex color (value).
* Returns a function that accepts a value (i.e. a string or number)
* and returns a hex color associated with that value
*/
return function (arrayOfStringsOrNumbers) {
// Takes an array of strings or numbers
if (!_.isArray(arrayOfStringsOrNumbers)) {
throw new Error('ColorUtil expects an array of strings or numbers');
}
// Creates lookup table of values (keys) and hex colors (values).
var colorObj = _.zipObject(arrayOfStringsOrNumbers, createColorPalette(arrayOfStringsOrNumbers.length));
return function (value) {
return colorObj[value];
};
};
};
});

View file

@ -2,7 +2,7 @@ define(function (require) {
return function ColorPaletteUtilService(d3, Private) {
var _ = require('lodash');
var seedColors = Private(require('components/vislib/components/_functions/color/seed_colors'));
var seedColors = Private(require('components/vislib/components/color/seed_colors'));
// Accepts a number that represents a length of an array
return function (num) {

View file

@ -1,4 +1,9 @@
define(function () {
/*
* Using a random color generator presented awful colors and unpredictable color schemes.
* So we needed to come up with one of our own that creates consistent, pleasing color patterns.
* The order allows us to guarantee that 1st, 2nd, 3rd, etc values always get the same color.
*/
return function SeedColorUtilService() {
// returns an array of 72 seed colors
return [

View file

@ -2,7 +2,7 @@ define(function (require) {
return function GetArrayUtilService(Private) {
var _ = require('lodash');
var flattenSeries = Private(require('components/vislib/components/_functions/labels/get_series'));
var flattenSeries = Private(require('components/vislib/components/labels/flatten_series'));
/* Takes a kibana obj object
* for example:

View file

@ -18,7 +18,6 @@ define(function (require) {
*/
return _.chain(obj)
.pluck()
.pluck('series')
.flatten()
.value();

View file

@ -1,7 +1,9 @@
define(function (require) {
return function LabelUtilService(Private) {
var getArr = Private(require('components/vislib/components/_functions/labels/data_array'));
var getArrOfUniqLabels = Private(require('components/vislib/components/_functions/labels/uniq_labels'));
var _ = require('lodash');
var getArr = Private(require('components/vislib/components/labels/data_array'));
var getArrOfUniqLabels = Private(require('components/vislib/components/labels/uniq_labels'));
/* Takes a kibana data object
* for example:
@ -14,8 +16,8 @@ define(function (require) {
* Data object can have a key that has rows, columns, or series.
*/
return function (obj) {
if (!obj instanceof Object) {
throw new Error(obj + ' should be an object');
if (!_.isObject(obj)) {
throw new Error('LabelUtil expects an object');
}
// Returns an array of unique chart labels

View file

@ -4,8 +4,8 @@ define(function (require) {
// Takes an array of objects
return function (arr) {
if (!arr instanceof Array) {
throw TypeError(arr + ' should be an array of objects');
if (!_.isArray(arr)) {
throw TypeError('UniqLabelUtil expects an array of objects');
}
// Returns a array of unique chart labels

View file

@ -10,7 +10,13 @@ define(function () {
var div = d3.select(this)
.attr('class', function () {
// Determine the parent class
return data.rows ? 'chart-wrapper-row' : data.columns ? 'chart-wrapper-column' : 'chart-wrapper';
if (data.rows) {
return 'chart-wrapper-row';
} else if (data.columns) {
return 'chart-wrapper-column';
} else {
return 'chart-wrapper';
}
});
var divClass;
@ -18,10 +24,19 @@ define(function () {
.append('div')
.data(function (d) {
// Determine the child class
divClass = d.rows ? 'chart-row' : d.columns ? 'chart-column' : 'chart';
return d.rows ? d.rows : d.columns ? d.columns : [d];
if (d.rows) {
divClass = 'chart-row';
return d.rows;
} else if (d.columns) {
divClass = 'chart-column';
return d.columns;
} else {
divClass = 'chart';
return [d];
}
})
.enter().append('div')
.enter()
.append('div')
.attr('class', function () {
return divClass;
});

View file

@ -11,11 +11,13 @@ define(function () {
var div = d3.select(this);
if (!data.series) {
div.selectAll('.chart-title').append('div')
div.selectAll('.chart-title')
.append('div')
.data(function (d) {
return d.rows ? d.rows : d.columns;
})
.enter().append('div')
.enter()
.append('div')
.attr('class', 'chart-title');
if (data.rows) {

View file

@ -99,10 +99,6 @@ define(function (require) {
{
type: 'div',
class: 'legend-col-wrapper'
},
{
type: 'div',
class: 'k4tip'
}
]
}

View file

@ -217,7 +217,7 @@ path, line, .axis line, .axis path {
stroke: 3px;
}
.k4tip {
.k4tip, .vis-tooltip {
line-height: 1.1;
font-size: 12px;
font-weight: normal;
@ -252,12 +252,13 @@ path, line, .axis line, .axis path {
.error {
.flex(1 1 100%);
.display(flex);
.align-items(center);
text-align: center;
p {
margin-top: 15%;
.flex(1 1 auto);
font-size: 18px;
text-wrap: wrap;
}
}

View file

@ -2,9 +2,9 @@ define(function (require) {
return function ZeroInjectionUtilService(Private) {
var _ = require('lodash');
var orderXValues = Private(require('components/vislib/components/_functions/zero_injection/ordered_x_keys'));
var createZeroFilledArray = Private(require('components/vislib/components/_functions/zero_injection/zero_filled_array'));
var zeroFillDataArray = Private(require('components/vislib/components/_functions/zero_injection/zero_fill_data_array'));
var orderXValues = Private(require('components/vislib/components/zero_injection/ordered_x_keys'));
var createZeroFilledArray = Private(require('components/vislib/components/zero_injection/zero_filled_array'));
var zeroFillDataArray = Private(require('components/vislib/components/zero_injection/zero_fill_data_array'));
// Takes the kibana data objects
return function (obj) {

View file

@ -1,7 +1,7 @@
define(function (require) {
return function OrderedXKeysUtilService(Private) {
var _ = require('lodash');
var getUniqKeys = Private(require('components/vislib/components/_functions/zero_injection/uniq_keys'));
var getUniqKeys = Private(require('components/vislib/components/zero_injection/uniq_keys'));
// Takes a kibana data objects
return function (obj) {

View file

@ -2,7 +2,7 @@ define(function (require) {
return function UniqueXValuesUtilService(Private) {
var _ = require('lodash');
var flattenDataArray = Private(require('components/vislib/components/_functions/zero_injection/flatten_data'));
var flattenDataArray = Private(require('components/vislib/components/zero_injection/flatten_data'));
// accepts a kibana data.series array of objects
return function (obj) {

View file

@ -2,8 +2,6 @@ define(function (require) {
return function ZeroFillDataArrayUtilService(Private) {
var _ = require('lodash');
var replaceIndex = Private(require('components/vislib/components/_functions/zero_injection/replace_index'));
// Accepts an array of zero-filled y value objects
// and a kibana data.series[i].values array of objects
return function (arr1, arr2) {
@ -18,7 +16,7 @@ define(function (require) {
for (i = 0; i < max; i++) {
val = arr2[i];
index = _.findIndex(arr1, getX);
replaceIndex(arr1, index, val);
arr1.splice(index, 1, val);
}
// Return a zero-filled array of objects

View file

@ -2,10 +2,10 @@ define(function (require) {
return function DataFactory(d3, Private) {
var _ = require('lodash');
var injectZeros = Private(require('components/vislib/components/_functions/zero_injection/inject_zeros'));
var orderKeys = Private(require('components/vislib/components/_functions/zero_injection/ordered_x_keys'));
var getLabels = Private(require('components/vislib/components/_functions/labels/labels'));
var color = Private(require('components/vislib/components/_functions/color/color'));
var injectZeros = Private(require('components/vislib/components/zero_injection/inject_zeros'));
var orderKeys = Private(require('components/vislib/components/zero_injection/ordered_x_keys'));
var getLabels = Private(require('components/vislib/components/labels/labels'));
var color = Private(require('components/vislib/components/color/color'));
/*
* Provides an API for pulling values off the data

View file

@ -1,6 +1,7 @@
define(function (require) {
return function LegendFactory(d3, Private) {
var _ = require('lodash');
var legendHeaderTemplate = _.template(require('text!components/vislib/partials/legend_header.html'));
// Dynamically adds css file
require('css!components/vislib/components/styles/main');
@ -39,13 +40,8 @@ define(function (require) {
.attr('class', 'header')
.append('div')
.attr('class', 'column-labels')
.html(function (d) {
if (args._attr.isOpen) {
return '<span class="btn btn-xs btn-default legend-toggle">' +
'<i class="fa fa-chevron-right"></i></span>';
}
return '<span class="btn btn-xs btn-default legend-toggle">' +
'<i class="fa fa-chevron-left"></i></span>';
.html(function () {
return legendHeaderTemplate(args._attr);
});
};

View file

@ -7,16 +7,10 @@ define(function (require) {
var reflowWatcher = Private(require('components/reflow_watcher'));
var sequencer = require('utils/sequencer');
var SCHEDULE_LONG = ResizeChecker.SCHEDULE_LONG = sequencer.createEaseOut(
250, // shortest delay
10000, // longest delay
150 // tick count
);
var SCHEDULE_SHORT = ResizeChecker.SCHEDULE_SHORT = sequencer.createEaseIn(
5, // shortest delay
500, // longest delay
100 // tick count
var SCHEDULE = ResizeChecker.SCHEDULE = sequencer.createEaseIn(
5, // shortest delay
10000, // longest delay
125 // tick count
);
// maximum ms that we can delay emitting 'resize'. This is only used
@ -47,7 +41,7 @@ define(function (require) {
}
ResizeChecker.prototype.onReflow = function () {
this.startSchedule(SCHEDULE_LONG);
this.startSchedule(SCHEDULE);
};
/**
@ -149,9 +143,7 @@ define(function (require) {
return this.continueSchedule();
}
// when the state changes start a new schedule. Use a schedule that quickly
// slows down if it is unknown wether there are will be additional changes
return this.startSchedule(dirty ? SCHEDULE_SHORT : SCHEDULE_LONG);
return this.startSchedule(SCHEDULE);
};
/**

View file

@ -16,15 +16,10 @@ define(function (require) {
if (!(this instanceof Tooltip)) {
return new Tooltip(el, formatter);
}
this.el = el;
this.tooltipFormatter = formatter;
// hard coded class name for the tooltip `div`
this.tooltipClass = 'k4tip';
// reference to the width and height of the chart DOM elements
// establishes the bounds for the tooltip per chart
this.chartWidth = $('.chart').width();
this.chartHeight = $('.chart').height();
this.tooltipClass = 'vis-tooltip';
this.containerClass = 'vis-wrapper';
}
Tooltip.prototype.render = function () {
@ -32,64 +27,96 @@ define(function (require) {
return function (selection) {
// if tooltip not appended to body, append one
if (d3.select('body').select('.' + self.tooltipClass)[0][0] === null) {
d3.select('body').append('div').attr('class', self.tooltipClass);
}
// if container not saved on Tooltip, save it
if (self.container === undefined || self.container !== d3.select(self.el).select('.' + self.containerClass)) {
self.container = d3.select(self.el).select('.' + self.containerClass);
}
var tooltipDiv = d3.select('.' + self.tooltipClass);
selection.each(function () {
var tooltipDiv = d3.select(self.el).select('.' + self.tooltipClass);
// DOM element on which the tooltip is called
var element = d3.select(this);
// define selections relative to el of tooltip
var chartXoffset;
var chartWidth;
var chartHeight;
var yaxisWidth;
var offset;
var tipWidth;
var tipHeight;
element
.on('mousemove.tip', function (d) {
// Calculate the x and y coordinates of the mouse on the page
// get x and y coordinates of the mouse event
var mouseMove = {
left: d3.event.clientX,
top: d3.event.clientY
};
// hack to keep active tooltip in front of gridster/dashboard list
if ($('.gridster').length) {
var gridsterUl = $('.gridster');
var gridsterLis = $('.gridster').find('li').removeClass('player-revert');
var tipLi = $(tooltipDiv.node()).closest('li').addClass('player-revert');
}
var chartWidth = $(tooltipDiv.node()).closest('.vis-wrapper').width();
var yaxisWidth = $('.y-axis-col-wrapper').width();
var offsetX = d3.event.offsetX === undefined ? d3.event.layerX : d3.event.offsetX;
var tipWidth = tooltipDiv[0][0].clientWidth;
var xOffset = 10;
// check position of tooltip relative to chart width
// to apply offset if tooltip should flip 'west'
// if tip width + offset puts it off chart, flip direction
// unless flip puts it off the left edge of vis wrapper
if ((chartWidth - offsetX) < (tipWidth + yaxisWidth + 10) && (offsetX + yaxisWidth + 10) > (tipWidth + 10)) {
xOffset = -10 - tipWidth;
}
var chartHeight = self.chartHeight;
var offsetY = d3.event.offsetY === undefined ? d3.event.layerY : d3.event.offsetY;
var tipHeight = tooltipDiv[0][0].clientHeight;
var yOffset = 5;
// apply y offset to keep tooltip within bottom of chart
if ((chartHeight - offsetY + 5) < (tipHeight)) {
yOffset = tipHeight - (chartHeight - offsetY + 0);
}
offset = self.getOffsets(tooltipDiv, mouseMove);
// return text and position for tooltip
return tooltipDiv.datum(d)
.text(self.tooltipFormatter)
.style('visibility', 'visible')
.style('left', mouseMove.left + xOffset + 'px')
.style('top', mouseMove.top - yOffset + 'px');
.style('left', mouseMove.left + offset.left + 'px')
.style('top', mouseMove.top - offset.top + 'px');
})
.on('mouseout.tip', function () {
// Hide tooltip
// hide tooltip
return tooltipDiv.style('visibility', 'hidden');
});
});
};
};
Tooltip.prototype.getOffsets = function (tooltipDiv, mouseMove) {
var self = this;
var offset = {top: 10, left: 10};
var container;
var chartXoffset;
var chartYoffset;
var chartWidth;
var chartHeight;
var tipWidth;
var tipHeight;
if ($(self.el).find('.' + self.containerClass)) {
container = $(self.el).find('.' + self.containerClass);
chartXoffset = container.offset().left;
chartYoffset = container.offset().top;
chartWidth = container.width();
chartHeight = container.height();
tipWidth = tooltipDiv[0][0].clientWidth;
tipHeight = tooltipDiv[0][0].clientHeight;
// change xOffset to keep tooltip within container
// if tip width + xOffset puts it over right edge of container, flip left
// unless flip left puts it over left edge of container
if ((mouseMove.left + offset.left + tipWidth) > (chartXoffset + chartWidth) &&
(mouseMove.left - tipWidth - 10) > chartXoffset) {
offset.left = -10 - tipWidth;
}
// change yOffset to keep tooltip within container
if ((mouseMove.top + tipHeight - 10) > (chartYoffset + chartHeight)) {
offset.top = chartYoffset + chartHeight;
}
}
return offset;
};
return Tooltip;
};
});

View file

@ -90,7 +90,6 @@ define(function (require) {
// Create the d3 xAxis function
XAxis.prototype.getXAxis = function (width) {
this.xAxisFormatter = this.xAxisFormatter;
// save a reference to the xScale
this.xScale = this.getXScale(this.ordered, width);
@ -216,7 +215,7 @@ define(function (require) {
// truncate str
selection.selectAll('.tick text')
.text(function (d) {
str = self.xAxisFormatter(d);
str = d;
if (maxWidth > size) {
endChar = 0;
if (Math.floor((size / pixPerChar) - 4) >= 4) {

View file

@ -0,0 +1,3 @@
<span class="btn btn-xs btn-default legend-toggle">
<i class="fa fa-chevron-<%= (isOpen) ? 'right' : 'left' %>"></i>
</span>

View file

@ -29,6 +29,22 @@ define(function (require) {
// Response to `click` and `hover` events
ColumnChart.prototype.eventResponse = function (d, i) {
// Adding a look up for the field. Currently this relies on filtering the
// data for the label then using that with the pointIndex to get the aggConfig.
// It works for now... but we need something a little more robust. That will
// come after the first beta. :)
//
// TODO: Replace the following code with something more robust for finding the field
var field, series, aggConfig;
if (d.label) {
series = _.find(this.chartData.series, { label: d.label });
aggConfig = _.last(series.values[i].aggConfigs);
if (aggConfig.aggType.name === 'terms') {
field = aggConfig.field.name;
}
}
return {
value : this._attr.yValue(d, i),
point : d,
@ -38,7 +54,10 @@ define(function (require) {
series : this.chartData.series,
config : this._attr,
data : this.chartData,
e : d3.event
e : d3.event,
field : field,
aggConfig : aggConfig,
vis : this.vis
};
};
@ -251,4 +270,4 @@ define(function (require) {
return ColumnChart;
};
});
});

View file

@ -80,6 +80,8 @@ define(function (require) {
$scope.httpActive = $http.pendingRequests;
window.$kibanaInjector = $injector;
// this is the only way to handle uncaught route.resolve errors
$rootScope.$on('$routeChangeError', function (event, next, prev, err) {
notify.fatal(err);
@ -151,21 +153,7 @@ define(function (require) {
});
$scope.opts = {
timefilter: timefilter,
activeFetchInterval: void 0,
fetchIntervals: [
{ display: 'none', val: null},
{ display: '5s', val: 5000 },
{ display: '10s', val: 10000 },
{ display: '30s', val: 30000 },
{ display: '1m', val: 60000 },
{ display: '5m', val: 300000 },
{ display: '15m', val: 900000 },
{ display: '30m', val: 1.8e+6 },
{ display: '1h', val: 3.6e+6 },
{ display: '2h', val: 7.2e+6 },
{ display: '1d', val: 8.64e+7 }
]
timefilter: timefilter
};
$scope.configure = function () {
@ -188,42 +176,6 @@ define(function (require) {
$scope.globalConfigTemplate = timepickerHtml;
}
};
/**
* Persist current settings
* @return {[type]} [description]
*/
$scope.saveOpts = function () {
config.set('refreshInterval', $scope.opts.activeFetchInterval.val);
};
$scope.setActiveFetchInterval = function (val) {
var option = _.find($scope.opts.fetchIntervals, { val: val });
if (option) {
$scope.opts.activeFetchInterval = option;
return;
}
// create a custom option for this value
option = { display: moment.duration(val).humanize(), val: val };
$scope.opts.fetchIntervals.unshift(option);
$scope.opts.activeFetchInterval = option;
};
$scope.activeFetchIntervalChanged = function (option, prev) {
var opts = $scope.opts;
if (option && typeof option !== 'object') {
$scope.setActiveFetchInterval(option);
return;
}
courier.fetchInterval(option.val);
};
$scope.setActiveFetchInterval(config.get('fetchInterval', null));
$scope.$on('change:config.refreshInterval', $scope.setActiveFetchInterval);
$scope.$watch('opts.activeFetchInterval', $scope.activeFetchIntervalChanged);
});
});
});

View file

@ -1,5 +1,6 @@
define(function (require) {
var module = require('modules').get('kibana');
var _ = require('lodash');
var $ = require('jquery');
module.directive('clickFocus', function () {
@ -9,11 +10,13 @@ define(function (require) {
},
restrict: 'A',
link: function ($scope, $elem) {
$elem.bind('click', function () {
function handler() {
var focusElem = $.find('input[name=' + $scope.clickFocus + ']');
if (focusElem[0]) focusElem[0].focus();
});
$scope.$on('$destroy', $elem.unbind);
}
$elem.bind('click', handler);
$scope.$on('$destroy', _.bindKey($elem, 'unbind', 'click', handler));
}
};
});

View file

@ -13,32 +13,64 @@
}
.alert {
padding: 0.00001px 15px;
padding: 0 15px;
margin: 0;
border-width: 0 0 1px 0;
border-radius: 0;
min-height: 40px;
line-height: 40px;
border: 0px;
}
.toaster-controls {
button.btn {
height: 40px;
border: 0;
border-radius: 0;
max-height: 40px;
.toast {
.display(flex);
.align-items(center);
> * {
.flex(0 0 auto);
&:not(:last-child) {
margin-right: 4px;
}
}
.toaster-stack {
padding-bottom: 10px;
&-message {
.flex(1 1 auto);
.ellipsis();
line-height: normal;
}
pre {
display: inline-block;
width: 100%;
margin: 10px 0;
word-break: normal;
word-wrap: normal;
&-stack {
padding-bottom: 10px;
pre {
display: inline-block;
width: 100%;
margin: 10px 0;
word-break: normal;
word-wrap: normal;
}
}
&-controls {
.display(flex);
button {
.flex(0 0 auto);
border: 0;
border-radius: 0;
padding: 10px 15px;
}
}
}
// add darkened background to the different badges
.alert-success .badge {
background: darken(@alert-success-bg, 25%);
}
.alert-info .badge {
background: darken(@alert-info-bg, 25%);
}
.alert-warning .badge {
background: darken(@alert-warning-bg, 25%);
}
.alert-danger .badge {
background: darken(@alert-danger-bg, 25%);
}
}

View file

@ -50,6 +50,10 @@ module.exports = function (grunt) {
src: '<%= root %>/LICENSE.md',
dest: '<%= build %>/dist/LICENSE.md'
},
{
src: '<%= root %>/README.md',
dest: '<%= build %>/dist/README.md'
},
{
expand: true,
cwd: '<%= build %>/kibana/',

View file

@ -12,7 +12,7 @@ module.exports = function (grunt) {
},
files: [
{
src: [join(src, 'server', 'DIST_README.md')],
src: [join(src, 'server', 'README.md')],
dest: join(build, 'dist', 'README.md')
},
{

View file

@ -3,19 +3,26 @@ module.exports = function (grunt) {
var jruby = jrubyPath + '/bin/jruby';
var cmd = grunt.config.get('src') + '/server/bin/initialize';
// config:
// wait: should task wait until the script exits before finishing
// ready: if not waiting, then how do we know the process is ready?
// quiet: ignore stdout from the process
// failOnError: the process is killed if output to stderr
var options = {
wait: false,
ready: /kibana server started/i,
quiet: true,
failOnError: true
};
var config = {
mri_server: {
options: {
wait: false,
ready: /kibana server started/i
},
options: options,
cmd: cmd
},
jruby_server: {
options: {
wait: false,
ready: /kibana server started/i
},
options: options,
cmd: jruby,
args: [
cmd
@ -24,5 +31,4 @@ module.exports = function (grunt) {
};
return config;
};

View file

@ -1,14 +1,19 @@
var buildId = 'test build';
if (process.env.TRAVIS_BUILD_ID) {
buildId = 'travis build #' + process.env.TRAVIS_BUILD_ID;
}
module.exports = {
unit: {
options: {
urls: [
'http://localhost:8000/test/unit/?saucelabs=true'
],
testname: 'Kibana Browser Tests',
build: process.env.TRAVIS_BUILD_ID || 'test build',
concurrency: 10,
username: 'kibana',
key: process.env.SAUCE_ACCESS_KEY,
urls: ['http://localhost:8000/test/unit/?saucelabs=true'],
testname: 'Kibana Browser Tests',
build: buildId,
concurrency: 10,
'max-duration': 60,
maxRetries: 1,
browsers: [
{
browserName: 'chrome',

84
tasks/render_readme.js Normal file
View file

@ -0,0 +1,84 @@
module.exports = function (grunt) {
var expect = require('expect.js');
var root = require('path').join.bind(null, __dirname, '../');
var README = root('README.md');
var RE_COMMENT = /<!--(.+?)-->/g;
var tags = {
render: function (args) {
return grunt.config.process(args.template);
},
include: function (args) {
return grunt.file.read(args.path);
}
};
grunt.registerTask('render_readme', function () {
var input = grunt.file.read(README);
var chunks = [];
var comments = findAndParseComments(input);
for (var i = 0; i < comments.length; i += 2) {
var open = comments[i];
var close = comments[i + 1];
expect(close.tag).to.be('/' + open.tag);
if (!tags[open.tag]) {
throw new TypeError('unkown tag name ' + open.tag);
}
chunks.push(input.substring(open.i0, open.i1));
chunks.push('\n' + tags[open.tag](open.args).trim() + '\n');
chunks.push(input.substring(close.i0, close.i1));
// add the text between this and the next tag
if (comments.length > i + 2) {
var next = comments[i + 2];
chunks.push(input.substring(close.i1, next.i0));
}
}
var output = chunks.join('');
if (output === input) {
grunt.log.ok('no update to the readme');
return;
}
grunt.log.ok('readme updated and added to git');
grunt.file.write(README, output);
grunt.util.spawn({
cmd: 'git',
args: ['add', README]
}, this.async());
});
function findAndParseComments(input) {
var comments = [];
var match;
var comment;
while (match = RE_COMMENT.exec(input)) {
var parts = match[1].trim().split(/\s+/);
comment = {
tag: parts.shift().trim(),
args: parts.join(' ').trim(),
i0: match.index,
i1: match.index + match[0].length
};
if (comment.args) {
comment.args = JSON.parse(comment.args);
} else {
comment.args = null;
}
comments.push(comment);
}
return comments;
}
};

View file

@ -2,12 +2,15 @@ var request = require('request');
module.exports = function (grunt) {
grunt.registerTask('ruby_server', function () {
var done = this.async();
request.get('http://localhost:5601/config', function (err, resp, body) {
request.get('http://localhost:5601/config', function (err, resp, body) {
// err is a failed response, no server is running
if (err) {
// run mri_server by default
var tasks = ['run:mri_server'];
grunt.config.set('ruby_server', 'mri_server');
// if jruby flag is set, use jruby
if (grunt.option('use-jruby')) {
tasks = [
'download_jruby',
@ -17,7 +20,10 @@ module.exports = function (grunt) {
];
grunt.config.set('ruby_server', 'jruby_server');
}
grunt.task.run(tasks);
// response means server is already running
} else {
grunt.log.error('Another ruby server is running on localhost:5601.');
}

View file

@ -3,9 +3,13 @@ module.exports = function (grunt) {
var done = this.async();
var DevServer = require('../test/utils/dev_server');
var server = new DevServer();
server.listen(8000).then(function () {
console.log('visit http://localhost:8000');
if (keepalive !== 'keepalive') done();
if (keepalive !== 'keepalive') {
done();
}
});
});
};

View file

@ -1,15 +1,27 @@
var _ = require('lodash');
module.exports = function (grunt) {
var testTask = process.env.TRAVIS ? 'saucelabs-mocha:unit' : 'mocha:unit';
grunt.registerTask('test', [
'jshint',
'ruby_server',
'maybe_start_server',
'jade',
testTask
]);
grunt.registerTask('test', function () {
var testTask = 'mocha:unit';
if (process.env.TRAVIS && !process.env.SAUCE_ACCESS_KEY) {
grunt.log.writeln(grunt.log.wordlist([
'>> SAUCE_ACCESS_KEY not set in env, running with Phantom'
], {color: 'yellow'}));
} else {
testTask = 'saucelabs-mocha:unit';
}
var tasks = [
'jshint',
'ruby_server',
'maybe_start_server',
'jade',
'less',
testTask
];
grunt.task.run(tasks);
});
grunt.registerTask('coverage', [
'blanket',

View file

@ -4,11 +4,8 @@
<title>Kibana4 Tests</title>
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
<link rel="stylesheet" href="/node_modules/mocha-screencast-reporter/screencast-reporter.css" />
<script src="/node_modules/expect.js/index.js"></script>
<script src="/node_modules/mocha/mocha.js"></script>
<script src="/node_modules/mocha-screencast-reporter/screencast-reporter.js"></script>
<script src="/src/kibana/bower_components/requirejs/require.js"></script>
<script src="/src/kibana/require.config.js"></script>
@ -18,7 +15,7 @@
mocha.setup({
ui: 'bdd',
reporter: SAUCELABS ? ScreencastReporter : 'html'
reporter: 'html'
});
require.config({
@ -31,7 +28,9 @@
sinon: '../../test/utils/sinon',
bluebird: 'bower_components/bluebird/js/browser/bluebird',
angular: 'bower_components/angular-mocks/angular-mocks',
angular_src: 'bower_components/angular/angular'
angular_src: 'bower_components/angular/angular',
screencast_reporter_css: '../../node_modules/mocha-screencast-reporter/screencast-reporter',
ScreencastReporter: '../../node_modules/mocha-screencast-reporter/screencast-reporter'
},
shim: {
angular: {
@ -63,6 +62,16 @@
});
}
function setupSaucelabsReporter(done) {
require([
'ScreencastReporter',
'css!screencast_reporter_css'
], function (ScreencastReporter) {
mocha.reporter(ScreencastReporter);
done()
});
}
function runTests() {
require([
'kibana',
@ -114,6 +123,7 @@
'specs/vislib/column_layout',
'specs/vislib/index',
'specs/vislib/vis',
'specs/vislib/tooltip',
'specs/vislib/handler',
'specs/vislib/_error_handler',
'specs/vislib/data',
@ -174,7 +184,11 @@
if (COVERAGE) {
setupCoverage(runTests);
} else {
}
else if (SAUCELABS) {
setupSaucelabsReporter(runTests);
}
else {
runTests();
}

View file

@ -145,49 +145,61 @@ define(function (require) {
expect(bucketCountBetween(a, b)).to.be(null);
});
it('can count', function () {
var schemas = visTypes.byName.histogram.schemas.buckets;
function countTest(pre, post) {
return function () {
var schemas = visTypes.byName.histogram.schemas.buckets;
// slow for this test is actually somewhere around 1/2 a sec
this.slow(500);
// slow for this test is actually somewhere around 1/2 a sec
this.slow(500);
function randBucketAggForVis(vis) {
var schema = _.sample(schemas);
var aggType = _.sample(aggTypes.byType.buckets);
function randBucketAggForVis(vis) {
var schema = _.sample(schemas);
var aggType = _.sample(aggTypes.byType.buckets);
return new AggConfig(vis, {
schema: schema,
type: aggType
});
}
_.times(50, function (n) {
var vis = new Vis(indexPattern, {
type: 'histogram',
aggs: []
});
var randBucketAgg = _.partial(randBucketAggForVis, vis);
var a = randBucketAgg();
var b = randBucketAgg();
// create n aggs between a and b
var aggs = [];
for (var i = 0; i < n; i++) {
aggs.push(randBucketAgg());
return new AggConfig(vis, {
schema: schema,
type: aggType
});
}
aggs.unshift(a);
aggs.push(b);
_.times(50, function (n) {
var vis = new Vis(indexPattern, {
type: 'histogram',
aggs: []
});
vis.setState({
type: 'histogram',
aggs: aggs
var randBucketAgg = _.partial(randBucketAggForVis, vis);
var a = randBucketAgg();
var b = randBucketAgg();
// create n aggs between a and b
var aggs = [];
aggs.fill = function (n) {
for (var i = 0; i < n; i++) {
aggs.push(randBucketAgg());
}
};
pre && aggs.fill(_.random(0, 10));
aggs.push(a);
aggs.fill(n);
aggs.push(b);
post && aggs.fill(_.random(0, 10));
vis.setState({
type: 'histogram',
aggs: aggs
});
expect(bucketCountBetween(a, b)).to.be(n);
});
};
}
expect(bucketCountBetween(a, b)).to.be(n);
});
});
it('can count', countTest());
it('can count with elements before', countTest(true));
it('can count with elements after', countTest(false, true));
it('can count with elements before and after', countTest(true, true));
}];
});

View file

@ -22,8 +22,8 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
seedColors = Private(require('components/vislib/components/_functions/color/seed_colors'));
getColors = Private(require('components/vislib/components/_functions/color/color'));
seedColors = Private(require('components/vislib/components/color/seed_colors'));
getColors = Private(require('components/vislib/components/color/color'));
// error = getColors(str);
color = getColors(arr);
});
@ -62,36 +62,6 @@ define(function (require) {
});
});
describe('Color Object', function () {
var createColorObj;
var arr1 = ['rashid', 'juan', 'chris', 'spencer'];
var arr2 = ['guru', 'datavis', 'architect', 'javascript'];
var dict;
beforeEach(function () {
module('ColorObjUtilService');
});
beforeEach(function () {
inject(function (d3, Private) {
createColorObj = Private(require('components/vislib/components/_functions/color/color_obj'));
dict = createColorObj(arr1, arr2);
});
});
it('should be a function', function () {
expect(typeof createColorObj).to.be('function');
});
it('should return an object', function () {
expect(dict instanceof Object).to.be(true);
});
it('should return the correct value', function () {
expect(dict[arr1[0]]).to.be(arr2[0]);
});
});
describe('Color Palette', function () {
var num1 = 45;
var num2 = 72;
@ -105,7 +75,7 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
createColorPalette = Private(require('components/vislib/components/_functions/color/color_palette'));
createColorPalette = Private(require('components/vislib/components/color/color_palette'));
colorPalette = createColorPalette(num1);
});
});

View file

@ -116,7 +116,7 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
getLabels = Private(require('components/vislib/components/_functions/labels/labels'));
getLabels = Private(require('components/vislib/components/labels/labels'));
seriesLabels = getLabels(seriesData);
rowsLabels = getLabels(rowsData);
seriesArr = _.isArray(seriesLabels);
@ -142,9 +142,10 @@ define(function (require) {
expect(rowsArr).to.be(true);
});
it('should return empty array if input is not an object', function () {
error = getLabels('string not object');
expect(error.length).to.be(0);
it('should throw an error if input is not an object', function () {
expect(function () {
getLabels('string not object');
}).to.throwError();
});
it('should return unique label values', function () {
@ -166,7 +167,7 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
dataArray = Private(require('components/vislib/components/_functions/labels/data_array'));
dataArray = Private(require('components/vislib/components/labels/data_array'));
seriesLabels = dataArray(seriesData);
rowsLabels = dataArray(rowsData);
testSeries = _.isArray(seriesLabels);
@ -222,7 +223,7 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
uniqLabels = Private(require('components/vislib/components/_functions/labels/uniq_labels'));
uniqLabels = Private(require('components/vislib/components/labels/uniq_labels'));
uniq = uniqLabels(arrObj);
testArr = _.isArray(uniq);
});
@ -258,7 +259,7 @@ define(function (require) {
beforeEach(function () {
inject(function (d3, Private) {
getSeries = Private(require('components/vislib/components/_functions/labels/get_series'));
getSeries = Private(require('components/vislib/components/labels/flatten_series'));
columnsLabels = getSeries(columnsData);
rowsLabels = getSeries(rowsData);
seriesLabels = getSeries(seriesData);

View file

@ -0,0 +1,99 @@
define(function (require) {
var angular = require('angular');
var _ = require('lodash');
var $ = require('jquery');
angular.module('TooltipFactory', ['kibana']);
describe('Vislib Tooltip', function () {
var Tooltip;
var bars;
var tip;
var el;
var chart;
var data = [
{
x: 10,
y: 8
},
{
x: 11,
y: 23
},
{
x: 12,
y: 30
},
{
x: 13,
y: 28
},
{
x: 14,
y: 36
},
{
x: 15,
y: 30
}
];
beforeEach(function () {
module('TooltipFactory');
});
beforeEach(function () {
inject(function (d3, Private) {
Tooltip = Private(require('components/vislib/lib/tooltip'));
el = d3.select('body')
.append('div')
.attr('class', 'vis-col-wrapper')
.style('width', '40px')
.style('height', '40px');
tip = new Tooltip(el[0][0], function (d) {
return 'd.x: ' + d.x + ', d.y: ' + d.y;
});
chart = el.append('div').attr('class', 'chart');
el.append('div').attr('class', 'y-axis-col-wrapper');
el.append('div').attr('class', 'k4tip');
bars = chart.selectAll('div')
.data(data)
.enter()
.append('div')
.attr('class', 'bars')
.style('width', function (d) {
return d.y;
})
.style('height', '10px')
.text(function (d) {
return d.y;
});
bars.call(tip.render());
// d3.select('.bars').on('mousemove.tip')(d3.select('.bars').node().__data__, 0);
});
});
afterEach(function () {
el.remove();
});
it('should be an object', function () {
expect(_.isObject(Tooltip)).to.be(true);
});
it('should return a function', function () {
expect(typeof Tooltip).to.be('function');
});
});
});

View file

@ -58,7 +58,7 @@ define(function (require) {
beforeEach(function () {
inject(function (Private) {
injectZeros = Private(require('components/vislib/components/_functions/zero_injection/inject_zeros'));
injectZeros = Private(require('components/vislib/components/zero_injection/inject_zeros'));
sample1 = injectZeros(seriesData);
sample2 = injectZeros(multiSeriesData);
});
@ -113,7 +113,7 @@ define(function (require) {
beforeEach(function () {
inject(function (Private) {
orderXValues = Private(require('components/vislib/components/_functions/zero_injection/ordered_x_keys'));
orderXValues = Private(require('components/vislib/components/zero_injection/ordered_x_keys'));
results = orderXValues(multiSeriesData);
});
});
@ -145,7 +145,7 @@ define(function (require) {
beforeEach(function () {
inject(function (Private) {
uniqueKeys = Private(require('components/vislib/components/_functions/zero_injection/uniq_keys'));
uniqueKeys = Private(require('components/vislib/components/zero_injection/uniq_keys'));
results = uniqueKeys(multiSeriesData.series);
});
});
@ -163,41 +163,6 @@ define(function (require) {
});
});
describe('Replace Index', function () {
var replaceIndex;
var arr = [
{ x: 1, y: 2},
{ x: 2, y: 3},
{ x: 3, y: 4}
];
var index = 1;
var obj = { x: 2, y: 5 };
var results;
beforeEach(function () {
module('ReplaceIndexUtilService');
});
beforeEach(function () {
inject(function (Private) {
replaceIndex = Private(require('components/vislib/components/_functions/zero_injection/replace_index'));
results = replaceIndex(arr, index, obj);
});
});
it('should return a function', function () {
expect(_.isFunction(replaceIndex)).to.be(true);
});
it('should return an array', function () {
expect(_.isArray(results)).to.be(true);
});
it('should replace the object at the index in the array with the new object', function () {
expect(results[1].y).to.be(5);
});
});
describe('Flatten Data', function () {
var flattenData;
var results;
@ -208,7 +173,7 @@ define(function (require) {
beforeEach(function () {
inject(function (Private) {
flattenData = Private(require('components/vislib/components/_functions/zero_injection/flatten_data'));
flattenData = Private(require('components/vislib/components/zero_injection/flatten_data'));
results = flattenData(multiSeriesData);
});
});
@ -241,7 +206,7 @@ define(function (require) {
beforeEach(function () {
inject(function (Private) {
createZeroArray = Private(require('components/vislib/components/_functions/zero_injection/zero_filled_array'));
createZeroArray = Private(require('components/vislib/components/zero_injection/zero_filled_array'));
results1 = createZeroArray(arr1);
results2 = createZeroArray(arr2);
});
@ -293,7 +258,7 @@ define(function (require) {
var xValueArr = [1, 2, 3, 4, 5];
var createZeroArray;
var arr1;
var arr2 = multiSeriesData.series[2].values;
var arr2 = [ {x: 3, y: 834} ];
var results;
beforeEach(function () {
@ -302,8 +267,8 @@ define(function (require) {
beforeEach(function () {
inject(function (Private) {
zeroFillArray = Private(require('components/vislib/components/_functions/zero_injection/zero_fill_data_array'));
createZeroArray = Private(require('components/vislib/components/_functions/zero_injection/zero_filled_array'));
zeroFillArray = Private(require('components/vislib/components/zero_injection/zero_fill_data_array'));
createZeroArray = Private(require('components/vislib/components/zero_injection/zero_filled_array'));
arr1 = createZeroArray(xValueArr);
// Takes zero array as 1st arg and data array as 2nd arg
results = zeroFillArray(arr1, arr2);
@ -327,7 +292,7 @@ define(function (require) {
it('should return an array with zeros injected in the appropriate objects as y values', function () {
expect(results[0].y).to.be(0);
expect(results[1].y).to.be(0);
expect(results[2].y).to.be(0);
expect(results[3].y).to.be(0);
expect(results[4].y).to.be(0);
});
});