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

Bring back version check & beacon reporting #7211

Merged
merged 8 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/restyled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
ref: ${{ github.event.pull_request.head.ref }}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did we mean to make this change? Restyled is failing on recent PRs....not clear to me why git checkout is failing.

Copy link
Member

Choose a reason for hiding this comment

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

Seems related to this? #7191 (comment)


- uses: restyled-io/actions/setup@v4
- id: restyler
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { clientConfig } from "@/services/auth";
import Link from "@/components/Link";
import { clientConfig, currentUser } from "@/services/auth";
import frontendVersion from "@/version.json";

export default function VersionInfo() {
Expand All @@ -9,6 +10,15 @@ export default function VersionInfo() {
Version: {clientConfig.version}
{frontendVersion !== clientConfig.version && ` (${frontendVersion.substring(0, 8)})`}
</div>
{clientConfig.newVersionAvailable && currentUser.hasPermission("super_admin") && (
<div className="m-t-10">
{/* eslint-disable react/jsx-no-target-blank */}
<Link href="https://version.redash.io/" className="update-available" target="_blank" rel="noopener">
Update Available <i className="fa fa-external-link m-l-5" aria-hidden="true" />
<span className="sr-only">(opens in a new tab)</span>
</Link>
</div>
)}
</React.Fragment>
);
}
79 changes: 79 additions & 0 deletions client/app/components/BeaconConsent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useState } from "react";
import Card from "antd/lib/card";
import Button from "antd/lib/button";
import Typography from "antd/lib/typography";
import { clientConfig } from "@/services/auth";
import Link from "@/components/Link";
import HelpTrigger from "@/components/HelpTrigger";
import DynamicComponent from "@/components/DynamicComponent";
import OrgSettings from "@/services/organizationSettings";

const Text = Typography.Text;

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 <Link href="settings/general">Settings</Link> page.
</Text>
</div>
</Card>
</div>
</DynamicComponent>
);
}

export default BeaconConsent;
22 changes: 13 additions & 9 deletions client/app/components/HelpTrigger.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const TYPES = mapValues(
VALUE_SOURCE_OPTIONS: ["/user-guide/querying/query-parameters#Value-Source-Options", "Guide: Value Source Options"],
SHARE_DASHBOARD: ["/user-guide/dashboards/sharing-dashboards", "Guide: Sharing and Embedding Dashboards"],
AUTHENTICATION_OPTIONS: ["/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"],
DS_BIGQUERY: ["/data-sources/bigquery-setup", "Guide: Help Setting up BigQuery"],
DS_URL: ["/data-sources/querying-urls", "Guide: Help Setting up URL"],
Expand Down Expand Up @@ -100,7 +101,7 @@ export function helpTriggerWithTypes(types, allowedDomains = [], drawerClassName
clearTimeout(this.iframeLoadingTimeout);
}

loadIframe = url => {
loadIframe = (url) => {
clearTimeout(this.iframeLoadingTimeout);
this.setState({ loading: true, error: false });

Expand All @@ -115,8 +116,8 @@ export function helpTriggerWithTypes(types, allowedDomains = [], drawerClassName
clearTimeout(this.iframeLoadingTimeout);
};

onPostMessageReceived = event => {
if (!some(allowedDomains, domain => startsWith(event.origin, domain))) {
onPostMessageReceived = (event) => {
if (!some(allowedDomains, (domain) => startsWith(event.origin, domain))) {
return;
}

Expand All @@ -133,7 +134,7 @@ export function helpTriggerWithTypes(types, allowedDomains = [], drawerClassName
return helpTriggerType ? helpTriggerType[0] : this.props.href;
};

openDrawer = e => {
openDrawer = (e) => {
// keep "open in new tab" behavior
if (!e.shiftKey && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
Expand All @@ -143,7 +144,7 @@ export function helpTriggerWithTypes(types, allowedDomains = [], drawerClassName
}
};

closeDrawer = event => {
closeDrawer = (event) => {
if (event) {
event.preventDefault();
}
Expand All @@ -160,7 +161,7 @@ export function helpTriggerWithTypes(types, allowedDomains = [], drawerClassName
const tooltip = get(types, `${this.props.type}[1]`, this.props.title);
const className = cx("help-trigger", this.props.className);
const url = this.state.currentUrl;
const isAllowedDomain = some(allowedDomains, domain => startsWith(url || targetUrl, domain));
const isAllowedDomain = some(allowedDomains, (domain) => startsWith(url || targetUrl, domain));
const shouldRenderAsLink = this.props.renderAsLink || !isAllowedDomain;

return (
Expand All @@ -179,13 +180,15 @@ export function helpTriggerWithTypes(types, allowedDomains = [], drawerClassName
)}
</>
) : null
}>
}
>
<Link
href={url || this.getUrl()}
className={className}
rel="noopener noreferrer"
target="_blank"
onClick={shouldRenderAsLink ? () => {} : this.openDrawer}>
onClick={shouldRenderAsLink ? () => {} : this.openDrawer}
>
{this.props.children}
</Link>
</Tooltip>
Expand All @@ -196,7 +199,8 @@ export function helpTriggerWithTypes(types, allowedDomains = [], drawerClassName
visible={this.state.visible}
className={cx("help-drawer", drawerClassName)}
destroyOnClose
width={400}>
width={400}
>
<div className="drawer-wrapper">
<div className="drawer-menu">
{url && (
Expand Down
9 changes: 6 additions & 3 deletions client/app/pages/home/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Link from "@/components/Link";
import routeWithUserSession from "@/components/ApplicationArea/routeWithUserSession";
import EmptyState, { EmptyStateHelpMessage } from "@/components/empty-state/EmptyState";
import DynamicComponent from "@/components/DynamicComponent";
import BeaconConsent from "@/components/BeaconConsent";
import PlainButton from "@/components/PlainButton";

import { axios } from "@/services/axios";
Expand All @@ -30,7 +31,8 @@ function DeprecatedEmbedFeatureAlert() {
<Link
href="https://discuss.redash.io/t/support-for-parameters-in-embedded-visualizations/3337"
target="_blank"
rel="noopener noreferrer">
rel="noopener noreferrer"
>
Read more
</Link>
.
Expand All @@ -42,7 +44,7 @@ function DeprecatedEmbedFeatureAlert() {

function EmailNotVerifiedAlert() {
const verifyEmail = () => {
axios.post("verification_email/").then(data => {
axios.post("verification_email/").then((data) => {
notification.success(data.message);
});
};
Expand Down Expand Up @@ -88,6 +90,7 @@ export default function Home() {
</DynamicComponent>
<DynamicComponent name="HomeExtra" />
<DashboardAndQueryFavoritesList />
<BeaconConsent />
</div>
</div>
);
Expand All @@ -98,6 +101,6 @@ routes.register(
routeWithUserSession({
path: "/",
title: "Redash",
render: pageProps => <Home {...pageProps} />,
render: (pageProps) => <Home {...pageProps} />,
})
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react";
import Form from "antd/lib/form";
import Checkbox from "antd/lib/checkbox";
import Skeleton from "antd/lib/skeleton";
import HelpTrigger from "@/components/HelpTrigger";
import DynamicComponent from "@/components/DynamicComponent";
import { SettingsEditorPropTypes, SettingsEditorDefaultProps } from "../prop-types";

export default function BeaconConsentSettings(props) {
const { values, onChange, loading } = props;

return (
<DynamicComponent name="OrganizationSettings.BeaconConsentSettings" {...props}>
<Form.Item
label={
<span>
Anonymous Usage Data Sharing
<HelpTrigger className="m-l-5 m-r-5" type="USAGE_DATA_SHARING" />
</span>
}
>
{loading ? (
<Skeleton title={{ width: 300 }} paragraph={false} active />
) : (
<Checkbox
name="beacon_consent"
checked={values.beacon_consent}
onChange={(e) => onChange({ beacon_consent: e.target.checked })}
>
Help Redash improve by automatically sending anonymous usage data
</Checkbox>
)}
</Form.Item>
</DynamicComponent>
);
}

BeaconConsentSettings.propTypes = SettingsEditorPropTypes;

BeaconConsentSettings.defaultProps = SettingsEditorDefaultProps;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DynamicComponent from "@/components/DynamicComponent";
import FormatSettings from "./FormatSettings";
import PlotlySettings from "./PlotlySettings";
import FeatureFlagsSettings from "./FeatureFlagsSettings";
import BeaconConsentSettings from "./BeaconConsentSettings";

export default function GeneralSettings(props) {
return (
Expand All @@ -13,6 +14,7 @@ export default function GeneralSettings(props) {
<FormatSettings {...props} />
<PlotlySettings {...props} />
<FeatureFlagsSettings {...props} />
<BeaconConsentSettings {...props} />
</DynamicComponent>
);
}
4 changes: 4 additions & 0 deletions redash/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ def create_app():
from .metrics import request as request_metrics
from .models import db, users
from .utils import sentry
from .version_check import reset_new_version_status

sentry.init()
app = Redash()

# Check and update the cached version for use by the client
reset_new_version_status()

security.init_app(app)
request_metrics.init_app(app)
db.init_app(app)
Expand Down
19 changes: 12 additions & 7 deletions redash/handlers/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
)
from redash.handlers import routes
from redash.handlers.base import json_response, org_scoped_rule
from redash.version_check import get_latest_version

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -256,11 +257,15 @@ def number_format_config():

def client_config():
if not current_user.is_api_user() and current_user.is_authenticated:
client_config_inner = {
client_config = {
"newVersionAvailable": bool(get_latest_version()),
"version": __version__,
}
else:
client_config_inner = {}
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 All @@ -280,12 +285,12 @@ def client_config():
"tableCellMaxJSONSize": settings.TABLE_CELL_MAX_JSON_SIZE,
}

client_config_inner.update(defaults)
client_config_inner.update({"basePath": base_href()})
client_config_inner.update(date_time_format_config())
client_config_inner.update(number_format_config())
client_config.update(defaults)
client_config.update({"basePath": base_href()})
client_config.update(date_time_format_config())
client_config.update(number_format_config())

return client_config_inner
return client_config


def messages():
Expand Down
11 changes: 10 additions & 1 deletion redash/handlers/setup.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
from flask import g, redirect, render_template, request, url_for
from flask_login import login_user
from wtforms import Form, PasswordField, StringField, validators
from wtforms import BooleanField, Form, PasswordField, StringField, validators
from wtforms.fields.html5 import EmailField

from redash import settings
from redash.authentication.org_resolving import current_org
from redash.handlers.base import routes
from redash.models import Group, Organization, User, db
from redash.tasks.general import subscribe


class SetupForm(Form):
name = StringField("Name", validators=[validators.InputRequired()])
email = EmailField("Email Address", validators=[validators.Email()])
password = PasswordField("Password", validators=[validators.Length(6)])
org_name = StringField("Organization Name", validators=[validators.InputRequired()])
security_notifications = BooleanField()
newsletter = BooleanField()


def create_org(org_name, user_name, email, password):
Expand Down Expand Up @@ -54,13 +57,19 @@ def setup():
return redirect("/")

form = SetupForm(request.form)
form.newsletter.data = True
form.security_notifications.data = True

if request.method == "POST" and form.validate():
default_org, user = create_org(form.org_name.data, form.name.data, form.email.data, form.password.data)

g.org = default_org
login_user(user)

# signup to newsletter if needed
if form.newsletter.data or form.security_notifications:
subscribe.delay(form.data)

return redirect(url_for("redash.index", org_slug=None))

return render_template("setup.html", form=form)
Loading
Loading