Skip to content

Commit

Permalink
Allow users to share aggregated usage information with us (getredash#…
Browse files Browse the repository at this point in the history
…4108)

* Initial commit of BeaconConsent component

* Add comment about being able to change setting

* Use <Text> 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
  • Loading branch information
arikfr authored and harveyrendell committed Nov 14, 2019
1 parent 7f9bbd9 commit 51ad852
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 34 deletions.
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():
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

0 comments on commit 51ad852

Please sign in to comment.