Skip to content

Commit

Permalink
Merge pull request #3 from voxmedia/prepare-for-package
Browse files Browse the repository at this point in the history
Prepare for Packaging
  • Loading branch information
Brian Anderson authored Jun 30, 2020
2 parents de51abb + aa5ef8c commit 56280fa
Show file tree
Hide file tree
Showing 15 changed files with 1,275 additions and 47 deletions.
13 changes: 13 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": ["last 2 versions", "> 1%", "IE 11", "not IE 10"]
}
}
]
],
"plugins": []
}
17 changes: 17 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '10.14.x'
registry-url: 'https://npm.pkg.github.com'
- run: yarn install
- run: yarn publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
*.DS_Store
dist
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
10.14.2
Empty file removed CHANGELOG
Empty file.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 1.1.0 (June 29th, 2020)

- Adds support for babel transpiled source
- Adds logging for framework
File renamed without changes.
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const PrivacyCompliance = require('./src/privacy_compliance');

module.exports = {
PrivacyCompliance,
};
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
{
"name": "data-privacy-compliance",
"version": "1.0",
"version": "1.1.0",
"description": "Vox Media's library for implementing data privacy frameworks",
"main": "index.js",
"license": "Apache-2.0",
"url": "https://github.com/voxmedia/data-privacy-compliance",
"private": false,
"files": [
"/dist"
],
"devDependencies": {
"@babel/cli": "^7.10.3",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.10.3",
"jest": "^26.0.1"
},
"scripts": {
"test": "jest"
"test": "jest",
"build": "NODE_ENV=production babel src --out-file dist/data-privacy-compliance.js --source-maps --ignore 'test/**' "
}
}
12 changes: 11 additions & 1 deletion src/frameworks/base.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
class FrameworkBase {
constructor() {}
constructor() {
this.privacyComplianceInstance = null;
}

get name() {
return this.constructor.name;
Expand All @@ -22,6 +24,14 @@ class FrameworkBase {
canAnswerCapability(capability) {
return this.supportedCapabilities().includes(capability);
}

setPrivacyComplianceInstance(pc) {
this.privacyComplianceInstance = pc;
}

log(...args) {
this.privacyComplianceInstance && this.privacyComplianceInstance.log(`[${this.name}]`, ...args);
}
}

module.exports = FrameworkBase;
5 changes: 0 additions & 5 deletions src/index.js

This file was deleted.

85 changes: 65 additions & 20 deletions src/privacy_compliance.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,83 @@ class PrivacyCompliance {
constructor() {
this.frameworks = [];
this.supportedCapabilities = new Set();
this.logEntries = [];
this.logger = (...args) => {
this.logEntries.push(args);
};
}

/**
* useConfig is a convenient way to pass configuration values to all frameworks.
* When a useConfig is called, every loaded framework will have their own
* useConfig methods called.
*
* The ideal pattern is to use keys to type the configs, for example:
* PrivacyCompliance.useConfig({
* usp: "1TFT"
* })
* Which could signal to any listening capabiltiy-frameworks to use the new
* US Privacy String
*
* @param {Object} someConfigs any config to share with all frameworks
*/
useConfig(someConfigs) {
this.frameworks.forEach(f => f.useConfig(someConfigs));
}

// For use with testing only
reset() {
this.frameworks = [];
this.supportedCapabilities = new Set();
/**
* Allows this libraries internal logs to be accessible to including libraries.
* The default is to also insert any missed logs. Because this is setup as a singleton,
* it is easy to miss the initial setup logs, that happened, before a logger was setup.
*
* @param {Function} logFunction to be called everytime a new log entry is generated
* @param {Boolean} relogMissedEntries when true will relog missed entries from before the logger was wired up
*/
useLogger(logFunction, relogMissedEntries = true) {
this.logger = logFunction;
if (relogMissedEntries) {
this.logEntries.forEach(entry => this.log(...entry));
}
}

log(...args) {
this.logger(...args);
}

addFramework(frameworkInstance) {
this.log('Adding new framework: ', frameworkInstance.name);
frameworkInstance.setPrivacyComplianceInstance(privacyComplianceSingleton);
this.frameworks.push(frameworkInstance);
frameworkInstance.supportedCapabilities().forEach(c => this.supportedCapabilities.add(c));
}

/**
* Checks if a given Capability (as a string) can be answered by the loaded
* frameworks.
*
* Caution: not all frameworks will be applicable and able to
* answer this capability for all environments.
*
* @param {String} capability the method name of the capability
* @returns Boolean true if the capability can be answered
*/
hasFrameworkLoadedToAnswerCapability(capability) {
return this.supportedCapabilities.has(capability);
}

/**
* Returns a list of applicable frameworks for this environment.
*/
applicableFrameworks() {
return this.frameworks.filter(f => f.isApplicable());
}

// For use with testing only
reset() {
this.frameworks = [];
this.supportedCapabilities = new Set();
}

/**
* This method will take a string, translate it into a method and call it
* on the added frameworks. If all applicable frameworks support this capability
Expand All @@ -49,12 +101,15 @@ class PrivacyCompliance {
*/
proxyToFrameworks(methodName) {
try {
return areAllTrue(
this.frameworks
.filter(f => f.isApplicable())
.filter(f => f.canAnswerCapability(methodName))
.map(f => f[methodName].call(f))
);
return this.frameworks
.filter(f => f.isApplicable())
.filter(f => f.canAnswerCapability(methodName))
.map(f => {
this.log(f.name + ' answering: ' + methodName);
return f;
})
.map(f => f[methodName].call(f))
.every(result => !!result);
} catch (e) {
console.error(`There was an error calling ${methodName} - ${e}`);
}
Expand Down Expand Up @@ -87,16 +142,6 @@ class PrivacyCompliance {
}
}

/**
* Returns true of all elements in the collection evaluate to true
*
* @param {Array} collection a collection of objects to evaluate
* @return {Boolean} returns true of all elements in the collection evaluate to true
*/
const areAllTrue = collection => {
return collection.filter(capability => !capability).length == 0;
};

const privacyComplianceSingleton = new PrivacyCompliance().applyProxy();

// Autoload all of the auto-loaded frameworks
Expand Down
22 changes: 22 additions & 0 deletions test/framework_base.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const PrivacyCompliance = require('../src/privacy_compliance');
const FrameworkBase = require('../src/frameworks/base');

describe('Basic Framework support', () => {
it('should get a link to the privacy compliance framework once added', () => {
let base = new FrameworkBase();
PrivacyCompliance.addFramework(base);
expect(base.privacyComplianceInstance).toBe(PrivacyCompliance);
});

it('should send logs back to the main privacy instance', () => {
let logEntries = [];
PrivacyCompliance.useLogger((...args) => {
logEntries.push(args.join(' '));
});

let base = new FrameworkBase();
PrivacyCompliance.addFramework(base);
base.log('hello from a framework');
expect(logEntries.includes('[FrameworkBase] hello from a framework')).toBeTruthy();
});
});
58 changes: 58 additions & 0 deletions test/privacy_compliance.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const makeFakeFramework = (methodName, result) => {
canAnswerCapability: capability => methodName === capability,
supportedCapabilities: () => [methodName],
useConfig: () => {},
setPrivacyComplianceInstance: () => {},
log: () => {},
name: `FakeFramework - ${methodName}`,
};
fakeFramework[methodName] = () => result;
Expand All @@ -25,6 +27,9 @@ describe('PrivacyCompliance', () => {
isApplicable: () => true,
canAnswerCapability: () => true,
supportedCapabilities: () => ['canFakeFeature'],
setPrivacyComplianceInstance: () => {},
name: 'FakeFramework',
log: () => {},
canFakeFeature,
});

Expand All @@ -41,13 +46,19 @@ describe('PrivacyCompliance', () => {
isApplicable: () => true,
canAnswerCapability: () => true,
supportedCapabilities: () => ['canFakeFeature'],
setPrivacyComplianceInstance: () => {},
name: 'FakeFramework',
log: () => {},
canFakeFeature,
});

PrivacyCompliance.addFramework({
isApplicable: () => true,
canAnswerCapability: () => true,
supportedCapabilities: () => ['canFakeFeature'],
setPrivacyComplianceInstance: () => {},
name: 'FakeFramework',
log: () => {},
canFakeFeature: canFakeFeature2,
});

Expand Down Expand Up @@ -113,4 +124,51 @@ describe('PrivacyCompliance', () => {
expect(canObserveMouseClicks.mock.calls.length).toBe(1);
});
});

describe('Logging', () => {
it('should support basic logging', () => {
expect(typeof PrivacyCompliance.log).toBe('function');
});

it('should not fail even when a logger is not setup', () => {
PrivacyCompliance.log('this goes no where');
});

it('should allow external logging systems to be used', () => {
let logEntries = [];
PrivacyCompliance.useLogger((...args) => logEntries.push(args.join(', ')));

PrivacyCompliance.log('I respect your data');
expect(logEntries.includes('I respect your data')).toBeTruthy;
});

it('should not relog entries if second argument is false', () => {
let logEntries = [];
PrivacyCompliance.useLogger((...args) => logEntries.push(args.join(', ')), false);

PrivacyCompliance.log('I respect your data');
expect(logEntries.length).toBe(1);
expect(logEntries.includes('I respect your data')).toBeTruthy;
});

it('should allow logs to include multiple values', () => {
let logEntries = [];
PrivacyCompliance.useLogger((...args) => logEntries.push(args.join(', ')));

PrivacyCompliance.log('I respect your data', 12);

expect(logEntries.includes('I respect your data, 12')).toBeTruthy;
});

it('should have a log called on the instance when it is answering a capability request', () => {
let logEntries = [];
PrivacyCompliance.useLogger((...args) => logEntries.push(args.join(' ')));

let fakeFramework = makeFakeFramework('canEatHotDog', true);

PrivacyCompliance.addFramework(fakeFramework);
PrivacyCompliance.canEatHotDog();
expect(logEntries.includes('FakeFramework - canEatHotDog answering: canEatHotDog')).toBeTruthy();
});
});
});
Loading

0 comments on commit 56280fa

Please sign in to comment.