Commit 052c9aca by Torkel Ödegaard

Merge branch 'master' of github.com:grafana/grafana

parents c1f77eea b0e975bf
......@@ -28,6 +28,7 @@ it allows you to add queries of differnet data source types & instances to the s
- [Issue #2708](https://github.com/grafana/grafana/issues/2708). InfluxDB: You can now set math expression for select clauses.
- [Issue #1575](https://github.com/grafana/grafana/issues/1575). Drilldown link: now you can click on the external link icon in the panel header to access drilldown links!
- [Issue #1646](https://github.com/grafana/grafana/issues/1646). OpenTSDB: Fetch list of aggregators from OpenTSDB
- [Issue #2955](https://github.com/grafana/grafana/issues/2955). Graph: More axis units (Length, Volume, Temperature, Pressure, etc), thanks @greglook
**Fixes**
- [Issue #2413](https://github.com/grafana/grafana/issues/2413). InfluxDB 0.9: Fix for handling empty series object in response from influxdb
......
......@@ -89,8 +89,13 @@ func main() {
}
func makeLatestDistCopies() {
rpmIteration := "-1"
if linuxPackageIteration != "" {
rpmIteration = "-" + linuxPackageIteration
}
runError("cp", "dist/grafana_"+version+"_amd64.deb", "dist/grafana_latest_amd64.deb")
runError("cp", "dist/grafana-"+strings.Replace(version, "-", "_", 5)+"-1.x86_64.rpm", "dist/grafana-latest-1.x86_64.rpm")
runError("cp", "dist/grafana-"+linuxPackageVersion+rpmIteration+".x86_64.rpm", "dist/grafana-latest-1.x86_64.rpm")
runError("cp", "dist/grafana-"+version+".linux-x64.tar.gz", "dist/grafana-latest.linux-x64.tar.gz")
}
......
......@@ -4,10 +4,6 @@ global:
evaluation_interval: 10s # By default, scrape targets every 15 seconds.
# scrape_timeout is set to the global default (10s).
# Attach these extra labels to all timeseries collected by this Prometheus instance.
labels:
monitor: 'codelab-monitor'
# Load and evaluate rules in this file every 'evaluation_interval' seconds.
rule_files:
# - "first.rules"
......
......@@ -29,7 +29,7 @@ Url | The http protocol, ip and port of you graphite-web or graphite-api install
Access | Proxy = access via Grafana backend, Direct = access directory from browser.
Proxy access means that the Grafana backend will proxy all requests from the browser, and send them on to the Data Source. This is useful because it can eliminate CORS (Cross Origin Site Resource) issues, as well as eliminate the need to disseminate authentication details to the Data Source to the brower.
Proxy access means that the Grafana backend will proxy all requests from the browser, and send them on to the Data Source. This is useful because it can eliminate CORS (Cross Origin Site Resource) issues, as well as eliminate the need to disseminate authentication details to the Data Source to the browser.
Direct access is still supported because in some cases it may be useful to access a Data Source directly depending on the use case and topology of Grafana, the user, and the Data Source.
......@@ -78,4 +78,4 @@ You can also create nested variables that use other variables in their definitio
## Query Reference
You can reference queries by the row “letter” that they’re on (similar to Microsoft Excel). If you add a second query to graph, you can reference the first query simply by typing in #A. This provides an easy and convenient way to build compounded queries.
\ No newline at end of file
You can reference queries by the row “letter” that they’re on (similar to Microsoft Excel). If you add a second query to graph, you can reference the first query simply by typing in #A. This provides an easy and convenient way to build compounded queries.
......@@ -28,12 +28,12 @@ Name | Description
Name | The data source name, important that this is the same as in Grafana v1.x if you plan to import old dashboards.
Default | Default data source means that it will be pre-selected for new panels.
Url | The http protocol, ip and port of you influxdb api (influxdb api port is by default 8086)
Access | Proxy = access via Grafana backend, Direct = access directory from browser.
Access | Proxy = access via Grafana backend, Direct = access directly from browser.
Database | Name of your influxdb database
User | Name of your database user
Password | Database user's password
> Proxy access means that the Grafana backend will proxy all requests from the browser, and send them on to the Data Source. This is useful because it can eliminate CORS (Cross Origin Site Resource) issues, as well as eliminate the need to disseminate authentication details to the Data Source to the brower.
> Proxy access means that the Grafana backend will proxy all requests from the browser, and send them on to the Data Source. This is useful because it can eliminate CORS (Cross Origin Site Resource) issues, as well as eliminate the need to disseminate authentication details to the Data Source to the browser.
> Direct access is still supported because in some cases it may be useful to access a Data Source directly depending on the use case and topology of Grafana, the user, and the Data Source.
......
......@@ -32,7 +32,7 @@ Open a graph in edit mode by click the title.
![](/img/v2/kairos_query_editor.png)
For details on KairosDB metric queries checkout the offical.
For details on KairosDB metric queries checkout the official.
- [Query Metrics - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/QueryMetrics.html).
## Templated queries
......@@ -49,4 +49,4 @@ For details of `metric names`, `tag names`, and `tag values`, please refer to th
- [List Metric Names - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListMetricNames.html)
- [List Tag Names - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListTagNames.html)
- [List Tag Values - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListTagValues.html)
- [Query Metrics - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/QueryMetrics.html).
\ No newline at end of file
- [Query Metrics - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/QueryMetrics.html).
......@@ -28,7 +28,7 @@ Basic Auth | Enable basic authentication to the Prometheus datasource.
User | Name of your Prometheus user
Password | Database user's password
> Proxy access means that the Grafana backend will proxy all requests from the browser, and send them on to the Data Source. This is useful because it can eliminate CORS (Cross Origin Site Resource) issues, as well as eliminate the need to disseminate authentication details to the Data Source to the brower.
> Proxy access means that the Grafana backend will proxy all requests from the browser, and send them on to the Data Source. This is useful because it can eliminate CORS (Cross Origin Site Resource) issues, as well as eliminate the need to disseminate authentication details to the Data Source to the browser.
> Direct access is still supported because in some cases it may be useful to access a Data Source directly depending on the use case and topology of Grafana, the user, and the Data Source.
......
......@@ -29,7 +29,7 @@ The image above shows you the top header for a Dashboard.
6. Settings: Manage Dashboard settings and features such as Templating and Annotations.
## Dashboards, Panels, Rows, the building blocks of Grafana...
Dashboards are at the core of what Grafana is all about. Dashboards are composed of individual Panels arranged on a number of Rows. Grafana ships with a variety of Panels. Gafana makes it easy to construct the right queries, and customize the display properities so that you can create the perfect Dashboard for your need. Each Panel can interact with data from any configured Grafana Data Source (currently InfluxDB, Graphite, OpenTSDB, and KairosDB). The [Core Concepts](/guides/basic_concepts) guide explores these key ideas in detail.
Dashboards are at the core of what Grafana is all about. Dashboards are composed of individual Panels arranged on a number of Rows. Grafana ships with a variety of Panels. Grafana makes it easy to construct the right queries, and customize the display properties so that you can create the perfect Dashboard for your need. Each Panel can interact with data from any configured Grafana Data Source (currently InfluxDB, Graphite, OpenTSDB, and KairosDB). The [Core Concepts](/guides/basic_concepts) guide explores these key ideas in detail.
## Adding & Editing Graphs and Panels
......
......@@ -32,7 +32,7 @@ no_toc: true
</div>
<div class="columns medium-6">
<h3><strong>Episode 4</strong> - Installation & Configuration on Ubuntu / Debian</h3>
Learn how to easily install the dependencies and packages to get Grafana 2.0 up and running on Ubuntu or Debian in just a few mintues.
Learn how to easily install the dependencies and packages to get Grafana 2.0 up and running on Ubuntu or Debian in just a few minutes.
<div class="video-container" style="margin-top:10px;">
<iframe height="215" src="https://www.youtube.com/embed/JY22EBOR9hQ?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
</div>
......
......@@ -112,7 +112,7 @@ for example make all series that contain the word **CPU** `red` and assigned to
![Define series color using regex rule](/img/v2/regex_color_override.png "Define series color using regex rule")
New series style override, negative-y transform and stack groups. Negative y tranform is
New series style override, negative-y transform and stack groups. Negative y transform is
very useful if you want to plot a series on the negative y scale without affecting the legend values like min or max or
the values shown in the hover tooltip.
......@@ -126,5 +126,5 @@ string values.
### Changelog
For a detailed list and link to github issues for everything included in the 2.1 release please
view the [CHANGELOG.md]("https://github.com/grafana/grafana/blob/master/CHANGELOG.md") file.
view the [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) file.
......@@ -266,7 +266,7 @@ automatically signed up.
### team_ids
Require an active team membership for at least one of the given teams on
GitHub. If the authenticated user isn't a member of at least one the
GitHub. If the authenticated user isn't a member of at least one of the
teams they will not be able to register or authenticate with your
Grafana instance. For example:
......@@ -274,7 +274,7 @@ Grafana instance. For example:
enabled = true
client_id = YOUR_GITHUB_APP_CLIENT_ID
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email
scopes = user:email,read:org
team_ids = 150,300
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
......
......@@ -9,7 +9,7 @@ page_keywords: grafana, installation, docker, container, guide
> **2.0.2 -> 2.1.0 Upgrade NOTICE!**
> The data and log paths were not correct in the previous image. The grafana database was placed by default in /usr/share/grafana/data instead of the correct path /var/lib/grafana. This means it was not in a dir that was marked as a volume. So if you remove the container it will remove the grafana database. So before updating make sure you copy the /usr/share/grafana/data path from inside the container to the host.
## Install from offical docker image
## Install from official docker image
Grafana has an official Docker container.
......
---
page_title: LDAP Integration
page_description: LDAP Integrtaion guide for Grafana.
page_description: LDAP Integration guide for Grafana.
page_keywords: grafana, ldap, configuration, documentation, integration
---
......@@ -85,12 +85,12 @@ bind_dn = "cn=%s,o=users,dc=grafana,dc=org"
```
In this case you skip providing a `bind_password` and instead provide a `bind_dn` value with a `%s` somewhere. This will be replaced with the username entered in on the Grafana login page.
The search filter and search bases settings are still needed to perform the LDAP search to retreive the other LDAP information (like LDAP groups and email).
The search filter and search bases settings are still needed to perform the LDAP search to retrieve the other LDAP information (like LDAP groups and email).
## LDAP to Grafana Org Role Sync
## Group Mappings
In `[[servers.group_mappings]]` you can map an LDAP group to a Grafana organization and role. These will be synced every time the user logs in, with LDAP being the authoratative source.
In `[[servers.group_mappings]]` you can map an LDAP group to a Grafana organization and role. These will be synced every time the user logs in, with LDAP being the authoritative source.
So, if you change a user's role in the Grafana Org. Users page, this change will be reset the next time the user logs in. If you change the LDAP groups of a user, the change will take effect the next time the user logs in.
### Priority between Multiple Mappings
......
---
page_title: Installing on RPM-based Linux
page_description: Grafana Installation guide for Centos, Fedora, Redhat.
page_description: Grafana Installation guide for Centos, Fedora, OpenSuse, Redhat.
page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
---
......@@ -10,7 +10,7 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
Description | Download
------------ | -------------
.RPM for Fedora / RHEL / CentOS Linux | [grafana-2.1.3-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.1.3-1.x86_64.rpm)
.RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.1.3-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.1.3-1.x86_64.rpm)
## Install from package file
......@@ -20,9 +20,15 @@ You can install Grafana using Yum directly.
Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat:
$ sudo yum install initscripts fontconfig
$ sudo rpm -Uvh grafana-2.1.3-1.x86_64.rpm
#### On OpenSuse:
$ sudo rpm -i --nodeps grafana-2.1.3-1.x86_64.rpm
## Install via YUM Repository
Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
......
......@@ -25,7 +25,7 @@ go get github.com/grafana/grafana
```
cd $GOPATH/src/github.com/grafana/grafana
go run build.go setup # (only needed once to install godep)
$GOPATH/bin/godep restore # (will pull down all golang lib dependecies in your current GOPATH)
$GOPATH/bin/godep restore # (will pull down all golang lib dependencies in your current GOPATH)
go run build.go build # (or 'go build .')
```
......
......@@ -24,7 +24,7 @@ modify Organization details and options.
<img src="/img/v2/admin_sidenav.png" class="right" style="margin-left: 15px">
As a Grafana Administrator, you have complete access to any Organization or User in that instance of Grafana.
When performing actions as a Grafana admin, the sidebar will change it's apperance as below to indicate you are performing global server administration.
When performing actions as a Grafana admin, the sidebar will change it's appearance as below to indicate you are performing global server administration.
From the Grafana Server Admin page, you can access the System Info page which summarizes all of the backend configuration settings of the Grafana server.
......
......@@ -8,7 +8,7 @@ page_keywords: grafana, dashlist, panel, documentation
## Overview
The dashboard list panel allows you to display dynamic links to other dashboards. The list can be configured to use starred dashbaords, a search query and/or dashboard tags.
The dashboard list panel allows you to display dynamic links to other dashboards. The list can be configured to use starred dashboards, a search query and/or dashboard tags.
<img class="no-shadow" src="/img/v2/dashboard_list_panels.png">
......
......@@ -30,7 +30,7 @@ The drilldown section allows adding dynamic links to the panel that can link to
or URLs
Each link has a title, a type and params. A link can be either a ``dashboard`` or ``absolute`` links.
If it is a dashboard links, the `dashboard` value must be the name of a dashbaord. If it's an
If it is a dashboard links, the `dashboard` value must be the name of a dashboard. If it's an
`absolute` link, the URL is the URL to link.
``params`` allows adding additional URL params to the links. The format is the ``name=value`` with
......@@ -127,7 +127,7 @@ If you have stack enabled you can select what the mouse hover feature should sho
### Rendering
- ``Flot`` - Render the graphs in the browser using Flot (default)
- ``Graphite PNG`` - Render the graph on the server using graphites render API.
- ``Graphite PNG`` - Render the graph on the server using graphite's render API.
### Tooltip
......
page_title: Kayboard Shortcuts
page_description: Kayboard Shortcuts for Grafana
page_title: Keyboard Shortcuts
page_description: Keyboard Shortcuts for Grafana
page_keywords: grafana, export, import, documentation
---
......
......@@ -11,7 +11,7 @@ Dashboards can be searched by the dashboard name, filtered by one (or many) tags
<img class="no-shadow" src="/img/v2/dashboard_search.png">
1. `Dashboard Picker`: The Dashboard Picker is your primary navigation tool to move between dashboards. It is present on all dashboards, and open the Dashboard Search. The dashboard picker also doubles as the title of the current dashboard.
2. `Search Bar`: The search bar allows you to enter any string and search both database and file based dashbaords in real-time.
2. `Search Bar`: The search bar allows you to enter any string and search both database and file based dashboards in real-time.
3. `Starred`: The starred link allows you to filter the list to display only starred dashboards.
4. `Tags`: The tags filter allows you to filter the list by dashboard tags.
......@@ -25,14 +25,14 @@ To search and load dashboards click the open folder icon in the header or use th
Dashboard search is:
- Real-time
- *Not* case senstitive
- *Not* case sensitive
- Functional across stored *and* file based dashboards.
## Filter by Tag(s)
Tags are a great way to organize your dashboards, especially as the number of dashbaords grow. Tags can be added and managed in the dashboard `Settings`.
Tags are a great way to organize your dashboards, especially as the number of dashboards grow. Tags can be added and managed in the dashboard `Settings`.
To filter the dashboard list by tag, click on any tag appearing in the right column. The list may be further filtered by cliking on additional tags:
To filter the dashboard list by tag, click on any tag appearing in the right column. The list may be further filtered by clicking on additional tags:
<img class="no-shadow" src="/img/v2/dashboard_search_tag_filtering.gif">
......@@ -40,7 +40,7 @@ Alternately, to see a list of all available tags, click the tags link in the sea
<img class="no-shadow" src="/img/v2/dashboard_search_tags_all_filtering.gif">
When using only a keybaord: `tab` to focus on the *tags* link, `▼` down arrow key to find a tag and select with the `Enter` key.
When using only a keyboard: `tab` to focus on the *tags* link, `▼` down arrow key to find a tag and select with the `Enter` key.
**Note**: When multiple tags are selected, Grafana will show dashboards that include **all**.
......@@ -51,4 +51,4 @@ Starring is a great way to organize and find commonly used dashboards. To show o
<img class="no-shadow" src="/img/v2/dashboard_search_starred_filtering.gif">
When using only a keybaord: `tab` to focus on the *stars* link, `▼` down arrow key to find a tag and select with the `Enter` key.
\ No newline at end of file
When using only a keyboard: `tab` to focus on the *stars* link, `▼` down arrow key to find a tag and select with the `Enter` key.
\ No newline at end of file
......@@ -5,7 +5,7 @@ page_keywords: grafana, sharing, guide, documentation
---
# Sharing features
Grafana provides a number of ways to share a dashboard or a specfic panel to other users within your
Grafana provides a number of ways to share a dashboard or a specific panel to other users within your
organization. It also provides ways to publish interactive snapshots that can be accessed by external partners.
## Share dashboard
......
......@@ -17,11 +17,11 @@ The singlestat panel has a normal query editor to allow you define your exact me
<img class="no-shadow" src="/img/v1/Singlestat-BaseSettings.png">
1. `Big Value`: Big Value refers to how we display the main stat for the Singlestat Panel. This is always a single value that is displayed in the Panel in between two strings, `Prefix` and `Suffix`. The single number is calculated by choosing a function (min,max,average,current,total) of your metric query. This functions reduces your query into a single numeric value.
2. `Font Size`: You can use this section
3. `Values`: The Value fields let you set the function (min, max, average, current, total) that your entire query is reduced into a single value with. You can also set the font size of theand font-size (as a %) of the metric query that the Panel is configured with. This reduces the entire query into a single summary value that is displayed.
2. `Font Size`: You can use this section to select the font size of the different texts in the Singlestat Panel, i.e. prefix, value and postfix.
3. `Values`: The Value fields let you set the function (min, max, average, current, total) that your entire query is reduced into a single value with. You can also set the font size of the Value field and font-size (as a %) of the metric query that the Panel is configured with. This reduces the entire query into a single summary value that is displayed.
4. `Postfixes`: The Postfix fields let you define a custom label and font-size (as a %) to appear *after* the value
5. `Units`: Units are appended to the the Singlestat within the panel, and will respect the color and threshold settings for the value.
6. `Decimals`: The Decimal field allows you to override the automatic decimal precision, and set it explicitely.
6. `Decimals`: The Decimal field allows you to override the automatic decimal precision, and set it explicitly.
### Coloring
......@@ -29,9 +29,9 @@ The coloring options of the Singlestat Panel config allow you to dynamically cha
<img class="no-shadow" src="/img/v1/Singlestat-Coloring.png">
1. `Background`: This checkbox applies the configured thresholds and colors to the entirity of the Singlestat Panel background.
1. `Background`: This checkbox applies the configured thresholds and colors to the entirety of the Singlestat Panel background.
2. `Value`: This checkbox applies the configured thresholds and colors to the summary stat.
3. `Thresholds`: Change the background and value colors dyanmically within the panel, depending on the Singlestat value. The threshold field accepts **3 comma-separated** values, corresponding to the three colors directly to the right.
3. `Thresholds`: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **3 comma-separated** values, corresponding to the three colors directly to the right.
4. `Colors`: Select a color and opacity
5. `Invert order`: This link toggles the threshold color order.</br>For example: Green, Orange, Red (<img class="no-shadow" src="/img/v1/gyr.png">) will become Red, Orange, Green (<img class="no-shadow" src="/img/v1/ryg.png">).
......
......@@ -63,7 +63,7 @@ Once configured, Multi-Select Tagging provides a convenient way to group and you
### Interval
Use the `Interval` type to create Template variables aroundr time ranges (eg. `1m`,`1h`, `1d`). There is also a special `auto` option that will change depending on the current time range, you can specify how many times the current time range should be divided to calculate the current `auto` range.
Use the `Interval` type to create Template variables around time ranges (eg. `1m`,`1h`, `1d`). There is also a special `auto` option that will change depending on the current time range, you can specify how many times the current time range should be divided to calculate the current `auto` range.
![](/img/v2/templated_variable_parameter.png)
......@@ -75,7 +75,7 @@ Use the `Custom` type to manually create Template variables around explicit valu
Template Variables can be very useful to dynamically change what you're visualizing on a given panel. Sometimes, you might want to create entire new Panels (or Rows) based on what Template Variables have been selected. This is now possible in Grafana 2.1.
Once you've got your Template variables (of any type) configured the way you'd like, check out the Repeating Panels and Repeating Row documentatione
Once you've got your Template variables (of any type) configured the way you'd like, check out the Repeating Panels and Repeating Row documentation
## Screencast - Templated Graphite Queries
......
......@@ -64,13 +64,13 @@ The `hubot-grafana` plugin requires a number of environment variables to be set
The hubot plugin will take advantage of the Grafana server side rendering feature that can
render any panel on the server using phantomjs. Grafana ships with a phantomjs binary (linux only).
To verify that this freature works try the `Direct link to rendered image` link in the panel share dialog.
To verify that this feature works try the `Direct link to rendered image` link in the panel share dialog.
If you do not get an image when opening this link verify that the required font packages are installed for phantomjs to work.
### Grafana API Key
<img src="/img/v2/orgdropdown_api_keys.png" style="width: 150px" class="right"></img>
You need to set the environment variable `HUBOT_GRAFANA_API_KEY` to a Grafana API Key.
You can add these from the API Keys page wich you find in the Organization dropdown.
You can add these from the API Keys page which you find in the Organization dropdown.
### Amazon S3
The `S3` options are optional but for the images to work properly in services like Slack and Hipchat they need
......@@ -118,7 +118,7 @@ Now you can add an alias like this:
## Summary
Grafana is going to ship with integrated Slack and Hiptchat features some day but you do
Grafana is going to ship with integrated Slack and Hipchat features some day but you do
not have to wait for that. Grafana 2 shipped with a very clever server side rendering feature
that can render any panel to a png using phantomjs. The hubot plugin for Grafana is something
you can install and use today!
......
......@@ -14,7 +14,7 @@ ExecStart=/usr/sbin/grafana-server \
--config=${CONF_FILE} \
--pidfile=${PID_FILE} \
cfg:default.paths.logs=${LOG_DIR} \
cfg:default.paths.data=${DATA_DIR} \
cfg:default.paths.data=${DATA_DIR}
LimitNOFILE=10000
TimeoutStopSec=20
UMask=0027
......
......@@ -14,7 +14,7 @@ ExecStart=/usr/sbin/grafana-server \
--config=${CONF_FILE} \
--pidfile=${PID_FILE} \
cfg:default.paths.logs=${LOG_DIR} \
cfg:default.paths.data=${DATA_DIR} \
cfg:default.paths.data=${DATA_DIR}
LimitNOFILE=10000
TimeoutStopSec=20
......
......@@ -40,7 +40,9 @@ func Register(r *macaron.Macaron) {
r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
r.Get("/admin/orgs", reqGrafanaAdmin, Index)
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
r.Get("/dashboard/*", reqSignedIn, Index)
r.Get("/dashboard-solo/*", reqSignedIn, Index)
// sign up
r.Get("/signup", Index)
......
......@@ -22,6 +22,7 @@ import (
"github.com/Unknwon/macaron"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
)
func Logger() macaron.Handler {
......@@ -36,7 +37,9 @@ func Logger() macaron.Handler {
switch rw.Status() {
case 200, 304:
content = fmt.Sprintf("%s", content)
return
if !setting.RouterLogging {
return
}
case 404:
content = fmt.Sprintf("%s", content)
case 500:
......
......@@ -91,7 +91,6 @@ func (scanner *PluginScanner) loadPluginJson(path string) error {
if !exists {
return errors.New("Did not find type property in plugin.json")
}
DataSources[datasourceType.(string)] = pluginJson
}
......
......@@ -100,12 +100,7 @@ function (angular, $, _, appLevelRequire) {
var $scope = this;
$scope.requireContext(deps, function () {
var deps = _.toArray(arguments);
// Check that this is a valid scope.
if($scope.$id) {
$scope.$apply(function () {
fn.apply($scope, deps);
});
}
fn.apply($scope, deps);
});
};
}]);
......
......@@ -328,9 +328,20 @@ function (angular, $, kbn, _, moment) {
}
}
if (oldVersion < 7 && old.nav && old.nav.length) {
this.timepicker = old.nav[0];
delete this.nav;
if (oldVersion < 7) {
if (old.nav && old.nav.length) {
this.timepicker = old.nav[0];
delete this.nav;
}
// ensure query refIds
panelUpgrades.push(function(panel) {
_.each(panel.targets, function(target) {
if (!target.refId) {
target.refId = this.getNextQueryLetter(panel);
}
}, this);
});
}
if (panelUpgrades.length === 0) {
......@@ -341,7 +352,7 @@ function (angular, $, kbn, _, moment) {
var row = this.rows[i];
for (j = 0; j < row.panels.length; j++) {
for (k = 0; k < panelUpgrades.length; k++) {
panelUpgrades[k](row.panels[j]);
panelUpgrades[k].call(this, row.panels[j]);
}
}
}
......
......@@ -75,7 +75,7 @@ function (angular, _, require, config) {
$scope.iframeHtml = '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
$scope.imageUrl = soloUrl.replace('/dashboard', '/render/dashboard');
$scope.imageUrl = soloUrl.replace('/dashboard-solo/', '/render/dashboard-solo/');
$scope.imageUrl += '&width=1000';
$scope.imageUrl += '&height=500';
};
......
......@@ -26,7 +26,7 @@ function (angular, $) {
$scope.initPanelScope = function() {
$scope.row = {
height: ($(window).height() - 10) + 'px',
height: $(window).height() + 'px',
};
$scope.test = "Hej";
......
......@@ -48,8 +48,22 @@ function (angular, kbn, _) {
return url;
}
url += (url.indexOf('?') !== -1 ? '&' : '?');
return url + paramsArray.join('&');
return this.appendToQueryString(url, paramsArray.join('&'));
};
this.appendToQueryString = function(url, stringToAppend) {
if (!_.isUndefined(stringToAppend) && stringToAppend !== null && stringToAppend !== '') {
var pos = url.indexOf('?');
if (pos !== -1) {
if (url.length - pos > 1) {
url += '&';
}
} else {
url += '?';
}
url += stringToAppend;
}
return url;
};
this.getAnchorInfo = function(link) {
......@@ -65,7 +79,6 @@ function (angular, kbn, _) {
info.target = link.targetBlank ? '_blank' : '_self';
info.href = templateSrv.replace(link.url || '', scopedVars);
info.title = templateSrv.replace(link.title || '', scopedVars);
info.href += '?';
}
else if (link.dashUri) {
info.href = 'dashboard/' + link.dashUri + '?';
......@@ -91,8 +104,9 @@ function (angular, kbn, _) {
}
info.href = this.addParamsToUrl(info.href, params);
if (link.params) {
info.href += "&" + templateSrv.replace(link.params, scopedVars);
info.href = this.appendToQueryString(info.href, templateSrv.replace(link.params, scopedVars));
}
return info;
......
......@@ -79,7 +79,7 @@
<div class="row-text pointer" ng-click="toggle_row(row)" ng-if="row.showTitle" ng-bind="row.title | interpolateTemplateVars:this">
</div>
<div ng-repeat="(name, panel) in row.panels track by panel.id" class="panel" ui-draggable="{{!dashboardViewState.fullscreen}}" drag="panel.id"
<div ng-repeat="(name, panel) in row.panels track by panel.id" class="panel" ui-draggable="!dashboardViewState.fullscreen" drag="panel.id"
ui-on-Drop="onDrop($data, row, panel)" drag-handle-class="drag-handle" panel-width>
<panel-loader type="panel.type" class="panel-margin"></panel-loader>
</div>
......
......@@ -159,7 +159,10 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
if (target.hide) {return;}
var esQuery = angular.toJson(this.queryBuilder.build(target));
esQuery = esQuery.replace("$lucene_query", target.query || '*');
var luceneQuery = angular.toJson(target.query || '*');
// remove inner quotes
luceneQuery = luceneQuery.substr(1, luceneQuery.length - 2);
esQuery = esQuery.replace("$lucene_query", luceneQuery);
payload += header + '\n' + esQuery + '\n';
sentTargets.push(target);
......
......@@ -20,7 +20,7 @@ function (_, moment) {
IndexPattern.prototype.getIndexForToday = function() {
if (this.interval) {
return moment().format(this.pattern);
return moment.utc().format(this.pattern);
} else {
return this.pattern;
}
......
<section class="grafana-metric-options">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-wrench"></i>
</li>
<li class="tight-form-item">
Group by time interval
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
spellcheck='false' placeholder="example: >10s">
</li>
<li class="tight-form-item">
<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
......
......@@ -15,15 +15,14 @@ function (angular) {
};
ElasticQueryBuilder.prototype.buildTermsAgg = function(aggDef, queryNode, target) {
var metricRef, metric, size, y;
var metricRef, metric, y;
queryNode.terms = { "field": aggDef.field };
if (!aggDef.settings) {
return queryNode;
}
size = parseInt(aggDef.settings.size, 10);
if (size > 0) { queryNode.terms.size = size; }
queryNode.terms.size = parseInt(aggDef.settings.size, 10);
if (aggDef.settings.orderBy !== void 0) {
queryNode.terms.order = {};
queryNode.terms.order[aggDef.settings.orderBy] = aggDef.settings.order;
......
......@@ -36,22 +36,21 @@ describe('ElasticDatasource', function() {
ctx.ds.testDatasource();
ctx.$rootScope.$apply();
var today = moment().format("YYYY.MM.DD");
var today = moment.utc().format("YYYY.MM.DD");
expect(requestOptions.url).to.be("http://es.com/asd-" + today + '/_stats');
});
});
describe('When issueing metric query with interval pattern', function() {
var requestOptions, parts, header;
beforeEach(function() {
ctx.ds = new ctx.service({
url: 'http://es.com',
index: '[asd-]YYYY.MM.DD',
jsonData: { interval: 'Daily' }
});
});
it('should translate index pattern to current day', function() {
var requestOptions;
ctx.backendSrv.datasourceRequest = function(options) {
requestOptions = options;
return ctx.$q.when({data: {responses: []}});
......@@ -62,13 +61,22 @@ describe('ElasticDatasource', function() {
from: moment([2015, 4, 30, 10]),
to: moment([2015, 5, 1, 10])
},
targets: [{ bucketAggs: [], metrics: [] }]
targets: [{ bucketAggs: [], metrics: [], query: 'escape\\:test' }]
});
ctx.$rootScope.$apply();
var parts = requestOptions.data.split('\n');
var header = angular.fromJson(parts[0]);
parts = requestOptions.data.split('\n');
header = angular.fromJson(parts[0]);
});
it('should translate index pattern to current day', function() {
expect(header.index).to.eql(['asd-2015.05.30', 'asd-2015.05.31', 'asd-2015.06.01']);
});
it('should json escape lucene query', function() {
var body = angular.fromJson(parts[1]);
expect(body.query.filtered.query.query_string.query).to.be('escape\\:test');
});
});
});
......@@ -11,7 +11,7 @@ describe('IndexPattern', function() {
describe('when getting index for today', function() {
it('should return correct index name', function() {
var pattern = new IndexPattern('[asd-]YYYY.MM.DD', 'Daily');
var expected = 'asd-' + moment().format('YYYY.MM.DD');
var expected = 'asd-' + moment.utc().format('YYYY.MM.DD');
expect(pattern.getIndexForToday()).to.be(expected);
});
......
......@@ -75,7 +75,7 @@ function (angular, _, $, config, dateMath) {
if (annotation.target) {
var target = templateSrv.replace(annotation.target);
var graphiteQuery = {
range: rangeUnparsed,
rangeRaw: rangeUnparsed,
targets: [{ target: target }],
format: 'json',
maxDataPoints: 100
......
......@@ -12,7 +12,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) {
var module = angular.module('grafana.services');
module.factory('InfluxDatasource', function($q, $http, templateSrv) {
module.factory('InfluxDatasource', function($q, backendSrv, templateSrv) {
function InfluxDatasource(datasource) {
this.type = 'influxdb';
......@@ -78,7 +78,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) {
};
InfluxDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
var timeFilter = getTimeFilter({ range: rangeUnparsed });
var timeFilter = getTimeFilter({ rangeRaw: rangeUnparsed });
var query = annotation.query.replace('$timeFilter', timeFilter);
query = templateSrv.replace(query);
......@@ -161,7 +161,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) {
options.headers.Authorization = self.basicAuth;
}
return $http(options).then(function(result) {
return backendSrv.datasourceRequest(options).then(function(result) {
return result.data;
}, function(err) {
if (err.status !== 0 || err.status >= 300) {
......
......@@ -9,7 +9,7 @@
<div class="editor-row">
<div class="section">
<h5>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names bellow. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
<h5>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
<div class="editor-option">
<label class="small">Title</label>
<input type="text" class="input-small" ng-model='currentAnnotation.titleColumn' placeholder=""></input>
......
......@@ -2,11 +2,11 @@ define([
'angular',
'lodash',
'app/core/utils/datemath',
'./influxSeries',
'./queryBuilder',
'./influx_series',
'./query_builder',
'./directives',
'./queryCtrl',
'./funcEditor',
'./query_ctrl',
'./func_editor',
],
function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) {
'use strict';
......@@ -266,11 +266,11 @@ function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) {
}
function getInfluxTime(date, roundUp) {
if (_.isString(date) && date.indexOf('/') === -1) {
if (_.isString(date)) {
if (date === 'now') {
return 'now()';
}
if (date.indexOf('now-') >= 0) {
if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) {
return date.replace('now', 'now()');
}
date = dateMath.parse(date, roundUp);
......
......@@ -9,7 +9,7 @@
<div class="editor-row">
<div class="section">
<h5>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names bellow. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
<h5>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
<div class="editor-option">
<label class="small">Title</label>
<input type="text" class="input-small" ng-model='currentAnnotation.titleColumn' placeholder=""></input>
......
define([
'./helpers',
'app/plugins/datasource/influxdb_08/datasource',
'app/services/backendSrv',
'app/services/alertSrv'
], function(helpers) {
'use strict';
describe('InfluxDatasource', function() {
var ctx = new helpers.ServiceTestContext();
beforeEach(module('grafana.services'));
beforeEach(ctx.providePhase(['templateSrv']));
beforeEach(ctx.createService('InfluxDatasource_08'));
beforeEach(function() {
ctx.ds = new ctx.service({ url: '', user: 'test', password: 'mupp' });
});
///<amd-dependency path="app/plugins/datasource/influxdb_08/datasource"/>
///<amd-dependency path="app/services/backendSrv"/>
///<amd-dependency path="app/services/alertSrv"/>
///<amd-dependency path="test/specs/helpers" name="helpers" />
describe('When querying influxdb with one target using query editor target spec', function() {
var results;
var urlExpected = "/series?p=mupp&q=select+mean(value)+from+%22test%22"+
"+where+time+%3E+now()-1h+group+by+time(1s)+order+asc";
var query = {
rangeRaw: { from: 'now-1h', to: 'now' },
targets: [{ series: 'test', column: 'value', function: 'mean' }],
interval: '1s'
};
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
var response = [{
columns: ["time", "sequence_nr", "value"],
name: 'test',
points: [[10, 1, 1]],
}];
declare var helpers: any;
beforeEach(function() {
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ctx.ds.query(query).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
describe('InfluxDatasource', function() {
var ctx = new helpers.ServiceTestContext();
beforeEach(angularMocks.module('grafana.services'));
beforeEach(ctx.providePhase(['templateSrv']));
beforeEach(ctx.createService('InfluxDatasource_08'));
beforeEach(function() {
ctx.ds = new ctx.service({ url: '', user: 'test', password: 'mupp' });
});
it('should generate the correct query', function() {
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
describe('When querying influxdb with one target using query editor target spec', function() {
var results;
var urlExpected = "/series?p=mupp&q=select+mean(value)+from+%22test%22+where+time+%3E+now()-1h+group+by+time(1s)+order+asc";
var query = {
rangeRaw: { from: 'now-1h', to: 'now' },
targets: [{ series: 'test', column: 'value', function: 'mean' }],
interval: '1s'
};
var response = [{
columns: ["time", "sequence_nr", "value"],
name: 'test',
points: [[10, 1, 1]],
}];
it('should return series list', function() {
expect(results.data.length).to.be(1);
expect(results.data[0].target).to.be('test.value');
});
beforeEach(function() {
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ctx.ds.query(query).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
it('should generate the correct query', function() {
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
describe('When querying influxdb with one raw query', function() {
var results;
var urlExpected = "/series?p=mupp&q=select+value+from+series"+
"+where+time+%3E+now()-1h";
var query = {
rangeRaw: { from: 'now-1h', to: 'now' },
targets: [{ query: "select value from series where $timeFilter", rawQuery: true }]
};
it('should return series list', function() {
expect(results.data.length).to.be(1);
expect(results.data[0].target).to.be('test.value');
});
var response = [];
});
beforeEach(function() {
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ctx.ds.query(query).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
describe('When querying influxdb with one raw query', function() {
var results;
var urlExpected = "/series?p=mupp&q=select+value+from+series+where+time+%3E+now()-1h";
var query = {
rangeRaw: { from: 'now-1h', to: 'now' },
targets: [{ query: "select value from series where $timeFilter", rawQuery: true }]
};
it('should generate the correct query', function() {
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
var response = [];
beforeEach(function() {
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ctx.ds.query(query).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
describe('When issuing annotation query', function() {
var results;
var urlExpected = "/series?p=mupp&q=select+title+from+events.backend_01"+
"+where+time+%3E+now()-1h";
it('should generate the correct query', function() {
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
var range = { from: 'now-1h', to: 'now' };
var annotation = { query: 'select title from events.$server where $timeFilter' };
var response = [];
});
beforeEach(function() {
ctx.templateSrv.replace = function(str) {
return str.replace('$server', 'backend_01');
};
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ctx.ds.annotationQuery(annotation, range).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
describe('When issuing annotation query', function() {
var results;
var urlExpected = "/series?p=mupp&q=select+title+from+events.backend_01+where+time+%3E+now()-1h";
it('should generate the correct query', function() {
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
var range = { from: 'now-1h', to: 'now' };
var annotation = { query: 'select title from events.$server where $timeFilter' };
var response = [];
beforeEach(function() {
ctx.templateSrv.replace = function(str) {
return str.replace('$server', 'backend_01');
};
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ctx.ds.annotationQuery(annotation, range).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
it('should generate the correct query', function() {
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
});
});
});
define([
'app/plugins/datasource/influxdb_08/queryBuilder'
], function(InfluxQueryBuilder) {
'use strict';
///<amd-dependency path="app/plugins/datasource/influxdb_08/query_builder" name="InfluxQueryBuilder"/>
describe('InfluxQueryBuilder', function() {
import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
describe('series with conditon and group by', function() {
var builder = new InfluxQueryBuilder({
series: 'google.test',
column: 'value',
function: 'mean',
condition: "code=1",
groupby_field: 'code'
});
declare var InfluxQueryBuilder: any;
var query = builder.build();
describe('InfluxQueryBuilder', function() {
it('should generate correct query', function() {
expect(query).to.be('select code, mean(value) from "google.test" where $timeFilter and code=1 ' +
'group by time($interval), code order asc');
});
describe('series with conditon and group by', function() {
var builder = new InfluxQueryBuilder({
series: 'google.test',
column: 'value',
function: 'mean',
condition: "code=1",
groupby_field: 'code'
});
it('should expose groupByFiled', function() {
expect(builder.groupByField).to.be('code');
});
var query = builder.build();
it('should generate correct query', function() {
expect(query).to.be('select code, mean(value) from "google.test" where $timeFilter and code=1 ' +
'group by time($interval), code order asc');
});
describe('series with fill and minimum group by time', function() {
var builder = new InfluxQueryBuilder({
series: 'google.test',
column: 'value',
function: 'mean',
fill: '0',
});
var query = builder.build();
it('should expose groupByFiled', function() {
expect(builder.groupByField).to.be('code');
});
it('should generate correct query', function() {
expect(query).to.be('select mean(value) from "google.test" where $timeFilter ' +
'group by time($interval) fill(0) order asc');
});
});
describe('series with fill and minimum group by time', function() {
var builder = new InfluxQueryBuilder({
series: 'google.test',
column: 'value',
function: 'mean',
fill: '0',
});
describe('merge function detection', function() {
it('should not quote wrap regex merged series', function() {
var builder = new InfluxQueryBuilder({
series: 'merge(/^google.test/)',
column: 'value',
function: 'mean'
});
var query = builder.build();
it('should generate correct query', function() {
expect(query).to.be('select mean(value) from "google.test" where $timeFilter ' +
'group by time($interval) fill(0) order asc');
});
var query = builder.build();
});
expect(query).to.be('select mean(value) from merge(/^google.test/) where $timeFilter ' +
'group by time($interval) order asc');
describe('merge function detection', function() {
it('should not quote wrap regex merged series', function() {
var builder = new InfluxQueryBuilder({
series: 'merge(/^google.test/)',
column: 'value',
function: 'mean'
});
it('should quote wrap series names that start with "merge"', function() {
var builder = new InfluxQueryBuilder({
series: 'merge.google.test',
column: 'value',
function: 'mean'
});
var query = builder.build();
var query = builder.build();
expect(query).to.be('select mean(value) from merge(/^google.test/) where $timeFilter ' +
'group by time($interval) order asc');
});
expect(query).to.be('select mean(value) from "merge.google.test" where $timeFilter ' +
'group by time($interval) order asc');
it('should quote wrap series names that start with "merge"', function() {
var builder = new InfluxQueryBuilder({
series: 'merge.google.test',
column: 'value',
function: 'mean'
});
var query = builder.build();
expect(query).to.be('select mean(value) from "merge.google.test" where $timeFilter ' +
'group by time($interval) order asc');
});
});
});
......@@ -152,7 +152,10 @@ define([
rows: [
{
panels: [
{type: 'graphite', legend: true, aliasYAxis: { test: 2 }, grid: { min: 1, max: 10 }}
{
type: 'graphite', legend: true, aliasYAxis: { test: 2 }, grid: { min: 1, max: 10 },
targets: [{refId: 'A'}, {}],
}
]
}
]
......@@ -178,6 +181,10 @@ define([
expect(graph.type).to.be('graph');
});
it('queries without refId should get it', function() {
expect(graph.targets[1].refId).to.be('B');
});
it('update legend setting', function() {
expect(graph.legend.show).to.be(true);
});
......
......@@ -38,9 +38,10 @@ define([
};
this.createControllerPhase = function(controllerName) {
return inject(function($controller, $rootScope, $q, $location) {
return inject(function($controller, $rootScope, $q, $location, $browser) {
self.scope = $rootScope.$new();
self.$location = $location;
self.$browser = $browser;
self.scope.contextSrv = {};
self.scope.panel = {};
self.scope.row = { panels:[] };
......
......@@ -4,6 +4,25 @@ define([
], function(kbn, dateMath) {
'use strict';
describe('unit format menu', function() {
var menu = kbn.getUnitFormats();
menu.map(function(submenu) {
describe('submenu ' + submenu.text, function() {
it('should have a title', function() { expect(submenu.text).to.be.a('string'); });
it('should have a submenu', function() { expect(submenu.submenu).to.be.an('array'); });
submenu.submenu.map(function(entry) {
describe('entry ' + entry.text, function() {
it('should have a title', function() { expect(entry.text).to.be.a('string'); });
it('should have a format', function() { expect(entry.value).to.be.a('string'); });
it('should have a valid format', function() {
expect(kbn.valueFormats[entry.value]).to.be.a('function');
});
});
});
});
});
});
function describeValueFormat(desc, value, tickSize, tickDecimals, result) {
describe('value format: ' + desc, function() {
......@@ -26,6 +45,18 @@ define([
describeValueFormat('none', 2.75e-10, 0, 10, '3e-10');
describeValueFormat('none', 0, 0, 2, '0');
describeValueFormat('dB', 10, 1000, 2, '10.00 dB');
describeValueFormat('percent', 0, 0, 0, '0%');
describeValueFormat('percent', 53, 0, 1, '53.0%');
describeValueFormat('percentunit', 0.0, 0, 0, '0%');
describeValueFormat('percentunit', 0.278, 0, 1, '27.8%');
describeValueFormat('percentunit', 1.0, 0, 0, '100%');
describeValueFormat('currencyUSD', 7.42, 10000, 2, '$7.42');
describeValueFormat('currencyUSD', 1532.82, 1000, 1, '$1.53K');
describeValueFormat('currencyUSD', 18520408.7, 10000000, 0, '$19M');
describeValueFormat('bytes', -1.57e+308, -1.57e+308, 2, 'NA');
describeValueFormat('ns', 25, 1, 0, '25 ns');
......
define([
'lodash',
'app/features/panellinks/linkSrv'
], function(_) {
'use strict';
describe('linkSrv', function() {
var _linkSrv;
beforeEach(module('grafana.services'));
beforeEach(inject(function(linkSrv) {
_linkSrv = linkSrv;
}));
describe('when appending query strings', function() {
it('add ? to URL if not present', function() {
var url = _linkSrv.appendToQueryString('http://example.com', 'foo=bar');
expect(url).to.be('http://example.com?foo=bar');
});
it('do not add & to URL if ? is present but query string is empty', function() {
var url = _linkSrv.appendToQueryString('http://example.com?', 'foo=bar');
expect(url).to.be('http://example.com?foo=bar');
});
it('add & to URL if query string is present', function() {
var url = _linkSrv.appendToQueryString('http://example.com?foo=bar', 'hello=world');
expect(url).to.be('http://example.com?foo=bar&hello=world');
});
it('do not change the URL if there is nothing to append', function() {
_.each(['', undefined, null], function(toAppend) {
var url1 = _linkSrv.appendToQueryString('http://example.com', toAppend);
expect(url1).to.be('http://example.com');
var url2 = _linkSrv.appendToQueryString('http://example.com?', toAppend);
expect(url2).to.be('http://example.com?');
var url3 = _linkSrv.appendToQueryString('http://example.com?foo=bar', toAppend);
expect(url3).to.be('http://example.com?foo=bar');
});
});
});
});
});
......@@ -31,6 +31,17 @@ define([
expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1000&to=2000&panelId=22&fullscreen');
});
it('should generate render url', function() {
ctx.$location.$$absUrl = 'http://dashboards.grafana.com/dashboard/db/my-dash';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/dashboard-solo/db/my-dash';
var params = '?from=1000&to=2000&panelId=22&fullscreen&width=1000&height=500';
expect(ctx.scope.imageUrl).to.be(base + params);
});
it('should remove panel id when no panel in scope', function() {
ctx.$location.path('/test');
ctx.scope.options.forCurrent = true;
......
......@@ -23,7 +23,6 @@ module.exports = function(grunt) {
'filerev',
'remapFilerev',
'usemin',
'clean:temp',
'uglify:genDir'
]);
......
......@@ -24,7 +24,6 @@ module.exports = function(config) {
],
dest: '<%= genDir %>/css/grafana.light.min.css'
},
js: {
src: [
'<%= tempDir %>/vendor/requirejs/require.js',
......
......@@ -6,7 +6,7 @@ module.exports = function(config) {
dest: '<%= genDir %>/app/components/partials.js',
options: {
bootstrap: function(module, script) {
return "define('components/partials', ['angular'], function(angular) { \n" +
return "define('app/components/partials', ['angular'], function(angular) { \n" +
"angular.module('grafana').run(['$templateCache', function($templateCache) { \n" +
script +
'\n}]);' +
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment