Skip to content
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

Data Controller Module: initial release #8484

Merged
merged 10 commits into from
Jul 25, 2022
Merged

Data Controller Module: initial release #8484

merged 10 commits into from
Jul 25, 2022

Conversation

SKOCHERI
Copy link
Contributor

Type of change

  • Bugfix
  • Feature
  • New bidder adapter
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Does this change affect user-facing APIs or examples documented on http://prebid.org?
  • Other

Description of change

  • test parameters for validating bids
{
  bidder: '<bidder name>',
  params: {
    // ...
  }
}

Be sure to test the integration with your adserver using the Hello World sample page.

  • contact email of the adapter’s maintainer
  • official adapter submission

For any changes that affect user-facing APIs or example code documented on http://prebid.org, please provide:

Other information

pbjs.setConfig({
dataController: {
filterEIDwhenSDA: ['*']
filterSADwhenEID: ['id5-sync.com']
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

| param name | type | Scope | Description | Params |
| :------------ | :------------ | :------ | :------ | :------ |
| filterEIDwhenSDA | function | optional | Filters user EIDs based on SDA | bidrequest |
| filterSADwhenEID | function | optional | Filters SDA based on configured EIDs | bidrequest |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

if(filterEIDs) {
config.setConfig({'dcUsersAsEids': filterEIDs});
}
} else if (dataControllerConfig.filterSADwhenEID) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patmmccann do we want to have the param as "supressSDAwhenEID"/"supressEIDwhenSDA"? Filter would be more generic rather than suppress.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All references to SAD should be changed to SDA

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated.

@patmmccann patmmccann changed the title Data Controller Module Data Controller Module: initial release May 26, 2022
@patmmccann patmmccann added feature needs docs needs 2nd review Core module updates require two approvals from the core team labels May 26, 2022
@patmmccann
Copy link
Collaborator

Thanks so much!

@patmmccann
Copy link
Collaborator

patmmccann commented May 26, 2022 via email

import {processBidderRequests} from '../../src/adapters/bidderFactory.js';
import {logError} from '../../src/utils.js';

let submodules = [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this planning for submodules? I'm not sure I understand the scope of the feature - going by this proposal it seems self-contained to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgirardi The proposal mentions about Publisher having ability to define the rules to filter EID and SDA hence providing submodule.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand - publishers do not have access to the module system and they wouldn't be able to add a submodule (they would have to fork the repo).

I would expect them to define the rules with setConfig, and I don't see the need multiple submodules to read that config unless it makes sense to want only part of that proposal - but I don't see anything in it that suggests that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will take a look

dataControllerConfig = config.getConfig(MODULE_NAME);

if (!dataControllerConfig) {
processBidderRequests.getHooks({hook: processBidderRequestHook}).remove();
Copy link
Collaborator

@dgirardi dgirardi Jun 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to keep configuration and business logic separate - this will get confusing fast as complexity increases. (IMO it makes more sense to turn the module off from the same place you turn it on).

@@ -296,6 +296,12 @@ export function newBidder(spec) {
* @param onCompletion {function()} invoked once when all bid requests have been processed
*/
export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onError, onBid, onCompletion}) {
let updatedUserAsEids = config.getConfig('dcUsersAsEids');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it's using config as a global variable, plus it's adding dead code for those who are not using the data controller module.

Why not do this directly from the hook added by the module?
Also, IMO the makeBidRequests hook is a better place for this - I don't know if it makes a difference now, but my instinct is that the earlier they have this data the better.

Copy link
Contributor Author

@SKOCHERI SKOCHERI Jun 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgirardi I have update this module to be self contained.
The SDA were not available if this module was called with makeBidRequests as the *RTBProviders are invoked until then

let dataControllerConfig;
const ALL = '*';

function filterEIDs(requestObject) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still a work in progress I assume - this logic looks like it belongs in the module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module development is completed. The test contains how the submodule looks like

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the test is testing test code? that can't be right. I'm missing the point entirely, once the bundle is built this code (and therefore the tests on it) are completely irrelevant no? the module in the actual codebase is an empty shell.

Copy link
Collaborator

@dgirardi dgirardi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. I think the approach would not work correctly (sorry if I missed it in the previous review) - for two reasons:

  1. processBidderRequest does not run for Prebid Server bids - only client adapters;
  2. since prebid 7, setBidderConfig (or setConfig) is not the right way to update first party data - it will not take effect until the next auction (see this)

I think the best way to do this is to hook on startAuction and:

  • perform filterSDA by modifying the ortb2Fragments parameter;
  • perform filterEIDs by modifying the adUnits[].bids[].userIdAsEid parameter - in the same way that property is set by the userId module

const MODULE_NAME = 'dataController';
let dataControllerConfig;

const _logInfo = createLogInfo(LOG_PRE_FIX);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a prefixLog utility function that does something similar already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgirardi Have updated the module to be called before startAuction and fixed other issues pointed out.

}
}

export function initHook() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the better pattern is config.getConfig(MODULE_NAME, (cfg) => {if (isValid(cfg) enable() else disable())}) - it's the approach taken by most (if not all) other modules:

  • it keeps better separation between setup logic and business logic
  • it doesn't use cpu cycles during the auction in the case when the module has not been enabled
  • it needs to worry about fewer edge cases, like what happens if configuration is set during (or after) the auction

}

function hasValidConfiguration() {
dataControllerConfig = config.getConfig(MODULE_NAME);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may I suggest something like setupConfig instead of hasValidConfiguration? to make it clear that the function has side effects.

@ChrisHuie ChrisHuie self-requested a review July 6, 2022 11:24
@ChrisHuie ChrisHuie self-assigned this Jul 6, 2022

function filterSDA(adUnits, ortb2Fragments) {
let bidderEIDSMap = getEIDsSource(adUnits);
for (const [key, value] of Object.entries(ortb2Fragments.bidder)) {
Copy link
Collaborator

@dgirardi dgirardi Jul 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. should this also remove user.data from global configuration, if it's not set by bidder? I'm not sure what the intent of the feature is.

If as the pub I do

setConfig({
  ortb2: {
    user: {
      data: [... stuff here ..]
    }
  }
})

and assuming there's nothing else, here ortb2Fragments would come in as:

{
   global: {user: {data: [.. stuff ..]}},
   bidder: {}
}

so this logic would have no effect, and downstream in the adapters the final ortb2 object would still contain user.data. Is that correct?

  1. value.user.data = [] should be deepSetValue(value, 'user.data', []), otherwise it can throw if any of the intermediates is missing. (Or should it be delete value?.user?.data - should it be empty, or missing?)

export function getSegmentConfig(ortb2Fragments) {
let bidderSDAMap = new Map();

for (const [key, value] of Object.entries(ortb2Fragments.bidder)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here - should this look at global config?

@dgirardi
Copy link
Collaborator

dgirardi commented Jul 7, 2022

Reading through https://docs.google.com/document/d/1g9GxuMtPwZyk5lRKCyoWVaYJGpqUGEbFNwEBMQswcjU/edit# again, I think the intent is - from the point of view of the publisher, "I don't want this data to be visible to the adapters / SSP".

So I think this module should be overzealous, and:

  • inspect / remove global configuration as well (ortb2Fragments.global)
  • also remove data from user.ext.eids and bid.userId, not just bid.userIdAsEid. otherwise, there might be a situation where for example an RTD module is populating the first, or an adapter is looking at the second (instead of userIdAsEid), and they still get access to the "forbidden" data.

@SKOCHERI
Copy link
Contributor Author

SKOCHERI commented Jul 7, 2022

Reading through https://docs.google.com/document/d/1g9GxuMtPwZyk5lRKCyoWVaYJGpqUGEbFNwEBMQswcjU/edit# again, I think the intent is - from the point of view of the publisher, "I don't want this data to be visible to the adapters / SSP".

So I think this module should be overzealous, and:

  • inspect / remove global configuration as well (ortb2Fragments.global)
  • also remove data from user.ext.eids and bid.userId, not just bid.userIdAsEid. otherwise, there might be a situation where for example an RTD module is populating the first, or an adapter is looking at the second (instead of userIdAsEid), and they still get access to the "forbidden" data.

Updating to consider the above

@patmmccann patmmccann linked an issue Jul 8, 2022 that may be closed by this pull request
@SKOCHERI
Copy link
Contributor Author

SKOCHERI commented Jul 8, 2022

  • user.ext.eids

@dgirardi I am trying to create request with user.ext.eids to test. Is this the right path where user eids are set "ortb2Fragments.bidder..user.ext.eids"

@dgirardi
Copy link
Collaborator

dgirardi commented Jul 8, 2022

they could be in either bidder or global, and both should be checked / filtered, like the other pieces of data. The logic is roughly speaking, when the auction starts, global is from getConfig, bidder is from getBidderConfig; then the object passes through FPD and RTD modules, then it gets here in dataController (which should be last). Any of the above may have added anything in them, so this should check global.user and each entry in bidder.[code].user. At the end each adapter sees only one object ortb2 = mergeDeep({}, ortb2Fragments.global, ortb2Fragments.bidder.[code]).

@SKOCHERI
Copy link
Contributor Author

SKOCHERI commented Jul 8, 2022

they could be in either bidder or global, and both should be checked / filtered, like the other pieces of data. The logic is roughly speaking, when the auction starts, global is from getConfig, bidder is from getBidderConfig; then the object passes through FPD and RTD modules, then it gets here in dataController (which should be last). Any of the above may have added anything in them, so this should check global.user and each entry in bidder.[code].user. At the end each adapter sees only one object ortb2 = mergeDeep({}, ortb2Fragments.global, ortb2Fragments.bidder.[code]).

@dgirardi Updated to consider global and bidder segments and update eids as mentioned above

Copy link
Collaborator

@dgirardi dgirardi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - I think this conforms to the proposal now as I understand it. I have a few more minor suggestions, I think the first (changing from GLOBAL = 'global' to GLOBAL = {}) should be done for correctness, but the rest is not that important.

const LOG_PRE_FIX = 'Data_Controller : ';
const ALL = '*';
const MODULE_NAME = 'dataController';
const GLOBAL = 'global';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use an empty object as a unique Map key (const GLOBAL = {}), it has the advantage that it cannot clash with, for example, a custom alias bidder named 'global'.

return false;
}
let containsEIDs = false;
_dataControllerConfig.filterSDAwhenEID.forEach(source => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be rewritten as return _dataControllerConfig.filterSDAwhenEID.some((source) => bidderEids.has(source)).

.forEach always loops over the whole iterable; .some will stop as soon as the predicate returns true.

if (bidderSegement == undefined) {
return false;
}
_dataControllerConfig.filterEIDwhenSDA.forEach(segment => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, .some would be more succinct and more efficient here.

}

function getSegmentConfig(ortb2Fragments) {
let bidderSDAMap = new Map();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's some duplication in here, which you could avoid by reusing the same logic for both global and bidder data. Here's a possible refactor to illustrate what I mean:

let bidderSDAMap = new Map();
function collectSegments(key, data) {
   let segmentSet = constructSegment(deepAccess(data, 'user.data') || []);
   if (segmentSet && segmentSet.size > 0) bidderSDAMap.set(key, segmentSet);
}
collectSegments(GLOBAL, ortb2Fragments.global); 
Object.entries(ortb2Fragments.bidder).forEach(([bidder, data]) => collectSegments(bidder, data));
return bidderSDAMap;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@ChrisHuie ChrisHuie removed their request for review July 13, 2022 13:21
@ChrisHuie ChrisHuie assigned mmoschovas and unassigned ChrisHuie Jul 13, 2022
# Description

This module will filter EIDs and SDA based on the configurations.
The filtered EIDs are stored in 'dcUsersAsEids' configuration and filtered SDA are updated in bidder configuration.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this detail no longer relevant? I am not seeing any of this stored any longer, so if that is the case, can this be removed

return;
}
confListener(); // unsubscribe config listener
_dataControllerConfig = dataControllerConfig.dataController;
Copy link
Contributor

@mmoschovas mmoschovas Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can clean this up a little to store the dataController in a const then reference that instead or referencing the full path each time.

i.e.

const dataController = dataControllerConfig && dataControllerConfig.dataController;

if (!dataController) {
    ...
}

if (dataController.filterEIDwhenSDA && dataController. filterSDAwhenEID) {
    ...
}

confListener(); // unsubscribe config listener
_dataControllerConfig = dataController;

getHook('startAuction').before(filterBidData);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Copy link
Contributor

@mmoschovas mmoschovas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update! lgtm

@patmmccann patmmccann merged commit 715a1f5 into prebid:master Jul 25, 2022
ccorbo pushed a commit to ccorbo/Prebid.js that referenced this pull request Jul 27, 2022
* Data Controller Module

* Data Controller Module

* Correcting typo

* Setting hook based on data controller configuration

* Setting hook based on data controller configuration

* Updating to filter data before startAuction event

* Updating to filter data before startAuction event

* Review comment fixes

* Review comment fixes

Co-authored-by: skocheri <skocheri@rubiconproject.com>
@mikemangas
Copy link

@SKOCHERI
@dgirardi
@mmoschovas

Thanks for your work. I would like to ask you some questions.

  • I am not sure, how can i implement and test this module. What are the Requirements for the Data Controller Module?
  • Are Seller Defined Audiences (SDA) required, in order to test/use this module? If yes, Is there a way to add SDA (provider name, segment id, taxonomy name) to my bid requests? if yes, is there a documentation for that?
  • What do i pass into "filterEIDwhenSDA" or "filterSDAwhenEID"? What are the requirements for using the one or the other?

Thank you for any helpful information.

@MartianTribe
Copy link

Docs PR:

prebid/prebid.github.io#3928

@mikemangas
Copy link

Does a bidAdapter have to change the code, in order to support this module?

JacobKlein26 pushed a commit to nextmillenniummedia/Prebid.js that referenced this pull request Feb 9, 2023
* Data Controller Module

* Data Controller Module

* Correcting typo

* Setting hook based on data controller configuration

* Setting hook based on data controller configuration

* Updating to filter data before startAuction event

* Updating to filter data before startAuction event

* Review comment fixes

* Review comment fixes

Co-authored-by: skocheri <skocheri@rubiconproject.com>
jorgeluisrocha pushed a commit to jwplayer/Prebid.js that referenced this pull request May 23, 2023
* Data Controller Module

* Data Controller Module

* Correcting typo

* Setting hook based on data controller configuration

* Setting hook based on data controller configuration

* Updating to filter data before startAuction event

* Updating to filter data before startAuction event

* Review comment fixes

* Review comment fixes

Co-authored-by: skocheri <skocheri@rubiconproject.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature needs docs needs 2nd review Core module updates require two approvals from the core team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Conditional permissioning of id's & data
8 participants