Skip to content

Commit

Permalink
Merge branch 'master' into snyk-upgrade-0bff64f29278f74d73cc49b2ae1067fb
Browse files Browse the repository at this point in the history
  • Loading branch information
hvardhan-unth authored Jun 28, 2024
2 parents 5026bb4 + 6cebdd4 commit 9ef3667
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 40 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# tsub-js

> [!NOTE]
> Segment has paused maintenance on this project, but may return it to an active status in the future. Issues and pull requests from external contributors are not being considered, although internal contributions may appear from time to time. The project remains available under its open source license for anyone to use.
tsub-js is a JavaScript implementation of the [tsub filtering and
transformations engine][tsub]. This is used to support Destination Filters and
Transformations in Analytics.js destinations.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@segment/tsub",
"version": "1.0.0",
"version": "2.0.0",
"description": "Tsub for JS",
"main": "dist/index.js",
"browser": "dist/index.js",
Expand Down
36 changes: 36 additions & 0 deletions src/__tests__/transformers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ describe('drop_properties', () => {
expect(simpleEvent.nest1.nest2.nest3.nest4.nest5).toBeUndefined()
})

test('drop_properties should drop fields from within arrays', () => {
simpleEvent.properties = {
product: [
{ id: 875134, category: 'Clothing' },
{ id: 875135, category: 'Sports' }
]
}
transformer.config = { drop: { 'properties.product': ['category'] } }

transform(simpleEvent, [transformer])
expect(simpleEvent.properties).toStrictEqual({
product: [
{ id: 875134 },
{ id: 875135 }
]
})
})

test('drop_properties should work quickly even on huge objects', () => {
manyPropertiesEvent.nest1 = {
nest2: {
Expand Down Expand Up @@ -160,6 +178,24 @@ describe('allow_properties', () => {
expect(simpleEvent.nest1.nest2.nest3.nest4).toStrictEqual({ nest5: 'hai :3' })
})

test('allow_properties should filter within arrays', () => {
simpleEvent.properties = {
product: [
{ id: 875134, category: 'Clothing' },
{ id: 875135, category: 'Sports' }
]
}
transformer.config = { allow: { 'properties.product': ['id'] } }

transform(simpleEvent, [transformer])
expect(simpleEvent.properties).toStrictEqual({
product: [
{ id: 875134 },
{ id: 875135 }
]
})
})

test('drop_properties should work quickly even on huge objects', () => {
manyPropertiesEvent.nest1 = {
nest2: {
Expand Down
70 changes: 31 additions & 39 deletions src/transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import ldexp from '@stdlib/math-base-special-ldexp'
import { dset } from 'dset';
import { unset } from './unset'

export type KeyTarget = Record<string, string[]>

export interface TransformerConfig {
allow?: Record<string, string[]>
drop?: Record<string, string[]>
allow?: KeyTarget
drop?: KeyTarget
sample?: TransformerConfigSample
map?: Record<string, TransformerConfigMap>
}
Expand Down Expand Up @@ -58,53 +60,43 @@ export default function transform(payload: any, transformers: Transformer[]): an

// dropProperties removes all specified props from the object.
function dropProperties(payload: any, config: TransformerConfig) {
for (const key in config.drop) {
if (!config.drop.hasOwnProperty(key)) {
continue
}

// If key is empty, it refers to the top-level object.
const field = key === '' ? payload : get(payload, key)

// Can only drop props off of arrays and objects.
if (typeof field !== 'object' || field === null) {
continue
}

for (const target of config.drop[key]) {
delete field[target]
}
}
filterProperties(payload, config.drop, (matchedObj, dropList) => {
dropList.forEach(k => delete matchedObj[k])
})
}

// allowProperties ONLY allows the specific targets within the keys. (e.g. "a.foo": ["bar", "baz"]
// on {a: {foo: {bar: 1, baz: 2}, other: 3}} will not have any drops, as it only looks inside a.foo
function allowProperties(payload: any, config: TransformerConfig) {
for (const key in config.allow) {
if (!config.allow.hasOwnProperty(key)) {
continue
}

// If key is empty, it refers to the top-level object.
const field = key === '' ? payload : get(payload, key)
filterProperties(payload, config.allow, (matchedObj, preserveList) => {
Object.keys(matchedObj).forEach(key => {
if (!preserveList.includes(key)) {
delete matchedObj[key]
}
})
})
}

// Can only drop props off of arrays and objects.
if (typeof field !== 'object' || field === null) {
continue
function filterProperties(payload: any, ruleSet: KeyTarget, filterCb: (matchedObject: any, targets: string[]) => void) {
Object.entries(ruleSet).forEach(([key, targets]) => {
const filter = (obj: any) => {
// Can only act on objects.
if (typeof obj !== 'object' || obj === null) {
return
}

filterCb(obj, targets)
}

// Execution order fortunately doesn't really matter (e.g. if someone filtered off of foo.bar, then foo.bar.baz)
// except for micro-optimization.
for (const k in field) {
if (!field.hasOwnProperty(k)) {
continue
}
// If key is empty, it refers to the top-level object.
const matchedObject = key === '' ? payload : get(payload, key)

if (config.allow[key].indexOf(k) === -1) {
delete field[k]
}
if (Array.isArray(matchedObject)) {
matchedObject.forEach(filter)
} else {
filter(matchedObject)
}
}
})
}

function mapProperties(payload: any, config: TransformerConfig) {
Expand Down

0 comments on commit 9ef3667

Please sign in to comment.