When you are currently using the legacy version of our tag and you want to upgrade to the new version? This is the document for you. This migration document only holds for migrating from the legacy tag to version 1.0.0 of the new tag.
Let’s start with some general changes:
The idea of plugins is still the same. If you want to use only Govolte, only configure that plugin etc.
The creation of the NPOtag object has changed. In the legacy tag you created a empty tag object and added the context labels afterwards. So if you forgot to set the context labels, the events would fail. In the new tag you call the appropriate factory/builder function and add the context labels there on creation of the object.
And the page/stream/recommendation specific context labels are moved to there own tracker objects. The page tracker object can be created using the npotag object. Then from the pagetracker object you can create a streamtracker and a recommendationtracker object. All these tracker objects contain functions to send events.
The setting and clearing of user ids is now done through login
and logout
functions on the npoTag instead of setUser
and clearUser
.
For platforms that have integrated the new NPO-ID a new field pseudo_id
is present on the login function.
Take a look at the new Tag creation and pageTracker object first and see how you set it up. Then come back here.
As said before, alle the fields are still there from the old tag context labels. They are only mapped differently over the top level object and the new pageTracker object.
legacy label | new field | context object | field type | required | comments |
---|---|---|---|---|---|
merk | brand | npoTag | string | true | brand of the platform the tag is implemented in (e.g npostart) |
merkId | brand_id | npoTag | int | true | integer associated with the brand (ask publieks-onderzoek) |
platform | platform | npoTag | string | true | site for website, app for iOS/Android, tvapp for SmartTVs |
platformVersie | platform_version | npoTag | string | true | version of the platform |
omgeving | environment | npoTag | string | false | indicator for prod/dev/stag, note that environments should also be differentiated by brandId |
gebruiker.npoId | user_id | npoTag | string | false | id associated with the currently logged in user (set by login, logout functions) |
gebruiker.profielId | user_profile | npoTag | string | false | id associated with the currently selected profile (set by login, logout functions) |
gebruiker.npoType | user_subscription | npoTag | string | false | subscription type othe the currently logged in user (set by login, logout functions) |
– (new) | user_pseudoid | npoTag | string | false | new ID type that will be used by platforms leveraging the new NPO-ID (set by login, logout functions) |
niveau1 | chapter_1 | pageTracker | string | false | the top-level context (follow tag-plan) |
niveau2 | chapter_2 | pageTracker | string | false | a narrower context (follow tag-plan) |
niveau3 | chapter_3 | pageTracker | string | false | most specific context (follow tag-plan) |
pagina | page | pageTracker | string | false | the page or view that is displayed |
zoek.term | query_context | pageTracker | string | false | the query that generated the result page a user is on |
omroeplabel1 | custom_label1 | pageTracker | string | false | free-form string variables for backward compatibility |
omroeplabel2 | custom_label2 | pageTracker | string | false | free-form string variables for backward compatibility |
omroeplabel3 | custom_label3 | pageTracker | string | false | free-form string variables for backward compatibility |
omroeplabel4 | custom_label4 | pageTracker | string | false | free-form string variables for backward compatibility |
omroeplabel5 | custom_label5 | pageTracker | string | false | free-form string variables for backward compatibility |
omroep | broadcasters | pageTracker | string | false | a string value, where implementers fill in a `_\ |
programma | program | pageTracker | string | false | lowercased, whitespace stripped name of a program on the page, filled in by implementers |
was a function on apps | location | pageTracker | string | false | current page url |
was a function on apps | referrer | pageTracker | string | false | url of the page where you came from |
– (new) | content_context_id | pageTracker | string | false | separate field for things that used to be stored in the ‘page’ |
– (new) | condition | pageTracker | string | false | A/B condition indicator |
– (new) | error_code | pageTracker | string | false | arbitrary error code when reaching an error page |
nobo.EventType | –(deprecated) | pageTracker | string | false | old type of event declaration for NOBO panel, no longer used |
nobo.mediaType | –(deprecated) | pageTracker | string | false | old type of media on page for NOBO panel, no longer used |
merkType | –(deprecated) | pageTracker | string | false | old indicator of the type of brand, no longer used |
For traditional applications we recommend that you create an instance of NPOTag
in the window.onload()
function:
<html>
<head>
...
<script lang="text/javascript">
window.onload = function() {
const tag = npotag.newTag(
// Set properties shared by all events
{
brand: 'brand',
brand_id: 1,
platform: 'web',
platform_version: '1.2.3',
},
// Create the tag plugins you want to use
[
npotag.newGovoltePlugin(),
npotag.newATInternetPlugin(),
]
);
};
</script>
</head>
...
</html>
For single-page-applications we recommend that you maintain an instance of NPOTag
globally. For example in _app.tsx
in NextJS:
// _app.tsx
import { newTag, newGovoltePlugin, newATInternetPlugin, NPOTag } from '@npotag/tag';
// NOTE: The result of `newTag` will be undefined if the browser window is unavailable
// (e.g. in server-side rendering)
export const npoTagInstance: NPOTag | undefined = newTag(
// Set properties shared by all events
{
brand: 'brand',
brand_id: 1,
platform: 'web',
platform_version: '1.2.3',
},
// Create the tag plugins you want to use
[newGovoltePlugin(), newATInternetPlugin()]
);
For iOS we recommend that you setup the SDK in your AppDelegate’s didFinishLaunchingWithOptions
method.
func application(_ application: UIApplication, didFinishLaunchingWithOptions ...) -> Bool {
// ...
// Set properties shared by all events
let sharedContext = NPOContext(
brand: "Product name",
brandId: 4,
plaform: "iOS",
platformVersion: "1.2.0"
)
// Instantiate plugins that you wish to send events with
let govoltePlugin = GovoltePlugin()
let atInternetPlugin = ATInternetPlugin()
// Will create and configure the shared NPOTag instance
NPOTag.configure(
withSharedContext: sharedContext,
plugins: [ // Pass the plugins you wish to send events with
govoltePlugin,
atInternetPlugin
],
isDebugEnabled: false // Set this to true to enable debug logging
)
return true
}
Fluent Builder Pattern is used to create an instance of NpoTag
. That means all required parameters have to be provided in strict order and there is no way to call build()
before providing all required values.
Instance of NpoTag
should be created as soon as possible to allow it to send potential events left in the queue from a previous session.
class App : Application() {
lateinit var npoTag: NpoTag
override fun onCreate() {
super.onCreate()
npoTag = NpoTag.builder()
.withContext(this)
.withBrand(
brand = "brand",
brandId = 12345
)
.withPlatform(
platform = "platform",
version = "platformVersion"
)
.withPluginsFactory { pluginContext ->
setOf(
LoggerPlugin(pluginContext),
GovoltePlugin(
pluginContext = pluginContext,
baseUrl = "https://topspin.npo.nl/",
),
ATInternetPlugin(pluginContext)
)
}
.withEnvironment(EnvironmentType.DEV)
.withDebug(true)
.build()
}
}
<html>
<head>
...
<script lang="text/javascript">
window.onload = function() {
...
// Create a PageTracker object, passing it the NPOTag and a PageContext object
const tracker = npotag.newPageTracker(tag, {
page: 'profile_page',
chapter1: 'home',
chapter2: 'profile',
// etc...
});
};
</script>
</head>
...
</html>
// Create a PageTracker object, passing it the NPOTag and a PageContext object
const tracker = newPageTracker(tag, {
page: 'profile_page',
chapter1: 'home',
chapter2: 'profile',
// etc...
});
// Create a context object with all property values
// All of the properties are optional
let context = PageContext(
page: "page",
chapter1: "chapter1",
chapter2: "chapter2",
chapter3: "chapter3",
broadcasters: "broadcasters",
program: "program",
contentContextId: "contextId",
queryContext: "queryContext",
condition: "condition",
errorCode: "errorCode",
customLabel1: "label1",
customLabel2: "label2",
customLabel3: "label3",
customLabel4: "label4",
customLabel5: "label5",
location: "location",
referrer: "referrer"
)
// Instantiate the tracker object from the NPOTag shared instance
let tracker = NPOTag.shared.newPageTracker(with: context)
// Instantiate the tracker object from the NpoTag instance
val tracker = npoTag.pageTrackerBuilder()
.withPageName("home")
.withContentContextId("homeContextId")
.withCondition("condition")
.withQueryContext("query")
.withErrorCode("errorContext")
.withBroadcasters("broadcaster")
.withProgram("program")
.withChapters(
chapter1 = "C1",
chapter2 = "C2",
chapter3 = "C3"
)
.withCustomLabels(
customLabel1 = "L1",
customLabel2 = "L2",
customLabel3 = "L3",
customLabel4 = "L4",
customLabel5 = "L5"
)
.build()
Replaces view()
, page()
and contentView
methods in prior versions.
This method is called on the pageTracker object. So make sure you created one.
The pageView method is still the same with no method variables, you only call it on the pageTracker object instead of the top level object.
pageTracker.pageView();
pageTracker.pageView();
public func pageView()
override fun pageView()
This method is called on the pageTracker object. So make sure you created one. The click method did not change for web, but is now called on the pageTracker object. For the apps it is a click method instead of a customEvent method.
The language of the method variables has changed though:
legacy var name | new var name | field type | required | comments |
---|---|---|---|---|
name | click_name | string | true | identifier of the click |
type | click_type | string | true | ‘navigation’ \ ‘action’ \ ‘exit’ \ ‘download’ \ string" |
niveau1 | chapter_1 | string | false | the top-level context for this click (follow tag-plan) |
niveau2 | chapter_2 | string | false | a narrower context for this click (follow tag-plan) |
niveau3 | chapter_3 | string | false | most specific context for this click (follow tag-plan) |
pageTracker.click({
click_name: 'menu',
click_type: 'navigation' | 'action' | 'exit' | 'download' | string,
click_chapter_1: 'Home',
click_chapter_2: 'Navigation bar',
click_chapter_3: 'Left',
});
The type parameter can take any of the defined string values, or a custom string value. Parameters click_chapter_1/2/3
are optional:
pageTracker.click({
click_name: 'menu',
click_type: 'custom-click-type',
});
pageTracker.click({
click_name: 'menu',
click_type: 'navigation' | 'action' | 'exit' | 'download' | string,
click_chapter_1: 'Home',
click_chapter_2: 'Navigation bar',
click_chapter_3: 'Left',
});
The type parameter can take any of the defined string values, or a custom string value. Parameters click_chapter_1/2/3
are optional:
pageTracker.click({
click_name: 'menu',
click_type: 'custom-click-type',
});
pageTracker.click(
name: "menu",
type: ClickType.action,
chapter1: "Home",
chapter2: "Navigation bar",
chapter3: "Left"
)
The type parameter can take any predefined value exposed by the ClickType
enum, or a custom value using the .other
enum case. Parameters chapter1/2/3
are optional:
pageTracker.click(
name: "menu",
type: ClickType.other(value: "your_custom_type_here")
)
pageTracker.click(
name = "menu",
type = ClickType.Action(),
chapter1 = "Home",
chapter2 = "Navigation bar",
chapter3 = "Left"
)
The type parameter can take any predefined value exposed by the ClickType
sealed class, or a custom value using the .Other
class. Parameters chapter1/2/3
are optional:
pageTracker.click(
name = "menu",
type = ClickType.Other("your_custom_type_here"),
)
We now have a new player team at the NPO that builds a player and integrates our measurements. So if you use the NPO player already and have configured the tracking info for that player, you dont have to integrate stream measurements yourself.
But if you dont use the NPO player… The stream measurements have changed somewhat. Instead of creating a recorder object on web or a streamTracker on app in the legacy SDKs, a streamTracker is created from a pageTracker in the new SDKs. There are some additional variables that need to be passed to the tracker now:
legacy var name | new var name | field type | required | comments |
---|---|---|---|---|
mid | stream_id | string | true | ID of the content being played |
– (new) | stream_length | float | true | length of stream in seconds |
– (new) | player_id | string | true | ID of the player used |
– (new) | av_type | string | true | video or audio |
– (new) | player_version | string | true | version of the player SDK |
– (new) | sko_player_version | string | true | player version for Stichting KijkOnderzoek |
– (new) | isLive | bool | false | set to true if you want to measure a live stream |
<script lang="text/javascript">
window.onload = function() {
...
const streamTracker = npotag.newStreamTracker(
pageTracker,
{
stream_length: videoElement.duration,
stream_id: 'video-stream',
player_id: 'embedded-video',
av_type: 'video',
player_version: '1.0.0',
sko_player_version: '1.0.0'
},
{
isLive: true,
}
);
}
</script>
const streamTracker = newStreamTracker(
pageTracker,
{
stream_length: videoElement.duration,
stream_id: 'video-stream',
player_id: 'embedded-video',
av_type: 'video',
player_version: '1.0.0',
sko_player_version: '1.0.0',
},
{
isLive: true,
}
);
let streamContext = StreamContext(
streamLength: video.duration,
streamID: "streamId",
avType: StreamContext.AVType.video, // Or StreamContext.AVType.audio
playerID: "playerId",
playerVersion: "1.0.0",
skoPlayerVersion: "1.0.0"
)
// Create a stream tracker instance for either a regular stream or live stream
let streamTracker = pageTracker.newStreamTracker(
withContext: streamContext,
isLiveStream: false
)
val streamTracker = pageTracker.streamTrackerBuilder()
.withStreamLength(STREAM_LENGTH)
.withStreamId("testStreamId")
.withPlayerId("testPlayerId")
.withAVType("video")
.withPlayerVersion("testPlayerVersion")
.withSkoPlayerVersion("testSkoPlayerVersion")
.withLiveStream(true)
.build()
When you created a streamTracker you can call stream related tracking methods on it.
The start
, pause
, resume
, seek
, complete
, fullscreen
, windowed
, load
methods are still there. But they require
you to send the current stream position with them as a method variable when called.
All the add related events have been deprecated (adStart
, adPause
, adResume
adComplete
)
There are some new methods that need to be implemented that were not in the previous SDK. We had the load
event already, bun now
you need to call loadComplete
when the loading of the stream is done. The same holds for the new buffering
and bufferingComplete
.
The stop
method is also new, this needs to be called when you leave a stream or reset it (pressing the stop button in some cases)
The last thing is new for the app implementations, but not new for web (but it is slightly different). Every “tick” of
your stream you need to call the time
method with the current stream position in seconds. The SDK will determine when it
needs to send waypoints to the configured plugins as long as you have implemented all the other methods.
For all the methods that need to be implemented, please look at thestreamTracker.
As all the previous methods, offers and choices have been given their own context object as well. The recommendationTracker
The distinction between editorial, search and media recommender objects have been deprecated. You now always use the same recommendation object.
For search offers and choices you need to fill the query_context
field in the pageContext builder/factory.
For every panel in your application you create a context object just as before, but with a different method call:
legacy var name | new var name | field type | required | comments |
---|---|---|---|---|
panel | panel_id | string | true | identifier of this panel |
was in the offer/choice method | total_offers | int | true | total amount of items in this panel |
– (new) | panel_format | string | true | the UX-format of the panel |
– (new) | panel_position | int | false | the position of the panel on the page, can be absent for panels such as a search-results pop-over |
const recommendationTracker = newRecommendationTracker(pageTracker, {
panel_id: '23889851-fec5-4106-8501-122f1e233686',
total_offers: 9,
panel_format: '5grid',
panel_position: 1,
});
const recommendationTracker = newRecommendationTracker(pageTracker, {
panel_id: '23889851-fec5-4106-8501-122f1e233686',
total_offers: 9,
panel_format: '5grid',
panel_position: 1,
});
let recommendationContext = RecommendationContext(
panelId: "panelId",
totalOffers: offers.count,
panelFormat: "panelFormat",
panelPosition: 0
)
// Create a recommendation tracker instance from our pageTracker
let recommendationTracker = pageTracker.newRecommendationTracker(
withContext: recommendationContext
)
val recommendationTracker = pageTracker.recommendationTrackerBuilder()
.withPanelId(panelId)
.withTotalOffers(NUMBER_OF_ITEMS)
.withPanelFormat("panelFormat")
.withPanelPosition(panelPosition)
.build()
The offer
event can be called on the recommendationTracker of the corresponding panel with a slightly different method call:
legacy var name | new var name | field type | required | comments |
---|---|---|---|---|
recommender | recommender | string | true | recommender algorithm used or lane identifier |
midRef | target_id | string | true | ID of the content being offered |
position | offer_index | int | true | the position of the offer within the recommendation panel |
recommendationTracker.offer({
recommender: 'ps-implicit-v0',
target_id: 'VPWON_123',
offer_index: 2,
});
recommendationTracker.offer({
recommender: 'ps-implicit-v0',
target_id: 'VPWON_123',
offer_index: 2,
});
recommendationTracker.offer(recommender: String, targetId: String, offerIndex: Int)
recommendationTracker.offer(
recommender = "recommender",
targetId = "targetId",
offerIndex = 1
)
For the choice
holds the same:
legacy var name | new var name | field type | required | comments |
---|---|---|---|---|
recommender | recommender | string | true | recommender algorithm used or lane identifier |
midRef | target_id | string | true | ID of the content being offered |
position | offer_index | int | true | the position of the offer within the recommendation panel |
recommendationTracker.choice({
recommender: 'ps-implicit-v0',
target_id: 'VPWON_123',
offer_index: 3,
});
recommendationTracker.choice({
recommender: 'ps-implicit-v0',
target_id: 'VPWON_123',
offer_index: 3,
});
recommendationTracker.choice(recommender: String, targetId: String, offerIndex: Int)
recommendationTracker.choice(
recommender = "recommender",
targetId = "targetId",
offerIndex = 1
)
If you have any further questions about migration from the legacy tag to the new one, please send a mail to diaz@npo.nl