diff --git a/dist/README.md b/dist/README.md index b095f13..4006be9 100644 --- a/dist/README.md +++ b/dist/README.md @@ -1,33 +1,57 @@ # macropower-analytics-panel + Grafana panel that forwards user data on mount & unmount ## Features + Have you ever found yourself wanting to know who is using your dashboards, and how frequently they are being used? -Grafana has some built in support with Google Analytics, but nothing that will actually define **who** is using a dashboard. Missing this data is big problem, because the resulting statistics hardly answer any questsions you might have. Questions like, "Who should I be catering to" or "What data should I be focusing on". Additionally, Google Analytics data can be quite inaccurate as you can't easily distinguish admins from end users. +Grafana has some built in support with Google Analytics, but nothing that will actually define **who** is using a dashboard. Missing this data is big problem, because the resulting statistics hardly answer any questions you might have. Questions like, "Who should I be catering to" or "What data should I be focusing on". Additionally, Google Analytics data can be quite inaccurate as you can't easily distinguish admins from end users. -This panel will forward data each time a panel is loaded or exited, including: +This panel will forward data each time a dashboard is loaded or exited, including: - Timestamps - Username +- User roles - Dashboard ID - Instance info +- Session duration You might want to consider this plugin until official support is implemented! ## Getting Started + You will need a server that accepts a JSON body identical to that provided by this plugin. This data will be sent to whatever URL you place in the plugin's settings. -I used Kotlin w/ Spring, but you may use anything you want. +### Telegraf + +You can use Telegraf's `http_listener_v2` input to accept data from this plugin. An example configuration for this input can be found in the [example](https://github.com/MacroPower/macropower-analytics-panel/tree/master/example) directory. This example is by no means perfect, and you will need to customize it to fit your specific use case. + +The only major caveat with this approach is that you can no longer gather session duration, and thus you will need to ensure that the "post end" option is disabled on the Visualization page. + +### Custom + +To fully utilize this plugin, you will need to design a custom service to accept payloads provided by the plugin, and store said payloads in a database of your choice. + +I used Kotlin w/ Spring with a MySQL database, but again, you may use anything you want. Your server should reply with `{location: }` on record creation, where `id` is some unique value your backend associates to records. Your server will also need to accept the same JSON body at `youraddress/yourpath/{id}`. This signals that the user's connection has ended. You can use this to write a new entry, or append your last entry with an END timestamp. Note that this will not be sent if the user closes the tab/browser. If you don't want this functionality, you can disable it on the Visualization page. The panel itself will display the JSON body that will be sent (until it is hidden), so you should be able to write your models around that data. -The other settings, "Unique ID" and "Description" are used to distinguish one dashboard from another. They will be auto populated when you load the Visualization page. Feel free to change them. +## The Panel + +This plugin works by using the session data provided to every panel in your dashboard. As such, you will need to ensure that analytics-panel is being loaded every time a dashboard you wish to monitor is being loaded. This means that you should place this panel in part of the dashboard that is guaranteed to be loaded by the user when they visit your dashboard. As of right now, **the only place that you absolutely _cannot_ place analytics-panel is inside a row**, as expanding and collapsing the row is essentially the same as loading and unloading the entire dashboard, from any panel's perspective. + +While you cannot place the panel inside a row, you can take several steps to make the panel very difficult to notice, and this will not have any negative effects on the plugin's behavior. -## Hiding the panel - In Visualization, set Hidden to True. - In General Settings, set Transparent to True. -- In General Settings, set Title/Description to nothing. -- Save and make the panel as small as you want. I found that 0 height, 100% width works well. \ No newline at end of file +- In General Settings, set Title/Description to nothing. Alternatively, you can set a Title/Description and use this panel as a title, separator, or footer. +- Save and make the panel as small as you want. I found that 0 height, 100% width works well. + +## Troubleshooting + +If something is not working properly, the first thing you should do is look at your browser's console and network inspector. After opening the inspector, load a dashboard with an analytics-panel and take a look at the console, the request, and the response. In most cases, problems should be evident here. + +If you run into an issue you cannot solve, please post an [issue](https://github.com/MacroPower/macropower-analytics-panel/issues) and I will do my best to look into your inquiry. diff --git a/dist/module.js b/dist/module.js index 78262d1..cad57e8 100644 --- a/dist/module.js +++ b/dist/module.js @@ -1,2 +1,3 @@ -define(["react","@grafana/ui","@grafana/data","app/core/core"],(function(e,t,n,o){return function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=4)}([function(t,n){t.exports=e},function(e,n){e.exports=t},function(e,t){e.exports=n},function(e,t){e.exports=o},function(e,t,n){"use strict";n.r(t);var o=n(2),r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)};function a(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var i=function(){return(i=Object.assign||function(e){for(var t,n=1,o=arguments.length;n{const r=Object.prototype.toString.call(e[o]);return!("[object Object]"===r||"[object Array]"===r)||function(t){const e=Object.prototype.toString.call(t),n="[object Array]"===e,o="[object Object]"===e;if(!t)return!0;if(n)return!t.length;if(o)return!Object.keys(t).length}(e[o])?(t[o]=e[o],t):function(t,e,n){return Object.keys(n).reduce((function(e,o){return e[t+i+o]=n[o],e}),e)}(o,t,a(e[o],n))},{}),Object.keys(e).forEach((function(o){const r=o.split(i).map(u);let a=l(r.shift()),p=l(r[0]),f=s;for(;void 0!==p;){const t=Object.prototype.toString.call(f[a]),e="[object Object]"===t||"[object Array]"===t;if(!c&&!e&&void 0!==f[a])return;(c&&!e||!c&&null==f[a])&&(f[a]="number"!=typeof p||n.object?{}:[]),f=f[a],r.length>0&&(a=l(r.shift()),p=l(r[0]))}f[a]=t(e[o],n)})),s}},function(t,e){t.exports=function(t){return null!=t&&null!=t.constructor&&"function"==typeof t.constructor.isBuffer&&t.constructor.isBuffer(t)}},function(t,e,n){"use strict";n.r(e);var o=n(2),r=function(t,e){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])})(t,e)};var a=n(0),i=n.n(a),c=n(3);function u(t){try{new URL(t)}catch(t){return!1}return!0}function s(t){return t.replace(/^.+\/\//g,"").replace(/\/.+$/g,"")}function l(){return Math.floor((new Date).getTime()/1e3)}function p(t){var e=t.status.toString();if(!/^(0)|(20[0-4])$/.test(e))throw new Error("Returned status "+e);return t}var f=n(4),d=n(1),y=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.state={update:""},e.body=function(){var t=e.props.timeRange,n={from:t.from.unix(),to:t.to.unix()},o={host:s(window.location.href),timeRange:n},r=e.props.options.analyticsOptions,a={options:r,environment:o,context:c.contextSrv.user,time:l()};return r.flatten?Object(f.flatten)(a):a},e.getRequestInit=function(){var t=e.props.options.analyticsOptions.noCors;return e.setState({error:void 0}),{method:"POST",mode:t?"no-cors":"cors",body:JSON.stringify(e.body()),headers:{"Content-Type":"application/json"}}},e.sendInitPayload=function(){var t=e.props.options.analyticsOptions,n=t.server,o=t.postEnd;if(u(n)){var r=fetch(n,e.getRequestInit());o?r.then((function(t){return p(t)})).then((function(t){return t.json()})).then((function(t){return e.setState({update:t.location})})).catch((function(t){e.setState({error:t})})):r.then((function(t){return p(t)})).catch((function(t){e.setState({error:t})}))}else{var a=new Error('"'+n+'" is not a valid URL');e.setState({error:a})}},e.sendFinPayload=function(){var t=e.props.options.analyticsOptions,n=t.server,o=t.postEnd,r=e.state.update;o&&r&&fetch(n+"/"+r,e.getRequestInit()).then((function(t){return p(t)})).catch((function(t){var e="analytics-panel final payload error : "+t.name+" : "+t.message;console.log(e)}))},e}return function(t,e){function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}(e,t),e.prototype.componentDidMount=function(){this.sendInitPayload()},e.prototype.componentWillUnmount=function(){this.sendFinPayload()},e.prototype.render=function(){var t=this,e=this.props,n=e.width,o=e.height,r=this.props.options.analyticsOptions,a=this.state.error;return i.a.createElement("div",{style:{position:"relative",overflow:"auto",width:n,height:o}},a&&i.a.createElement("div",{style:{display:"inline-block",textAlign:"center",width:"100%"}},i.a.createElement(d.ErrorWithStack,{title:"analytics-panel error",error:a,errorInfo:null}),i.a.createElement(d.Button,{onClick:function(){return t.sendInitPayload()}},"Retry")),!r.hidden&&i.a.createElement(d.JSONFormatter,{json:this.body()}))},e}(a.PureComponent);n.d(e,"plugin",(function(){return b}));var h=window.location.href,b=new o.PanelPlugin(y).setPanelOptions((function(t){t.addTextInput({path:"analyticsOptions.server",name:"Endpoint",defaultValue:"https://localhost:8080/telegraf",description:"Location to POST data on panel load."}).addTextInput({path:"analyticsOptions.key",name:"Dashboard ID",defaultValue:h.replace(/^.+\/d\//g,"").replace(/\/.+$/g,""),description:"Unique value to identify the dashboard."}).addTextInput({path:"analyticsOptions.description",name:"Dashboard Description",defaultValue:h.replace(/^.+\/d\/.+\//g,"").replace(/\?.+$/g,""),description:"Description of the dashboard."}).addBooleanSwitch({path:"analyticsOptions.hidden",name:"Hide JSON",description:"Hides the printed JSON object on the panel.",defaultValue:!1}).addBooleanSwitch({path:"analyticsOptions.noCors",name:"No CORS",description:"Sets request mode to no-cors.",defaultValue:!1}).addBooleanSwitch({path:"analyticsOptions.postEnd",name:"Post End",description:"Sends a second request when the panel is unloaded. Disable if you are using Telegraf.",defaultValue:!1}).addBooleanSwitch({path:"analyticsOptions.flatten",name:"Flatten",description:"Flattens the payload JSON. Enable if you are using Telegraf.",defaultValue:!0})}))}])})); //# sourceMappingURL=module.js.map \ No newline at end of file diff --git a/dist/plugin.json b/dist/plugin.json index f3aac3b..c3ca661 100644 --- a/dist/plugin.json +++ b/dist/plugin.json @@ -5,19 +5,33 @@ "info": { "description": "Analytics Panel forwards data about your dashboard's users to an HTTP listener.", "author": { - "name": "Jacob Colvin" + "name": "Jacob Colvin", + "url": "https://jacobcolvin.com" }, - "keywords": ["dashboard","analytics","user","username"], + "keywords": [ + "dashboard", + "analytics", + "user", + "username", + "reports", + "report", + "reporting", + "track", + "tracking" + ], "logos": { "small": "img/logo.svg", "large": "img/logo.svg" }, "links": [ - {"name": "Website", "url": "https://github.com/MacroPower/macropower-analytics-panel"} + { + "name": "Website", + "url": "https://github.com/MacroPower/macropower-analytics-panel" + } ], "screenshots": [], - "version": "0.0.3", - "updated": "2020-06-09" + "version": "1.0.0", + "updated": "2020-06-15" }, "dependencies": { "grafanaVersion": "7.0.x",