diff --git a/app/cdap/components/CookieBanner/constants.ts b/app/cdap/components/CookieBanner/constants.ts new file mode 100644 index 00000000000..f593571d97f --- /dev/null +++ b/app/cdap/components/CookieBanner/constants.ts @@ -0,0 +1,17 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +export const DEFAULT_COOKIE_LINK = 'https://policies.google.com/technologies/cookies'; diff --git a/app/cdap/components/CookieBanner/index.tsx b/app/cdap/components/CookieBanner/index.tsx new file mode 100644 index 00000000000..788e2f29f71 --- /dev/null +++ b/app/cdap/components/CookieBanner/index.tsx @@ -0,0 +1,144 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import SnackBar from '@material-ui/core/Snackbar'; +import Button from '@material-ui/core/Button'; +import IconButton from '@material-ui/core/IconButton'; +import ClearIcon from '@material-ui/icons/Clear'; +import Container from '@material-ui/core/Container'; +import Cookies from 'universal-cookie'; +import T from 'i18n-react'; +import { DEFAULT_COOKIE_LINK } from './constants'; + +const okText = 'commons.gotIt'; +const learnMore = 'commons.learnMore'; +const cookieBannerText = 'commons.cookieBanner'; + +const thirteenMonths = new Date(); +thirteenMonths.setMonth(thirteenMonths.getMonth() + 13); + +const cookies = new Cookies(); +const cdapDataCookieWithTag = (tag: string) => { + return `CDAP_DATA_CONSENT_COOKIE_${tag}`; +}; + +interface IBannerProps { + cookieName: string; + cookieLink: string; + cookieText: string; +} + +type IModalBodyProps = Omit & { + close: () => void; +}; + +const SnackbarContainer = styled(Container)` + && { + background-color: #333333; + border-radius: 4px; + display: flex; + padding: 20px; + } +`; + +const SnackbarText = styled.span` + color: white; + flex: 3; + font-size: 13px; + font-weight: 400; + line-height: 20px; + a { + color: #a1c2fa; + } +`; + +const ActionsContainer = styled.div` + flex: 1; + display: flex; + justify-content: space-evenly; +`; + +const ConsentButton = styled(Button)` + &&& { + color: #a1c2fa; + font-size: 14px; + whitespace: nowrap; + } +`; + +const SnackbarContent = ({ cookieLink, cookieText, close }: IModalBodyProps) => { + return ( + + + {cookieText} {T.translate(learnMore).toString()} + + + + {T.translate(okText).toString()} + + + + + + + ); +}; + +/** + * If the user has analytics enabled and if the consent cookie doesn't exist + * on their computer, show the cookie banner + */ +export const CookieBanner = () => { + const gtm = window.CDAP_CONFIG.googleTagManager; + if (gtm === '') { + return null; + } + + const cookieText = + window.CDAP_CONFIG.cookieBannerText || T.translate(cookieBannerText).toString(); + const cookieLink = window.CDAP_CONFIG.cookieBannerLink || DEFAULT_COOKIE_LINK; + const dataCookie = cdapDataCookieWithTag(gtm); + const cdapDataConsented = cookies.get(dataCookie); + if (!cdapDataConsented && cookieLink !== '' && cookieText !== '') { + return ; + } + + return null; +}; + +const Banner = ({ cookieName, cookieLink, cookieText }: IBannerProps) => { + const [open, setOpen] = useState(true); + + const handleClose = () => { + setOpen(false); + cookies.set(cookieName, true, { + expires: thirteenMonths, + path: '/', + }); + }; + + return ( + + + + ); +}; diff --git a/app/cdap/main.js b/app/cdap/main.js index b297e80ed90..384fce67502 100644 --- a/app/cdap/main.js +++ b/app/cdap/main.js @@ -62,6 +62,7 @@ import ee from 'event-emitter'; import globalEvents from 'services/global-events'; import { handlePageLevelError, objectQuery, setupExperiments } from 'services/helpers'; import history from 'services/history'; +import { CookieBanner } from 'components/CookieBanner'; // See ./graphql/fragements/README.md import introspectionQueryResultData from '../../graphql/fragments/fragmentTypes.json'; @@ -365,6 +366,7 @@ class CDAP extends Component { objectQuery(this.state, 'pageLevelError', 'errorCode') === 403; return ( +
diff --git a/app/cdap/text/text-en.yaml b/app/cdap/text/text-en.yaml index 01ca2205cab..86af0cf6453 100644 --- a/app/cdap/text/text-en.yaml +++ b/app/cdap/text/text-en.yaml @@ -5,6 +5,9 @@ commons: apply: Apply as: as back: Back + learnMore: Learn More + gotIt: Ok, Got it. + cookieBanner: CDF uses cookies from Google to deliver and enhance the quality of its services and to analyze traffic. cask: CASK cdap: CDAP clickhere: click here diff --git a/server/express.js b/server/express.js index a85cb0763e0..f0693b16f4a 100644 --- a/server/express.js +++ b/server/express.js @@ -258,6 +258,9 @@ function makeApp(authAddress, cdapConfig, uiSettings) { featureFlags: cdapConfig.featureFlags, analyticsTag: cdapConfig['ui.analyticsTag'], googleTagManager: cdapConfig['ui.GTM'], + cookieBannerText: cdapConfig['ui.cookieBannerText'], + cookieBannerLink: cdapConfig['ui.cookieBannerLink'] + }); res.header({