-
Notifications
You must be signed in to change notification settings - Fork 2
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
Cookie consent#45 #47
Open
justin-b-yee
wants to merge
14
commits into
main
Choose a base branch
from
cookie-consent#45
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
27ff54f
Installed vanilla cookie consent package.
justin-b-yee 0408a71
Inserted changes from branch in Enciv Home to display the cookie cons…
justin-b-yee a158cfb
Added initial Consent model and tests to store and manage GDPR data.
justin-b-yee bb663b0
Updated Consent model to update multiple consent categories at once, …
justin-b-yee 614fb5a
Removed hardcoded analytics run, implemented partial progress in hand…
justin-b-yee a1545ac
Added individual service toggles and hooks.
justin-b-yee 124b827
Added socket-api for saving consent and changed update consent who se…
justin-b-yee cbdc743
Added more tests to save-consent socket API, fixed IP address retrieval.
justin-b-yee e61bca0
Partial progress in calling save-consent from client.
justin-b-yee aba7b9d
Call save-consent socket API when cookie is updated.
justin-b-yee 6a7e38f
Split cookies into its own component.
justin-b-yee 6d8ba5d
Passed synuser in socket API call, and moved cookie-consent CSS to re…
justin-b-yee 337fb2d
Changed helmet import to react-helmet.
justin-b-yee dbad8c4
window.process.env different than process.env
ddfridley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import React, { useEffect, useState, useRef } from 'react' | ||
import Helmet from 'react-helmet' | ||
import * as CookieConsent from 'vanilla-cookieconsent' | ||
|
||
const CConsentStyleHelmet = () => ( | ||
<Helmet> | ||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orestbida/[email protected]/dist/cookieconsent.css" /> | ||
</Helmet> | ||
) | ||
|
||
function EncivCookies(props) { | ||
const { user } = props | ||
const [cookie, setCookie] = useState() | ||
const hasMounted = useRef(false) | ||
|
||
useEffect(() => { | ||
// Prevent this running on the initial render | ||
if (!hasMounted.current) { | ||
hasMounted.current = true | ||
return | ||
} | ||
|
||
const userId = user?.id || user?.tempId | ||
const synuser = { synuser: { id: userId } } | ||
|
||
const consent = CookieConsent.getCookie() | ||
|
||
// Retrieve information from lookups and format | ||
let formattedConsentData = [] | ||
for (const category of Object.keys(modalSections)) { | ||
formattedConsentData.push({ | ||
category: category, | ||
isGranted: consent.categories.includes(category), | ||
terms: modalSections[category].description, | ||
services: consent.services[category], | ||
}) | ||
} | ||
|
||
// Call the server to save consent to database | ||
window.socket.emit('save-consent', synuser, formattedConsentData, () => { | ||
console.log('Consent data successfully saved.') | ||
}) | ||
}, [cookie]) | ||
|
||
useEffect(() => { | ||
CookieConsent.run({ | ||
onFirstConsent: cookie => { | ||
setCookie(cookie) | ||
}, | ||
onChange: cookie => { | ||
setCookie(cookie) | ||
}, | ||
categories: consentCategories, | ||
language: { | ||
default: 'en', | ||
translations: { | ||
en: { | ||
consentModal: { | ||
title: 'We use cookies', | ||
description: 'Cookie modal description', | ||
acceptAllBtn: 'Accept all', | ||
acceptNecessaryBtn: 'Reject all', | ||
showPreferencesBtn: 'Manage Individual preferences', | ||
}, | ||
preferencesModal: { | ||
title: 'Manage cookie preferences', | ||
acceptAllBtn: 'Accept all', | ||
acceptNecessaryBtn: 'Reject all', | ||
savePreferencesBtn: 'Accept current selection', | ||
closeIconLabel: 'Close modal', | ||
sections: [ | ||
...Object.values(modalSections), | ||
{ | ||
title: 'More information', | ||
description: | ||
'For any queries in relation to my policy on cookies and your choices, please <a href="#contact-page">contact us</a>', | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
}, []) | ||
|
||
// The sections that show in the consent modal | ||
const modalSections = { | ||
necessary: { | ||
title: 'Strictly Necessary cookies', | ||
description: 'These cookies are essential for the proper functioning of the website and cannot be disabled.', | ||
|
||
//this field will generate a toggle linked to the 'necessary' category | ||
linkedCategory: 'necessary', | ||
}, | ||
analytics: { | ||
title: 'Performance and Analytics', | ||
description: | ||
'These cookies collect information about how you use our website. All of the data is anonymized and cannot be used to identify you.', | ||
linkedCategory: 'analytics', | ||
}, | ||
} | ||
|
||
// We can extend this by storing in the database | ||
const services = { | ||
necessary: [], | ||
analytics: [ | ||
{ | ||
label: 'Google Analytics', | ||
onAccept: () => { | ||
if (window.process.env.GOOGLE_ANALYTICS) { | ||
// using window.process becase there's a process.env that's different | ||
window.dataLayer = window.dataLayer || [] | ||
window.gtag = function () { | ||
dataLayer.push(arguments) | ||
} | ||
gtag('js', new Date()) | ||
gtag('config', `${window.process.env.GOOGLE_ANALYTICS}`) | ||
const script = document.createElement('script') // create a script DOM node | ||
script.src = `https://www.googletagmanager.com/gtag/js?id=${window.process.env.GOOGLE_ANALYTICS}` | ||
script.id = 'googletagmanager' // so we can find it and delete it if needed | ||
document.head.appendChild(script) | ||
} | ||
}, | ||
onReject: () => { | ||
delete window.dataLayer | ||
delete window.gtag | ||
const gtmElement = document.getElementById('googletagmanager') | ||
if (gtmElement) gtmElement.remove() | ||
}, | ||
}, | ||
], | ||
} | ||
|
||
/* | ||
Format the services data lists for each category. | ||
|
||
Was a bit hard to find documentation, | ||
but this is the object structure for displaying individual services. | ||
{ | ||
service1: { | ||
label: 'service1', | ||
onAccept: Func(), | ||
onReject: Func(), | ||
}, | ||
service2: {...} | ||
... | ||
} | ||
*/ | ||
|
||
const consentCategories = {} | ||
// Init the services lists | ||
for (const key of Object.keys(services)) { | ||
consentCategories[key] = { | ||
services: services[key].reduce((result, service) => { | ||
result[service.label] = { ...service } | ||
return result | ||
}, {}), | ||
} | ||
|
||
if (key === 'necessary') { | ||
consentCategories[key].readOnly = true | ||
consentCategories[key].enabled = true | ||
} | ||
} | ||
|
||
return ( | ||
<div> | ||
<CConsentStyleHelmet /> | ||
</div> | ||
) | ||
} | ||
|
||
export default EncivCookies |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
const { Mongo } = require('@enciv/mongo-collections') | ||
import { MongoMemoryServer } from 'mongodb-memory-server' | ||
const Consent = require('../consent') | ||
|
||
const USER_ID = '6667d5a33da5d19ddc304a6b' | ||
|
||
// dummy out logger for tests | ||
if (!global.logger) { | ||
global.logger = console | ||
} | ||
|
||
let MemoryServer | ||
beforeAll(async () => { | ||
MemoryServer = await MongoMemoryServer.create() | ||
const uri = MemoryServer.getUri() | ||
await Mongo.connect(uri) | ||
}) | ||
|
||
afterAll(async () => { | ||
Mongo.disconnect() | ||
MemoryServer.stop() | ||
}) | ||
|
||
test('Test the database is empty on startup.', async () => { | ||
const count = await Consent.count({}) | ||
expect(count).toBe(0) | ||
}) | ||
|
||
test('Testing adding a consent obj.', async () => { | ||
const aConsent = { who: { userId: USER_ID } } | ||
const consent = await Consent.create(aConsent) | ||
expect(consent).toMatchObject(aConsent) | ||
}) | ||
|
||
test('Test consents exist in the DB.', async () => { | ||
const consents = await Consent.find({}).toArray() | ||
expect(consents.length).toBeGreaterThan(0) | ||
// this will fail if there are other test running and putting things in the database | ||
}) | ||
|
||
test('Test adding consent data.', async () => { | ||
const aConsent = { who: { userId: USER_ID }, what: {} } | ||
await Consent.create(aConsent) | ||
|
||
const updatedDoc = await Consent.updateConsent({ userId: USER_ID }, [ | ||
{ category: 'ConsentOption1', isGranted: true, terms: 'By consenting, you agree to consent to this agreement.' }, | ||
]) | ||
|
||
expect(updatedDoc).toMatchObject({ | ||
_id: /./, | ||
who: { userId: '6667d5a33da5d19ddc304a6b' }, | ||
what: { | ||
ConsentOption1: { | ||
isGranted: true, | ||
consentDate: expect.any(Date), | ||
terms: 'By consenting, you agree to consent to this agreement.', | ||
history: [], | ||
}, | ||
}, | ||
}) | ||
}) | ||
|
||
test('Test historical consent data is pushed.', async () => { | ||
const aConsent = { who: { userId: USER_ID }, what: {} } | ||
await Consent.create(aConsent) | ||
|
||
const userIdQuery = { userId: USER_ID } | ||
|
||
// Update the same consent option twice | ||
await Consent.updateConsent(userIdQuery, [ | ||
{ | ||
category: 'ConsentOption1', | ||
isGranted: true, | ||
terms: 'By consenting a second time, you agree to consent to this agreement being pushed to the history.', | ||
}, | ||
]) | ||
|
||
// Test adding multiple at once | ||
await Consent.updateConsent(userIdQuery, [ | ||
{ | ||
category: 'ConsentOption2', | ||
isGranted: true, | ||
terms: "By consenting to another option, you agree there's two options now.", | ||
}, | ||
{ | ||
category: 'ConsentOption3', | ||
isGranted: false, | ||
terms: "By consenting to a third option, you agree there's three options now.", | ||
}, | ||
]) | ||
|
||
const updatedDoc = await Consent.updateConsent(userIdQuery, [ | ||
{ category: 'ConsentOption1', isGranted: false, terms: "By revoking your consent, you don't agree to consent." }, | ||
]) | ||
|
||
expect(updatedDoc).toMatchObject({ | ||
_id: /./, | ||
who: { userId: '6667d5a33da5d19ddc304a6b' }, | ||
what: { | ||
ConsentOption1: { | ||
isGranted: false, | ||
consentDate: expect.any(Date), | ||
terms: "By revoking your consent, you don't agree to consent.", | ||
history: [ | ||
{ | ||
isGranted: true, | ||
consentDate: expect.any(Date), | ||
terms: 'By consenting, you agree to consent to this agreement.', | ||
}, | ||
{ | ||
isGranted: true, | ||
consentDate: expect.any(Date), | ||
terms: 'By consenting a second time, you agree to consent to this agreement being pushed to the history.', | ||
}, | ||
], | ||
}, | ||
ConsentOption2: { | ||
isGranted: true, | ||
consentDate: expect.any(Date), | ||
terms: "By consenting to another option, you agree there's two options now.", | ||
history: [], | ||
}, | ||
ConsentOption3: { | ||
isGranted: false, | ||
consentDate: expect.any(Date), | ||
terms: "By consenting to a third option, you agree there's three options now.", | ||
history: [], | ||
}, | ||
}, | ||
}) | ||
}) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this from process.env to window.process.env because it was getting a different value. I added a an issue to civil-client to fix the code in main to use process.env rather then global.env - but this fixes the immediate problem.