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.
+
+ confirmConsent(true)}>
+ Yes
+
+ confirmConsent(false)}>
+ No
+
+
+
+
+
+
+ );
+}
+
+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 a698dfb496..2a5082d601 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)