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