Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to share aggregated usage information with us #4108

Merged
merged 12 commits into from
Sep 15, 2019
83 changes: 83 additions & 0 deletions client/app/components/BeaconConsent.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<DynamicComponent name="BeaconConsent">
<div className="m-t-10 tiled">
<Card
title={(
<>
Would you be ok with sharing anonymous usage data with the Redash team?{' '}
<HelpTrigger type="USAGE_DATA_SHARING" />
</>
)}
bordered={false}
>
<Text>Help Redash improve by automatically sending anonymous usage data:</Text>
<div className="m-t-5">
<ul>
<li> Number of users, queries, dashboards, alerts, widgets and visualizations.</li>
<li> Types of data sources, alert destinations and visualizations.</li>
</ul>
</div>
<Text>All data is aggregated and will never include any sensitive or private data.</Text>
<div className="m-t-5">
<Button type="primary" className="m-r-5" onClick={() => confirmConsent(true)}>
Yes
</Button>
<Button type="default" onClick={() => confirmConsent(false)}>
No
</Button>
</div>
<div className="m-t-15">
<Text type="secondary">
You can change this setting anytime from the <a href="settings/organization">Organization Settings</a> page.
</Text>
</div>
</Card>
</div>
</DynamicComponent>
);
}

export default function init(ngModule) {
ngModule.component('beaconConsent', react2angular(BeaconConsent));
}

init.init = true;
4 changes: 4 additions & 0 deletions client/app/components/HelpTrigger.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
52 changes: 40 additions & 12 deletions client/app/pages/home/home.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
<div class="container">
<div ng-if="$ctrl.messages.includes('using-deprecated-embed-feature')" class="alert alert-warning">
You have enabled <code>ALLOW_PARAMETERS_IN_EMBEDS</code>. This setting is now deprecated and should be turned off. Parameters in embeds are supported by default. <a href="https://discuss.redash.io/t/support-for-parameters-in-embedded-visualizations/3337" target="_blank">Read more</a>.
<div
ng-if="$ctrl.messages.includes('using-deprecated-embed-feature')"
class="alert alert-warning"
>
You have enabled <code>ALLOW_PARAMETERS_IN_EMBEDS</code>. This setting is
now deprecated and should be turned off. Parameters in embeds are supported
by default.
<a
href="https://discuss.redash.io/t/support-for-parameters-in-embedded-visualizations/3337"
target="_blank"
>Read more</a
>.
</div>
<div ng-if="$ctrl.messages.includes('email-not-verified')" class="alert alert-warning">
We have sent an email with a confirmation link to your email address. Please follow the link to verify your email address. <a ng-click="$ctrl.verifyEmail()">Resend email</a>.
<div
ng-if="$ctrl.messages.includes('email-not-verified')"
class="alert alert-warning"
>
We have sent an email with a confirmation link to your email address. Please
follow the link to verify your email address.
<a ng-click="$ctrl.verifyEmail()">Resend email</a>.
</div>
<empty-state
title="'Welcome to Redash 👋'"
Expand Down Expand Up @@ -31,13 +46,19 @@
</p>

<div class="list-group">
<a ng-href="dashboard/{{dashboard.slug}}" class="list-group-item" ng-repeat="dashboard in $ctrl.favoriteDashboards"
ng-if="dashboard.is_favorite">
<a
ng-href="dashboard/{{ dashboard.slug }}"
class="list-group-item"
ng-repeat="dashboard in $ctrl.favoriteDashboards"
ng-if="dashboard.is_favorite"
>
<span class="btn-favourite">
<i class="fa fa-star" aria-hidden="true"></i>
</span>
{{dashboard.name}}
<span class="label label-default" ng-if="dashboard.is_draft">Unpublished</span>
{{ dashboard.name }}
<span class="label label-default" ng-if="dashboard.is_draft"
>Unpublished</span
>
</a>
</div>
</div>
Expand All @@ -51,17 +72,24 @@
Favorite <a href="queries">Queries</a> will appear here
</p>
<div class="list-group">
<a ng-href="queries/{{query.id}}" class="list-group-item" ng-repeat="query in $ctrl.favoriteQueries" ng-if="query.is_favorite">
<a
ng-href="queries/{{ query.id }}"
class="list-group-item"
ng-repeat="query in $ctrl.favoriteQueries"
ng-if="query.is_favorite"
>
<span class="btn-favourite">
<i class="fa fa-star" aria-hidden="true"></i>
</span>
{{query.name}}
<span class="label label-default" ng-if="query.is_draft">Unpublished</span>
{{ query.name }}
<span class="label label-default" ng-if="query.is_draft"
>Unpublished</span
>
</a>
</div>
</div>
</div>

</div>
</div>
<beacon-consent></beacon-consent>
</div>
36 changes: 24 additions & 12 deletions client/app/pages/settings/OrganizationSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -155,32 +156,43 @@ class OrganizationSettings extends React.Component {
))}
</Select>
</Form.Item>
<Form.Item label="Multi-byte Search">
<Form.Item label="Feature Flags">
<Checkbox
name="multi_byte_search_enabled"
checked={formValues.multi_byte_search_enabled}
onChange={e => 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
</Checkbox>
</Form.Item>
<Form.Item label="Email Reports">
<Form.Item>
<Checkbox
name="send_email_on_failed_scheduled_queries"
checked={formValues.send_email_on_failed_scheduled_queries}
onChange={e => this.handleChange('send_email_on_failed_scheduled_queries', e.target.checked)}
>Email query owners when scheduled queries fail
</Checkbox>
</Form.Item>
<Form.Item label="Feature Flags">
<Form.Item>
<Checkbox
name="feature_show_permissions_control"
checked={formValues.feature_show_permissions_control}
onChange={e => 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)
</Checkbox>
</Form.Item>
<DynamicComponent name="BeaconConsentSetting">
<Form.Item label={<>Anonymous Usage Data Sharing <HelpTrigger type="USAGE_DATA_SHARING" /></>}>
<Checkbox
name="beacon_consent"
checked={formValues.beacon_consent}
onChange={e => this.handleChange('beacon_consent', e.target.checked)}
>
Help Redash improve by automatically sending anonymous usage data
</Checkbox>
</Form.Item>
</DynamicComponent>
</React.Fragment>
);
}
Expand Down
15 changes: 9 additions & 6 deletions client/app/services/organizationSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
}),
};
3 changes: 3 additions & 0 deletions redash/handlers/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions redash/settings/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
59 changes: 55 additions & 4 deletions redash/version_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is not only checking for a new version anymore, should we rename it to something like "diagnostics" or something that will look less fishy when we send out info?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shows that we check for consent and such, so not sure it will seem fishy. I don't mind changing the name but it should be better than diagnostics ;-)

Going to merge this, but we can still rename it in a follow up.

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)
Expand Down