diff --git a/assets/styles/layouts/article/_code.scss b/assets/styles/layouts/article/_code.scss
index 104e5fe31..a7038a799 100644
--- a/assets/styles/layouts/article/_code.scss
+++ b/assets/styles/layouts/article/_code.scss
@@ -56,6 +56,7 @@ pre {
padding: 0;
font-size: .95rem;
line-height: 1.5rem;
+ white-space: pre-wrap;
}
}
diff --git a/content/v2.0/query-data/guides/histograms.md b/content/v2.0/query-data/guides/histograms.md
index 738645ce3..64e4b0659 100644
--- a/content/v2.0/query-data/guides/histograms.md
+++ b/content/v2.0/query-data/guides/histograms.md
@@ -6,7 +6,7 @@ menu:
v2_0:
name: Create histograms
parent: How-to guides
-weight: 207
+weight: 208
---
diff --git a/content/v2.0/query-data/guides/sql.md b/content/v2.0/query-data/guides/sql.md
new file mode 100644
index 000000000..19d49c54d
--- /dev/null
+++ b/content/v2.0/query-data/guides/sql.md
@@ -0,0 +1,217 @@
+---
+title: Query SQL data sources
+seotitle: Query SQL data sources with InfluxDB
+description: >
+ The Flux `sql` package provides functions for working with SQL data sources.
+ Use `sql.from()` to query SQL databases like PostgreSQL and MySQL
+v2.0/tags: [query, flux, sql]
+menu:
+ v2_0:
+ parent: How-to guides
+weight: 207
+---
+
+The [Flux](/v2.0/reference/flux) `sql` package provides functions for working with SQL data sources.
+[`sql.from()`](/v2.0/reference/flux/functions/sql/from/) lets you query SQL data sources
+like [PostgreSQL](https://www.postgresql.org/) and [MySQL](https://www.mysql.com/)
+and use the results with InfluxDB dashboards, tasks, and other operations.
+
+- [Query a SQL data source](#query-a-sql-data-source)
+- [Join SQL data with data in InfluxDB](#join-sql-data-with-data-in-influxdb)
+- [Use SQL results to populate dashboard variables](#use-sql-results-to-populate-dashboard-variables)
+- [Sample sensor data](#sample-sensor-data)
+
+## Query a SQL data source
+To query a SQL data source:
+
+1. Import the `sql` package in your Flux query
+2. Use the `sql.from()` function to specify the driver, data source name (DSN),
+ and query used to query data from your SQL data source:
+
+{{< code-tabs-wrapper >}}
+{{% code-tabs %}}
+[PostgreSQL](#)
+[MySQL](#)
+{{% /code-tabs %}}
+
+{{% code-tab-content %}}
+```js
+import "sql"
+
+sql.from(
+ driverName: "postgres",
+ dataSourceName: "postgresql://user:password@localhost",
+ query: "SELECT * FROM example_table"
+)
+```
+{{% /code-tab-content %}}
+
+{{% code-tab-content %}}
+```js
+import "sql"
+
+sql.from(
+ driverName: "mysql",
+ dataSourceName: "user:password@tcp(localhost:3306)/db",
+ query: "SELECT * FROM example_table"
+)
+```
+{{% /code-tab-content %}}
+{{< /code-tabs-wrapper >}}
+
+_See the [`sql.from()` documentation](/v2.0/reference/flux/functions/sql/from/) for
+information about required function parameters._
+
+## Join SQL data with data in InfluxDB
+One of the primary benefits of querying SQL data sources from InfluxDB
+is the ability to enrich query results with data stored outside of InfluxDB.
+
+Using the [air sensor sample data](#sample-sensor-data) below, the following query
+joins air sensor metrics stored in InfluxDB with sensor information stored in PostgreSQL.
+The joined data lets you query and filter results based on sensor information
+that isn't stored in InfluxDB.
+
+```js
+// Import the "sql" package
+import "sql"
+
+// Query data from PostgreSQL
+sensorInfo = sql.from(
+ driverName: "postgres",
+ dataSourceName: "postgresql://localhost?sslmode=disable",
+ query: "SELECT * FROM sensors"
+)
+
+// Query data from InfluxDB
+sensorMetrics = from(bucket: "example-bucket")
+ |> range(start: -1h)
+ |> filter(fn: (r) => r._measurement == "airSensors")
+
+// Join InfluxDB query results with PostgreSQL query results
+join(tables: {metric: sensorMetrics, info: sensorInfo}, on: ["sensor_id"])
+```
+
+## Use SQL results to populate dashboard variables
+Use `sql.from()` to [create dashboard variables](/v2.0/visualize-data/variables/create-variable/)
+from SQL query results.
+The following example uses the [air sensor sample data](#sample-data) below to
+create a variable that lets you select the location of a sensor.
+
+```js
+import "sql"
+
+sql.from(
+ driverName: "postgres",
+ dataSourceName: "postgresql://localhost?sslmode=disable",
+ query: "SELECT * FROM sensors"
+ )
+ |> rename(columns: {location: "_value"})
+ |> keep(columns: ["_value"])
+```
+
+Use the variable to manipulate queries in your dashboards.
+
+{{< img-hd src="/img/2-0-sql-dashboard-variable.png" alt="Dashboard variable from SQL query results" />}}
+
+---
+
+## Sample sensor data
+The [sample data generator](#download-and-run-the-sample-data-generator) and
+[sample sensor information](#import-the-sample-sensor-information) simulate a
+group of sensors that measure temperature, humidity, and carbon monoxide
+in rooms throughout a building.
+Each collected data point is stored in InfluxDB with a `sensor_id` tag that identifies
+the specific sensor it came from.
+Sample sensor information is stored in PostgreSQL.
+
+**Sample data includes:**
+
+- Simulated data collected from each sensor and stored in the `airSensors` measurement in **InfluxDB**:
+ - temperature
+ - humidity
+ - co
+
+- Information about each sensor stored in the `sensors` table in **PostgreSQL**:
+ - sensor_id
+ - location
+ - model_number
+ - last_inspected
+
+### Import and generate sample sensor data
+
+#### Download and run the sample data generator
+`air-sensor-data.rb` is a script that generates air sensor data and stores the data in InfluxDB.
+To use `air-sensor-data.rb`:
+
+1. [Create a bucket](/v2.0/organizations/buckets/create-bucket/) to store the data.
+2. Download the sample data generator. _This tool requires [Ruby](https://www.ruby-lang.org/en/)._
+
+ Download Air Sensor Generator
+
+3. Give `air-sensor-data.rb` executable permissions:
+
+ ```
+ chmod +x air-sensor-data.rb
+ ```
+
+4. Start the generator. Specify your organization, bucket, and authorization token.
+ _For information about retrieving your token, see [View tokens](/v2.0/security/tokens/view-tokens/)._
+
+ ```
+ ./air-sensor-data.rb -o your-org -b your-bucket -t YOURAUTHTOKEN
+ ```
+
+ The generator begins to write data to InfluxDB and will continue until stopped.
+ Use `ctrl-c` to stop the generator.
+
+ _**Note:** Use the `--help` flag to view other configuration options._
+
+
+5. [Query your target bucket](v2.0/query-data/execute-queries/) to ensure the
+ generated data is writing successfully.
+ The generator doesn't catch errors from write requests, so it will continue running
+ even if data is not writing to InfluxDB successfully.
+
+ ```
+ from(bucket: "example-bucket")
+ |> range(start: -1m)
+ |> filter(fn: (r) => r._measurement == "airSensors")
+ ```
+
+#### Import the sample sensor information
+1. [Download and install PostgreSQL](https://www.postgresql.org/download/).
+2. Download the sample sensor information CSV.
+
+ Download Sample Data
+
+3. Use a PostgreSQL client (`psql` or a GUI) to create the `sensors` table:
+
+ ```
+ CREATE TABLE sensors (
+ sensor_id character varying(50),
+ location character varying(50),
+ model_number character varying(50),
+ last_inspected date
+ );
+ ```
+
+4. Import the downloaded CSV sample data.
+ _Update the `FROM` file path to the path of the downloaded CSV sample data._
+
+ ```
+ COPY sensors(sensor_id,location,model_number,last_inspected)
+ FROM '/path/to/sample-sensor-info.csv' DELIMITER ',' CSV HEADER;
+ ```
+
+5. Query the table to ensure the data was imported correctly:
+
+ ```
+ SELECT * FROM sensors;
+ ```
+
+#### Import the sample data dashboard
+Download and import the Air Sensors dashboard to visualize the generated data:
+
+Download Air Sensors dashboard
+
+_For information about importing a dashboard, see [Create a dashboard](/v2.0/visualize-data/dashboards/create-dashboard/#create-a-new-dashboard)._
diff --git a/content/v2.0/reference/flux/functions/sql/from.md b/content/v2.0/reference/flux/functions/sql/from.md
index e55e074d0..fedbe981f 100644
--- a/content/v2.0/reference/flux/functions/sql/from.md
+++ b/content/v2.0/reference/flux/functions/sql/from.md
@@ -60,11 +60,11 @@ _**Data type:** String_
```js
import "sql"
- sql.from(
- driverName: "mysql",
- dataSourceName: "user:password@tcp(localhost:3306)/db",
- query:"SELECT * FROM ExampleTable"
- )
+sql.from(
+ driverName: "mysql",
+ dataSourceName: "user:password@tcp(localhost:3306)/db",
+ query:"SELECT * FROM ExampleTable"
+)
```
### Query a Postgres database
diff --git a/static/downloads/air-sensor-data.rb b/static/downloads/air-sensor-data.rb
new file mode 100755
index 000000000..70fd9136f
--- /dev/null
+++ b/static/downloads/air-sensor-data.rb
@@ -0,0 +1,131 @@
+#! /usr/bin/ruby
+require "optparse"
+require "net/http"
+require"openssl"
+require "uri"
+
+# CLI Options
+options = {
+ protocol: "http",
+ host: "localhost",
+ port: "9999",
+ interval: 5
+}
+
+OptionParser.new do |opt|
+ opt.banner = "Usage: air-sensor-data [OPTIONS]"
+
+ opt.on("-o","--org ORG","The organization to write data to. REQUIRED.") do |org|
+ options[:org] = org
+ end
+
+ opt.on("-b","--bucket BUCKET","The bucket to write data to. REQUIRED.") do |bucket|
+ options[:bucket] = bucket
+ end
+
+ opt.on("-t","--token TOKEN","Your InfluxDB authentication token. REQUIRED.") do |token|
+ options[:token] = token
+ end
+
+ opt.on("-h","--host host","Your InfluxDB host. Defaults to 'localhost'") do |host|
+ options[:host] = host
+ end
+
+ opt.on("-p","--port port","Your InfluxDB port. Defaults to '9999'") do |port|
+ options[:port] = port
+ end
+
+ opt.on("-i","--interval interval",Integer,"The interval (in seconds) at which to write data. Defaults to '5'.") do |interval|
+ options[:interval] = interval
+ end
+
+ opt.on("-s","--tls", "Sends data over HTTPS.") do |tls|
+ options[:protocol] = "https"
+ end
+
+ opt.on("--help","Displays this help information.") do
+ puts opt
+ exit
+ end
+end.parse!
+
+unless options[:org] && options[:bucket] && options[:token]
+ $stderr.puts "\nError: you must specify an organization, bucket, and token.\nUse the '--help' flag for more info.\n\n"
+ exit 1
+end
+
+# Global Variables
+$protocol = options[:protocol]
+$host = options[:host]
+$port = options[:port]
+$org = options[:org]
+$bucket = options[:bucket]
+$token = options[:token]
+$interval = options[:interval]
+
+# Seed Data
+seeds = [
+ {id: 100, t: 71.2, h: 35.1, c: 0.5, t_inc: -0.05..0.05, h_inc: -0.05..0.05, c_inc: -0.02..0.02},
+ {id: 101, t: 71.8, h: 34.9, c: 0.5, t_inc: -0.05..0.05, h_inc: -0.05..0.05, c_inc: -0.02..0.02},
+ {id: 102, t: 72.0, h: 34.9, c: 0.5, t_inc: -0.05..0.05, h_inc: -0.05..0.05, c_inc: -0.02..0.02},
+ {id: 103, t: 71.3, h: 35.2, c: 0.4, t_inc: -0.05..0.05, h_inc: -0.05..0.05, c_inc: -0.02..0.02},
+ {id: 200, t: 73.6, h: 35.8, c: 0.5, t_inc: -0.05..0.05, h_inc: -0.05..0.05, c_inc: -0.02..0.05},
+ {id: 201, t: 74.0, h: 35.2, c: 0.5, t_inc: -0.05..0.05, h_inc: -0.05..0.05, c_inc: -0.02..0.02},
+ {id: 202, t: 75.3, h: 35.7, c: 0.5, t_inc: -0.05..0.05, h_inc: -0.05..0.05, c_inc: -0.02..0.02},
+ {id: 203, t: 74.8, h: 35.9, c: 0.4, t_inc: -0.05..0.05, h_inc: -0.05..0.05, c_inc: -0.02..0.02},
+]
+
+def increment_data(data={})
+ data[:t] += rand(data[:t_inc])
+ data[:h] += rand(data[:h_inc])
+ data[:c] += rand(data[:c_inc])
+
+ # Avoid negative humidity and co
+ if data[:h] < 0
+ data[:h] = 0
+ end
+ if data[:c] < 0
+ data[:c] = 0
+ end
+
+ return data
+end
+
+def line_protocol_batch(point_data=[])
+ batch = []
+ point_data.each do |v|
+ batch << "airSensors,sensor_id=TLM0#{v[:id]} temperature=#{v[:t]},humidity=#{v[:h]},co=#{v[:c]}"
+ end
+ return batch.join("\n")
+end
+
+def send_data(batch)
+ uri = URI.parse("#{$protocol}://#{$host}:#{$port}/api/v2/write?org=#{URI::encode($org)}&bucket=#{URI::encode($bucket)}")
+ request = Net::HTTP::Post.new(uri)
+ request["Authorization"] = "Token #{$token}"
+ request.body = "#{batch}"
+
+ req_options = {
+ use_ssl: uri.scheme == "https",
+ ssl_version: :SSLv23
+ }
+
+ response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
+ http.request(request)
+ end
+end
+
+def send_batches(dataset=[], interval=$interval)
+ dataset.map! { |seed| increment_data(seed) }
+ send_data(line_protocol_batch(dataset))
+ sleep interval
+ send_batches(dataset,interval)
+end
+
+begin
+ puts "Sending data to #{$protocol}://#{$host}:#{$port}..."
+ puts " (ctrl-c to kill the data stream)"
+ send_batches(seeds)
+rescue Interrupt
+ puts "\nStopping data stream..."
+end
diff --git a/static/downloads/air_sensors_dashboard.json b/static/downloads/air_sensors_dashboard.json
new file mode 100644
index 000000000..e74b76558
--- /dev/null
+++ b/static/downloads/air_sensors_dashboard.json
@@ -0,0 +1,1029 @@
+{
+ "meta": {
+ "version": "1",
+ "type": "dashboard",
+ "name": "Air Sensors-Template",
+ "description": "template created from dashboard: Air Sensors"
+ },
+ "content": {
+ "data": {
+ "type": "dashboard",
+ "attributes": {
+ "name": "Air Sensors",
+ "description": ""
+ },
+ "relationships": {
+ "label": {
+ "data": []
+ },
+ "cell": {
+ "data": [
+ {
+ "type": "cell",
+ "id": "0423614244f70000"
+ },
+ {
+ "type": "cell",
+ "id": "042361d158770000"
+ },
+ {
+ "type": "cell",
+ "id": "04236a6e6cf70000"
+ },
+ {
+ "type": "cell",
+ "id": "042372352a770000"
+ },
+ {
+ "type": "cell",
+ "id": "04237241bf770000"
+ },
+ {
+ "type": "cell",
+ "id": "042372d4bcf70000"
+ },
+ {
+ "type": "cell",
+ "id": "0423737a1ff70000"
+ },
+ {
+ "type": "cell",
+ "id": "042373fce5370000"
+ },
+ {
+ "type": "cell",
+ "id": "0423753affb70000"
+ },
+ {
+ "type": "cell",
+ "id": "04237b90e1370000"
+ }
+ ]
+ },
+ "variable": {
+ "data": [
+ {
+ "type": "variable",
+ "id": "03bdc0c5a4ff0000"
+ },
+ {
+ "type": "variable",
+ "id": "04237161ad770000"
+ }
+ ]
+ }
+ }
+ },
+ "included": [
+ {
+ "id": "0423614244f70000",
+ "type": "cell",
+ "attributes": {
+ "x": 4,
+ "y": 1,
+ "w": 4,
+ "h": 2
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "0423614244f70000"
+ }
+ }
+ }
+ },
+ {
+ "id": "042361d158770000",
+ "type": "cell",
+ "attributes": {
+ "x": 8,
+ "y": 1,
+ "w": 4,
+ "h": 2
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "042361d158770000"
+ }
+ }
+ }
+ },
+ {
+ "id": "04236a6e6cf70000",
+ "type": "cell",
+ "attributes": {
+ "x": 0,
+ "y": 7,
+ "w": 12,
+ "h": 4
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "04236a6e6cf70000"
+ }
+ }
+ }
+ },
+ {
+ "id": "042372352a770000",
+ "type": "cell",
+ "attributes": {
+ "x": 0,
+ "y": 4,
+ "w": 3,
+ "h": 3
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "042372352a770000"
+ }
+ }
+ }
+ },
+ {
+ "id": "04237241bf770000",
+ "type": "cell",
+ "attributes": {
+ "x": 0,
+ "y": 1,
+ "w": 4,
+ "h": 2
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "04237241bf770000"
+ }
+ }
+ }
+ },
+ {
+ "id": "042372d4bcf70000",
+ "type": "cell",
+ "attributes": {
+ "x": 3,
+ "y": 4,
+ "w": 3,
+ "h": 3
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "042372d4bcf70000"
+ }
+ }
+ }
+ },
+ {
+ "id": "0423737a1ff70000",
+ "type": "cell",
+ "attributes": {
+ "x": 6,
+ "y": 4,
+ "w": 3,
+ "h": 3
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "0423737a1ff70000"
+ }
+ }
+ }
+ },
+ {
+ "id": "042373fce5370000",
+ "type": "cell",
+ "attributes": {
+ "x": 9,
+ "y": 4,
+ "w": 3,
+ "h": 3
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "042373fce5370000"
+ }
+ }
+ }
+ },
+ {
+ "id": "0423753affb70000",
+ "type": "cell",
+ "attributes": {
+ "x": 0,
+ "y": 3,
+ "w": 12,
+ "h": 1
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "0423753affb70000"
+ }
+ }
+ }
+ },
+ {
+ "id": "04237b90e1370000",
+ "type": "cell",
+ "attributes": {
+ "x": 0,
+ "y": 0,
+ "w": 12,
+ "h": 1
+ },
+ "relationships": {
+ "view": {
+ "data": {
+ "type": "view",
+ "id": "04237b90e1370000"
+ }
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "0423614244f70000",
+ "attributes": {
+ "name": "Humidity (All Rooms)",
+ "properties": {
+ "shape": "chronograf-v2",
+ "queries": [
+ {
+ "text": "import \"sql\"\n\nsensorInfo = sql.from(\n driverName: \"postgres\",\n dataSourceName: \"postgresql://localhost?sslmode=disable\",\n query: \"SELECT * FROM sensors\"\n)\n\nsensorMetrics = from(bucket: v.bucket)\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"airSensors\")\n\ndata = join(tables: {metric: sensorMetrics, info: sensorInfo}, on: [\"sensor_id\"])\n\ndata\n |> filter(fn: (r) => r._field == \"humidity\")\n |> keep(columns: [\"_time\",\"_value\",\"location\"])\n |> group(columns: [\"location\"])",
+ "editMode": "advanced",
+ "name": "",
+ "builderConfig": {
+ "buckets": [],
+ "tags": [
+ {
+ "key": "_measurement",
+ "values": []
+ }
+ ],
+ "functions": [],
+ "aggregateWindow": {
+ "period": "auto"
+ }
+ }
+ }
+ ],
+ "axes": {
+ "x": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "",
+ "base": "10",
+ "scale": "linear"
+ },
+ "y": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "%",
+ "base": "10",
+ "scale": "linear"
+ }
+ },
+ "type": "xy",
+ "legend": {},
+ "geom": "monotoneX",
+ "colors": [
+ {
+ "id": "cb8568fb-29b5-4c17-9399-05dcce537935",
+ "type": "scale",
+ "hex": "#FDC44F",
+ "name": "Cthulhu",
+ "value": 0
+ },
+ {
+ "id": "4f1a924d-009b-45f5-8419-9fa53204bdf7",
+ "type": "scale",
+ "hex": "#007C76",
+ "name": "Cthulhu",
+ "value": 0
+ },
+ {
+ "id": "3f2c8336-6b0d-431a-a3c2-8c5713479225",
+ "type": "scale",
+ "hex": "#8983FF",
+ "name": "Cthulhu",
+ "value": 0
+ }
+ ],
+ "note": "",
+ "showNoteWhenEmpty": false,
+ "xColumn": "_time",
+ "yColumn": "_value",
+ "shadeBelow": false
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "042361d158770000",
+ "attributes": {
+ "name": "Carbon Monoxide (All Rooms)",
+ "properties": {
+ "shape": "chronograf-v2",
+ "queries": [
+ {
+ "text": "import \"sql\"\n\nsensorInfo = sql.from(\n driverName: \"postgres\",\n dataSourceName: \"postgresql://localhost?sslmode=disable\",\n query: \"SELECT * FROM sensors\"\n)\n\nsensorMetrics = from(bucket: v.bucket)\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"airSensors\")\n\ndata = join(tables: {metric: sensorMetrics, info: sensorInfo}, on: [\"sensor_id\"])\n\ndata\n |> filter(fn: (r) => r._field == \"co\")\n |> keep(columns: [\"_time\",\"_value\",\"location\"])\n |> group(columns: [\"location\"])",
+ "editMode": "advanced",
+ "name": "",
+ "builderConfig": {
+ "buckets": [],
+ "tags": [
+ {
+ "key": "_measurement",
+ "values": []
+ }
+ ],
+ "functions": [],
+ "aggregateWindow": {
+ "period": "auto"
+ }
+ }
+ }
+ ],
+ "axes": {
+ "x": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "",
+ "base": "10",
+ "scale": "linear"
+ },
+ "y": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": " ppm",
+ "base": "10",
+ "scale": "linear"
+ }
+ },
+ "type": "xy",
+ "legend": {},
+ "geom": "monotoneX",
+ "colors": [
+ {
+ "id": "d6574d38-e7b7-447b-8ca1-ad3f36296bba",
+ "type": "scale",
+ "hex": "#8F8AF4",
+ "name": "Do Androids Dream of Electric Sheep?",
+ "value": 0
+ },
+ {
+ "id": "67ce5515-4411-4fc0-a5a6-282b4ee76b97",
+ "type": "scale",
+ "hex": "#A51414",
+ "name": "Do Androids Dream of Electric Sheep?",
+ "value": 0
+ },
+ {
+ "id": "5168db82-ac7e-4d57-b8ff-5747fa01762b",
+ "type": "scale",
+ "hex": "#F4CF31",
+ "name": "Do Androids Dream of Electric Sheep?",
+ "value": 0
+ }
+ ],
+ "note": "",
+ "showNoteWhenEmpty": false,
+ "xColumn": "_time",
+ "yColumn": "_value",
+ "shadeBelow": false
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "04236a6e6cf70000",
+ "attributes": {
+ "name": "Sensor Info",
+ "properties": {
+ "shape": "chronograf-v2",
+ "type": "table",
+ "queries": [
+ {
+ "text": "import \"sql\"\n\nsql.from(\n driverName: \"postgres\",\n dataSourceName: \"postgresql://localhost?sslmode=disable\",\n query: \"SELECT * FROM sensors\"\n)\n |> drop(columns: [\"sensor_id\"])\n |> rename(columns: {location: \"Sensor Location\", model_number: \"Sensor Model Number\", last_inspected: \"Last Inspected\"})",
+ "editMode": "advanced",
+ "name": "",
+ "builderConfig": {
+ "buckets": [],
+ "tags": [
+ {
+ "key": "_measurement",
+ "values": []
+ }
+ ],
+ "functions": [],
+ "aggregateWindow": {
+ "period": "auto"
+ }
+ }
+ }
+ ],
+ "colors": [
+ {
+ "id": "base",
+ "type": "text",
+ "hex": "#00C9FF",
+ "name": "laser",
+ "value": 0
+ }
+ ],
+ "tableOptions": {
+ "verticalTimeAxis": true,
+ "sortBy": {
+ "internalName": "",
+ "displayName": "",
+ "visible": false
+ },
+ "wrapping": "",
+ "fixFirstColumn": false
+ },
+ "fieldOptions": [
+ {
+ "internalName": "",
+ "displayName": "",
+ "visible": true
+ },
+ {
+ "internalName": "result",
+ "displayName": "result",
+ "visible": true
+ },
+ {
+ "internalName": "table",
+ "displayName": "table",
+ "visible": true
+ },
+ {
+ "internalName": "Sensor Location",
+ "displayName": "Sensor Location",
+ "visible": true
+ },
+ {
+ "internalName": "Sensor Model Number",
+ "displayName": "Sensor Model Number",
+ "visible": true
+ },
+ {
+ "internalName": "Last Inspected",
+ "displayName": "Last Inspected",
+ "visible": true
+ }
+ ],
+ "timeFormat": "YYYY-MM-DD HH:mm:ss",
+ "decimalPlaces": {
+ "isEnforced": false,
+ "digits": 2
+ },
+ "note": "",
+ "showNoteWhenEmpty": false
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "042372352a770000",
+ "attributes": {
+ "name": "Temperature",
+ "properties": {
+ "shape": "chronograf-v2",
+ "queries": [
+ {
+ "text": "import \"sql\"\n\nsensorInfo = sql.from(\n driverName: \"postgres\",\n dataSourceName: \"postgresql://localhost?sslmode=disable\",\n query: \"SELECT * FROM sensors\"\n)\n\nsensorMetrics = from(bucket: v.bucket)\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"airSensors\")\n\ndata = join(tables: {metric: sensorMetrics, info: sensorInfo}, on: [\"sensor_id\"])\n\ndata\n |> filter(fn: (r) => r._field == \"temperature\" and r.location == v.room)",
+ "editMode": "advanced",
+ "name": "",
+ "builderConfig": {
+ "buckets": [],
+ "tags": [
+ {
+ "key": "_measurement",
+ "values": []
+ }
+ ],
+ "functions": [],
+ "aggregateWindow": {
+ "period": "auto"
+ }
+ }
+ }
+ ],
+ "axes": {
+ "x": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "",
+ "base": "10",
+ "scale": "linear"
+ },
+ "y": {
+ "bounds": [
+ "65",
+ "85"
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "°F",
+ "base": "10",
+ "scale": "linear"
+ }
+ },
+ "type": "line-plus-single-stat",
+ "legend": {},
+ "colors": [
+ {
+ "id": "base",
+ "type": "text",
+ "hex": "#00C9FF",
+ "name": "laser",
+ "value": 0
+ },
+ {
+ "id": "22088d41-bea2-47bc-a697-ab8093e3d447",
+ "type": "scale",
+ "hex": "#FDC44F",
+ "name": "Cthulhu",
+ "value": 0
+ },
+ {
+ "id": "9edb23f5-6e0c-4365-80e3-320736c7334c",
+ "type": "scale",
+ "hex": "#007C76",
+ "name": "Cthulhu",
+ "value": 0
+ },
+ {
+ "id": "2c39bd4c-4954-4991-8c03-89e9186e4271",
+ "type": "scale",
+ "hex": "#8983FF",
+ "name": "Cthulhu",
+ "value": 0
+ }
+ ],
+ "prefix": "",
+ "suffix": "°F",
+ "decimalPlaces": {
+ "isEnforced": true,
+ "digits": 1
+ },
+ "note": "",
+ "showNoteWhenEmpty": false,
+ "xColumn": "_time",
+ "yColumn": "_value",
+ "shadeBelow": false
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "04237241bf770000",
+ "attributes": {
+ "name": "Temperature (All Rooms)",
+ "properties": {
+ "shape": "chronograf-v2",
+ "queries": [
+ {
+ "text": "import \"sql\"\n\nsensorInfo = sql.from(\n driverName: \"postgres\",\n dataSourceName: \"postgresql://localhost?sslmode=disable\",\n query: \"SELECT * FROM sensors\"\n)\n\nsensorMetrics = from(bucket: v.bucket)\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"airSensors\")\n\ndata = join(tables: {metric: sensorMetrics, info: sensorInfo}, on: [\"sensor_id\"])\n\ndata\n |> filter(fn: (r) => r._field == \"temperature\")\n |> keep(columns: [\"_time\",\"_value\",\"location\"])\n |> group(columns: [\"location\"])",
+ "editMode": "advanced",
+ "name": "",
+ "builderConfig": {
+ "buckets": [],
+ "tags": [
+ {
+ "key": "_measurement",
+ "values": []
+ }
+ ],
+ "functions": [],
+ "aggregateWindow": {
+ "period": "auto"
+ }
+ }
+ }
+ ],
+ "axes": {
+ "x": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "",
+ "base": "10",
+ "scale": "linear"
+ },
+ "y": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "°F",
+ "base": "10",
+ "scale": "linear"
+ }
+ },
+ "type": "xy",
+ "legend": {},
+ "geom": "monotoneX",
+ "colors": [
+ {
+ "id": "9ae217cd-ae69-42b2-b5b9-229a450d854c",
+ "type": "scale",
+ "hex": "#31C0F6",
+ "name": "Nineteen Eighty Four",
+ "value": 0
+ },
+ {
+ "id": "03684e9b-7ad4-478a-afcc-11c46ff136cb",
+ "type": "scale",
+ "hex": "#A500A5",
+ "name": "Nineteen Eighty Four",
+ "value": 0
+ },
+ {
+ "id": "4e437663-7938-4429-b772-913cb82c8b08",
+ "type": "scale",
+ "hex": "#FF7E27",
+ "name": "Nineteen Eighty Four",
+ "value": 0
+ }
+ ],
+ "note": "",
+ "showNoteWhenEmpty": false,
+ "xColumn": "_time",
+ "yColumn": "_value",
+ "shadeBelow": false
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "042372d4bcf70000",
+ "attributes": {
+ "name": "Humidity",
+ "properties": {
+ "shape": "chronograf-v2",
+ "queries": [
+ {
+ "text": "import \"sql\"\n\nsensorInfo = sql.from(\n driverName: \"postgres\",\n dataSourceName: \"postgresql://localhost?sslmode=disable\",\n query: \"SELECT * FROM sensors\"\n)\n\nsensorMetrics = from(bucket: v.bucket)\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"airSensors\")\n\ndata = join(tables: {metric: sensorMetrics, info: sensorInfo}, on: [\"sensor_id\"])\n\ndata\n |> filter(fn: (r) => r._field == \"humidity\" and r.location == v.room)",
+ "editMode": "advanced",
+ "name": "",
+ "builderConfig": {
+ "buckets": [],
+ "tags": [
+ {
+ "key": "_measurement",
+ "values": []
+ }
+ ],
+ "functions": [],
+ "aggregateWindow": {
+ "period": "auto"
+ }
+ }
+ }
+ ],
+ "axes": {
+ "x": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "",
+ "base": "10",
+ "scale": "linear"
+ },
+ "y": {
+ "bounds": [
+ "25",
+ "55"
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "%",
+ "base": "10",
+ "scale": "linear"
+ }
+ },
+ "type": "line-plus-single-stat",
+ "legend": {},
+ "colors": [
+ {
+ "id": "base",
+ "type": "text",
+ "hex": "#00C9FF",
+ "name": "laser",
+ "value": 0
+ },
+ {
+ "id": "99f3c23b-c495-451e-8b30-162ba1739972",
+ "type": "scale",
+ "hex": "#8F8AF4",
+ "name": "Do Androids Dream of Electric Sheep?",
+ "value": 0
+ },
+ {
+ "id": "2b27ca35-0a93-4506-99ce-b6c338c4f0e1",
+ "type": "scale",
+ "hex": "#A51414",
+ "name": "Do Androids Dream of Electric Sheep?",
+ "value": 0
+ },
+ {
+ "id": "44866d00-0182-4136-b8a2-ea991e1300af",
+ "type": "scale",
+ "hex": "#F4CF31",
+ "name": "Do Androids Dream of Electric Sheep?",
+ "value": 0
+ }
+ ],
+ "prefix": "",
+ "suffix": "%",
+ "decimalPlaces": {
+ "isEnforced": true,
+ "digits": 2
+ },
+ "note": "",
+ "showNoteWhenEmpty": false,
+ "xColumn": "_time",
+ "yColumn": "_value",
+ "shadeBelow": false
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "0423737a1ff70000",
+ "attributes": {
+ "name": "Carbon Monoxide",
+ "properties": {
+ "shape": "chronograf-v2",
+ "queries": [
+ {
+ "text": "import \"sql\"\n\nsensorInfo = sql.from(\n driverName: \"postgres\",\n dataSourceName: \"postgresql://localhost?sslmode=disable\",\n query: \"SELECT * FROM sensors\"\n)\n\nsensorMetrics = from(bucket: v.bucket)\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"airSensors\")\n\ndata = join(tables: {metric: sensorMetrics, info: sensorInfo}, on: [\"sensor_id\"])\n\ndata\n |> filter(fn: (r) => r._field == \"co\" and r.location == v.room)",
+ "editMode": "advanced",
+ "name": "",
+ "builderConfig": {
+ "buckets": [],
+ "tags": [
+ {
+ "key": "_measurement",
+ "values": []
+ }
+ ],
+ "functions": [],
+ "aggregateWindow": {
+ "period": "auto"
+ }
+ }
+ }
+ ],
+ "axes": {
+ "x": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": "",
+ "base": "10",
+ "scale": "linear"
+ },
+ "y": {
+ "bounds": [
+ "",
+ ""
+ ],
+ "label": "",
+ "prefix": "",
+ "suffix": " ppm",
+ "base": "10",
+ "scale": "linear"
+ }
+ },
+ "type": "line-plus-single-stat",
+ "legend": {},
+ "colors": [
+ {
+ "id": "base",
+ "type": "text",
+ "hex": "#00C9FF",
+ "name": "laser",
+ "value": 0
+ },
+ {
+ "id": "6618c85e-1b5f-4c4f-9a03-c852a1a3cc47",
+ "type": "scale",
+ "hex": "#DA6FF1",
+ "name": "Ectoplasm",
+ "value": 0
+ },
+ {
+ "id": "ea594da3-a224-4f9d-b592-3623b9e84b0f",
+ "type": "scale",
+ "hex": "#00717A",
+ "name": "Ectoplasm",
+ "value": 0
+ },
+ {
+ "id": "0add3620-1c62-4798-89ab-7435f19caec6",
+ "type": "scale",
+ "hex": "#ACFF76",
+ "name": "Ectoplasm",
+ "value": 0
+ }
+ ],
+ "prefix": "",
+ "suffix": " ppm",
+ "decimalPlaces": {
+ "isEnforced": true,
+ "digits": 1
+ },
+ "note": "",
+ "showNoteWhenEmpty": false,
+ "xColumn": "_time",
+ "yColumn": "_value",
+ "shadeBelow": false
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "042373fce5370000",
+ "attributes": {
+ "name": "Carbon Monoxide Level",
+ "properties": {
+ "shape": "chronograf-v2",
+ "type": "gauge",
+ "queries": [
+ {
+ "text": "import \"sql\"\n\nsensorInfo = sql.from(\n driverName: \"postgres\",\n dataSourceName: \"postgresql://localhost?sslmode=disable\",\n query: \"SELECT * FROM sensors\"\n)\n\nsensorMetrics = from(bucket: v.bucket)\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"airSensors\")\n\ndata = join(tables: {metric: sensorMetrics, info: sensorInfo}, on: [\"sensor_id\"])\n\ndata\n |> filter(fn: (r) => r._field == \"co\" and r.location == v.room)",
+ "editMode": "advanced",
+ "name": "",
+ "builderConfig": {
+ "buckets": [],
+ "tags": [
+ {
+ "key": "_measurement",
+ "values": []
+ }
+ ],
+ "functions": [],
+ "aggregateWindow": {
+ "period": "auto"
+ }
+ }
+ }
+ ],
+ "prefix": "",
+ "suffix": " ppm",
+ "colors": [
+ {
+ "id": "0",
+ "type": "min",
+ "hex": "#4ED8A0",
+ "name": "rainforest",
+ "value": 0
+ },
+ {
+ "id": "eba43308-b8ae-40fd-bc0a-d056f4ceb435",
+ "type": "threshold",
+ "hex": "#FFB94A",
+ "name": "pineapple",
+ "value": 30
+ },
+ {
+ "id": "5a51cc65-ec3d-45a4-b5c9-94156cdf279b",
+ "type": "threshold",
+ "hex": "#BF3D5E",
+ "name": "ruby",
+ "value": 70
+ },
+ {
+ "id": "1",
+ "type": "max",
+ "hex": "#BF3D5E",
+ "name": "ruby",
+ "value": 140
+ }
+ ],
+ "decimalPlaces": {
+ "isEnforced": true,
+ "digits": 2
+ },
+ "note": "",
+ "showNoteWhenEmpty": false
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "0423753affb70000",
+ "attributes": {
+ "name": "Name this Cell",
+ "properties": {
+ "shape": "chronograf-v2",
+ "type": "markdown",
+ "note": " \n**`⬆` View metrics for a specific room by selecting the a room above.**"
+ }
+ }
+ },
+ {
+ "type": "view",
+ "id": "04237b90e1370000",
+ "attributes": {
+ "name": "Name this Cell",
+ "properties": {
+ "shape": "chronograf-v2",
+ "type": "markdown",
+ "note": " \n**`⬆` Select the bucket that contains the airSensor measurement.**"
+ }
+ }
+ },
+ {
+ "id": "03bdc0c5a4ff0000",
+ "type": "variable",
+ "attributes": {
+ "name": "bucket",
+ "arguments": {
+ "type": "query",
+ "values": {
+ "query": "buckets()\n |> map(fn: (r) => ({ _value: r.name }))\n",
+ "language": "flux"
+ }
+ },
+ "selected": null
+ },
+ "relationships": {
+ "label": {
+ "data": []
+ }
+ }
+ },
+ {
+ "id": "04237161ad770000",
+ "type": "variable",
+ "attributes": {
+ "name": "room",
+ "arguments": {
+ "type": "query",
+ "values": {
+ "query": "import \"sql\"\n\nsql.from(\n driverName: \"postgres\",\n dataSourceName: \"postgresql://localhost?sslmode=disable\",\n query: \"SELECT * FROM sensors\"\n)\n |> rename(columns: {location: \"_value\"})\n |> keep(columns: [\"_value\"])\n",
+ "language": "flux"
+ }
+ },
+ "selected": null
+ },
+ "relationships": {
+ "label": {
+ "data": []
+ }
+ }
+ }
+ ]
+ },
+ "labels": []
+}
diff --git a/static/downloads/sample-sensor-info.csv b/static/downloads/sample-sensor-info.csv
new file mode 100644
index 000000000..07ce7c2f3
--- /dev/null
+++ b/static/downloads/sample-sensor-info.csv
@@ -0,0 +1,9 @@
+sensor_id,location,model_number,last_inspected
+TLM0100,Main Lobby,TLM89092A,1/11/2019
+TLM0101,Room 101,TLM89092A,1/11/2019
+TLM0102,Room 102,TLM89092B,1/11/2019
+TLM0103,Mechanical Room,TLM90012Z,1/14/2019
+TLM0200,Conference Room,TLM89092B,9/24/2018
+TLM0201,Room 201,TLM89092B,9/24/2018
+TLM0202,Room 202,TLM89092A,9/24/2018
+TLM0203,Room 203,TLM89092A,9/24/2018
diff --git a/static/img/2-0-sql-dashboard-variable.png b/static/img/2-0-sql-dashboard-variable.png
new file mode 100644
index 000000000..4b8238f04
Binary files /dev/null and b/static/img/2-0-sql-dashboard-variable.png differ