├── .replit ├── images ├── replit-url.png ├── TableDataSource.png ├── Grafana-Dashboard.png ├── JSON-Plugin-Config.png └── Grafana-Dashboard-Config.png ├── utils.js ├── package.json ├── LICENSE ├── .gitignore ├── README.md ├── index.js └── Bitrise Dashboard.json /.replit: -------------------------------------------------------------------------------- 1 | run = "npm i && node index.js" 2 | -------------------------------------------------------------------------------- /images/replit-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DamienBitrise/bitrise-grafana-api/HEAD/images/replit-url.png -------------------------------------------------------------------------------- /images/TableDataSource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DamienBitrise/bitrise-grafana-api/HEAD/images/TableDataSource.png -------------------------------------------------------------------------------- /images/Grafana-Dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DamienBitrise/bitrise-grafana-api/HEAD/images/Grafana-Dashboard.png -------------------------------------------------------------------------------- /images/JSON-Plugin-Config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DamienBitrise/bitrise-grafana-api/HEAD/images/JSON-Plugin-Config.png -------------------------------------------------------------------------------- /images/Grafana-Dashboard-Config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DamienBitrise/bitrise-grafana-api/HEAD/images/Grafana-Dashboard-Config.png -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | BASE_URL: 'https://api.bitrise.io/v0.1/apps', 3 | getHeaders: (api_key) => { 4 | return { 5 | headers: { 6 | 'accept': 'application/json', 7 | 'Authorization': api_key 8 | }, 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.2", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "run": "node index.js" 8 | }, 9 | "scripts": {}, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "body-parser": "^1.19.0", 14 | "cors": "^2.8.5", 15 | "express": "^4.17.1", 16 | "node-fetch": "^2.6.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 DamienBitrise 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitrise-grafana-api 2 | 3 | Grafana Wrapper for Bitrise API 4 | 5 | ![](images/Grafana-Dashboard.png) 6 | 7 | # Prerequisites 8 | 9 | ## Grafana Account 10 | 11 | Sign up for a free trial account here: https://grafana.com/signup/cloud/select-org 12 | 13 | # Run Locally 14 | 15 | ## Install Dependencies 16 | 17 | ```npm i``` 18 | 19 | ## Running Grafana JSON Data Source 20 | 21 | ```node index.js``` 22 | 23 | # Run on Repl.it 24 | 25 | [![Run on Repl.it](https://repl.it/badge/github/DamienBitrise/bitrise-grafana-api)](https://repl.it/github/DamienBitrise/bitrise-grafana-api) 26 | 27 | Just click the button above and when the Repl loads click Run. 28 | 29 | Copy the base URL from the running Repl 30 | 31 | ![](images/replit-url.png) 32 | 33 | Make sure to update the url in the code here also: https://github.com/DamienBitrise/bitrise-grafana-api/blob/master/index.js#L12 34 | 35 | ## The following Grafana JSON Endpoints will be available 36 | 37 | - /builds 38 | - /queue 39 | - /running 40 | - /stats 41 | 42 | ## Each endpoint will have the following APIs 43 | 44 | - **/** should return 200 ok. Used for "Test connection" on the datasource config page. 45 | - **/search** should return available metrics when invoked. 46 | - **/query** should return metrics based on input. 47 | - **/annotations** should return annotations. 48 | 49 | ## Install Grafana JSON Plugin 50 | 51 | **Table Data Source** 52 | 53 | - https://grafana.com/grafana/plugins/grafana-simple-json-datasource/installation 54 | 55 | **Chart Data Source** 56 | 57 | - https://grafana.com/grafana/plugins/simpod-json-datasource 58 | 59 | ## Generate Personal Access Token for Bitrise API 60 | 61 | - https://devcenter.bitrise.io/api/authentication/#authentication 62 | 63 | ## Configure Grafana JSON Data Sources 64 | 65 | **Table** 66 | 67 | ![](images/TableDataSource.png) 68 | 69 | **Charts** 70 | 71 | ![](images/JSON-Plugin-Config.png) 72 | 73 | Using the Base URL from your server or Repl configure two Grafana JSON Data Sources 74 | 75 | ### Builds Table 76 | 77 | **URL:** BASE_URL/running 78 | 79 | **Headers** 80 | - Authorization (Personal Access Token for Bitrise API) 81 | - content ('application/json') 82 | - appSlugs (comma seperated list of app slugs) [Optional omit to select all apps] 83 | 84 | **URL:** BASE_URL/stats 85 | 86 | **Headers** 87 | - Authorization (Personal Access Token for Bitrise API) 88 | - content ('application/json') 89 | - appSlugs (comma seperated list of app slugs) [Optional omit to select all apps] 90 | 91 | ### Builds Chart 92 | 93 | **URL:** BASE_URL/builds 94 | 95 | **Headers** 96 | - Authorization (Personal Access Token for Bitrise API) 97 | - content ('application/json') 98 | - appSlugs (comma seperated list of app slugs) [Optional omit to select all apps] 99 | 100 | ### Queue Chart 101 | 102 | **URL:** BASE_URL/queue 103 | 104 | **Headers** 105 | - Authorization (Personal Access Token for Bitrise API) 106 | - content ('application/json') 107 | - appSlugs (comma seperated list of app slugs) [Optional omit to select all apps] 108 | 109 | ## Configure Grafana JSON Dashboard 110 | 111 | Just Import Grafana Dashboard JSON here: 112 | 113 | https://github.com/DamienBitrise/bitrise-grafana-api/blob/master/Bitrise%20Dashboard.json 114 | 115 | ## Updating Data Sources 116 | 117 | **Note:** Due to a Grafana bug with importing dashboards with Data Sources, you may need to unselect and reselect the data sources in the Dashboard panels. 118 | 119 | ## Reselect the DataSources: 120 | 121 | **Step 1:** Click the title of each panel in the Dashboard view and click edit. 122 | 123 | **Step 2:** Select the "Query" dropdown and select a different data source, then select the original data source again. 124 | 125 | This updates the Data Source IDs to match the new DataSources you added previously. 126 | 127 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const cors = require('cors') 4 | const fetch = require('node-fetch'); 5 | const builds = require('./builds') 6 | const app = express(); 7 | app.use(cors()) 8 | app.use(bodyParser.json()); 9 | app.use(bodyParser.urlencoded({ extended: true })); 10 | 11 | function keepAlive(){ 12 | fetch('https://BitriseAPI--xxx.repl.co/') 13 | .then(res => res.json()) 14 | .then((res) => { 15 | console.log('Keep Alive'); 16 | }); 17 | } 18 | 19 | setInterval(()=>{ 20 | keepAlive() 21 | }, 10*1000*60); 22 | 23 | app.get('/', (req, res) => { 24 | console.log('/health check'); 25 | res.status(200).json({alive:true}); 26 | }); 27 | 28 | app.get('/builds', (req, res) => { 29 | console.log('/builds health check'); 30 | res.status(200).json({alive:true}); 31 | }); 32 | 33 | app.get('/running', (req, res) => { 34 | console.log('/running health check'); 35 | res.status(200).json({alive:true}); 36 | }); 37 | 38 | app.get('/stats', (req, res) => { 39 | console.log('/stats health check'); 40 | res.status(200).json({alive:true}); 41 | }); 42 | 43 | app.get('/queue', (req, res) => { 44 | console.log('/queue health check'); 45 | res.status(200).json({alive:true}); 46 | }); 47 | 48 | app.post('/builds/search', (req, res) => { 49 | console.log('/builds/search'); 50 | res.json(["builds"]); 51 | }); 52 | 53 | app.post('/builds/query', (req, res) => { 54 | console.log('/builds/query'); 55 | let body = req.body; 56 | let from = body.range.from; 57 | let to = body.range.to; 58 | let timeseries_data = []; 59 | let appSlugs = req.header('appSlugs'); 60 | if(!appSlugs || appSlugs == ''){ 61 | appSlugs = null; 62 | } else { 63 | appSlugs = appSlugs.split(',') 64 | } 65 | const API_KEY = req.header('Authorization'); 66 | builds.getAllData(appSlugs, API_KEY, from, to, (data) => { 67 | let timeseries_data = builds.getBuildTimeseriesData(appSlugs, data); 68 | res.json(timeseries_data); 69 | }); 70 | }); 71 | 72 | 73 | app.post('/builds/annotations', (req, res) => { 74 | console.log('/builds/annotations'); 75 | res.json([]); 76 | }); 77 | 78 | app.post('/queue/search', (req, res) => { 79 | console.log('/queue/search'); 80 | res.json(["queue"]); 81 | }); 82 | 83 | app.post('/queue/query', (req, res) => { 84 | console.log('/queue/query'); 85 | let body = req.body; 86 | let from = body.range.from; 87 | let to = body.range.to; 88 | let timeseries_data = []; 89 | let appSlugs = req.header('appSlugs'); 90 | if(!appSlugs || appSlugs == ''){ 91 | appSlugs = null; 92 | } else { 93 | appSlugs = appSlugs.split(',') 94 | } 95 | console.log('App Slug: ', appSlugs); 96 | const API_KEY = req.header('Authorization'); 97 | builds.getAllData(appSlugs, API_KEY, from, to, (data) => { 98 | let timeseries_data = builds.getQueueTimeseriesData(appSlugs, data); 99 | res.json(timeseries_data); 100 | }); 101 | }); 102 | 103 | 104 | app.post('/queue/annotations', (req, res) => { 105 | console.log('/queue/annotations'); 106 | res.json([]); 107 | }); 108 | 109 | app.post('/running/search', (req, res) => { 110 | console.log('/running/search'); 111 | res.json(["running"]); 112 | }); 113 | 114 | app.post('/running/query', (req, res) => { 115 | console.log('/running/query'); 116 | 117 | let body = req.body; 118 | let from = body.range.from; 119 | let to = body.range.to; 120 | let timeseries_data = []; 121 | let appSlugs = req.header('appSlugs'); 122 | if(!appSlugs || appSlugs == ''){ 123 | appSlugs = null; 124 | } else { 125 | appSlugs = appSlugs.split(',') 126 | } 127 | console.log('App Slug: ', appSlugs); 128 | const API_KEY = req.header('Authorization'); 129 | builds.getAllData(appSlugs, API_KEY, from, to, (data) => { 130 | let table_data = builds.getBuildTableData(appSlugs, data); 131 | res.json(table_data); 132 | }); 133 | }); 134 | 135 | app.post('/running/annotations', (req, res) => { 136 | console.log('/running/annotations'); 137 | res.json([]); 138 | }); 139 | 140 | app.post('/stats/search', (req, res) => { 141 | console.log('/stats/search'); 142 | res.json(["stats"]); 143 | }); 144 | 145 | app.post('/stats/query', (req, res) => { 146 | console.log('/stats/query'); 147 | 148 | let body = req.body; 149 | let from = body.range.from; 150 | let to = body.range.to; 151 | let timeseries_data = []; 152 | let appSlugs = req.header('appSlugs'); 153 | if(!appSlugs || appSlugs == ''){ 154 | appSlugs = null; 155 | } else { 156 | appSlugs = appSlugs.split(',') 157 | } 158 | console.log('App Slug: ', appSlugs); 159 | const API_KEY = req.header('Authorization'); 160 | builds.getAllData(appSlugs, API_KEY, from, to, (data) => { 161 | let table_data = builds.getStatsTableData(appSlugs, data); 162 | res.json(table_data); 163 | }); 164 | }); 165 | 166 | app.post('/stats/annotations', (req, res) => { 167 | console.log('/stats/annotations'); 168 | res.json([]); 169 | }); 170 | 171 | app.listen(3000, () => console.log('server started')); 172 | -------------------------------------------------------------------------------- /Bitrise Dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "$$hashKey": "object:58", 6 | "builtIn": 1, 7 | "datasource": "-- Grafana --", 8 | "enable": true, 9 | "hide": true, 10 | "iconColor": "rgba(0, 211, 255, 1)", 11 | "name": "Annotations & Alerts", 12 | "type": "dashboard" 13 | } 14 | ] 15 | }, 16 | "description": "Build List, Build Time, Build Queue", 17 | "editable": true, 18 | "gnetId": null, 19 | "graphTooltip": 0, 20 | "id": 1, 21 | "links": [], 22 | "panels": [ 23 | { 24 | "columns": [], 25 | "datasource": "Running", 26 | "fontSize": "100%", 27 | "gridPos": { 28 | "h": 10, 29 | "w": 12, 30 | "x": 0, 31 | "y": 0 32 | }, 33 | "id": 6, 34 | "pageSize": null, 35 | "showHeader": true, 36 | "sort": { 37 | "col": 0, 38 | "desc": true 39 | }, 40 | "styles": [ 41 | { 42 | "$$hashKey": "object:201", 43 | "alias": "Time", 44 | "align": "auto", 45 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 46 | "pattern": "Time", 47 | "type": "date" 48 | }, 49 | { 50 | "$$hashKey": "object:686", 51 | "alias": "", 52 | "align": "auto", 53 | "colorMode": null, 54 | "colors": [ 55 | "rgba(245, 54, 54, 0.9)", 56 | "rgba(237, 129, 40, 0.89)", 57 | "rgba(50, 172, 45, 0.97)" 58 | ], 59 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 60 | "decimals": 2, 61 | "link": true, 62 | "linkTargetBlank": true, 63 | "linkTooltip": "Click to View Build", 64 | "linkUrl": "${__cell}", 65 | "mappingType": 1, 66 | "pattern": "App", 67 | "sanitize": true, 68 | "thresholds": [], 69 | "type": "string", 70 | "unit": "short" 71 | }, 72 | { 73 | "$$hashKey": "object:452", 74 | "alias": "", 75 | "align": "auto", 76 | "colorMode": null, 77 | "colors": [ 78 | "rgba(245, 54, 54, 0.9)", 79 | "rgba(237, 129, 40, 0.89)", 80 | "rgba(50, 172, 45, 0.97)" 81 | ], 82 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 83 | "decimals": 2, 84 | "link": true, 85 | "linkTargetBlank": true, 86 | "linkUrl": "${__cell}", 87 | "mappingType": 1, 88 | "pattern": "Build #", 89 | "preserveFormat": false, 90 | "sanitize": true, 91 | "thresholds": [], 92 | "type": "string", 93 | "unit": "short" 94 | }, 95 | { 96 | "$$hashKey": "object:607", 97 | "alias": "", 98 | "align": "auto", 99 | "colorMode": null, 100 | "colors": [ 101 | "rgba(245, 54, 54, 0.9)", 102 | "rgba(237, 129, 40, 0.89)", 103 | "rgba(50, 172, 45, 0.97)" 104 | ], 105 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 106 | "decimals": 2, 107 | "link": true, 108 | "mappingType": 1, 109 | "pattern": "Workflow", 110 | "sanitize": true, 111 | "thresholds": [], 112 | "type": "string", 113 | "unit": "short" 114 | }, 115 | { 116 | "$$hashKey": "object:799", 117 | "alias": "", 118 | "align": "auto", 119 | "colorMode": null, 120 | "colors": [ 121 | "rgba(245, 54, 54, 0.9)", 122 | "rgba(237, 129, 40, 0.89)", 123 | "rgba(50, 172, 45, 0.97)" 124 | ], 125 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 126 | "decimals": 2, 127 | "mappingType": 1, 128 | "pattern": "Started At", 129 | "thresholds": [], 130 | "type": "date", 131 | "unit": "short" 132 | } 133 | ], 134 | "targets": [ 135 | { 136 | "refId": "A", 137 | "target": "running", 138 | "type": "table" 139 | } 140 | ], 141 | "timeFrom": null, 142 | "timeShift": null, 143 | "title": "Builds", 144 | "transform": "table", 145 | "type": "table" 146 | }, 147 | { 148 | "columns": [], 149 | "datasource": "Stats", 150 | "fontSize": "100%", 151 | "gridPos": { 152 | "h": 10, 153 | "w": 12, 154 | "x": 12, 155 | "y": 0 156 | }, 157 | "id": 8, 158 | "pageSize": null, 159 | "showHeader": true, 160 | "sort": { 161 | "col": 0, 162 | "desc": false 163 | }, 164 | "styles": [ 165 | { 166 | "$$hashKey": "object:352", 167 | "alias": "Time", 168 | "align": "auto", 169 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 170 | "pattern": "Time", 171 | "type": "date" 172 | } 173 | ], 174 | "targets": [ 175 | { 176 | "refId": "A", 177 | "target": "stats", 178 | "type": "table" 179 | } 180 | ], 181 | "timeFrom": null, 182 | "timeShift": null, 183 | "title": "Build Stats", 184 | "transform": "table", 185 | "type": "table" 186 | }, 187 | { 188 | "aliasColors": {}, 189 | "bars": false, 190 | "dashLength": 10, 191 | "dashes": false, 192 | "datasource": "Build Times", 193 | "fill": 1, 194 | "fillGradient": 0, 195 | "gridPos": { 196 | "h": 9, 197 | "w": 24, 198 | "x": 0, 199 | "y": 10 200 | }, 201 | "hiddenSeries": false, 202 | "id": 2, 203 | "legend": { 204 | "alignAsTable": false, 205 | "avg": false, 206 | "current": false, 207 | "max": false, 208 | "min": false, 209 | "show": true, 210 | "total": false, 211 | "values": false 212 | }, 213 | "lines": true, 214 | "linewidth": 1, 215 | "nullPointMode": "null", 216 | "options": { 217 | "dataLinks": [ 218 | { 219 | "targetBlank": true, 220 | "title": "Open Build", 221 | "url": "https://app.bitrise.io/build/#?tab=log" 222 | } 223 | ] 224 | }, 225 | "percentage": false, 226 | "pointradius": 2, 227 | "points": true, 228 | "renderer": "flot", 229 | "seriesOverrides": [], 230 | "spaceLength": 10, 231 | "stack": false, 232 | "steppedLine": false, 233 | "targets": [ 234 | { 235 | "data": "", 236 | "hide": false, 237 | "refId": "A", 238 | "target": "builds", 239 | "type": "timeseries" 240 | } 241 | ], 242 | "thresholds": [], 243 | "timeFrom": null, 244 | "timeRegions": [], 245 | "timeShift": null, 246 | "title": "Build Time", 247 | "tooltip": { 248 | "shared": false, 249 | "sort": 0, 250 | "value_type": "individual" 251 | }, 252 | "type": "graph", 253 | "xaxis": { 254 | "buckets": null, 255 | "mode": "time", 256 | "name": null, 257 | "show": true, 258 | "values": [] 259 | }, 260 | "yaxes": [ 261 | { 262 | "$$hashKey": "object:117", 263 | "format": "short", 264 | "label": null, 265 | "logBase": 1, 266 | "max": null, 267 | "min": null, 268 | "show": true 269 | }, 270 | { 271 | "$$hashKey": "object:118", 272 | "format": "short", 273 | "label": null, 274 | "logBase": 1, 275 | "max": null, 276 | "min": null, 277 | "show": true 278 | } 279 | ], 280 | "yaxis": { 281 | "align": false, 282 | "alignLevel": null 283 | } 284 | }, 285 | { 286 | "aliasColors": {}, 287 | "bars": false, 288 | "dashLength": 10, 289 | "dashes": false, 290 | "datasource": "Build Queue", 291 | "fill": 1, 292 | "fillGradient": 0, 293 | "gridPos": { 294 | "h": 9, 295 | "w": 24, 296 | "x": 0, 297 | "y": 19 298 | }, 299 | "hiddenSeries": false, 300 | "id": 4, 301 | "legend": { 302 | "avg": false, 303 | "current": false, 304 | "max": false, 305 | "min": false, 306 | "show": true, 307 | "total": false, 308 | "values": false 309 | }, 310 | "lines": true, 311 | "linewidth": 1, 312 | "nullPointMode": "null", 313 | "options": { 314 | "dataLinks": [] 315 | }, 316 | "percentage": false, 317 | "pointradius": 2, 318 | "points": true, 319 | "renderer": "flot", 320 | "seriesOverrides": [], 321 | "spaceLength": 10, 322 | "stack": false, 323 | "steppedLine": false, 324 | "targets": [ 325 | { 326 | "data": "", 327 | "hide": false, 328 | "refId": "A", 329 | "target": "queue", 330 | "type": "timeseries" 331 | } 332 | ], 333 | "thresholds": [], 334 | "timeFrom": null, 335 | "timeRegions": [], 336 | "timeShift": null, 337 | "title": "Build Queue", 338 | "tooltip": { 339 | "shared": false, 340 | "sort": 0, 341 | "value_type": "individual" 342 | }, 343 | "type": "graph", 344 | "xaxis": { 345 | "buckets": null, 346 | "mode": "time", 347 | "name": null, 348 | "show": true, 349 | "values": [] 350 | }, 351 | "yaxes": [ 352 | { 353 | "$$hashKey": "object:148", 354 | "format": "short", 355 | "label": null, 356 | "logBase": 1, 357 | "max": null, 358 | "min": null, 359 | "show": true 360 | }, 361 | { 362 | "$$hashKey": "object:149", 363 | "format": "short", 364 | "label": null, 365 | "logBase": 1, 366 | "max": null, 367 | "min": null, 368 | "show": true 369 | } 370 | ], 371 | "yaxis": { 372 | "align": false, 373 | "alignLevel": null 374 | } 375 | } 376 | ], 377 | "schemaVersion": 22, 378 | "style": "dark", 379 | "tags": [ 380 | "bitrise" 381 | ], 382 | "templating": { 383 | "list": [] 384 | }, 385 | "time": { 386 | "from": "now-30d", 387 | "to": "now" 388 | }, 389 | "timepicker": { 390 | "refresh_intervals": [ 391 | "5s", 392 | "10s", 393 | "30s", 394 | "1m", 395 | "5m", 396 | "15m", 397 | "30m", 398 | "1h", 399 | "2h", 400 | "1d" 401 | ] 402 | }, 403 | "timezone": "", 404 | "title": "Bitrise Dashboard", 405 | "uid": "kmgckPCZk", 406 | "variables": { 407 | "list": [] 408 | }, 409 | "version": 35 410 | } --------------------------------------------------------------------------------