From 51ad8520e55a4a36ee9247bac9f07520b5ecf4e5 Mon Sep 17 00:00:00 2001 From: Arik Fraimovich Date: Sun, 15 Sep 2019 15:18:48 +0300 Subject: [PATCH] Allow users to share aggregated usage information with us (#4108) * Initial commit of BeaconConsent component * Add comment about being able to change setting * Use correctly * Final version of consent screen * Show beacon consent message on homepage only if it wasn't enabled already. * Add consent setting to organization settings screen. * Add support for custom message in OrgSetting.save. * Implmenet consent saving. * If consent given, send extra data * Add HelpTrigger * Make CodeClimate happy * Wrap everything with DynamicComponent --- client/app/components/BeaconConsent.jsx | 83 +++++++++++++++++++ client/app/components/HelpTrigger.jsx | 4 + client/app/pages/home/home.html | 52 +++++++++--- .../pages/settings/OrganizationSettings.jsx | 36 +++++--- client/app/services/organizationSettings.js | 15 ++-- redash/handlers/authentication.py | 3 + redash/settings/organization.py | 1 + redash/version_check.py | 59 ++++++++++++- 8 files changed, 219 insertions(+), 34 deletions(-) create mode 100644 client/app/components/BeaconConsent.jsx diff --git a/client/app/components/BeaconConsent.jsx b/client/app/components/BeaconConsent.jsx new file mode 100644 index 0000000000..e551eb6a0f --- /dev/null +++ b/client/app/components/BeaconConsent.jsx @@ -0,0 +1,83 @@ +import React, { useState } from 'react'; +import { react2angular } from 'react2angular'; +import Card from 'antd/lib/card'; +import Button from 'antd/lib/button'; +import Typography from 'antd/lib/typography'; +import { clientConfig } from '@/services/auth'; +import { HelpTrigger } from '@/components/HelpTrigger'; +import DynamicComponent from '@/components/DynamicComponent'; +import OrgSettings from '@/services/organizationSettings'; + +const Text = Typography.Text; + +export function BeaconConsent() { + const [hide, setHide] = useState(false); + + if (!clientConfig.showBeaconConsentMessage || hide) { + return null; + } + + const hideConsentCard = () => { + clientConfig.showBeaconConsentMessage = false; + setHide(true); + }; + + const confirmConsent = (confirm) => { + let message = '🙏 Thank you.'; + + if (!confirm) { + message = 'Settings Saved.'; + } + + OrgSettings.save({ beacon_consent: confirm }, message) + // .then(() => { + // // const settings = get(response, 'settings'); + // // this.setState({ settings, formValues: { ...settings } }); + // }) + .finally(hideConsentCard); + }; + + return ( + +
+ + Would you be ok with sharing anonymous usage data with the Redash team?{' '} + + + )} + bordered={false} + > + Help Redash improve by automatically sending anonymous usage data: +
+
    +
  • Number of users, queries, dashboards, alerts, widgets and visualizations.
  • +
  • Types of data sources, alert destinations and visualizations.
  • +
+
+ All data is aggregated and will never include any sensitive or private data. +
+ + +
+
+ + You can change this setting anytime from the Organization Settings page. + +
+
+
+
+ ); +} + +export default function init(ngModule) { + ngModule.component('beaconConsent', react2angular(BeaconConsent)); +} + +init.init = true; diff --git a/client/app/components/HelpTrigger.jsx b/client/app/components/HelpTrigger.jsx index ce2bbd20ad..e3ece0cc9d 100644 --- a/client/app/components/HelpTrigger.jsx +++ b/client/app/components/HelpTrigger.jsx @@ -32,6 +32,10 @@ export const TYPES = { '/user-guide/users/authentication-options', 'Guide: Authentication Options', ], + USAGE_DATA_SHARING: [ + '/open-source/admin-guide/usage-data', + 'Help: Anonymous Usage Data Sharing', + ], DS_ATHENA: [ '/data-sources/amazon-athena-setup', 'Guide: Help Setting up Amazon Athena', diff --git a/client/app/pages/home/home.html b/client/app/pages/home/home.html index 0e362af2b5..29d8eace91 100644 --- a/client/app/pages/home/home.html +++ b/client/app/pages/home/home.html @@ -1,9 +1,24 @@
-
- You have enabled ALLOW_PARAMETERS_IN_EMBEDS. This setting is now deprecated and should be turned off. Parameters in embeds are supported by default. Read more. +
+ You have enabled ALLOW_PARAMETERS_IN_EMBEDS. This setting is + now deprecated and should be turned off. Parameters in embeds are supported + by default. + Read more.
-
- We have sent an email with a confirmation link to your email address. Please follow the link to verify your email address. Resend email. +
+ We have sent an email with a confirmation link to your email address. Please + follow the link to verify your email address. + Resend email.
@@ -51,17 +72,24 @@ Favorite Queries will appear here

- + diff --git a/client/app/pages/settings/OrganizationSettings.jsx b/client/app/pages/settings/OrganizationSettings.jsx index 75bdfbbf0e..32e4d6c83b 100644 --- a/client/app/pages/settings/OrganizationSettings.jsx +++ b/client/app/pages/settings/OrganizationSettings.jsx @@ -10,13 +10,14 @@ import Select from 'antd/lib/select'; import Checkbox from 'antd/lib/checkbox'; import Tooltip from 'antd/lib/tooltip'; import LoadingState from '@/components/items-list/components/LoadingState'; -import { HelpTrigger } from '@/components/HelpTrigger'; import { routesToAngularRoutes } from '@/lib/utils'; import { clientConfig } from '@/services/auth'; import settingsMenu from '@/services/settingsMenu'; import recordEvent from '@/services/recordEvent'; import OrgSettings from '@/services/organizationSettings'; +import { HelpTrigger } from '@/components/HelpTrigger'; +import DynamicComponent from '@/components/DynamicComponent'; const Option = Select.Option; @@ -155,16 +156,16 @@ class OrganizationSettings extends React.Component { ))} - + this.handleChange('multi_byte_search_enabled', e.target.checked)} + name="feature_show_permissions_control" + checked={formValues.feature_show_permissions_control} + onChange={e => this.handleChange('feature_show_permissions_control', e.target.checked)} > - Enable multi-byte (Chinese, Japanese, and Korean) search for query names and descriptions (slower) + Enable experimental multiple owners support - + Email query owners when scheduled queries fail - + this.handleChange('feature_show_permissions_control', e.target.checked)} + name="multi_byte_search_enabled" + checked={formValues.multi_byte_search_enabled} + onChange={e => this.handleChange('multi_byte_search_enabled', e.target.checked)} > - Enable experimental multiple owners support + Enable multi-byte (Chinese, Japanese, and Korean) search for query names and descriptions (slower) + + Anonymous Usage Data Sharing }> + this.handleChange('beacon_consent', e.target.checked)} + > + Help Redash improve by automatically sending anonymous usage data + + + ); } diff --git a/client/app/services/organizationSettings.js b/client/app/services/organizationSettings.js index 1226bd35a6..dde3c01ea6 100644 --- a/client/app/services/organizationSettings.js +++ b/client/app/services/organizationSettings.js @@ -3,10 +3,13 @@ import notification from '@/services/notification'; export default { get: () => $http.get('api/settings/organization').then(response => response.data), - save: data => $http.post('api/settings/organization', data).then((response) => { - notification.success('Settings changes saved.'); - return response.data; - }).catch(() => { - notification.error('Failed saving changes.'); - }), + save: (data, message = 'Settings changes saved.') => $http + .post('api/settings/organization', data) + .then((response) => { + notification.success(message); + return response.data; + }) + .catch(() => { + notification.error('Failed saving changes.'); + }), }; diff --git a/redash/handlers/authentication.py b/redash/handlers/authentication.py index d7ad6e3511..dd1eea6451 100644 --- a/redash/handlers/authentication.py +++ b/redash/handlers/authentication.py @@ -225,6 +225,9 @@ def client_config(): } else: client_config = {} + + if current_user.has_permission('admin') and current_org.get_setting('beacon_consent') is None: + client_config['showBeaconConsentMessage'] = True defaults = { 'allowScriptsInUserInput': settings.ALLOW_SCRIPTS_IN_USER_INPUT, diff --git a/redash/settings/organization.py b/redash/settings/organization.py index 37c5b73260..853a6cd4ec 100644 --- a/redash/settings/organization.py +++ b/redash/settings/organization.py @@ -35,6 +35,7 @@ os.environ.get('REDASH_SEND_EMAIL_ON_FAILED_SCHEDULED_QUERIES', 'false')) settings = { + "beacon_consent": None, "auth_password_login_enabled": PASSWORD_LOGIN_ENABLED, "auth_saml_enabled": SAML_LOGIN_ENABLED, "auth_saml_entity_id": SAML_ENTITY_ID, diff --git a/redash/version_check.py b/redash/version_check.py index 8d6e5b0bfd..0870460b8b 100644 --- a/redash/version_check.py +++ b/redash/version_check.py @@ -4,23 +4,74 @@ from redash import __version__ as current_version from redash import redis_connection +from redash.models import db, Organization from redash.utils import json_dumps REDIS_KEY = "new_version_available" +def usage_data(): + counts_query = """ + SELECT 'users_count' as name, count(0) as value + FROM users + WHERE disabled_at is null + + UNION ALL + + SELECT 'queries_count' as name, count(0) as value + FROM queries + WHERE is_archived is false + + UNION ALL + + SELECT 'alerts_count' as name, count(0) as value + FROM alerts + + UNION ALL + + SELECT 'dashboards_count' as name, count(0) as value + FROM dashboards + WHERE is_archived is false + + UNION ALL + + SELECT 'widgets_count' as name, count(0) as value + FROM widgets + WHERE visualization_id is not null + + UNION ALL + + SELECT 'textbox_count' as name, count(0) as value + FROM widgets + WHERE visualization_id is null + """ + + data_sources_query = "SELECT type, count(0) FROM data_sources GROUP by 1" + visualizations_query = "SELECT type, count(0) FROM visualizations GROUP by 1" + destinations_query = "SELECT type, count(0) FROM notification_destinations GROUP by 1" + + data = {name: value for (name, value) in db.session.execute(counts_query)} + data['data_sources'] = {name: value for (name, value) in db.session.execute(data_sources_query)} + data['visualization_types'] = {name: value for (name, value) in db.session.execute(visualizations_query)} + data['destination_types'] = {name: value for (name, value) in db.session.execute(destinations_query)} + + return data + + def run_version_check(): logging.info("Performing version check.") logging.info("Current version: %s", current_version) - data = json_dumps({ + data = { 'current_version': current_version - }) - headers = {'content-type': 'application/json'} + } + + if Organization.query.first().get_setting('beacon_consent'): + data['usage'] = usage_data() try: response = requests.post('https://version.redash.io/api/report?channel=stable', - data=data, headers=headers, timeout=3.0) + json=data, timeout=3.0) latest_version = response.json()['release']['version'] _compare_and_update(latest_version)