diff --git a/.github/styles/Snowplow/Acronyms.yml b/.github/styles/Snowplow/Acronyms.yml index d386c1dae4..b2111bc7d7 100644 --- a/.github/styles/Snowplow/Acronyms.yml +++ b/.github/styles/Snowplow/Acronyms.yml @@ -10,6 +10,7 @@ second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)' exceptions: - API - ASP + - CDN - CLI - CPU - CSS diff --git a/.github/styles/Snowplow/Headings.yml b/.github/styles/Snowplow/Headings.yml index 6fbbe9abaa..de4fc93f7e 100644 --- a/.github/styles/Snowplow/Headings.yml +++ b/.github/styles/Snowplow/Headings.yml @@ -9,6 +9,7 @@ exceptions: - CLI - Cosmos - Docker + - DOM - Emmet - gRPC - I diff --git a/.gitignore b/.gitignore index 408f2a80d7..6e7b4d92b9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ yarn-error.log* # Python __pycache__ manifests + +# Local Netlify folder +.netlify diff --git a/README.md b/README.md index b38bb8c097..4ac50894c5 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Vale only checks normal prose. Text that's marked as code—code blocks or in-li To install the extension, find "[Vale VSCode](https://marketplace.visualstudio.com/items?itemName=ChrisChinchilla.vale-vscode)" in the Extensions Marketplace within VS Code, then click **Install**. -The Vale extension will automatically check files when they're opened or saved. It underlines the flagged sections in different colours, based on the severity of the alert - red for errors, orange for warnings, and blue for suggestions. Mouse-over the underlined section to see the alert message, or check the VS Code **Problems** tab. +The Vale extension will automatically check files when they're opened or saved. It underlines the flagged sections in different colors, based on the severity of the alert - red for errors, orange for warnings, and blue for suggestions. Mouse-over the underlined section to see the alert message, or check the VS Code **Problems** tab. ### Vale command-line interface diff --git a/docs/api-reference/loaders-storage-targets/bigquery-loader/_deploy-overview.md b/docs/api-reference/loaders-storage-targets/bigquery-loader/_deploy-overview.md index 90ecf442b6..addcabdf57 100644 --- a/docs/api-reference/loaders-storage-targets/bigquery-loader/_deploy-overview.md +++ b/docs/api-reference/loaders-storage-targets/bigquery-loader/_deploy-overview.md @@ -19,4 +19,4 @@ To run the loader, mount your config file into the docker image, and then provid --iglu-config /myconfig/iglu.hocon `} -Where `loader.hocon` is loader's [configuration file](/docs/api-reference/loaders-storage-targets/bigquery-loader/#configuring-the-loader) and `iglu.hocon` is [iglu resolver](/docs/api-reference/iglu/iglu-resolver/index.md) configuration. +Where `loader.hocon` is loader's [configuration file](/docs/api-reference/loaders-storage-targets/bigquery-loader/index.md#configuring-the-loader) and `iglu.hocon` is [iglu resolver](/docs/api-reference/iglu/iglu-resolver/index.md) configuration. diff --git a/docs/data-product-studio/data-quality/failed-events/monitoring-failed-events/index.md b/docs/data-product-studio/data-quality/failed-events/monitoring-failed-events/index.md index 5bc51bc2ec..9001765858 100644 --- a/docs/data-product-studio/data-quality/failed-events/monitoring-failed-events/index.md +++ b/docs/data-product-studio/data-quality/failed-events/monitoring-failed-events/index.md @@ -35,7 +35,7 @@ We've filtered on these two types of errors to reduce noise like bot traffic tha At the top there is a data quality score that compares the volume of failed events to the volume that were successfully loaded into a data warehouse. -The bar chart shows how the total number of failed events varies over time, colour-coding validation and enrichment errors. The time window is 7 days be default but can be extended to 14 or 30 days. +The bar chart shows how the total number of failed events varies over time, color-coding validation and enrichment errors. The time window is 7 days be default but can be extended to 14 or 30 days. In the table, failed events are aggregated by the unique type of failure (validation, enrichment) and the offending schema. @@ -49,7 +49,7 @@ The detailed view shows the error message as well as other useful metadata (when The data quality dashboard allows you to see failed events directly from your warehouse. Your browser connects directly to an API running within your infrastructure, such that no failed event information flows through Snowplow. The discussion of architecture is important as it highlights the trade-offs between the two ways of monitoring failed events. The aforementioned API is a simple proxy that connects to your warehouse and serves the failed events to your browser. The connection is fully secure, using an encrypted channel (HTTPS) and authenticating/authorizing via the same mechanism used by Console. -In order for you to be able to deploy and use the data quality dashboard, you need to sink failed events to the warehouse via a [Failed Events Loader](/docs/data-product-studio/data-quality/failed-events/exploring-failed-events/warehouse-lake/#setup). The data quality dashboard only supports Snowflake. +In order for you to be able to deploy and use the data quality dashboard, you need to sink failed events to the warehouse via a [Failed Events Loader](/docs/data-product-studio/data-quality/failed-events/exploring-failed-events/index.md#configure). The data quality dashboard currently supports Snowflake and Bigquery connections. ![](images/dqd-architecture.png) @@ -67,7 +67,7 @@ Clicking on a particular error type will take you to a detailed view: ![](images/dqd-details.png) -The detailed view also shows a description of the root cause and the application version ([web](/docs/sources/trackers/snowplow-tracker-protocol/ootb-data/app-information/#application-context-entity-on-web-apps), [mobile](/docs/sources/trackers/mobile-trackers/tracking-events/platform-and-application-context/)). It provides a sample of the failed events in their entirety, as found in your warehouse. +The detailed view also shows a description of the root cause and the application version ([web](/docs/sources/trackers/snowplow-tracker-protocol/ootb-data/app-information/index.md#application-context-entity-on-web-apps), [mobile](/docs/sources/trackers/mobile-trackers/tracking-events/platform-and-application-context/index.md)). It provides a sample of the failed events in their entirety, as found in your warehouse. Some columns are too wide to fit in the table: click on them to see the full pretty-printed, syntax-highlighted content. The most useful column to explore is probably `CONTEXTS_COM_SNOWPLOWANALYTICS_SNOWPLOW_FAILURE_1`, which contains the actual error information encoded as a JSON object: diff --git a/docs/data-product-studio/data-structures/index.md b/docs/data-product-studio/data-structures/index.md index 15f8199852..b43272c582 100644 --- a/docs/data-product-studio/data-structures/index.md +++ b/docs/data-product-studio/data-structures/index.md @@ -8,4 +8,4 @@ This section explains how to create, manage, and update [data structures](/docs/ * Snowplow BDP Console UI * Data structures API * [Snowplow CLI](/docs/data-product-studio/snowplow-cli/index.md) -* For Community users: [Iglu](/docs/api-reference/iglu/iglu-repositories/iglu-server/) +* For Community users: [Iglu](/docs/api-reference/iglu/iglu-repositories/iglu-server/index.md) diff --git a/docs/fundamentals/schemas/index.md b/docs/fundamentals/schemas/index.md index 3a7b881638..0d5bbebfb1 100644 --- a/docs/fundamentals/schemas/index.md +++ b/docs/fundamentals/schemas/index.md @@ -117,7 +117,7 @@ After the self section, the remainder of the schema is where you will begin desc **“properties”** - Here is where you will describe the fields you intend on collecting. Each field is given a name and a number of arguments, these are important as they feed directly into the validation process. - **“description”** - Similar to the description field for the schema, this argument is where you should put detailed information on what this field represents to avoid any misunderstanding or misinterpretation during analysis. -- **"type"** - This denotes the type of data that is collected through this field. The most common types of data collected are `string`, `number`, `integer`, `object`, `array`, `boolean` and `null`. A single field can allow multiple types as shown in the field `job title` in the example schema which allows both `string` and `null` +- **"type"** - This denotes the type of data that is collected through this field. The most common types of data collected are `string`, `number`, `integer`, `object`, `array`, `boolean` and `null`. A single field can allow multiple types as shown in the field `job role` in the example schema which allows both `string` and `null` - Validation arguments can then be passed into the field such as `minLength`, `maxLength` and `enum` for strings and `minimum` and `maximum` for integers. A full set of valid arguments can be found on the [JSON schema specification](https://datatracker.ietf.org/doc/html/draft-fge-json-schema-validation-00#section-5). **“$supersedes”** / **“$supersededBy”** - _Optional, not shown_. See [marking schemas as superseded](/docs/data-product-studio/data-structures/version-amend/amending/index.md#marking-the-schema-as-superseded). diff --git a/docs/get-started/feature-comparison/index.md b/docs/get-started/feature-comparison/index.md index 5330681fc4..9aa9d05d99 100644 --- a/docs/get-started/feature-comparison/index.md +++ b/docs/get-started/feature-comparison/index.md @@ -12,7 +12,7 @@ Here is a detailed list of product features, including which are available as pa | [35+ trackers and webhooks](/docs/sources/index.md) | ✅ | ✅ | | First party tracking | ✅ | ✅ | | [Anonymous data collection](/docs/resources/recipes-tutorials/recipe-anonymous-tracking/index.md) | ✅ | ✅ | -| [ID service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md#what-is-an-id-service) | ✅ | ✅ | +| [Cookie Extension service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md#what-is-a-cookie-extension-service) | ✅ | ✅ | | High availability and auto-scaling | ✅ | ❌ | | [Enrichments](/docs/pipeline/enrichments/available-enrichments/index.md) | ✅ | ✅ | | [Failed events](/docs/fundamentals/failed-events/index.md) | ✅ | ✅ | diff --git a/docs/get-started/tracking/cookies-and-ad-blockers/index.md b/docs/get-started/tracking/cookies-and-ad-blockers/index.md index 67378e4fc7..8cd4b75fd2 100644 --- a/docs/get-started/tracking/cookies-and-ad-blockers/index.md +++ b/docs/get-started/tracking/cookies-and-ad-blockers/index.md @@ -21,14 +21,14 @@ Although first-party cookies don't allow sharing user and session identifiers ac Using Snowplow, you can set up your collector to be on the same domain as the website, which is the requirement for the use of first-party cookies. To learn how to set up the collector domain, [visit this page](/docs/sources/first-party-tracking/index.md). -**Strategy 2: Snowplow ID service for Safari ITP** +**Strategy 2: Snowplow Cookie Extension service for Safari ITP** As of Safari 16.4 released in April 2023, Safari sets the [lifetime of server-set cookies](https://webkit.org/tracking-prevention/#cname-and-third-party-ip-address-cloaking-defense) to a maximum of 7 days under some circumstances even if they are first-party cookies. This greatly limits the effectiveness of tracking a customer journey where users are not regularly returning to your website. In particular, it affects the `network_userid` identifier in Snowplow events. -Snowplow provides the ID service solution that fully mitigates the impact of this change. -Visit the [documentation for the ID service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md#itp-mitigation) to learn more. +Snowplow provides the Cookie Extension service solution that fully mitigates the impact of this change. +Visit the [documentation for the Cookie Extension service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md#itp-mitigation) to learn more. ## 2. Mitigating the impact of ad-blockers diff --git a/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-models/dbt-attribution-data-model/index.md b/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-models/dbt-attribution-data-model/index.md index 2be05418c6..d1f1ce689e 100644 --- a/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-models/dbt-attribution-data-model/index.md +++ b/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-models/dbt-attribution-data-model/index.md @@ -213,7 +213,7 @@ You can do either for campaigns, too, with the `snowplow__channels_to_exclude` a In order to reduce unneccesarily long paths you can apply a number of path transformations that are created as part of user defined functions automatically in your warehouse by the package. -In order to apply these transformations, all you have to do is to define them in the `snowplow__path_transforms` variable as a list of dictionaries, with the transformation name as key and optionally the parameter as value (for `remove_if_last_and_not_all` and `remove_if_not_all`). If the transformation requires no parameter you can just use `null` as values for the dictionary. For more details on how to do this, check out the [configuration page](/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-configuration/attribution/index.mdx) E.g.: `{'exposure_path': null, 'remove_if_last_and_not_all': 'direct'}` +In order to apply these transformations, all you have to do is to define them in the `snowplow__path_transforms` variable as a dictionary. In case of `remove_if_last_and_not_all` and `remove_if_not_all` transformations, the transformation name is the key and a non-empty array is the value. For other transformations (`exposure_path`, `first_path`, `unique_path`), no additional parameter is required, you can just use `null` as values. For more details on how to do this, check out the [configuration page](/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-configuration/attribution/index.mdx) E.g.: `{'exposure_path': null, 'remove_if_last_and_not_all': ['channel_to_remove_1', 'campaign_to_remove_1', 'campaign_to_remove_2']}` Please note that the transformations are applied on both campaign and channel paths equally.
Path transform options diff --git a/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-operation/debugging/index.md b/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-operation/debugging/index.md index 04048f42b0..c88f3b662e 100644 --- a/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-operation/debugging/index.md +++ b/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-operation/debugging/index.md @@ -32,7 +32,7 @@ The most common scenario that may happen is there are certain session_identifier We process the data based on sessions: for each run we take a look at the new events (with default 6 hours of lookback window) then look at the sessions manifest table and identify how far to look back in time to cover those sessions with new events as a whole. One overlooked common scenario is that in case of pipeline issues, if some data is getting processed by the model, but the rest are only added to the warehouse outside of that default 6 hour lookback window, then the package will leave those events behind unless the same session will have new events coming in, and it needs to be reprocessed as a whole again (unless the data modeling jobs were immediately paused after the pipeline went partially down). In such cases you need partial backfilling (see the [late loaded events](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/late-arriving-data/index.md#late-loaded-events) section). -The events can also be [late sent](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/late-arriving-data/#late-sent-events), in which case if they fall out of the limit, they will not get processed. +The events can also be [late sent](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/late-arriving-data/index.md#late-sent-events), in which case if they fall out of the limit, they will not get processed. We also "quarantine" the sessions at times, as however hard we may try to filter out bots, there could be odd outliers that keep sessions alive for days, for which we have the `snowplow__max_session_days` variable. If bots keep sending events for longer than defined in that variable, the package will only process the first of those in a subsequent run and archives the session_identifier in the [quarantine table](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/manifest-tables/index.md#quarantine-table) after it runs. In such a case it is always best to look for the missing event's session_identifier (domain_userid as defaulted for web events) in the table to explain why the event is missing. diff --git a/docs/modeling-your-data/modeling-your-data-with-dbt/migration-guides/attribution/index.md b/docs/modeling-your-data/modeling-your-data-with-dbt/migration-guides/attribution/index.md new file mode 100644 index 0000000000..57bfa8f755 --- /dev/null +++ b/docs/modeling-your-data/modeling-your-data-with-dbt/migration-guides/attribution/index.md @@ -0,0 +1,13 @@ +--- +title: "Attribution" +sidebar_position: 20 +--- + +### Upgrading to 0.5.0 +- From now on the `snowplow__path_transforms` variable parameters only accept non-empty arrays for `remove_if_last_and_not_all` and `remove_if_not_all` variables instead of strings, please your variable overwrites in your dbt_project.yml accordingly. Previously you could only remove one specific channel or campaign, now you can do multiple, if needed. + +```yml title="dbt_project.yml" +vars: + snowplow_attribution: + snowplow__path_transforms: {'exposure_path': null, 'remove_if_last_and_not_all': ['channel_to_remove_1', 'campaign_to_remove_1, 'campaign_to_remove_2']} + ``` diff --git a/docs/pipeline/enrichments/available-enrichments/custom-javascript-enrichment/writing/index.md b/docs/pipeline/enrichments/available-enrichments/custom-javascript-enrichment/writing/index.md index 15a66ccf7d..7627491bcf 100644 --- a/docs/pipeline/enrichments/available-enrichments/custom-javascript-enrichment/writing/index.md +++ b/docs/pipeline/enrichments/available-enrichments/custom-javascript-enrichment/writing/index.md @@ -243,25 +243,12 @@ You might be tempted to update derived entities in a similar way by using `event ## Discarding the event -Sometimes you don’t want the event to appear in your data warehouse or lake, e.g. because you suspect it comes from a bot and not a real user. In this case, you can `throw` an exception in your JavaScript code, which will send the event to [failed events](/docs/fundamentals/failed-events/index.md): +```mdx-code-block +import DiscardingEvents from "@site/docs/reusable/discarding-events/_index.md" -```js -const botPattern = /.*Googlebot.*/; - -function process(event) { - const useragent = event.getUseragent(); - - if (useragent !== null && botPattern.test(useragent)) { - throw "Filtered event produced by Googlebot"; - } -} + ``` -:::caution - -This will create an “enrichment failure” failed event, which may be tricky to distinguish from genuine failures in your enrichment code, e.g. due to a mistake. In the future, we might provide a better mechanism for discarding events. - -::: ## Accessing Java methods diff --git a/docs/pipeline/enrichments/available-enrichments/pii-pseudonymization-enrichment/index.md b/docs/pipeline/enrichments/available-enrichments/pii-pseudonymization-enrichment/index.md index a06205d01d..4aa316f2a4 100644 --- a/docs/pipeline/enrichments/available-enrichments/pii-pseudonymization-enrichment/index.md +++ b/docs/pipeline/enrichments/available-enrichments/pii-pseudonymization-enrichment/index.md @@ -12,7 +12,7 @@ In Europe the obligations regarding Personal Data handling have been outlined on ## Configuration -- [Schema](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow.enrichments/pii_enrichment_config/jsonschema/2-0-0) +- [Schema](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow.enrichments/pii_enrichment_config/jsonschema/2-0-1) - [Example](https://github.com/snowplow/enrich/blob/master/config/enrichments/pii_enrichment_config.json) ```mdx-code-block @@ -23,7 +23,7 @@ import TestingWithMicro from "@site/docs/reusable/test-enrichment-with-micro/_in Two types of fields can be configured to be hashed: -- `pojo`: field that is effectively a scalar field in the enriched event (full list of fields that can be pseudonymized [here](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow.enrichments/pii_enrichment_config/jsonschema/2-0-0#L43-L60)) +- `pojo`: field that is effectively a scalar field in the enriched event (full list of fields that can be pseudonymized [here](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow.enrichments/pii_enrichment_config/jsonschema/2-0-1#L43-L60)) - `json`: field contained inside a self-describing JSON (e.g. in `unstruct_event`) With the configuration example, the fields `user_id` and `user_ipaddress` of the enriched event would be hashed, as well as the fields `email` and `ip_opt` of the unstructured event in case its schema matches _iglu:com.mailchimp/subscribe/jsonschema/1-\*-\*_. @@ -42,9 +42,16 @@ It's **important** to keep these things in mind when using this enrichment: - Hashing a field can change its format (e.g. email) and its length, thus making a whole valid original event invalid if its schema is not compatible with the hashing. - When updating the `salt` after it has already been used, same original values hashed with previous and new salt will have different hashes, thus making a join impossible and/or creating duplicate values. +### `anonymousOnly` mode +Enrich 5.3.0 introduced the `anonymousOnly` mode. When [anonymousOnly](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow.enrichments/pii_enrichment_config/jsonschema/2-0-1#L155) is set to true, PII fields are masked only in events tracked in anonymous mode (i.e. the `SP-Anonymous` header is present). + +This is useful for compliance with regulation such as GDPR, where you would start with [anonymous tracking](/docs/sources/trackers/javascript-trackers/web-tracker/anonymous-tracking/index.md) by default (all identifiers are masked) and switch to non-anonymous tracking when the user consents to data collection (all identifiers are kept). + +By default, `anonymousOnly` is `false`, i.e. PII fields are always masked. + ## Input -[These fields](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow.enrichments/pii_enrichment_config/jsonschema/2-0-0#L43-L60) of the enriched event and any field of an unstructured event or context can be hashed. +[These fields](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow.enrichments/pii_enrichment_config/jsonschema/2-0-1#L43-L60) of the enriched event and any field of an unstructured event or context can be hashed. ## Output diff --git a/docs/pipeline/enrichments/filtering-bot-events/index.md b/docs/pipeline/enrichments/filtering-bot-events/index.md new file mode 100644 index 0000000000..9ea9c7beab --- /dev/null +++ b/docs/pipeline/enrichments/filtering-bot-events/index.md @@ -0,0 +1,10 @@ +--- +title: "Filtering bot events" +sidebar_position: 100 +--- + +```mdx-code-block +import DiscardingEvents from "@site/docs/reusable/discarding-events/_index.md" + + +``` \ No newline at end of file diff --git a/docs/reusable/discarding-events/_index.md b/docs/reusable/discarding-events/_index.md new file mode 100644 index 0000000000..b31854f831 --- /dev/null +++ b/docs/reusable/discarding-events/_index.md @@ -0,0 +1,43 @@ +Sometimes you don’t want the event to appear in your data warehouse or lake, e.g. because you suspect it comes from a bot and not a real user. + +Starting with Enrich 5.3.0, it is possible to drop an event by calling `event.drop()` in JavaScript enrichment code: + +```js +const botPattern = /.*Googlebot.*/; + +function process(event) { + const useragent = event.getUseragent(); + + if (useragent !== null && botPattern.test(useragent)) { + event.drop(); + } +} +``` + +This mechanism can be used to drop not only good events, but also invalid events. The dropped events will not be sent to any stream or destination, thus lowering the infrastructure costs. + +:::caution + +There is no way to recover dropped events therefore use it with caution. + +::: + +Another way to discard events is throwing an exception in your JavaScript code, which will send the event to [failed events](/docs/fundamentals/failed-events/index.md): + +```js +const botPattern = /.*Googlebot.*/; + +function process(event) { + const useragent = event.getUseragent(); + + if (useragent !== null && botPattern.test(useragent)) { + throw "Filtered event produced by Googlebot"; + } +} +``` + +:::caution + +This will create an “enrichment failure” failed event, which may be tricky to distinguish from genuine failures in your enrichment code, e.g. due to a mistake. + +::: diff --git a/docs/sources/first-party-tracking/index.md b/docs/sources/first-party-tracking/index.md index e9b7e3f1b7..5c919191ec 100644 --- a/docs/sources/first-party-tracking/index.md +++ b/docs/sources/first-party-tracking/index.md @@ -16,7 +16,7 @@ When your collector domain (e.g. `collector.snwplow.net`) does not match your pr With first-party tracking, you can configure a custom collector domain (e.g. `c.flowershop.ai`) to match your primary domain (e.g. `flowershop.ai`), sidestepping these limitations. -Note that in light of the [latest ITP restrictions](https://webkit.org/tracking-prevention/#cname-and-third-party-ip-address-cloaking-defense), you will also need to [use an ID service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md#itp-mitigation) to fully persist the cookies. +Note that in light of the [latest ITP restrictions](https://webkit.org/tracking-prevention/#cname-and-third-party-ip-address-cloaking-defense), you will also need to [use a Cookie Extension service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md#itp-mitigation) to fully persist the cookies. :::info diff --git a/docs/sources/trackers/google-tag-manager/settings-template/index.md b/docs/sources/trackers/google-tag-manager/settings-template/index.md index 4e85ab77d7..a77e01a000 100644 --- a/docs/sources/trackers/google-tag-manager/settings-template/index.md +++ b/docs/sources/trackers/google-tag-manager/settings-template/index.md @@ -86,9 +86,9 @@ This setting disables client-side user identifiers but tracks session informatio See [here](/docs/resources/recipes-tutorials/recipe-anonymous-tracking/index.md) for more information on anonymous tracking. -#### ID Service +#### Cookie Extension Service -This allows you to set the endpoint for the [ID service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/#what-is-an-id-service-). +This allows you to set the endpoint for the [Cookie Extension Service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md#what-is-a-cookie-extension-service-). ## Cookie Settings @@ -125,7 +125,7 @@ This setting allows you to specify the timeout for the session cookie. By defaul ### Synchronously Write Cookies -This setting allows you to specify whether the Snowplow tracker should [write cookies synchronously](/docs/sources/trackers/javascript-trackers/web-tracker/configuring-how-events-sent/#synchronous-cookie-writes). By default, the tracker will write cookies asynchronously. +This setting allows you to specify whether the Snowplow tracker should [write cookies synchronously](/docs/sources/trackers/javascript-trackers/web-tracker/configuring-how-events-sent/index.md#synchronous-cookie-writes). By default, the tracker will write cookies asynchronously. ## Dispatching @@ -169,7 +169,7 @@ If an event is generated that is over the maximum payload size, the event will b #### Enable keepalive -This setting allows you to enable or disable the [keepalive](/docs/sources/trackers/javascript-trackers/web-tracker/configuring-how-events-sent/#keepalive-option-for-collector-requests) feature. This will enable requests to continue to be sent, even if the user navigates away from the page that sent the request. +This setting allows you to enable or disable the [keepalive](/docs/sources/trackers/javascript-trackers/web-tracker/configuring-how-events-sent/index.md#keepalive-option-for-collector-requests) feature. This will enable requests to continue to be sent, even if the user navigates away from the page that sent the request. Defaults to `false`. diff --git a/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md b/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md index dc4794b966..552f2de428 100644 --- a/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md +++ b/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md @@ -53,15 +53,15 @@ As of Safari 16.4 released in April 2023, Safari sets the [lifetime of server-se This greatly limits the effectiveness of tracking a customer journey where users are not regularly returning to your website. Without a strong understanding of your customers, downstream use cases including marketing attribution, product analytics and personalized recommendations are difficult to achieve. -**The ID service can help enhance the confidence in persistent browser identifiers that Snowplow tracking provides and specifically for this guide, the `network_userid`.** +**The Cookie Extension service ,previously ID service, can help enhance the confidence in persistent browser identifiers that Snowplow tracking provides and specifically for this guide, the `network_userid`.** -### What is an ID service ? +### What is a Cookie Extension service ? -An _ID service_, as we chose to call it, is a process that allows for generating a unique browser identifier and enhancing the Snowplow tracking capabilities in environments where the Intelligent Tracking Prevention (ITP) feature is enabled such as on iOS browsers (Safari, Chrome, Firefox) and desktop Safari. +An _Cookie Extension service_, as we chose to call it, is a process that allows for generating a unique browser identifier and enhancing the Snowplow tracking capabilities in environments where the Intelligent Tracking Prevention (ITP) feature is enabled such as on iOS browsers (Safari, Chrome, Firefox) and desktop Safari. -### Developing and deploying an ID service +### Developing and deploying a Cookie Extension service -An ID service is code that needs to be deployed on and executed from the same IP space that serves the main web document of your application. This is probably the web application system or the CDN in front of the application. +An Cookie Extension service is code that needs to be deployed on and executed from the same IP space that serves the main web document of your application. This is probably the web application system or the CDN in front of the application. This code has minimal functionality and based on our experience can either be: @@ -70,50 +70,54 @@ This code has minimal functionality and based on our experience can either be: - **A custom middleware based on the customer’s framework**. E.g. ExpressJS middleware, Next.js middleware, Play custom action etc. which can run on every document request. - **A low-footprint application with a single endpoint**. A Go web server or something along these lines. -#### Developing the ID service code +#### Developing the Cookie Extension service code The responsibilities of this service are: 1. Create a unique identifier (UUID v4) for this browser, set it in a cookie and return it in a `Set-Cookie` response header on a domain accessible by the service at all times. 2. Increase the expiry for the cookie used as the network_userid identifier (by default, a cookie named sp, configured via collector.config.name) which should have the same value as the first cookie. -_The new unique identifier cookie for sake of simplicity in this document will have the name `spIdService`._ +_The new unique identifier cookie for sake of simplicity in this document will have the name `spCookieExtensionService`._ -#### ID service business logic +#### Cookie Extension service business logic -The ID service code should include the following logic: +The Cookie Extension service code should include the following logic: -- If the ID service new identifier cookie already exists on the request, then it should re-set the cookies with the same values and updated expiration for both the `spIdService` and `sp` cookies. -- If `spIdService` does not exist, but the collector's `sp` cookie does exist, then the it should set `spIdService value = sp cookie value`. _This will make sure we keep old identifiers in place and not lose any data._ -- If `spIdService` and `sp` are both missing, then it generates a new ID in the `spIdService` and `sp` cookies with the same unique identifier generation algorithm with the Snowplow pipeline, currently UUID v4. +- If the Cookie Extension service new identifier cookie already exists on the request, then it should re-set the cookies with the same values and updated expiration for both the `spCookieExtensionService` and `sp` cookies. +- If `spCookieExtensionService` does not exist, but the collector's `sp` cookie does exist, then the it should set `spCookieExtensionService value = sp cookie value`. _This will make sure we keep old identifiers in place and not lose any data._ +- If `spCookieExtensionService` and `sp` are both missing, then it generates a new ID in the `spCookieExtensionService` and `sp` cookies with the same unique identifier generation algorithm with the Snowplow pipeline, currently UUID v4. - The HTTP response should have a 200 OK status code but any additional payload is not necessary. ### Code examples -Below we showcase a couple of code samples for ID service API endpoints: +Below we showcase a couple of code samples for Cookie Extension service API endpoints: - + ```ts reference -https://github.com/snowplow-industry-solutions/id-service-examples/blob/main/examples/typescript/Next.js/api-route.ts +https://github.com/snowplow-industry-solutions/cookie-extension-service-examples/blob/main/examples/typescript/Next.js/api-route.ts ``` ```php reference -https://github.com/snowplow-industry-solutions/id-service-examples/blob/main/examples/php/wordpress/api-route.php +https://github.com/snowplow-industry-solutions/cookie-extension-service-examples/blob/main/examples/php/wordpress/api-route.php ``` -### Using the ID service on the Snowplow browser tracker +### Using the Cookie Extension service on the Snowplow browser tracker -When the ID service has been deployed on a system with the same resolved IP as the main document, the tracker can then be configured to orchestrate the required ID service API calls. +:::note +Before version 4.5.0 of the tracker this attribute was available as `idService`. +::: + +When the Cookie Extension service has been deployed on a system with the same resolved IP as the main document, the tracker can then be configured to orchestrate the required Cookie Extension service API calls. -This process is opt-in by using the `idService` option during tracker initialization: +This process is opt-in by using the `cookieExtensionService` option during tracker initialization: @@ -121,7 +125,7 @@ This process is opt-in by using the `idService` option during tracker initializa ```tsx window.snowplow("newTracker", "sp", "{{collector_url_here}}", { /* ...Rest of the tracker options */ - idService: "/id-service-endpoint" + cookieExtensionService: "/cookie-extension-service-endpoint" }); ``` @@ -130,7 +134,7 @@ window.snowplow("newTracker", "sp", "{{collector_url_here}}", { ```tsx newTracker('sp1', 'c.customer.com', { - idService: "/id-service-endpoint", + cookieExtensionService: "/cookie-extension-service-endpoint", /* ...Rest of the tracker options */ }); ``` @@ -143,15 +147,15 @@ When the tracker detects this option it will send an HTTP request during initial ```mermaid sequenceDiagram autonumber - participant Service as ID Service Endpoint + participant Service as Cookie Extension service Endpoint participant Tracking as Snowplow Tracking Code participant Collector as Snowplow Collector Tracking->>Service: Request an ID - note over Service: ID Service code + note over Service: Cookie Extension service code Service->>Tracking: OK response - note over Tracking,Service: Set-Cookie: sp=...
Set-Cookie: spIdService=... + note over Tracking,Service: Set-Cookie: sp=...
Set-Cookie: spCookieExtensionService=... loop Normal flow Tracking->>Collector: Event Tracking - note over Tracking,Collector: Cookie: sp=...#59; spIdService=... + note over Tracking,Collector: Cookie: sp=...#59; spCookieExtensionService=... end ``` diff --git a/docs/sources/trackers/javascript-trackers/web-tracker/plugins/index.md b/docs/sources/trackers/javascript-trackers/web-tracker/plugins/index.md index 2c1dac5a03..76426af7a4 100644 --- a/docs/sources/trackers/javascript-trackers/web-tracker/plugins/index.md +++ b/docs/sources/trackers/javascript-trackers/web-tracker/plugins/index.md @@ -30,6 +30,7 @@ If you are using the JavaScript tracker with the full `sp.js` and your plugin is | [Ecommerce (Snowplow)](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/ecommerce/index.md) | Events and entities | Manual | ✅ | ❌ | `browser-plugin-snowplow-ecommerce` | | [Ecommerce (Enhanced)](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/ecommerce/enhanced/index.md) | Events | Manual | ❌ | ❌ | `browser-plugin-enhanced-ecommerce` | | [Errors](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/errors/index.md) | Events | Manual and automatic | ✅ | ❌ | `browser-plugin-error-tracking` | +| [Element visibility](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/element-tracking/index.md) | Events | Automatic | ❌ | ❌ | `browser-plugin-element-tracking` | | [Event Specifications](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/event-specifications/index.md) | Entities | Automatic | ❌ | ❌ | `browser-plugin-event-specifications` | | [Forms](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/form-tracking/index.md) | Events | Automatic | ✅ | ❌ | `browser-plugin-form-tracking` | | [GA cookies](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/ga-cookies/index.md) | Entities | Automatic | ✅ | ❌ | `browser-plugin-ga-cookies` | diff --git a/docs/sources/trackers/javascript-trackers/web-tracker/previous-versions/javascript-tracker-v2/tracker-setup/initializing-a-tracker-2/index.md b/docs/sources/trackers/javascript-trackers/web-tracker/previous-versions/javascript-tracker-v2/tracker-setup/initializing-a-tracker-2/index.md index be1c5a1801..b178924ff4 100644 --- a/docs/sources/trackers/javascript-trackers/web-tracker/previous-versions/javascript-tracker-v2/tracker-setup/initializing-a-tracker-2/index.md +++ b/docs/sources/trackers/javascript-trackers/web-tracker/previous-versions/javascript-tracker-v2/tracker-setup/initializing-a-tracker-2/index.md @@ -473,7 +473,7 @@ When initialising the tracker, you can specify an array of features to skip. Full list of features: - `res` - Screen Resolution -- `cd` - Screen Colour Depth +- `cd` - Screen Color Depth - `cookie` - Cookie support (if cookies enabled in tracker initialization) - `pdf` - PDF MimeType Support (application/pdf) - `qt` - Quicktime Video MimeType Support (video/quicktime) diff --git a/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/element-tracking/index.md b/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/element-tracking/index.md new file mode 100644 index 0000000000..e89d32da96 --- /dev/null +++ b/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/element-tracking/index.md @@ -0,0 +1,816 @@ +--- +title: "Element tracking" +sidebar_position: 55 +--- + +# Element visibility and lifecycle tracking + +```mdx-code-block +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +``` + +Element tracking enables declarative tracking of page elements existing on web pages and scrolling into view. + +The plugin lets you define rules for which elements to track, and lets you trigger events for any combination of: + +- New matching elements get added to a page +- Existing elements get changed to match a rule +- Matching elements get scrolled into a user's view and become visible +- Matching elements get scrolled out of a user's view and become no longer visible +- Elements get changed to no longer match a rule +- Matching elements get removed from a page + +As a configuration-based plugin, you only need to define which elements should generate events, and in which scenarios. +You can reuse the same configuration for generic tracking across a varying number of pages or sites. + +Each event contains information about the matching element, and you can configure extra details to extract to allow dynamic event payloads. + +Example use cases for these events include: + +- Funnel steps (form on page > form in view > [form tracking events](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/form-tracking/index.md)) +- List impression tracking (product impressions) +- Component performance (recommendations performance, newsletter sign-up forms, modal popups) +- Product usage (elements that appear on-hover, labeling or grouping events related to specific features) +- Advertisement impression tracking + +## Install plugin + + + + +| Tracker Distribution | Included | +|----------------------|----------| +| `sp.js` | ❌ | +| `sp.lite.js` | ❌ | + +**Download:** + +
Download from GitHub Releases (Recommended)GitHub Releases (plugins.umd.zip)
Available on jsDelivrjsDelivr (latest)
Available on unpkgunpkg (latest)
+ +:::note + +The links to the CDNs point to the current latest version. +You should pin to a specific version when integrating this plugin on your website if you are using a third-party CDN in production. + +::: + +
+ + +- `npm install @snowplow/browser-plugin-element-tracking` +- `yarn add @snowplow/browser-plugin-element-tracking` +- `pnpm add @snowplow/browser-plugin-element-tracking` + + +
+ +## Examples + +Here are some example rules for simple use cases on the [Snowplow website](https://snowplow.io/) ([snapshot at time of writing](https://web.archive.org/web/20250422013533/https://snowplow.io/)). + +The code examples use the [JavaScript Tracker syntax](/docs/sources/trackers/javascript-trackers/web-tracker/index.md), but should easily adapt to Browser Tracker syntax if needed. + +
+ Scroll sections + + The homepage has content grouped into distinct "layers" as you scroll down the page. + To see when users scroll down to each section, you can track an `expose` event for each section. + You can capture the header element text to identify each one. + + ```javascript title="Rule configuration" + snowplow('startElementTracking', { + elements: { + selector: "section", + expose: { when: "element" }, + details: { child_text: { title: "h2" } } + } + }); + ``` + + ```json title="Event: expose_event" + { + "schema": "iglu:com.snowplowanalytics.snowplow/element/jsonschema/1-0-0", + "data": { + "element_name": "section", + "width": 1920, + "height": 1111.7333984375, + "position_x": 0, + "position_y": 716.4500122070312, + "doc_position_x": 0, + "doc_position_y": 716.4500122070312, + "element_index": 2, + "element_matches": 10, + "originating_page_view": "06dbb0a2-9acf-4ae4-9562-1469b6d12c5d", + "attributes": [ + { + "source": "child_text", + "attribute": "title", + "value": "Why Data Teams Choose Snowplow" + } + ] + } + } + ``` + + ```json title="Event: expose_event" + { + "schema": "iglu:com.snowplowanalytics.snowplow/element/jsonschema/1-0-0", + "data": { + "element_name": "section", + "width": 1920, + "height": 2880, + "position_x": 0, + "position_y": 896.683349609375, + "doc_position_x": 0, + "doc_position_y": 1828.183349609375, + "element_index": 3, + "element_matches": 10, + "originating_page_view": "06dbb0a2-9acf-4ae4-9562-1469b6d12c5d", + "attributes": [ + { + "source": "child_text", + "attribute": "title", + "value": "How Does Snowplow Work?" + } + ] + } + } + ``` + +
+ +
+ Content depth + + The blog posts have longer-form content. + Snowplow's page ping events track scroll depth by pixels, but those measurements become inconsistent between devices and page. + To see how much content gets consumed, you can generate stats based on the paragraphs in the content. + You can also get periodic stats based on the entire article in page pings. + + ```javascript title="Rule configuration" + snowplow('startElementTracking', { + elements: [ + { + selector: ".blogs_blog-post-body_content", + name: "blog content", + expose: false, + includeStats: ["page_ping"] + }, + { + selector: ".blogs_blog-post-body_content p", + name: "blog paragraphs" + } + ] + }); + ``` + + Because the expose event contains the `element_index` and `element_matches`, you can easily query the largest `element_index` by page view ID. + The result tells you consumption statistics for individual views of each article. + You can then summarize that metric to the content or category level, or converted to a percentage by comparing with `element_matches`. + + ```json title="Event: expose_event" + { + "schema": "iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0", + "data": [ + { + "schema": "iglu:com.snowplowanalytics.snowplow/element/jsonschema/1-0-0", + "data": { + "element_name": "blog paragraphs", + "width": 800, + "height": 48, + "position_x": 320, + "position_y": 533.25, + "doc_position_x": 320, + "doc_position_y": 1373, + "element_index": 6, + "element_matches": 24, + "originating_page_view": "f390bec5-f63c-48af-b3ad-a03f0511af7f", + "attributes": [] + } + }, + { + "schema": "iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0", + "data": { + "id": "f390bec5-f63c-48af-b3ad-a03f0511af7f" + } + } + ] + } + ``` + + The periodic page ping events also give you a summary of the total progress in the `max_y_depth_ratio`/`max_y_depth` values. + With `y_depth_ratio` you can also see when users backtrack up the page. + + ```json title="Event: page_ping" + { + "schema": "iglu:com.snowplowanalytics.snowplow/element_statistics/jsonschema/1-0-0", + "data": { + "element_name": "blog content", + "element_index": 1, + "element_matches": 1, + "current_state": "unknown", + "min_size": "800x3928", + "current_size": "800x3928", + "max_size": "800x3928", + "y_depth_ratio": 0.20302953156822812, + "max_y_depth_ratio": 0.4931262729124236, + "max_y_depth": "1937/3928", + "element_age_ms": 298379, + "times_in_view": 0, + "total_time_visible_ms": 0 + } + } + ``` + +
+ +
+ Simple funnels + + A newsletter sign-up form exists at the bottom of the page. + Performance measurement becomes difficult because many visitors don't even see it. + To test this you first need to know: + + - When the form exists on a page + - When the form is actually seen + - When people actually interact with the form + - When the form is finally submitted + + The form tracking plugin can only do the last parts, but the element tracker gives you the earlier steps. + If you end up adding more forms in the future, you'll want to know which is which, so you can mark the footer as a component so you can split it out later. + + ```javascript title="Rule configuration" + snowplow('startElementTracking', { + elements: [ + { + selector: ".hbspt-form", + name: "newsletter signup", + create: true, + }, + { + selector: "footer", + component: true, + expose: false + } + ] + }); + ``` + + If you try this on a blog page, you actually get two `create_element` events. + Blog posts have a second newsletter sign-up in a sidebar next to the content. + Because only the second form is a member of the `footer` component, you can easily see which one you are trying to measure when you query the data later. + + ```json title="Event: create_element" + { + "schema": "iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0", + "data": [ + { + "schema": "iglu:com.snowplowanalytics.snowplow/element/jsonschema/1-0-0", + "data": { + "element_name": "newsletter signup", + "width": 336, + "height": 161, + "position_x": 1232, + "position_y": 238.88333129882812, + "doc_position_x": 1232, + "doc_position_y": 3677.883331298828, + "element_index": 1, + "element_matches": 2, + "originating_page_view": "02e30714-a84a-42f8-8b07-df106d669db0", + "attributes": [] + } + }, + { + "schema": "iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0", + "data": { + "id": "02e30714-a84a-42f8-8b07-df106d669db0" + } + } + ] + } + ``` + + ```json title="Event: create_element" + { + "schema": "iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0", + "data": [ + { + "schema": "iglu:com.snowplowanalytics.snowplow/element/jsonschema/1-0-0", + "data": { + "element_name": "newsletter signup", + "width": 560, + "height": 137, + "position_x": 320, + "position_y": 1953.5, + "doc_position_x": 320, + "doc_position_y": 5392.5, + "element_index": 2, + "element_matches": 2, + "originating_page_view": "02e30714-a84a-42f8-8b07-df106d669db0", + "attributes": [] + } + }, + { + "schema": "iglu:com.snowplowanalytics.snowplow/component_parents/jsonschema/1-0-0", + "data": { + "element_name": "newsletter signup", + "component_list": [ + "footer" + ] + } + }, + { + "schema": "iglu:com.snowplowanalytics.snowplow/element/jsonschema/1-0-0", + "data": { + "element_name": "footer", + "width": 1920, + "height": 1071.5, + "position_x": 0, + "position_y": 1212, + "doc_position_x": 0, + "doc_position_y": 4651, + "originating_page_view": "", + "attributes": [] + } + }, + { + "schema": "iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0", + "data": { + "id": "02e30714-a84a-42f8-8b07-df106d669db0" + } + } + ] + } + ``` + +
+ +
+ Recommendations performance + + The homepage contains a section for the "Latest Blogs from Snowplow." + This could represent recommendations or some other form of personalization. + If it did, one might want to optimize it. + Link tracking could tell you when a recommendation worked and a visitor clicked it, but how would identify the recommendation not encouraging clicks? + If you track when the widget becomes visible and include the items that got recommended, you could correlate that with the clicks to measure performance. + For fairer measurement of visibility, you can configure that visibility only counts if at least 50% is in view, and it has to be on screen for at least 1.5 seconds. + You'll also collect the post title and author information. + + + ```javascript title="Rule configuration" + snowplow('startElementTracking', { + elements: [ + { + selector: ".blog_list-header_list-wrapper", + name: "recommended_posts", + create: true, + expose: { when: "element", minTimeMillis: 1500, minPercentage: 0.5 }, + contents: [ + { + selector: ".collection-item", + name: "recommended_item", + details: { child_text: { title: "h3", author: ".blog_list-header_author-text > p" } } + } + ] + } + ] + }); + ``` + + Scrolling down to see the items and you see the items that get served to the visitor: + + ```json title="Event: expose_element" + { + "schema": "iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0", + "data": [ + { + "schema": "iglu:com.snowplowanalytics.snowplow/element/jsonschema/1-0-0", + "data": { + "element_name": "recommended_posts", + "width": 1280, + "height": 680.7666625976562, + "position_x": 320, + "position_y": 437.70001220703125, + "doc_position_x": 320, + "doc_position_y": 6261.066711425781, + "element_index": 1, + "element_matches": 1, + "originating_page_view": "034db1d6-1d60-42ca-8fe1-9aafc0442a22", + "attributes": [] + } + }, + { + "schema": "iglu:com.snowplowanalytics.snowplow/element_content/jsonschema/1-0-0", + "data": { + "element_name": "recommended_item", + "parent_name": "recommended_posts", + "parent_position": 1, + "position": 1, + "attributes": [ + { + "source": "child_text", + "attribute": "title", + "value": "Data Pipeline Architecture Patterns for AI: Choosing the Right Approach" + }, + { + "source": "child_text", + "attribute": "author", + "value": "Matus Tomlein" + } + ] + } + }, + { + "schema": "iglu:com.snowplowanalytics.snowplow/element_content/jsonschema/1-0-0", + "data": { + "element_name": "recommended_item", + "parent_name": "recommended_posts", + "parent_position": 1, + "position": 2, + "attributes": [ + { + "source": "child_text", + "attribute": "title", + "value": "Data Pipeline Architecture For AI: Why Traditional Approaches Fall Short" + }, + { + "source": "child_text", + "attribute": "author", + "value": "Matus Tomlein" + } + ] + } + }, + { + "schema": "iglu:com.snowplowanalytics.snowplow/element_content/jsonschema/1-0-0", + "data": { + "element_name": "recommended_item", + "parent_name": "recommended_posts", + "parent_position": 1, + "position": 3, + "attributes": [ + { + "source": "child_text", + "attribute": "title", + "value": "Agentic AI Applications: How They Will Turn the Web Upside Down" + }, + { + "source": "child_text", + "attribute": "author", + "value": "Yali\tSassoon" + } + ] + } + }, + { + "schema": "iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0", + "data": { + "id": "034db1d6-1d60-42ca-8fe1-9aafc0442a22" + } + } + ] + } + ``` + +
+ +## Enable element tracking + +You can begin tracking elements by providing configuration to the plugin's `startElementTracking` method: + + + + +```javascript +window.snowplow('addPlugin', + "https://cdn.jsdelivr.net/npm/@snowplow/browser-plugin-element-tracking@latest/dist/index.umd.min.js", + ["snowplowElementTracking", "SnowplowElementTrackingPlugin"] +); + +snowplow('startElementTracking', { elements: [/* configuration */] }); +``` + + + + +Element tracking is part of a separate plugin, `@snowplow/browser-plugin-element-tracking`. You need to install it with your favorite package manager: `npm install @snowplow/browser-plugin-element-tracking` and then initialize it: + +```javascript +import { newTracker } from '@snowplow/browser-tracker'; +import { SnowplowElementTrackingPlugin, startElementTracking } from '@snowplow/browser-plugin-element-tracking'; + +newTracker('sp1', '{{collector_url}}', { + appId: 'my-app-id', + plugins: [ SnowplowElementTrackingPlugin() ], +}); + +startElementTracking({ elements: [/* configuration */] }); +``` + + + +Each use of this method adds the given list of element rules to the plugin configuration to start automatically tracking events. + +The `elements` configuration can take a single rule or an array of rules. + +Beyond `elements`, you can also specify `context`: an array of static entities or entity-generating functions to include custom information with all events generated by the plugin. +This can also exist at the individual rule level for more specific entity requirements. + +For the specifics of rule configuration, see [Rule configuration](#rule-configuration) below. + +## Disabling element tracking + +To turn off tracking, use `endElementTracking` to remove the rule configuration. +Providing no options to `endElementTracking` removes all earlier configured rules. +If all rules get removed, the plugin removes its listeners until new rules get configured. + +If you want to stop tracking based for specific rules, you can provide the `name` or `id` values to the `endElementTracking` method. +Each rule provided to `startElementTracking` gets associated with a `name` - and optionally, an `id`. +If you don't specify a `name`, the `name` defaults to the `selector` value (required for all rules). + +For more complex requirements, you can also specify a callback function to decide if a rule should turn off (callback returns `true`) or not (callback returns `false`). + + + + +```javascript +snowplow('endElementTracking', { elements: ['name1', 'name2'] }); // removes based on `name` matching; multiple rules may share a name +snowplow('endElementTracking', { elementIds: ['id1'] }); // removes rules based on `id` matching; at most one rule can have the same `id` +snowplow('endElementTracking', { filter: (rule) => /recommendations/i.test(rule.name) }); // more complicated matching; rules where the `filter` function returns true will be removed +snowplow('endElementTracking'); // remove all configured rules +``` + + + + +```javascript +endElementTracking({ elements: ['name1', 'name2'] }); // removes based on `name` matching; multiple rules may share a name +endElementTracking({ elementIds: ['id1'] }); // removes rules based on `id` matching; at most one rule can have the same `id` +endElementTracking({ filter: (rule) => /recommendations/i.test(rule.name) }); // more complicated matching; rules where the `filter` function returns true will be removed +endElementTracking(); // remove all configured rules +``` + + + +Removing rules by name removes all rules with matching names - rule names don't require uniqueness. +Rule IDs _must be_ unique, so only a single rule matches per `elementIds` value. +If you specify more than one of the `elementIds`, `elements`, and `filter` options, they get evaluated in that order. +Passing an empty object to `endElementTracking` counts as specifying no options - and removes no rules - which differs to calling it with no arguments. + +## Rule configuration + +When calling `startElementTracking`, you specify the `elements` option with either a single rule or an array of rules. +Each rule defines core information like: the elements to match, events to fire, extra details to collect about each element (or their contents), and custom entities to attach. + +### Core configuration + +The foundational configuration required for working with the plugin APIs. + +| Rule property | Type | Description | Status | +|---------------|------|-------------|----------| +| `selector`|`string`|A CSS selector string that matches one or more elements on the page that should trigger events from this rule.|**Required**| +| `name`|`string`|A label to name this rule. Allows you to keep a stable name for events generated by this rule, even if the `selector` changes, so the data produced remains consistent. You can share a single `name` between many rules to have different configurations for different selectors. If not supplied, the `selector` value becomes the `name`.|_Recommended_| +| `id`|`string`|A specific identifier for this rule. Useful if you share a `name` between many rules and need to specifically remove individual rules within that group.|Optional| + +### Event configuration + +These settings define which events should automatically fire, and the situations when they should occur. +By default, only the `expose` setting gets enabled, so the plugin tracks when elements matching the rule's `selector` become visible on the user's viewport. +For convenience, each option can use a boolean to turn on or off each event type for elements matching the selector. +You can also use an object to have more control on when the events get triggered. + +| Rule property | Type | Description | Default | +|---------------|------|-------------|----------| +| `create`|`boolean` or `object`|Controls firing `element_create` events when the element gets added to the page (or already exists when the rule gets configured).|`false`| +| `destroy`|`boolean` or `object`|Controls firing `element_destroy` events when the element gets removed from the page.|`false`| +| `expose`|`boolean` or `object`|Controls firing `element_expose` events when the element becomes visible in the user's viewport.|`true`| +| `obscure`|`boolean` or `object`|Controls firing `element_obscure` events when the element becomes no longer visible in the user's viewport.|`false`| + +#### General event options + +These common options are available for the `create`, `destroy`, `expose`, and `obscure` settings and allow limiting how often the event fires. + +| Rule property | Type | Description | Status | +|---------------|------|-------------|----------| +| `when`|`string` or `object`|Sets the limit on how many times the event should fire for matched elements.|**Required**| +| `condition`|`array`|A single or list of many [data selectors](#data-selectors); if the final result has no elements the event won't trigger.|Optional| + +For `when`, the available options include, in descending order of frequency: + +- `always`: generate an event every time an element becomes eligible (for example, every time an event becomes visible) +- `element`: only fire 1 event for each specific element that matches the rule for the lifetime of the rule (for example, just the first time each element becomes visible) +- `pageview`: like `element`, but reset the state when the tracker next tracks a page view event; this can be useful for single page applications where the plugin may have a long lifetime but you still want to limit the number of events +- `once`: only fire 1 event _per rule_, so even if there are many elements matching `selector` only track the first time this occurs +- `never`: never track this event for this rule. This is useful for defining `components` + +When using the `boolean` shorthand, `true` is identical to `{ when: "always" }`, and `false` is `{ when: "never" }`. + +#### Expose event options + +As well as the [general event options](#general-event-options), `expose` has some extra options specific to its use case. + +| Rule property | Type | Description | +|---------------|------|-------------| +| `minPercentage`|`number`|For larger elements, only consider the element visible if at least this percentage of its area is visible.| +| `minTimeMillis`|`number`|Only consider the element visible if it's cumulative time on screen exceeds this value, in milliseconds.| +| `minSize`|`number`|Unless the elements area (height * width) is at least this size, don't consider the element as visible (for example, don't track empty elements).| +| `boundaryPixels`|`number` or `array`|Add this number of pixels to the dimensions (top, right, bottom, left) of the element when calculating its dimensions for `minPercentage` purposes. You can specify a single value, a pair for vertical and horizontal values, or specific values for each of top, right, bottom, and left.| + +### Shadow DOM compatibility + +If the elements you want to track exist within [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) trees, the plugin may not identify them. +Use these settings to notify the plugin that it should descend into shadow hosts to identify elements to match the rule against. + +| Rule property | Type | Description | +|---------------|------|-------------| +| `shadowSelector`|`string`|A CSS selector for elements that are shadow hosts containing the actual `selector`-targeted elements.| +| `shadowOnly`|`boolean`|By default, the plugin matches `selector` elements both outside and inside shadow hosts matched by `shadowSelector`; set this to `true` to only match elements within shadow hosts matched by `shadowSelector`. (for example, you may want all `button` elements in a web component, but that selector is too generic when applied to your whole site, so this setting can limit the matches to only those within those shadow hosts).| + +### Element data + +These settings control extra information captured about the event generating the event. + +| Rule property | Type | Description | +|---------------|------|-------------| +| `component`|`boolean`|When `true`, defines these elements as being a component. Events generated by this rule, or other rules targeting their child elements, have this rules `name` attached via the `component_parents` entity, showing the component hierarchy that the element belongs to.| +| `details`|`array`|A list of [data selectors](#data-selectors) of information to capture about this element. The selected values populate the `attributes` object in the [`element` entity](#events-and-entities).| +| `includeStats`|`array`|An array of `event_name` values that the plugin should attach an [`element_statistics` entity](#element-statistics) to.| +| `contents`|`array`|You can nest configurations in this property to collect data about elements nested within the elements matched by this rule (for example, this rule could target a recommendations widget, and using `contents` you could describe the individual recommendations served within it). The nested configurations can not trigger their own events, and their [event configuration](#event-configuration) gets ignored. Nested `details` work for the extra `element_content` entities that get generated (based on the nested `name`), and you can further nest `contents` arbitrarily, though you may end up with a large number of entities.| + +## Data selectors + +Data selectors are a declarative way to extract information from elements matched by rules. + +The plugin uses data selectors when deciding if an element should trigger an event (using [`condition`](#general-event-options)), or when building the `element` entity's `attributes` property based on a rule's [`details` and `contents` settings](#element-data). + +The declarative configuration lets you safely extract information without having to explicitly write code, or still get information where callbacks aren't possible. +For example, a function defined in Google Tag Manager that passes through a Tag Template can not work with DOM elements directly, which limits the data it could extract. + +The declarative use is optional, and you can also just provide a callback function that accepts an element and returns an object if you prefer. + +You define data selectors as a list, so you can also combine the two approaches. +When evaluating each list of data selectors, the result is a list of triplets describing: + +1. The `source`/type of the data selector +2. The selected `attribute` name +3. The selected attribute `value` + +Each data selector should be a function or an object with any of the following properties: + +| Data selector property | Value type | Description | +|---------------|------|-------------| +||`function`|A custom callback. The function should return an object with `string` properties, each of which produce a result in the output list. Values get cast to `string`; empty values (such as `undefined`) get skipped entirely.| +|`attributes`|`array`|Produces a result for each provided attribute extracted via the [`getAttribute()` API](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute). The value is often the initial value set in the HTML of the page, compared to `properties` which may be a more recent value.| +|`properties`|`array`|Produces a result for each provided property name sourced from the element.| +|`dataset`|`array`|Produces a result for each provided property name sourced from the element's dataset attribute. This should be the camel-case format version, rather than the attribute-style name.| +|`child_text`|`object`|The value should be an object mapping names to CSS selectors; produces a result for each name mapped to the `textContent` of the first matching child element. Be cautious of large text values.| +|`content`|`object`|The value should be an object mapping names to regular expression patterns. Each pattern gets evaluated against the `textContent` of the matching element and produces an attribute with the matched value. If the pattern contains matching groups, uses the first captured group.| +|`selector`|`boolean`|Attach the rule's `selector` as an attribute. Can be useful if you are sharing `names` between rules and need to know which rule matched.| +|`match`|`object`|The value should be an object mapping other attribute names to values or functions. The current set of attribute results get checked against this object; if no attributes have the same value (or the function doesn't return `true` for the value) then discard the current list of results. This can be useful for the `condition` setting.| + +The `source` matches the property used, or `callback` if a callback function is the source. +If the callback encounters an error, it produces an `error`-sourced value. + +For the purposes of `condition` matching, events don't fire if the resulting list of attributes is empty. + +## Events and entities + +Events generated by the plugin have simple payloads, consisting of an `element_name` property that's referenced by the entities attached to the event. + +The event schemas are: + +- [`create_element`](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/create_element/jsonschema/1-0-0) +- [`destroy_element`](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/destroy_element/jsonschema/1-0-0) +- [`expose_element`](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/expose_element/jsonschema/1-0-0) +- [`obscure_element`](https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/obscure_element/jsonschema/1-0-0) + +These events include an `element` entity which includes: + +- An `element_name` matching the one in the event payload +- Size and position information +- Any attributes collected via the [`detail` setting](#element-data) + +```json reference title="Entity - iglu:com.snowplowanalytics.snowplow/element/jsonschema/1-0-0" referenceLinkText="See schema on GitHub" +https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/element/jsonschema/1-0-0 +``` + +### Optional entities + +Depending on configuration, events may also include the following entities: + +#### Element statistics + +By using the [`includeStats` setting](#element-data), you can configure the plugin to attach this entity to any events sent to the tracker (including those not generated by this plugin). + +For each rule with this configured, entities for each matching element include: + +- Visibility state at the time of the event +- Smallest, largest, and current size +- Element-specific min/max scroll depth information +- Time since the element was first observed (element age) +- How many times the element has been in view +- Cumulative total time the element has been in view + +If the selector matches a lot of elements, this can enlarge event payload sizes, use caution with the `selector` used with this setting. + +Note that `includeStats` requires opt-in for all event types, even those generated by this plugin: + + + + +```javascript +snowplow('startElementTracking', { elements: { + selector: 'main.article', + name: 'article_content', + includeStats: ['expose_element', 'page_ping'] +} }); +``` + + + + +```javascript +import { SnowplowElementTrackingPlugin, startElementTracking } from '@snowplow/browser-plugin-element-tracking'; + +startElementTracking({ elements: { + selector: 'main.article', + name: 'article_content', + includeStats: ['expose_element', 'page_ping'] +} }); +``` + + + +Define non-self-describing events with the event names assigned to them during enrichment (`page_view`, `page_ping`, `event` (structured events), `transaction`, and `transaction_item`). + +```json reference title="Entity - iglu:com.snowplowanalytics.snowplow/element_statistics/jsonschema/1-0-0" referenceLinkText="See schema on GitHub" +https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/element_statistics/jsonschema/1-0-0 +``` + +#### Component hierarchy + +If any configured rules are a [`component`](#element-data), events generated by the plugin may include the `component_parents` entity. + +This includes an `element_name` reference, and a `component_list` that's a list of any `component`-rule names that are ancestors of that element. +Use these values to aggregate events to different levels of a component hierarchy. + +The plugin also exposes a `getComponentListGenerator` command, that returns a function that accepts an element and returns this entity. +This function gets used to attach the entity to custom events, or events generated by other plugins like the [form](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/form-tracking/index.md) or [link](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/link-click/index.md) tracking plugins. + + + + +```javascript +snowplow('getComponentListGenerator', function (componentGenerator, componentGeneratorWithDetail) { + // access a context generator aware of the startElementTracking "components" configuration + // this will attach the component_parents entity to events generated by these plugins that show the component hierarchy + snowplow('enableLinkClickTracking', { context: [componentGenerator] }); + snowplow('enableFormTracking', { context: [componentGenerator] }); + + // componentGeneratorWithDetail will also populate element_detail entities for each component, but is not directly compatible with the above plugin APIs +}); +``` + + + + +```javascript +import { getComponentListGenerator } from '@snowplow/browser-plugin-element-tracking'; +import { enableLinkClickTracking } from '@snowplow/browser-plugin-link-click-tracking'; +import { enableFormTracking } from '@snowplow/browser-plugin-form-tracking'; + +// access a context generator aware of the startElementTracking "components" configuration +const [componentGenerator, componentGeneratorWithDetail] = getComponentListGenerator(); + +// this will attach the component_parents entity to events generated by these plugins that show the component hierarchy +enableLinkClickTracking({ options: { ... }, psuedoClicks: true, context: [componentGenerator] }); +enableFormTracking({ context: [componentGenerator] }); + +// componentGeneratorWithDetail will also populate element_detail entities for each component, but is not directly compatible with the above plugin APIs +``` + + + +```json reference title="Entity - iglu:com.snowplowanalytics.snowplow/component_parents/jsonschema/1-0-0" referenceLinkText="See schema on GitHub" +https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/component_parents/jsonschema/1-0-0 +``` + +#### Element contents + +The `element_content` schema gets attached when you use the [`contents` setting](#element-data). + +There can be many instances of this entity in individual events, and the list of them are a flattened tree representation of the nested configuration provided. +Each instance contains references to the parent `element_content` or `element` entity instance that contains it in the `parent_name` and `parent_index` properties (via `element_name` and `element_index`, respectively). +Nested `details` configurations are also used to populate the `attributes` for each instance. + +```json reference title="Entity - iglu:com.snowplowanalytics.snowplow/element_content/jsonschema/1-0-0" referenceLinkText="See schema on GitHub" +https://github.com/snowplow/iglu-central/blob/master/schemas/com.snowplowanalytics.snowplow/element_content/jsonschema/1-0-0 +``` + +#### Custom context + +You can attach custom entities to the events generated by the plugin. + +- You can include `context` alongside `elements` when calling `startElementTracking`. You can pass an array of static entities _or_ a callback function that returns such an array. The function receives the element that the event is relevant to, and the matching rule that defined the event should fire. +- Individual rules may also contain specific `context` in the same format as in the preceding. diff --git a/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/event-specifications/index.md b/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/event-specifications/index.md index 9ab2b8caed..c701c13e8e 100644 --- a/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/event-specifications/index.md +++ b/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/event-specifications/index.md @@ -15,7 +15,7 @@ The plugin allows you to integrate with Event Specifications for a selected set The plugin will automatically add an Event Specification context to the events matching the configuration added. :::note -The plugin is available since version 3.23 of the tracker. +The plugin is available since version 3.23 of the tracker and is currently only available for Data Products created using the [Media Web template](/docs/data-product-studio/data-products/data-product-templates/#media-web). ::: ## Install plugin diff --git a/docs/sources/trackers/mobile-trackers/hybrid-apps/index.md b/docs/sources/trackers/mobile-trackers/hybrid-apps/index.md index ee583230d7..0ba6c4c385 100644 --- a/docs/sources/trackers/mobile-trackers/hybrid-apps/index.md +++ b/docs/sources/trackers/mobile-trackers/hybrid-apps/index.md @@ -56,7 +56,7 @@ We recommend using the Web tracker (v4.3+) to forward all Web events to the mobi 4. Track events as usual. -The Web tracker will automatically intercept all web events and forward them to the mobile tracker. The forwarded events will have the tracker version from Web, e.g. "js-4.1.0", but will otherwise be tracked like the mobile events. They may contain additional information not present in the native mobile events, such as a browser useragent string or URL, or Web context entities e.g. the [WebPage entity](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/page-views/#webpage-page-view-id-context-entity). +The Web tracker will automatically intercept all web events and forward them to the mobile tracker. The forwarded events will have the tracker version from Web, e.g. "js-4.1.0", but will otherwise be tracked like the mobile events. They may contain additional information not present in the native mobile events, such as a browser useragent string or URL, or Web context entities e.g. the [WebPage entity](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/page-views/index.md#webpage-page-view-id-context-entity). The forwarded events are filtered out of the Web tracker event queue so that they are not tracked twice. diff --git a/docs/sources/trackers/php-tracker/emitters/index.md b/docs/sources/trackers/php-tracker/emitters/index.md index 52a768bb05..1424b58384 100644 --- a/docs/sources/trackers/php-tracker/emitters/index.md +++ b/docs/sources/trackers/php-tracker/emitters/index.md @@ -4,12 +4,12 @@ date: "2020-02-26" sidebar_position: 40 --- -We currently support four different emitters: sync, socket, curl and an out-of-band file emitter. The most basic emitter only requires you to select the type of emitter to be used and specify the collector's hostname as parameters. +We currently support four different emitters: sync, socket, curl, and an out-of-band file emitter. The most basic emitter only requires you to select the type of emitter to be used and specify the Collector's hostname as parameters. -All emitters support both `GET` and `POST` as methods for sending events to Snowplow collectors. +All emitters support both `GET` and `POST` as methods for sending events to Snowplow Collectors. For the sake of performance, we recommend using `POST` as the tracker can then batch many events together into a single request. -Note that depending on your pipeline architecture, your collector may have limits on the maximum request size it will accept that could be exceeded by large batch sizes. +Note that depending on your pipeline architecture, your Collector may have limits on the maximum request size it accepts that could be exceeded by large batch sizes. It is recommended that after you have finished logging all of your events to call the following method: @@ -17,18 +17,18 @@ It is recommended that after you have finished logging all of your events to cal $tracker->flushEmitters(); ``` -This will empty the event buffers of all emitters associated with your tracker object and send any left over events. In future releases, this will be an automatic process but for now, it remains manual. +This empties the event buffers of all emitters associated with your tracker object and sends any left over events. In future releases, this may be an automatic process but for now, it remains manual. ### Sync -The Sync emitter is a very basic synchronous emitter which supports both `GET` and `POST` request types. +The Sync emitter is a basic synchronous emitter which supports both `GET` and `POST` request types. -By default, this emitter uses the Request type POST, HTTP and a buffer size of 50. +By default, this emitter uses the Request type POST, HTTP, and a buffer size of 50. As of version 0.7.0, the emitter has the capability to retry failed requests. -In case connection to the collector can't be established or the request fails with a 4xx (except for 400, 401, 403, 410, 422) or 5xx status code, the same request is retried. +In case connection to the Collector can't be established or the request fails with a 4xx (except for 400, 401, 403, 410, 422) or 5xx status code, the same request is retried. The number of times a request should be retried is configurable but defaults to 1. -There is a back-off period between subsequent retries, which starts with 100ms (configurable) and increases exponentially. +The default back-off period between subsequent retries, starts with 100 ms (configurable) and increases exponentially. Example emitter creation: @@ -36,12 +36,12 @@ Example emitter creation: $emitter = new SyncEmitter($collector_uri, "http", "POST", 50); ``` -Whilst you can force the buffer size to be greater than 1 for a GET Request; it will not yield any performance changes as we can still only send 1 event at a time. +Whilst you can force the buffer size to be greater than 1 for a GET Request; it doesn't yield any performance changes as we can still only send 1 event at a time. Constructor: ```php -public function __construct($uri, $protocol = NULL, $type = NULL, $buffer_size = NULL, $debug = false, $max_retry_attempts = NULL, $retry_backoff_ms = NULL) +public function __construct($uri, $protocol = NULL, $type = NULL, $buffer_size = NULL, $debug = false, $max_retry_attempts = NULL, $retry_backoff_ms = NULL, $server_anonymization = false) ``` Arguments: @@ -54,16 +54,17 @@ Arguments: | `$buffer_size` | Amount of events to store before flush | No | Int | | `$debug` | Whether or not to log errors | No | Boolean | | `$max_retry_attempts` | The maximum number of times to retry a request. Defaults to 1. | No | Int | -| `$retry_backoff_ms` | The number of milliseconds to backoff before retrying a request. Defaults to 100ms, increases exponentially in subsequent retries. | No | Int | +| `$retry_backoff_ms` | The number of milliseconds to backoff before retrying a request. Defaults to 100 ms, increases exponentially in subsequent retries. | No | Int | +| `$server_anonymization` | Enable Server Anonymization for sent events; IP and Network User ID information isn't associated with tracked events | No | Int | ### Socket -The Socket emitter allows for the much faster transmission of Requests to the collector by allowing us to write data directly to the HTTP socket. However, this solution is still, in essence, a synchronous process and will block the execution of the main script. +The Socket emitter allows for the much faster transmission of Requests to the Collector by allowing us to write data directly to the HTTP socket. However, this solution is still, in essence, a synchronous process and blocks the execution of the main script. As of version 0.7.0, the emitter has the capability to retry failed requests. -In case connection to the collector can't be established or the request fails with a 4xx (except for 400, 401, 403, 410, 422) or 5xx status code, the same request is retried. +In case connection to the Collector can't be established or the request fails with a 4xx (except for 400, 401, 403, 410, 422) or 5xx status code, the same request is retried. The number of times a request should be retried is configurable but defaults to 1. -There is a back-off period between subsequent retries, which starts with 100ms (configurable) and increases exponentially. +The default back-off period between subsequent retries, starts with 100 ms (configurable) and increases exponentially. Example Emitter creation: @@ -71,7 +72,7 @@ Example Emitter creation: $emitter = new SocketEmitter($collector_uri, NULL, "GET", NULL, NULL); ``` -Whilst you can force the buffer size to be greater than 1 for a GET Request; it will not yield any performance changes as we can still only send 1 event at a time. +Whilst you can force the buffer size to be greater than 1 for a GET Request; it does not yield any performance changes as we can still only send 1 event at a time. Constructor: @@ -90,16 +91,17 @@ Arguments: | `$buffer_size` | Amount of events to store before flush | No | Int | | `$debug` | Whether or not to log errors | No | Boolean | | `$max_retry_attempts` | The maximum number of times to retry a request. Defaults to 1. | No | Int | -| `$retry_backoff_ms` | The number of milliseconds to backoff before retrying a request. Defaults to 100ms, increases exponentially in subsequent retries. | No | Int | +| `$retry_backoff_ms` | The number of milliseconds to backoff before retrying a request. Defaults to 100 ms, increases exponentially in subsequent retries. | No | Int | +| `$server_anonymization` | Enable Server Anonymization for sent events; IP and Network User ID information isn't associated with tracked events | No | Int | ### Curl -The Curl Emitter allows us to have the closest thing to native asynchronous requests in PHP. The curl emitter uses the `curl_multi_init` resource which allows us to send any number of requests asynchronously. This garners quite a performance gain over the sync and socket emitters as we can now send more than one request at a time. +The Curl Emitter allows us to have the closest thing to native asynchronous requests in PHP. The curl emitter uses the `curl_multi_init` resource which allows us to send any number of requests asynchronously. This garners a performance gain over the sync and socket emitters as we can now send more than one request at a time. On top of this, we are also using a modified version of this **[Rolling Curl library](https://github.com/joshfraser/rolling-curl)** for the actual sending of the curl requests. This allows for a more efficient implementation of asynchronous curl requests as we can now have multiple requests sending at the same time, and in addition as soon as one is done a new request is started. :::note -The collector does not retry failed requests to the collector. Failed requests to the collector (e.g., due to it being not reachable) result in lost events. +The emitter doesn't retry failed requests to the Collector. Failed requests to the Collector (for example due to it being not reachable) result in lost events. ::: Example Emitter creation: @@ -108,7 +110,7 @@ Example Emitter creation: $emitter = new CurlEmitter($collector_uri, false, "GET", 2); ``` -Whilst you can force the buffer size to be greater than 1 for a GET request, it will not yield any performance changes as we can still only send 1 event at a time. +Whilst you can force the buffer size to be greater than 1 for a GET request, it doesn't yield any performance changes as we can still only send 1 event at a time. Constructor: @@ -118,16 +120,17 @@ public function __construct($uri, $protocol = NULL, $type = NULL, $buffer_size = Arguments: -| **Argument** | **Description** | **Required?** | **Validation** | -|-----------------|---------------------------------------------------------|---------------|------------------| -| `$uri` | Collector hostname | Yes | Non-empty string | -| `$protocol` | Collector Protocol (HTTP or HTTPS) | No | String | -| `$type` | Request Type (POST or GET) | No | String | -| `$buffer_size` | Amount of events to store before flush | No | Int | -| `$debug` | Whether or not to log errors | No | Boolean | -| `$curl_timeout` | Maximum time the request is allowed to take, in seconds | No | Int | +| **Argument** | **Description** | **Required?** | **Validation** | +|-------------------------|---------------------------------------------------------|---------------|------------------| +| `$uri` | Collector hostname | Yes | Non-empty string | +| `$protocol` | Collector Protocol (HTTP or HTTPS) | No | String | +| `$type` | Request Type (POST or GET) | No | String | +| `$buffer_size` | Amount of events to store before flush | No | Int | +| `$debug` | Whether or not to log errors | No | Boolean | +| `$curl_timeout` | Maximum time the request is allowed to take, in seconds | No | Int | +| `$server_anonymization` | Enable Server Anonymization for sent events | No | Int | -#### Curl Default Settings +#### Curl default settings The internal emitter default settings are as follows: @@ -140,25 +143,25 @@ The internal emitter default settings are as follows: Since version 0.8 of the PHP tracker, you can change these settings using the following setter functions: -- `$curl_emitter.setCurlAmount($curl_amount)` – update the curl buffer size (number of times we need to reach the buffer size before we initiate sending) -- `$curl_emitter.setRollingWindow($rolling_window)` – update the rolling window configuration (max number of concurrent requests) +- `$curl_emitter.setCurlAmount($curl_amount)`: update the curl buffer size (number of times we need to reach the buffer size before we initiate sending) +- `$curl_emitter.setRollingWindow($rolling_window)`: update the rolling window configuration (max number of concurrent requests) ### File :::caution -When running under Windows, PHP cannot spawn truly separate processes, and slowly eats more and more resources when more processes are spawned. Thus, Windows might crash under high load when using the File Emitter. +When running under Windows, PHP can't spawn truly separate processes, and slowly eats more and more resources when more processes are spawned. Thus, Windows might crash under high load when using the File Emitter. ::: The File Emitter is the only true non-blocking solution. The File Emitter works via spawning workers which grab created files of logged events from a local temporary folder. The workers then load the events using the same asynchronous curl properties from the above emitter. -All of the worker processes are created as background processes so none of them will delay the execution of the main script. Currently, they are configured to look for files inside created worker folders until there are none left and they hit their `timeout` limit, at which point the process will kill itself. +All of the worker processes are created as background processes so none of them delay the execution of the main script. Currently, they're configured to look for files inside created worker folders until there are none left and they hit their `timeout` limit, at which point the process terminates itself. -If the worker for any reason fails to successfully send a request it will rename the entire file to `failed` and leave it in the `/temp/failed-logs/` folder. +If the worker for any reason fails to successfully send a request it renames the entire file to `failed` and leaves it in the `/temp/failed-logs/` folder. :::note -The collector does not retry failed requests to the collector. Failed requests to the collector (e.g., due to it being not reachable) result in lost events. +The emitter doesn't retry failed requests to the Collector. Failed requests to the Collector (for example due to it being not reachable) result in lost events. ::: Example Emitter creation: @@ -167,7 +170,7 @@ Example Emitter creation: $emitter = new FileEmitter($collector_uri, false, "POST", 2, 15, 100, "/tmp/snowplow"); ``` -The buffer for the file emitter works a bit differently to the other emitters in that here it refers to the number of events needed before an `events-random.log` is produced for a worker. If you are anticipating it taking a long time to reach the buffer be aware that the worker will kill itself after 75 seconds by default (15 x 5). Adjust the timeout amount in the construction of the FileEmitter if the default is not suitable. +The buffer for the file emitter works a bit differently to the other emitters in that here it refers to the number of events needed before an `events-random.log` is produced for a worker. If you are anticipating it taking a long time to reach the buffer be aware that the worker terminates itself after 75 seconds by default (15 x 5). Adjust the timeout amount in the construction of the FileEmitter if the default isn't suitable. Constructor: @@ -188,7 +191,7 @@ Arguments: | `$debug` | Whether or not to log errors | No | Boolean | | `$log_dir` | The directory for event log and worker log subdirectories to be created in | No | String | -### Emitter Debug Mode +### Emitter debug mode Debug mode is enabled on emitters by setting the `$debug` argument in the emitter constructor to `true`: @@ -196,21 +199,21 @@ Debug mode is enabled on emitters by setting the `$debug` argument in the emitte $emitter = new SyncEmitter($collector_uri, "http", "POST", 50, true); ``` -By default, debug mode will create a new directory called `/debug/` in the root of the tracker's directory. It will then create a log file with the following structure; `sync-events-log-[[random number]].log`: i.e. the type of emitter and a randomized number to prevent it from being accidentally overwritten. +By default, debug mode creates a new directory called `/debug/` in the root of the tracker's directory. It then creates a log file with the following structure; `sync-events-log-[[random number]].log`: which is the type of emitter and a randomized number to prevent it from being accidentally overwritten. -If physically storing the information is not possible due to not having the correct write permissions or simply not wanted it can be turned off by updating the following value in the Constants class: +If physically storing the information isn't possible due to not having the correct write permissions or simply not wanted it can be turned off by updating the following value in the Constants class: ```php const DEBUG_LOG_FILES = false; ``` -Now all debugging information will be printed to the console. +Now all debugging information is printed to the console. -Every time the events buffer is flushed we will be able to see if the flush was successful. In the case of an error, it records the entire event payload the tracker was trying to send, along with the error code. +Every time the events buffer is flushed we can see if the flush was successful. In the case of an error, it records the entire event payload the tracker was trying to send, along with the error code. -#### Event Specific Information +#### Event specific information -Debug Mode if enabled will also have the emitter begin storing information internally. It will store the HTTP response code and the payload for every request made by the emitter. +If debug mode is enabled the emitter begins storing information internally. It stores the HTTP response code and the payload for every request made by the emitter. ```php array( @@ -222,7 +225,7 @@ array( The `data` is stored as a JSON-encoded string. To locally test whether or not your emitters are successfully sending, we can retrieve this information with the following commands: ```php -$emitters = $tracker->returnEmitters(); # Will store all of the emitters as an array. +$emitters = $tracker->returnEmitters(); # Store all of the emitters as an array. $emitter = $emitters[0]; # Get the first emitter stored by the tracker $results = $emitter->returnRequestResults(); # Return the stored results. @@ -233,7 +236,7 @@ print("Data: ".$results[0]["data"]); This allows you to debug on a request by request basis to ensure that everything is being sent properly. -#### Turn Debug Off +#### Turn debug off As debugging stores a lot of information, we can end debug mode by calling the following command: @@ -241,7 +244,7 @@ As debugging stores a lot of information, we can end debug mode by calling the f $tracker->turnOffDebug(); ``` -This will stop all logging activity, both to the external files and to the local arrays. We can go one step further though and pass a `true` boolean to the function. This will delete all of the tracker's associated physical debug log files as well as emptying the local arrays within each linked emitter. +This stops all logging activity, both to the external files and to the local arrays. We can go one step further though and pass a `true` boolean to the function. This deletes all of the tracker's associated physical debug log files as well as emptying the local arrays within each linked emitter. ```php $tracker->turnOffDebug(true); diff --git a/docs/sources/trackers/react-native-tracker/hybrid-apps/index.md b/docs/sources/trackers/react-native-tracker/hybrid-apps/index.md index a83b9fa6f0..5315933db4 100644 --- a/docs/sources/trackers/react-native-tracker/hybrid-apps/index.md +++ b/docs/sources/trackers/react-native-tracker/hybrid-apps/index.md @@ -34,7 +34,7 @@ We recommend using the Web tracker (v4.3+) to forward all Web events to the Reac 4. Track events as usual. -The Web tracker will automatically intercept all web events and forward them to the React Native tracker. The forwarded events will have the tracker version from Web, e.g. "js-4.1.0", but will otherwise be tracked like the mobile events. They may contain additional information not present in the React Native mobile events, such as a browser useragent string or URL, or Web context entities e.g. the [WebPage entity](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/page-views/#webpage-page-view-id-context-entity). +The Web tracker will automatically intercept all web events and forward them to the React Native tracker. The forwarded events will have the tracker version from Web, e.g. "js-4.1.0", but will otherwise be tracked like the mobile events. They may contain additional information not present in the React Native mobile events, such as a browser useragent string or URL, or Web context entities e.g. the [WebPage entity](/docs/sources/trackers/javascript-trackers/web-tracker/tracking-events/page-views/index.md#webpage-page-view-id-context-entity). The forwarded events are filtered out of the Web tracker event queue so that they are not tracked twice. diff --git a/docs/sources/trackers/snowplow-tracker-protocol/ootb-data/user-and-session-identification/index.md b/docs/sources/trackers/snowplow-tracker-protocol/ootb-data/user-and-session-identification/index.md index 8924a0f16b..05f1426786 100644 --- a/docs/sources/trackers/snowplow-tracker-protocol/ootb-data/user-and-session-identification/index.md +++ b/docs/sources/trackers/snowplow-tracker-protocol/ootb-data/user-and-session-identification/index.md @@ -48,7 +48,7 @@ The Snowplow Collector generates a user identifier that is stored in cookies for The identifier is available both in Web and mobile apps. However, in Android apps, it is stored in memory so it is reset after the app restarts. -In most scenarios, this identifier may have a longer lifetime than the tracker generated identifier. However, browsers can restrict it's lifetime for different reasons, such as when the Snowplow Collector is on a third-party domain from the website (not recommended), or due to the ITP restrictions in Safari (Snowplow provides a solution to mitigate this problem – [the ID service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md#itp-mitigation)). +In most scenarios, this identifier may have a longer lifetime than the tracker generated identifier. However, browsers can restrict it's lifetime for different reasons, such as when the Snowplow Collector is on a third-party domain from the website (not recommended), or due to the ITP restrictions in Safari (Snowplow provides a solution to mitigate this problem – [the Cookie Extension service](/docs/sources/trackers/javascript-trackers/web-tracker/browsers/index.md#itp-mitigation)). :::info `network_userid` is captured via a cookie set by the Snowplow Collector. It can be overriden by setting `tnuid` on a Tracker request payload but is typically expected to be populated by the Collector cookies. diff --git a/docs/sources/trackers/webview-tracker/index.md b/docs/sources/trackers/webview-tracker/index.md index a721ac01bb..63cd1081ab 100644 --- a/docs/sources/trackers/webview-tracker/index.md +++ b/docs/sources/trackers/webview-tracker/index.md @@ -276,11 +276,11 @@ This method is used internally by the WebView plugin. We recommend implementing Use this method to track any kind of Snowplow event e.g. a page ping. You will need to define the event name yourself, e.g. "pp" for page ping. It also allows you to set a tracker version, to help distinguish between native and WebView events (e.g. "webview-0.3.0" while the native tracker version might be something like "ios-6.1.0"). -| Argument | Description | Required? | -| ------------ | ------------------------------------------------------------------------------------------------- | --------- | -| `properties` | Event properties that are ["baked-in"](/docs/fundamentals/canonical-event/#out-of-the-box-fields) | Yes | -| `event` | An optional self-describing event, with `schema` and `data` properties | No | -| `context` | List of context entities as self-describing JSON | No | +| Argument | Description | Required? | +| ------------ | --------------------------------------------------------------------------------------------------------- | --------- | +| `properties` | Event properties that are ["baked-in"](/docs/fundamentals/canonical-event/index.md#out-of-the-box-fields) | Yes | +| `event` | An optional self-describing event, with `schema` and `data` properties | No | +| `context` | List of context entities as self-describing JSON | No | | Event type | `eventName` | | -------------------------- | -------------- | diff --git a/netlify/functions/product_fruits_key.js b/netlify/functions/product_fruits_key.js new file mode 100644 index 0000000000..5ff019ac95 --- /dev/null +++ b/netlify/functions/product_fruits_key.js @@ -0,0 +1,5 @@ +exports.handler = async () => ({ + statusCode: 200, + headers: { 'Content-Type': 'text/plain' }, + body: process.env.PRODUCT_FRUITS || '', +}) diff --git a/package.json b/package.json index f8d720e510..1af5519ca0 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "react-dom": "^17.0.2", "react-markdown": "^8.0.5", "react-player": "^2.10.1", + "react-product-fruits": "^2.2.61", "rehype-katex": "5", "remark-gfm": "^3.0.1", "remark-math": "3", diff --git a/src/componentVersions.js b/src/componentVersions.js index dac1bf7896..637d46c4b4 100644 --- a/src/componentVersions.js +++ b/src/componentVersions.js @@ -10,7 +10,7 @@ export const versions = { javaTracker: '2.1.0', javaScriptTracker: '4.5.0', luaTracker: '0.2.0', - phpTracker: '0.8.0', + phpTracker: '0.9.2', pixelTracker: '0.3.0', pythonTracker: '1.0.3', rokuTracker: '0.2.0', @@ -21,8 +21,10 @@ export const versions = { webViewTracker: '0.3.0', // Core pipeline + collector: '3.3.0', + enrich: '5.3.0', collector: '3.4.0', - enrich: '5.2.0', + enrich: '5.3.0', sqs2kinesis: '1.0.4', dataflowRunner: '0.7.5', snowbridge: '3.1.1', @@ -36,17 +38,17 @@ export const versions = { rdbLoader: '6.1.3', s3Loader: '2.2.9', s3Loader22x: '2.2.9', - lakeLoader: '0.6.2', + lakeLoader: '0.6.3', snowflakeStreamingLoader: '0.4.1', // Data Modelling // dbt - dbtSnowplowAttribution: '0.4.0', + dbtSnowplowAttribution: '0.5.0', dbtSnowplowUnified: '0.5.2', dbtSnowplowWeb: '1.0.1', dbtSnowplowMobile: '1.0.0', dbtSnowplowMediaPlayer: '0.9.2', - dbtSnowplowUtils: '0.17.1', + dbtSnowplowUtils: '0.17.2', dbtSnowplowNormalize: '0.4.0', dbtSnowplowFractribution: '0.3.6', dbtSnowplowEcommerce: '0.9.0', @@ -68,7 +70,7 @@ export const versions = { analyticsSdkScala: '3.0.0', // Iglu - igluServer: '0.14.0', + igluServer: '0.14.1', igluctl: '0.13.0', igluObjCClient: '0.1.1', igluRubyClient: '0.2.0', @@ -76,5 +78,5 @@ export const versions = { // Testing & debugging snowplowMicro: '2.2.0', - snowplowMini: '0.22.0', + snowplowMini: '0.23.0', } diff --git a/src/components/JsonSchemaValidator/Schemas/dbtAttribution_0.5.0.json b/src/components/JsonSchemaValidator/Schemas/dbtAttribution_0.5.0.json new file mode 100644 index 0000000000..838d0417f6 --- /dev/null +++ b/src/components/JsonSchemaValidator/Schemas/dbtAttribution_0.5.0.json @@ -0,0 +1,388 @@ +{ + "definitions": { + "passthrough_vars": { + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "items": { + "title": "Type", + "oneOf": [ + { + "type": "string", + "title": "Column Name" + }, + { + "type": "object", + "title": "SQL & Alias", + "properties": { + "sql": { + "type": "string" + }, + "alias": { + "type": "string" + } + }, + "required": [ + "sql", + "alias" + ], + "additionalProperties": false + } + ] + }, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "snowplow__conversions_source": { + "recommendFullRefresh": true, + "order": 81, + "consoleGroup": "advanced", + "type": "string", + "title": "Conversions Source", + "description": "Source of conversion events", + "longDescription": "The source (schema and table) of your conversion events, likely the conversions table generated by the Unified package. Optionally it may be hardcoded with a string reference instead of a source with a schema.table or database.schema.table format.", + "packageDefault": "{{ source('derived', 'snowplow_unified_conversions') }}", + "group": "Warehouse and Tracker" + }, + "snowplow__conversion_path_source": { + "recommendFullRefresh": true, + "order": 76, + "consoleGroup": "advanced", + "type": "string", + "title": "Conversion Path Source", + "description": "Source of paths (touchpoints) table", + "longDescription": "The source (schema and table) of the paths (touchpoints). By default it is the derived `snowplow_unified_views` table. Optionally it may be hardcoded with a string reference instead of a source with a schema.table or database.schema.table format.", + "packageDefault": "{{ source('derived', 'snowplow_unified_views') }}", + "group": "Warehouse and Tracker" + }, + "snowplow__user_mapping_source": { + "recommendFullRefresh": true, + "order": 125, + "consoleGroup": "advanced", + "type": "string", + "title": "User Mapping Source", + "description": "Source of user mapping table to be used for stitching by default", + "longDescription": "The source (schema and table) of the user mapping table produced by the unified package. In case the user mapping table is not available, the paths_to_conversion macro needs to be overwritten in the dbt project where the package is referenced.", + "packageDefault": "{{ source('derived', 'snowplow_unified_user_mapping') }}", + "group": "Warehouse and Tracker" + }, + "snowplow__conversion_window_start_date": { + "recommendFullRefresh": true, + "order": 80, + "consoleGroup": "advanced", + "type": "string", + "format": "date", + "title": "Conversion Window Start Date", + "group": "Operation and Logic", + "longDescription": "The start date in UTC for the window of conversions to include. It is only used for the drop and recompute report tables/views.", + "packageDefault": "current_date()-31" + }, + "snowplow__conversion_window_end_date": { + "recommendFullRefresh": true, + "order": 79, + "consoleGroup": "advanced", + "type": "string", + "format": "date", + "title": "Conversion Window End Date", + "group": "Operation and Logic", + "longDescription": "The end date in UTC for the window of conversions to include. It is only used for the drop and recompute report tables/views.", + "packageDefault": "" + }, + "snowplow__conversion_window_days": { + "recommendFullRefresh": true, + "order": 78, + "consoleGroup": "advanced", + "type": "number", + "minimum": 0, + "title": "Conversion Window Days", + "group": "Operation and Logic", + "longDescription": "The last complete nth number of days (calculated from the last processed pageview within page_views_source) to dynamically update the conversion_window_start_date and end_date with. Will only apply if both variables are left as an empty string.", + "packageDefault": "30" + }, + "snowplow__path_lookback_days": { + "recommendFullRefresh": true, + "order": 18, + "consoleGroup": "basic", + "type": "number", + "minimum": 0, + "title": "Path Lookback Days", + "group": "Operation and Logic", + "longDescription": "Restricts the model to marketing channels within this many days of the conversion (values of 30, 14 or 7 are recommended).", + "packageDefault": "30" + }, + "snowplow__path_lookback_steps": { + "recommendFullRefresh": true, + "order": 19, + "consoleGroup": "basic", + "type": "number", + "minimum": 0, + "title": "Path Lookback Steps", + "group": "Operation and Logic", + "longDescription": "The limit for the number of marketing channels to look at before the conversion.", + "packageDefault": "0 (unlimited)" + }, + "snowplow__path_transforms": { + "recommendFullRefresh": false, + "order": 116, + "consoleGroup": "advanced", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "title": "Path Transforms", + "longDescription": "Dictionary of path transforms (and their argument) to perform on the conversion path (see the transform path options in our [model docs](/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-models/dbt-attribution-data-model/)). For `remove_if_not_all` and `remove_if_last_and_not_all` transformations, the argument has to be a non-empty array, for the others it should be null.", + "packageDefault": "{'exposure_path': null}", + "group": "Contexts, Filters, and Logs", + "items": { + "type": "object", + "title": "Path transform", + "oneOf": [ + { + "title": "exposure", + "required": [ + "exposure" + ], + "properties": { + "exposure": { + "type": "string", + "default": "null" + } + } + }, + { + "title": "first", + "required": [ + "first" + ], + "properties": { + "first": { + "type": "string", + "default": "null" + } + } + }, + { + "title": "unique", + "required": [ + "unique" + ], + "properties": { + "unique": { + "type": "string", + "default": "null" + } + } + }, + { + "title": "remove if last and not all", + "required": [ + "remove_if_last_and_not_all" + ], + "properties": { + "remove_if_last_and_not_all": { + "type": "string" + } + } + }, + { + "title": "remove if not all", + "required": [ + "remove_if_not_all" + ], + "properties": { + "remove_if_not_all": { + "type": "string" + } + } + } + ] + }, + "uniqueItems": true + }, + "snowplow__channels_to_exclude": { + "recommendFullRefresh": true, + "order": 68, + "consoleGroup": "advanced", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "title": "Channels to Exclude", + "longDescription": "List of channels to exclude from analysis (empty to keep all channels). For example, users may want to exclude the `Direct` channel from the analysis.", + "packageDefault": "[ ] (no filter applied)", + "group": "Contexts, Filters, and Logs", + "items": { + "type": "string" + } + }, + "snowplow__channels_to_include": { + "recommendFullRefresh": true, + "order": 69, + "consoleGroup": "advanced", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "title": "Channels to Include", + "longDescription": "List of channels to include in the analysis (empty to keep all channels). For example, users may want to include the `Direct` channel only in the analysis.", + "packageDefault": "[ ] (no filter applied)", + "group": "Contexts, Filters, and Logs", + "items": { + "type": "string" + } + }, + "snowplow__campaigns_to_exclude": { + "recommendFullRefresh": true, + "order": 64, + "consoleGroup": "advanced", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "title": "Campaigns to Exclude", + "longDescription": "List of campaigns to exclude from analysis (empty to keep all campaigns).", + "packageDefault": "[ ] (no filter applied)", + "group": "Contexts, Filters, and Logs", + "items": { + "type": "string" + } + }, + "snowplow__campaigns_to_include": { + "recommendFullRefresh": true, + "order": 65, + "consoleGroup": "advanced", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "title": "Campaigns to Include", + "longDescription": "List of campaigns to include in the analysis (empty to keep all campaigns).", + "packageDefault": "[ ] (no filter applied)", + "group": "Contexts, Filters, and Logs", + "items": { + "type": "string" + } + }, + "snowplow__conversion_hosts": { + "recommendFullRefresh": true, + "order": 74, + "consoleGroup": "advanced", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "title": "URL Hosts", + "longDescription": "`url_hosts` to filter to in the data processing", + "packageDefault": "[] (no filter applied)", + "group": "Contexts, Filters, and Logs", + "items": { + "type": "string" + } + }, + "snowplow__consider_intrasession_channels": { + "recommendFullRefresh": true, + "order": 72, + "consoleGroup": "advanced", + "type": "boolean", + "group": "Contexts, Filters, and Logs", + "longDescription": "If `false`, only considers the channel at the start of the session (i.e. first page view). If `true`, considers multiple channels in the conversion session as well as historically.", + "packageDefault": "true", + "title": "Consider Intrasession Channels" + }, + "snowplow__spend_source": { + "recommendFullRefresh": true, + "order": 124, + "consoleGroup": "advanced", + "title": "Spend Source", + "description": "Source of marketing spend table", + "longDescription": "The source (schema and table) of your marketing spend source. Optional, needed for the ROAS calculation of the snowplow_attribution_overview. Should be changed to a table reference with `spend` by `channel` and/or `campaign` by `spend_tstamp` (which denotes a timestamp field) information.", + "type": "string", + "packageDefault": "not defined", + "group": "Warehouse and Tracker" + }, + "snowplow__conversion_stitching": { + "recommendFullRefresh": true, + "order": 77, + "consoleGroup": "advanced", + "type": "boolean", + "group": "Contexts, Filters, and Logs", + "longDescription": "This should be set to true if both the snowplow__conversion_stitching and snowplow__view_stitching variables are also enabled in the Unified package. If allowed it will consider the stitched_user_id field, not the user_identifier in the source data for more accurate results.", + "packageDefault": "false", + "title": "Conversion Stitching" + }, + "snowplow__conversion_clause": { + "recommendFullRefresh": true, + "order": 73, + "consoleGroup": "advanced", + "type": "string", + "title": "Conversions Clause", + "group": "Operation and Logic", + "longDescription": "A string of sql to filter on certain conversion events.", + "packageDefault": "cv_value > 0 and ev.user_identifier is not null" + }, + "snowplow__attribution_list": { + "recommendFullRefresh": false, + "order": 61, + "consoleGroup": "advanced", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 1, + "title": "Attribution List", + "longDescription": "List of attribution types to use for reporting. Can be at least one of: first_touch, last_touch, linear, position_based).", + "packageDefault": "['first_touch', 'last_touch', 'linear', 'position_based']", + "group": "Contexts, Filters, and Logs", + "items": { + "type": "string" + } + }, + "snowplow__attribution_start_date": { + "recommendFullRefresh": true, + "order": 7, + "consoleGroup": "required", + "type": "string", + "format": "date", + "title": "Attribution Start Date", + "group": "Operation and Logic", + "longDescription": "The date to start processing events from in the package on first run or a full refresh, based on the cv_tstamp (conversion timestamp).", + "packageDefault": "2023-01-01", + "description": "The date to start processing events from in the package on first run or a full refresh, based on `cv_tstamp`" + }, + "snowplow__enable_paths_to_non_conversion": { + "recommendFullRefresh": false, + "order": 101, + "consoleGroup": "advanced", + "type": "boolean", + "group": "Contexts, Filters, and Logs", + "longDescription": "If `true`, enable the paths_to_non_conversion model, which is a drop and recompute table that may be needed for more in-depth attribution analysis (used in the `path_summary` table as well)", + "packageDefault": "false", + "title": "Enable Paths To Non Conversion" + }, + "snowplow__enable_attribution_overview": { + "recommendFullRefresh": false, + "order": 102, + "consoleGroup": "advanced", + "type": "boolean", + "group": "Contexts, Filters, and Logs", + "longDescription": "If `true`, enable the attribution_overview model, which is a view that creates a great source for BI tools to use for reporting", + "packageDefault": "true", + "title": "Enable Attribution Overview" + }, + "snowplow__dev_target_name": { + "recommendFullRefresh": false, + "order": 87, + "consoleGroup": "advanced", + "type": "string", + "title": "Dev Target", + "description": "Target name of your development environment as defined in your `profiles.yml` file", + "longDescription": "The [target name](https://docs.getdbt.com/docs/core/connect-data-platform/profiles.yml) of your development environment as defined in your `profiles.yml` file. See the [Manifest Tables](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/manifest-tables/) section for more details.", + "packageDefault": "dev", + "group": "Warehouse and Tracker" + }, + "snowplow__allow_refresh": { + "recommendFullRefresh": true, + "order": 39, + "consoleGroup": "advanced", + "type": "boolean", + "title": "Allow Refresh", + "group": "Operation and Logic", + "longDescription": "Used as the default value to return from the `allow_refresh()` macro. This macro determines whether the manifest tables can be refreshed or not, depending on your environment. See the [Manifest Tables](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/manifest-tables/) section for more details.", + "packageDefault": "false" + } + } +} diff --git a/src/components/JsonSchemaValidator/Schemas/dbtUtils_0.17.2.json b/src/components/JsonSchemaValidator/Schemas/dbtUtils_0.17.2.json new file mode 100644 index 0000000000..0cf430daab --- /dev/null +++ b/src/components/JsonSchemaValidator/Schemas/dbtUtils_0.17.2.json @@ -0,0 +1,346 @@ +{ + "definitions": { + "passthrough_vars": { + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "items": { + "title": "Type", + "oneOf": [ + { + "type": "string", + "title": "Column Name" + }, + { + "type": "object", + "title": "SQL & Alias", + "properties": { + "sql": { + "type": "string", + "title": "Field SQL", + "format": "sql", + "order": 1 + }, + "alias": { + "type": "string", + "title": "Field Alias", + "order": 2 + } + }, + "required": [ + "sql", + "alias" + ], + "additionalProperties": false + } + ] + }, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "snowplow__database": { + "recommendFullRefresh": true, + "order": 1, + "consoleGroup": "required", + "type": "string", + "title": "Database", + "description": "Database that contains your atomic events", + "longDescription": "The database that contains your atomic events table.", + "packageDefault": "target.database", + "group": "Warehouse and Tracker" + }, + "snowplow__dev_target_name": { + "recommendFullRefresh": false, + "order": 87, + "consoleGroup": "advanced", + "type": "string", + "title": "Dev Target", + "description": "Target name of your development environment as defined in your `profiles.yml` file", + "longDescription": "The [target name](https://docs.getdbt.com/docs/core/connect-data-platform/profiles.yml) of your development environment as defined in your `profiles.yml` file. See the [Manifest Tables](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/manifest-tables/) section for more details.", + "packageDefault": "dev", + "group": "Warehouse and Tracker" + }, + "snowplow__events_table": { + "recommendFullRefresh": true, + "order": 5, + "consoleGroup": "required", + "type": "string", + "title": "Events Table", + "description": "The name of the table that contains your atomic events", + "longDescription": "The name of the table that contains your atomic events.", + "packageDefault": "events", + "group": "Warehouse and Tracker" + }, + "snowplow__events_schema": { + "recommendFullRefresh": true, + "order": 4, + "consoleGroup": "basic", + "type": "string", + "title": "Events Schema", + "description": "The name of the schema that contains your atomic events", + "longDescription": "The name of the schema that contains your atomic events.", + "packageDefault": "events", + "group": "Warehouse and Tracker" + }, + "snowplow__allow_refresh": { + "recommendFullRefresh": true, + "order": 39, + "consoleGroup": "advanced", + "type": "boolean", + "title": "Allow Refresh", + "group": "Operation and Logic", + "longDescription": "Used as the default value to return from the `allow_refresh()` macro. This macro determines whether the manifest tables can be refreshed or not, depending on your environment. See the [Manifest Tables](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/manifest-tables/) section for more details.", + "packageDefault": "false" + }, + "snowplow__backfill_limit_days": { + "recommendFullRefresh": false, + "order": 41, + "consoleGroup": "advanced", + "type": "number", + "minimum": 0, + "title": "Backfill Limit", + "group": "Operation and Logic", + "longDescription": "The maximum numbers of days of new data to be processed since the latest event processed. Please refer to the [incremental logic](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/incremental-processing/#package-state) section for more details.", + "packageDefault": "30", + "description": "The maximum numbers of days of new data to be processed since the latest event processed" + }, + "snowplow__custom_sql": { + "recommendFullRefresh": false, + "order": 84, + "consoleGroup": "advanced", + "type": "string", + "title": "Custom SQL", + "group": "Operation and Logic", + "longDescription": "This allows you to introduce custom sql to the `snowplow_base_events_this_run` table, which you can then leverage in downstream models. For more information on the usage, see the following page on the [advanced usage of the utils package](/docs/modeling-your-data/modeling-your-data-with-dbt/dbt-custom-models/examples/additional-sql-on-events-this-run/).", + "packageDefault": "", + "description": "Custom SQL for your base events this run table." + }, + "snowplow__days_late_allowed": { + "recommendFullRefresh": true, + "order": 42, + "consoleGroup": "advanced", + "type": "number", + "minimum": 0, + "title": "Days Late Allowed", + "group": "Operation and Logic", + "longDescription": "The maximum allowed number of days between the event creation and it being sent to the collector. Exists to reduce lengthy table scans that can occur as a result of late arriving data.", + "packageDefault": "3", + "description": "The maximum allowed number of days between the event creation and it being sent to the collector" + }, + "snowplow__max_session_days": { + "recommendFullRefresh": true, + "order": 110, + "consoleGroup": "advanced", + "type": "number", + "minimum": 0, + "title": "Max Session Length", + "longDescription": "The maximum allowed session length in days. For a session exceeding this length, all events after this limit will stop being processed. Exists to reduce lengthy table scans that can occur due to long sessions which are usually a result of bots.", + "packageDefault": "3", + "group": "Operation and Logic", + "description": "The maximum allowed session length in days. For a session exceeding this length, all events after this limit will stop being processed" + }, + "snowplow__package_name": { + "recommendFullRefresh": false, + "order": 113, + "consoleGroup": "advanced", + "type": "number", + "minimum": 0, + "title": "Package Name", + "group": "Operation and Logic", + "longDescription": "The name of the package you are running this macro under. This has implications for your `manifest` table.", + "packageDefault": "snowplow", + "description": "The name of the package you are running this macro under" + }, + "snowplow__session_lookback_days": { + "recommendFullRefresh": false, + "order": 121, + "consoleGroup": "advanced", + "type": "number", + "minimum": 0, + "title": "Session Lookback Window", + "longDescription": "Number of days to limit scan on `snowplow_web_base_sessions_lifecycle_manifest` manifest. Exists to improve performance of model when we have a lot of sessions. Should be set to as large a number as practical.", + "packageDefault": "730", + "group": "Operation and Logic", + "description": "Number of days to limit scan on `snowplow_web_base_sessions_lifecycle_manifest` manifest" + }, + "snowplow__upsert_lookback_days": { + "recommendFullRefresh": false, + "order": 126, + "consoleGroup": "advanced", + "type": "number", + "minimum": 0, + "title": "Upsert Lookback Days", + "group": "Operation and Logic", + "longDescription": "Number of days to look back over the incremental derived tables during the upsert. Where performance is not a concern, should be set to as long a value as possible. Having too short a period can result in duplicates. Please see the [Snowplow Optimized Materialization](/docs/modeling-your-data/modeling-your-data-with-dbt/package-mechanics/optimized-upserts/) section for more details.", + "packageDefault": "30", + "description": "Number of days to look back over the incremental derived tables during the upsert" + }, + "snowplow__app_id": { + "recommendFullRefresh": false, + "order": 8, + "consoleGroup": "basic", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "title": "App IDs", + "longDescription": "A list of `app_id`s to filter the events table on for processing within the package.", + "packageDefault": "[ ] (no filter applied)", + "group": "Contexts, Filters, and Logs", + "items": { + "type": "string" + } + }, + "snowplow__session_identifiers": { + "recommendFullRefresh": true, + "order": 46, + "consoleGroup": "advanced", + "title": "Session Identifiers", + "group": "Operation and Logic", + "longDescription": "A list of key:value dictionaries which contain all of the contexts and fields where your session identifiers are located. For each entry in the list, if your map contains the `schema` value `atomic`, then this refers to a field found directly in the atomic `events` table. If you are trying to introduce a context/entity with an identifier in it, the package will look for the context in your events table with the name specified in the `schema` field. It will use the specified value in the `field` key as the field name to access. For Redshift/Postgres, using the `schema` key the package will try to find a table in your `snowplow__events_schema` schema with the same name as the `schema` value provided, and join that. If multiple fields are specified, the package will try to coalesce all fields in the order specified in the list. For a better understanding of the advanced usage of this variable, please see the [Custom Identifiers](/docs/modeling-your-data/modeling-your-data-with-dbt/package-features/custom-identifiers/) section for more details.", + "packageDefault": "[{\"schema\" : \"atomic\", \"field\" : \"domain_sessionid\"}]", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "items": { + "type": "object", + "title": "Identifier", + "properties": { + "schema": { + "type": "string", + "title": "(JSON) schema name for the field", + "order": 1, + "description": "The schema name of your events table, atomic in most use cases, alternatively for sdes/contexts this should instead be the name of the field itself" + }, + "field": { + "type": "string", + "order": 2, + "title": "Field name", + "description": "The name of the field to use as session identifier, alternatively, in case of sdes/contexts it is the name of the element that refers to the field to be extracted" + } + }, + "required": [ + "schema", + "field" + ], + "additionalProperties": false + }, + "uniqueItems": true + }, + "snowplow__session_sql": { + "recommendFullRefresh": true, + "order": 47, + "consoleGroup": "advanced", + "type": "string", + "title": "SQL for your session identifier", + "longDescription": "This allows you to override the `session_identifiers` SQL, to define completely custom SQL in order to build out a session identifier for your events. If you are interested in using this instead of providing identifiers through the `session_identifiers` variable, please see the [Custom Identifiers](/docs/modeling-your-data/modeling-your-data-with-dbt/package-features/custom-identifiers/) section for more details on how to do that.", + "packageDefault": "", + "group": "Operation and Logic" + }, + "snowplow__session_timestamp": { + "recommendFullRefresh": false, + "order": 55, + "consoleGroup": "advanced", + "type": "string", + "title": "Timestamp used for incremental processing, should be your partition field", + "group": "Operation and Logic", + "longDescription": "Determines which timestamp is used to build the sessionization logic. It's a good idea to have this timestamp be the same timestamp as the field you partition your events table on.", + "packageDefault": "collector_tstamp" + }, + "snowplow__user_identifiers": { + "recommendFullRefresh": true, + "order": 48, + "consoleGroup": "advanced", + "title": "User Identifiers", + "group": "Operation and Logic", + "longDescription": "A list of key:value dictionaries which contain all of the contexts and fields where your user identifiers are located. For each entry in the list, if your map contains the `schema` value `atomic`, then this refers to a field found directly in the atomic `events` table. If you are trying to introduce a context/entity with an identifier in it, the package will look for the context in your events table with the name specified in the `schema` field. It will use the specified value in the `field` key as the field name to access. For Redshift/Postgres, using the `schema` key the package will try to find a table in your `snowplow__events_schema` schema with the same name as the `schema` value provided, and join that. If multiple fields are specified, the package will try to coalesce all fields in the order specified in the list. For a better understanding of the advanced usage of this variable, please see the [Custom Identifiers](/docs/modeling-your-data/modeling-your-data-with-dbt/package-features/custom-identifiers/) section for more details.", + "packageDefault": "[{\"schema\" : \"atomic\", \"field\" : \"domain_userid\"}]", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "items": { + "type": "object", + "title": "Identifier", + "properties": { + "schema": { + "type": "string", + "title": "(JSON) schema name for the field", + "order": 1, + "description": "The schema name of your events table, atomic in most use cases, alternatively for sdes/contexts this should instead be the name of the field itself" + }, + "field": { + "type": "string", + "order": 2, + "title": "Field name", + "description": "The name of the field to use as user identifier, alternatively, in case of sdes/contexts it is the name of the element that refers to the field to be extracted" + } + }, + "required": [ + "schema", + "field" + ], + "additionalProperties": false + }, + "uniqueItems": true + }, + "snowplow__user_sql": { + "recommendFullRefresh": true, + "order": 49, + "consoleGroup": "advanced", + "type": "string", + "title": "SQL for your user identifier", + "longDescription": "This allows you to override the `user_identifiers` SQL, to define completely custom SQL in order to build out a user identifier for your events. If you are interested in using this instead of providing identifiers through the `user_identifiers` variable, please see the [Custom Identifiers](/docs/modeling-your-data/modeling-your-data-with-dbt/package-features/custom-identifiers/) section for more details on how to do that.", + "packageDefault": "", + "group": "Operation and Logic" + }, + "snowplow__entities_or_sdes": { + "recommendFullRefresh": false, + "order": 104, + "consoleGroup": "advanced", + "title": "(Redshift) Entities or SDEs", + "longDescription": "A list of dictionaries defining the `context` or `self-describing` event tables to join onto your base events table. Please use the tool below or see the section on [Utilizing custom contexts or SDEs](/docs/modeling-your-data/modeling-your-data-with-dbt/package-features/modeling-entities/) for details of the structure.", + "packageDefault": "[]", + "warehouse": "Redshift", + "group": "Warehouse Specific", + "type": "array", + "description": "> Click the plus sign to add a new entry", + "minItems": 0, + "items": { + "type": "object", + "title": "Entity or SDE", + "properties": { + "schema": { + "type": "string", + "title": "Table name", + "description": "Table name", + "order": 1 + }, + "prefix": { + "type": "string", + "title": "Column prefix", + "description": "Prefix to add to columns", + "order": 2 + }, + "alias": { + "type": "string", + "title": "CTE Alias", + "description": "Table alias for the subquery", + "order": 3 + }, + "single_entity": { + "type": "boolean", + "title": "Is single entity?", + "order": 4 + } + }, + "required": [ + "schema", + "prefix" + ], + "additionalProperties": false + }, + "uniqueItems": true + } + } +} diff --git a/src/components/ManageCookiePreferences/styles.module.css b/src/components/ManageCookiePreferences/styles.module.css index 00f1e1917a..b6f741f159 100644 --- a/src/components/ManageCookiePreferences/styles.module.css +++ b/src/components/ManageCookiePreferences/styles.module.css @@ -14,9 +14,7 @@ 0px 1px 2px rgba(16, 24, 40, 0.05); border-radius: 4px; - font-family: 'Inter'; - font-style: normal; - font-weight: 500; + font-size: 14px; line-height: 20px; color: #412476; diff --git a/src/components/MuiTheme/index.js b/src/components/MuiTheme/index.js index 9257a65aaa..f7084db71b 100644 --- a/src/components/MuiTheme/index.js +++ b/src/components/MuiTheme/index.js @@ -9,17 +9,17 @@ const extTheme = extendTheme({ main: '#6638b8', }, background: { - default: '#f2f4f7', + default: '#F9FAFB', }, }, }, dark: { palette: { primary: { - main: '#6638b8', + main: '#916CE7', }, background: { - default: '#f2f4f7', + default: '#050505', }, }, }, diff --git a/src/components/ui/button.jsx b/src/components/ui/button.jsx index 2b18329f30..a573d1474d 100644 --- a/src/components/ui/button.jsx +++ b/src/components/ui/button.jsx @@ -4,20 +4,20 @@ import { cva } from "class-variance-authority"; import { cn } from "../../lib/utils.js"; const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border-none", { variants: { variant: { default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: - "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + "bg-secondary text-destructive border-solid border border-destructive shadow-sm hover:bg-destructive/10", outline: - "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 border-solid border border-border hover:bg-accent hover:text-accent-foreground", secondary: - "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 border-solid border border-border hover:bg-accent hover:text-accent-foreground", + ghost: "bg-transparent hover:bg-accent hover:text-accent-foreground", + link: "bg-transparent text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2", diff --git a/src/components/ui/navigation.jsx b/src/components/ui/navigation.jsx new file mode 100644 index 0000000000..bef1463ae2 --- /dev/null +++ b/src/components/ui/navigation.jsx @@ -0,0 +1,50 @@ +import React from "react"; +import { cn } from "../../lib/utils.js"; +import { + Home, + BookOpen, + Settings, + User, + ChevronDown, + Menu +} from "lucide-react"; +import ThemeSwitcher from "./theme-switcher.jsx"; + +const Navigation = ({ className }) => { + return ( + + ); +}; + +export default Navigation; \ No newline at end of file diff --git a/src/components/ui/theme-switcher.jsx b/src/components/ui/theme-switcher.jsx new file mode 100644 index 0000000000..2779264d28 --- /dev/null +++ b/src/components/ui/theme-switcher.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { useColorMode } from '@docusaurus/theme-common'; +import { Sun, Moon } from 'lucide-react'; +import { Button } from './button.jsx'; + +const ThemeSwitcher = () => { + const { colorMode, setColorMode } = useColorMode(); + + const toggleTheme = () => { + const newTheme = colorMode === 'dark' ? 'light' : 'dark'; + setColorMode(newTheme); + // Ensure the theme is applied to the root element + document.documentElement.setAttribute('data-theme', newTheme); + }; + + return ( + + ); +}; + +export default ThemeSwitcher; \ No newline at end of file diff --git a/src/css/custom.css b/src/css/custom.css index faa094226a..db353086cb 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -2,102 +2,130 @@ @tailwind components; @tailwind utilities; -@layer base { +/*** Shadcn UI theme */ + @layer base { :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; + --background: 210 20% 98.04%; + --foreground: 220.91 39.29% 10.98%; + --muted: 220 13.04% 90.98%; + --muted-foreground: 220 8.94% 46.08%; --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --popover-foreground: 220.91 39.29% 10.98%; + --card: 0 0% 100%; + --card-foreground: 220.91 39.29% 10.98%; + --border: 220 13.04% 90.98%; + --input: 220 13.04% 90.98%; + --primary: 261.56 53.33% 47.06%; + --primary-foreground: 0 0% 100%; + --secondary: 0 0% 100%; + --secondary-foreground: 220.91 39.29% 10.98%; + --accent: 220 14.29% 95.88%; + --accent-foreground: 220.91 39.29% 10.98%; + --destructive: 3.96 72.8% 50.98%; + --destructive-foreground: 0 0% 100%; + --ring: 262.11 53.27% 79.02%; + --chart-1: 262.11 53.27% 79.02%; + --chart-2: 183.6 84.27% 34.9%; + --chart-3: 40.11 80% 54.9%; + --chart-4: 339.77 74.78% 54.9%; + --chart-5: 220.45 70.08% 50.2%; --radius: 0.5rem; + + --font-fallback: -apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif ; } - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; + [data-theme='dark'] { + --background: 0 0% 1.96%; + --foreground: 0 0% 90.2%; + --muted: 156 9% 8%; + --muted-foreground: 0 0% 60%; + --popover: 0 0% 6.27%; + --popover-foreground: 0 0% 90.2%; + --card: 0 0% 9.8%; + --card-foreground: 0 0% 90.2%; + --border: 0 0% 13.73%; + --input: 0 0% 17.65%; + --primary: 258.05 71.93% 66.47%; + --primary-foreground: 0 0% 90.2%; + --secondary: 0 0% 0%; + --secondary-foreground: 0 0% 90.2%; + --accent: 0 0% 15.69%; + --accent-foreground: 0 0% 60%; + --destructive: 3.95 72.93% 44.9%; + --destructive-foreground: 0 0% 100%; + --ring: 262.11 53.27% 79.02%; + --chart-1: 261.82 54.1% 88.04%; + --chart-2: 183.75 45.28% 58.43%; + --chart-3: 40.27 76.84% 62.75%; + --chart-4: 340 73.06% 62.16%; + --chart-5: 220.66 71.03% 58.04%; } } + /** * Any CSS included here will be global. The classic template * bundles Infima by default. Infima is a CSS framework designed to * work well for content-centric websites. */ + @font-face { + font-family: 'Inter18pt-Bold'; + src: url('/fonts/Inter18pt-BoldItalic.woff2') format('woff2'), + url('/fonts/Inter18pt-BoldItalic.woff') format('woff'); + font-weight: bold; + font-style: italic; + font-display: swap; +} @font-face { - font-family: 'Rubik'; - src: url('/fonts/Rubik-Regular.ttf') format('truetype'); + font-family: 'Inter18pt-BoldItalic'; + src: url('/fonts/Inter18pt-Italic.woff2') format('woff2'), + url('/fonts/Inter18pt-Italic.woff') format('woff'); + font-weight: normal; + font-style: italic; + font-display: swap; } @font-face { - font-family: 'Inter'; - src: url('/fonts/Inter-Medium.ttf') format('truetype'); - font-weight: 500; + font-family: 'Inter18pt-Bold'; + src: url('/fonts/Inter18pt-Bold.woff2') format('woff2'), + url('/fonts/Inter18pt-Bold.woff') format('woff'); + font-weight: bold; font-style: normal; + font-display: swap; } @font-face { - font-family: 'Roboto'; - src: url('/fonts/Roboto-Regular.ttf') format('truetype'); - font-weight: 400; + font-family: 'Inter18pt-Regular'; + src: url('/fonts/Inter18pt-Regular.woff2') format('woff2'), + url('/fonts/Inter18pt-Regular.woff') format('woff'); + font-weight: normal; font-style: normal; + font-display: swap; } @font-face { - font-family: 'Roboto'; - src: url('/fonts/Roboto-Medium.ttf') format('truetype'); - font-weight: 500; + font-family: 'Inter18pt-Light'; + src: url('/fonts/Inter18pt-Light.woff2') format('woff2'), + url('/fonts/Inter18pt-Light.woff') format('woff'); + font-weight: 300; font-style: normal; + font-display: swap; } @font-face { - font-family: 'Roboto'; - src: url('/fonts/Roboto-Italic.ttf') format('truetype'); - font-weight: 400; + font-family: 'Inter18pt-LightItalic'; + src: url('/fonts/Inter18pt-LightItalic.woff2') format('woff2'), + url('/fonts/Inter18pt-LightItalic.woff') format('woff'); + font-weight: 300; font-style: italic; + font-display: swap; } -@font-face { - font-family: 'Roboto'; - src: url('/fonts/Roboto-MediumItalic.ttf') format('truetype'); - font-weight: 500; - font-style: italic; -} /* You can override the default Infima variables here. */ :root { - --ifm-background-color: #f2f4f7; + --ifm-background-color: var(--background); --ifm-color-primary: #6638b8; --ifm-color-primary-dark: #522d93; --ifm-color-primary-darker: #472781; @@ -110,9 +138,9 @@ --ifm-color-announcement: #e6f6fe; --ifm-code-font-size: 95%; --ifm-code-padding-vertical: 0.05rem; - --ifm-font-family-base: 'Roboto', Arial, Helvetica, sans-serif; + --ifm-font-family-base: 'Inter18pt-Regular', var(--font-fallback); --ifm-menu-color-active: #6638b8; - --ifm-font-color-base: #000000; + --ifm-font-color-base: var(--foreground); --ifm-font-color-base-inverse: rgb(218, 221, 225); --ifm-toc-padding-horizontal: 20px; --docusaurus-highlighted-code-line-bg: rgba(224, 199, 50, 0.2); @@ -121,8 +149,11 @@ --ifm-menu-link-sublist-icon: url('data:image/svg+xml;utf8,'); } + /* For readability concerns, you should choose a lighter palette in dark mode. */ [data-theme='dark'] { + --ifm-background-color: var(--background ); + --ifm-menu-color-active: #b198df; --ifm-color-primary: #b198df; --ifm-color-primary-dark: #522d93; @@ -185,7 +216,7 @@ button.snowplow-button { border: none; border-radius: 4px; - font-family: 'Inter'; + font-family: 'Inter18pt-Regular', var(--font-fallback); font-style: normal; font-weight: 500; font-size: 14px; @@ -277,30 +308,23 @@ h2, h3, h4, h5 { - font-family: 'Rubik'; + font-family: 'Inter18pt-Bold', var(--font-fallback); } -h1, -h2, -h3, -h4, -h5 { - font-weight: 500; -} h1 { - font-size: 32px; - line-height: 40px; + font-size: 38px; + line-height: 44px; } h2 { font-size: 28px; - line-height: 36px; + line-height: 40px; } h3 { - font-size: 24px; - line-height: 32px; + font-size: 20px; + line-height: 24px; } table h1, @@ -310,7 +334,7 @@ table h3 { } .table-of-contents__left-border { - border-left-color: #98a2b3; + border-left-color: var(--border); } .theme-doc-sidebar-item-link-level-1.header { @@ -427,7 +451,7 @@ code { input[type='text'], input[type='email'], textarea { - font-family: 'Roboto', Arial, Helvetica, sans-serif; + font-family: 'Inter18pt-Regular', var(--font-fallback); font-size: 14px; } @@ -555,20 +579,7 @@ Controls the display of navbar/sidebar items margin-top: 2rem; } -.tutorial-doc-page .feedback-prompt { - margin-top: 3rem; -} - -.tutorial-doc-page .theme-doc-footer { - display: none; - margin-top: 0; -} - -.tutorial-doc-page .feedback-prompt { - margin-top: 2rem; -} - -/* Hide the feedback and last updated times on the tutorial home page */ +/* Hide the last updated times on the tutorial home page */ .tutorial-home-page article > footer { display: none; } @@ -589,3 +600,57 @@ Controls the display of navbar/sidebar items grid-gap: 20px; padding: 20px; } + +/*START doc markdown grid layout control */ +.theme-doc-markdown { + --padding-inline: 1rem; + --content-max-width: 680px; + --breakout-max-width: 1200px; + + --breakout-size: calc( + (var(--breakout-max-width) - var(--content-max-width)) / 2 + ); + + display: grid; + grid-template-columns: + [full-width-start] minmax(var(--padding-inline), 1fr) + [breakout-start] minmax(0, var(--breakout-size)) + [content-start] min( + 100% - (var(--padding-inline) * 2), + var(--content-max-width) + ) + [content-end] + minmax(0, var(--breakout-size)) [breakout-end] + minmax(var(--padding-inline), 1fr) [full-width-end]; +} + +.theme-doc-markdown > :not(.breakout, .full-width), +.full-width > :not(.breakout, .full-width) { + grid-column: content; +} + +.theme-doc-markdown > .breakout, .theme-doc-markdown p img { + grid-column: breakout; +} + +.theme-doc-markdown > .full-width { + grid-column: full-width; + display: grid; + grid-template-columns: inherit; +} + +img.full-width { + width: 100%; + max-height: 100vh; + object-fit: cover; +} + +/*Control rest of the elements erase after we upgrade */ +.docItemContainer_src-theme-DocItem-Layout-styles-module nav, +.docItemContainer_src-theme-DocItem-Layout-styles-module article nav, +.docItemContainer_src-theme-DocItem-Layout-styles-module article footer { + max-width: 680px; + margin: 3rem auto 0 auto; +} + +/* doc markdown grid layout control END*/ diff --git a/src/dbtVersions.js b/src/dbtVersions.js index 53de276db5..f4bc7810ae 100644 --- a/src/dbtVersions.js +++ b/src/dbtVersions.js @@ -41,6 +41,13 @@ export const dbtVersions = { "dbt-labs/dbt_utils": ">=1.0.0 <2.0.0", "snowplow/snowplow_utils": ">=0.17.0 <0.18.0" } + }, + "0.5.0": { + "dbtversion": ">=1.6.0 <2.0.0", + "packages": { + "dbt-labs/dbt_utils": ">=1.0.0 <2.0.0", + "snowplow/snowplow_utils": ">=0.17.0 <0.18.0" + } } }, "snowplow/snowplow_ecommerce": { diff --git a/src/pages/tailwind-test.js b/src/pages/tailwind-test.js index d36ea45584..0de479c3e6 100644 --- a/src/pages/tailwind-test.js +++ b/src/pages/tailwind-test.js @@ -14,10 +14,12 @@ import { Info } from 'lucide-react'; import { Button } from '../components/ui/button.jsx'; +import Navigation from '../components/ui/navigation.jsx'; export default function TailwindTest() { return ( +

diff --git a/src/remark/abbreviations.js b/src/remark/abbreviations.js index 14d4b9b812..2af1c27d16 100644 --- a/src/remark/abbreviations.js +++ b/src/remark/abbreviations.js @@ -7,6 +7,7 @@ const plugin = () => { AWS: 'Amazon Web Services', BDP: 'Behavioral Data Platform', CDI: 'Customer Data Infrastructure', + CDN: 'Content Delivery Network', CDP: 'Customer Data Platform', CLI: 'Command Line Interface', EC2: 'Amazon Elastic Compute Cloud', diff --git a/src/theme/DocItem/Footer/index.js b/src/theme/DocItem/Footer/index.js deleted file mode 100644 index e3abccdc45..0000000000 --- a/src/theme/DocItem/Footer/index.js +++ /dev/null @@ -1,136 +0,0 @@ -import React, { useState, useRef } from 'react' -import clsx from 'clsx' -import Footer from '@theme-original/DocItem/Footer' -import styles from './styles.module.css' -import { trackStructEvent } from '@snowplow/browser-tracker' -import { useLocation } from '@docusaurus/router' -import { useDoc } from '@docusaurus/theme-common/internal' - -function CommentBox({ handleSubmit, feedbackTextRef }) { - const placeholder = 'How can we improve it?' - - return ( -
-