Skip to content

Commit

Permalink
Add integration for Osano/GDPR (#17565)
Browse files Browse the repository at this point in the history
  • Loading branch information
timroes committed Oct 5, 2022
1 parent dfaf7d8 commit 23274b5
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 11 deletions.
6 changes: 0 additions & 6 deletions airbyte-webapp/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="%PUBLIC_URL%/index.css">
<title>Airbyte</title>
<% if (process.env.REACT_APP_OSANO) { %>
<style>
.osano-cm-widget { display: none; }
</style>
<script src="https://cmp.osano.com/%REACT_APP_OSANO%/osano.js"></script>
<% } %>
</head>
<body>
<noscript>
Expand Down
8 changes: 6 additions & 2 deletions airbyte-webapp/src/components/ui/SideMenu/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ export interface SideMenuItem {
path: string;
name: string | React.ReactNode;
indicatorCount?: number;
component: React.ComponentType;
component?: React.ComponentType;
id?: string;
/**
* Will be called instead of the onSelect of the component if this link is clicked.
*/
onClick?: () => void;
}

export interface CategoryItem {
Expand Down Expand Up @@ -52,7 +56,7 @@ export const SideMenu: React.FC<SideMenuProps> = ({ data, onSelect, activeItem }
name={route.name}
isActive={activeItem?.endsWith(route.path)}
count={route.indicatorCount}
onClick={() => onSelect(route.path)}
onClick={route.onClick ?? (() => onSelect(route.path))}
/>
))}
</Category>
Expand Down
6 changes: 6 additions & 0 deletions airbyte-webapp/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ReactDOM from "react-dom";

import "react-reflex/styles.css";
import { isCloudApp } from "utils/app";
import { loadOsano } from "utils/dataPrivacy";

import "./globals";

Expand All @@ -16,6 +17,11 @@ Sentry.init({
tracesSampleRate: 1.0, // may need to adjust this in the future
});

// In Cloud load the Osano script (GDPR consent tool before anything else)
if (isCloudApp()) {
loadOsano();
}

const CloudApp = lazy(() => import(`packages/cloud/App`));
const App = lazy(() => import(`./App`));

Expand Down
1 change: 1 addition & 0 deletions airbyte-webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@
"settings.newsletter": "Newsletter with feature updates.",
"settings.account": "Account",
"settings.accountSettings.updateEmailSuccess": "Email updated",
"settings.cookiePreferences": "Cookie Preferences",

"connector.requestConnectorBlock": "+ Request a new connector",
"connector.requestConnector": "Request a new connector",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import * as yup from "yup";

import HeadTitle from "components/HeadTitle";

import { isGdprCountry } from "utils/dataPrivacy";

import { FieldError } from "../lib/errors/FieldError";
import { useAuthService } from "../services/auth/AuthService";
import { EmailLinkErrorCodes } from "../services/auth/types";
Expand Down Expand Up @@ -35,7 +37,7 @@ export const AcceptEmailInvite: React.FC = () => {
name: "",
email: "",
password: "",
news: true,
news: !isGdprCountry(),
}}
validationSchema={ValidationSchema}
onSubmit={async ({ name, email, password, news }, { setFieldError, setStatus }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useConfig } from "config";
import { useExperiment } from "hooks/services/Experiment";
import { FieldError } from "packages/cloud/lib/errors/FieldError";
import { useAuthService } from "packages/cloud/services/auth/AuthService";
import { isGdprCountry } from "utils/dataPrivacy";

import CheckBoxControl from "../../components/CheckBoxControl";
import { BottomBlock, FieldItem, Form, RowFieldItem } from "../../components/FormComponents";
Expand Down Expand Up @@ -205,7 +206,7 @@ export const SignupForm: React.FC = () => {
companyName: search.company ?? "",
email: search.email ?? "",
password: "",
news: true,
news: !isGdprCountry(),
};
return (
<Formik<FormValues>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
// import ConfigurationsPage from "pages/SettingsPage/pages/ConfigurationsPage";
import NotificationPage from "pages/SettingsPage/pages/NotificationPage";
import { PageConfig, SettingsRoute } from "pages/SettingsPage/SettingsPage";
import { isOsanoActive, showOsanoDrawer } from "utils/dataPrivacy";

const CloudSettingsRoutes = {
Configuration: SettingsRoute.Configuration,
Expand Down Expand Up @@ -40,6 +41,15 @@ export const CloudSettingsPage: React.FC = () => {
name: <FormattedMessage id="settings.account" />,
component: AccountSettingsView,
},
...(isOsanoActive()
? [
{
name: <FormattedMessage id="settings.cookiePreferences" />,
path: "__COOKIE_PREFERENCES__", // Special path with no meaning, since the onClick will be triggered
onClick: () => showOsanoDrawer(),
},
]
: []),
],
},
{
Expand Down
6 changes: 5 additions & 1 deletion airbyte-webapp/src/pages/SettingsPage/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import HeadTitle from "components/HeadTitle";
import LoadingPage from "components/LoadingPage";
import MainPageWithScroll from "components/MainPageWithScroll";
import { PageHeader } from "components/ui/PageHeader";
import { SideMenu, CategoryItem } from "components/ui/SideMenu";
import { SideMenu, CategoryItem, SideMenuItem } from "components/ui/SideMenu";

import useConnector from "hooks/services/useConnector";

Expand Down Expand Up @@ -105,6 +105,10 @@ const SettingsPage: React.FC<SettingsPageProps> = ({ pageConfig }) => {
<Routes>
{menuItems
.flatMap((menuItem) => menuItem.routes)
.filter(
(menuItem): menuItem is SideMenuItem & { component: NonNullable<SideMenuItem["component"]> } =>
!!menuItem.component
)
.map(({ path, component: Component }) => (
<Route key={path} path={path} element={<Component />} />
))}
Expand Down
33 changes: 33 additions & 0 deletions airbyte-webapp/src/utils/dataPrivacy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { isGdprCountry } from "./dataPrivacy";

const mockTimeZone = (timeZone: string) => {
jest.spyOn(Intl, "DateTimeFormat").mockImplementation(
() =>
({
resolvedOptions: () =>
({
timeZone,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any)
);
};

describe("dataPrivacy", () => {
describe("isGdprCountry()", () => {
afterEach(() => {
jest.clearAllMocks();
});

it("should return true for timezones inside EU", () => {
mockTimeZone("Europe/Berlin");
expect(isGdprCountry()).toBe(true);
});

it("should return false for non EU countries", () => {
mockTimeZone("America/Chicago");
expect(isGdprCountry()).toBe(false);
});
});
});
75 changes: 75 additions & 0 deletions airbyte-webapp/src/utils/dataPrivacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
declare global {
interface Window {
Osano?: {
cm: {
mode: "production" | "debug";
showDrawer: (type: string) => void;
};
};
}
}

const GDPR_TIMEZONES = [
"Africa/Ceuta",
"Asia/Famagusta",
"Asia/Nicosia",
"Atlantic/Azores",
"Atlantic/Canary",
"Atlantic/Madeira",
"Europe/Amsterdam",
"Europe/Athens",
"Europe/Berlin",
"Europe/Bratislava",
"Europe/Brussels",
"Europe/Bucharest",
"Europe/Budapest",
"Europe/Busingen",
"Europe/Copenhagen",
"Europe/Dublin",
"Europe/Helsinki",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/Luxembourg",
"Europe/Madrid",
"Europe/Malta",
"Europe/Paris",
"Europe/Prague",
"Europe/Riga",
"Europe/Rome",
"Europe/Sofia",
"Europe/Stockholm",
"Europe/Tallinn",
"Europe/Vienna",
"Europe/Vilnius",
"Europe/Warsaw",
"Europe/Zagreb",
];

export const isGdprCountry = (): boolean => {
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
return GDPR_TIMEZONES.includes(timeZone);
};

export const loadOsano = (): void => {
if (!process.env.REACT_APP_OSANO) {
return;
}

// Create style element to hide osano widget
const style = document.createElement("style");
style.appendChild(document.createTextNode(".osano-cm-widget { display: none; }"));
document.head.appendChild(style);

// Create and append the script tag to load osano
const script = document.createElement("script");
script.src = `https://cmp.osano.com/${process.env.REACT_APP_OSANO}/osano.js`;
document.head.appendChild(script);
};

export const isOsanoActive = (): boolean => {
return window.Osano?.cm.mode === "production";
};

export const showOsanoDrawer = (): void => {
window.Osano?.cm.showDrawer("osano-cm-dom-info-dialog-open");
};

0 comments on commit 23274b5

Please sign in to comment.