Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Google data layer #2613

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 63 additions & 10 deletions DATA_LAYER_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,61 @@ limitations under the License.

# Data Layer Integration with the Core Components

The Core Components provide an out-of-the-box integration with the [Adobe Client Data Layer](https://github.com/adobe/adobe-client-data-layer), which for convenience is called data layer in this page.
The Core Components provide an out-of-the-box integration with:
- The [Adobe Client Data Layer](https://github.com/adobe/adobe-client-data-layer), which for convenience is called the ACDL in this page.
- The [Google Data Layer](https://developers.google.com/tag-platform/tag-manager/datalayer), which is called the GDL in this page.

## Enabling the Data Layer

The data layer is disabled by default.
The data layer is disabled by default.

To enable the data layer for your site:
To enable the ACDL for your site:
1. Create the following structure below the `/conf` node:
`/conf/<my-site>/sling:configs/com.adobe.cq.wcm.core.components.internal.DataLayerConfig`
1. Add the `enabled` boolean property and set it to `true`.
1. Add a `sling:configRef` property to the `jcr:content` node of your site below `/content` (e.g. `/content/<my-site>/jcr:content`) and set it to `/conf/<my-site>`
2. Add the `enabled` boolean property and set it to `true`. This will add the ACDL library and supporting clientlibs to your page.
3. Add a `sling:configRef` property to the `jcr:content` node of your site below `/content` (e.g. `/content/<my-site>/jcr:content`) and set it to `/conf/<my-site>`

To enable the GDL for your site:
1. Add the appropriate Google code to your page (normally either Google Tag Manager or the Google Tag (GTag)).
Unlike the ACDL integration this code is not included in Core Components.
2. Create the following structure below the `/conf` node:
`/conf/<my-site>/sling:configs/com.adobe.cq.wcm.core.components.internal.DataLayerConfig`
3. Add the `enabled` boolean property and set it to `true`. This will add the *ACDL* library and supporting clientlibs to your page.
4. Add the `skipAcdlInclude` boolean property and set it to `true`. This keeps the data layer support but prevents the ACDL library from loading.
5. Rename the data layer array/object by adding a string property `name`, normally `dataLayer`.
6. Add a `sling:configRef` property to the `jcr:content` node of your site below `/content` (e.g. `/content/<my-site>/jcr:content`) and set it to `/conf/<my-site>`

## Preventing the Data Layer client library from being included

The data layer client library is included by default by the Page component. As there are other ways to include this library (e.g. through Adobe Launch), it might be needed to prevent its inclusion through the Page component.
## Preventing the Data Layer client library and/or the ACDL library from being included

To prevent the data layer client library from being included by the Page component:
1. Create the following structure below the `/conf` node:
`/conf/<my-site>/sling:configs/com.adobe.cq.wcm.core.components.internal.DataLayerConfig`
1. Add the `skipClientlibInclude` boolean property and set it to `true`.
1. Add a `sling:configRef` property to the `jcr:content` node of your site below `/content` (e.g. `/content/<my-site>/jcr:content`) and set it to `/conf/<my-site>`
2. Add the `skipClientlibInclude` boolean property and set it to `true`.
3. Add a `sling:configRef` property to the `jcr:content` node of your site below `/content` (e.g. `/content/<my-site>/jcr:content`) and set it to `/conf/<my-site>`

To retain the data layer client library but exclude the ACDL library (for example when using a GDL):
1. Create the following structure below the `/conf` node:
`/conf/<my-site>/sling:configs/com.adobe.cq.wcm.core.components.internal.DataLayerConfig`
2. Ensure that
3. Add the `skipAcdlInclude` boolean property and set it to `true` (remove `skipClientlibInclude` property if present, or set it to `false`).
4. Add a `sling:configRef` property to the `jcr:content` node of your site below `/content` (e.g. `/content/<my-site>/jcr:content`) and set it to `/conf/<my-site>`

## Choosing the Google push function

## Data Layer State Structure
When Google Tag Manager is used to implement tracking, a push to the data layer is normally done using the push() method.
This is the default function for the data layer integration and the code is identical to that used for the ACDL.
If native Google Analytics code is used (gtag library), gtag() is normally used instead of push().

To push to the data layer using gtag():

1. Follow the configurative steps above to set-up the basic Google configuration
2. Add the `pushFunctionUseGtag` boolean property to the `DataLayerConfig` node described above and set it to `true`.

> Note: the data layer integration logic checks that the gtag function is available before calling it. If not available the code will fall back to the push() method.
If you load the gtag code too late in the page load, gtag will not be available during page load and the fallback will be used.

## ACDL State Structure

When the data layer is enabled, the javascript `adobeDataLayer` object is available on the page and is populated with the components and their properties that are used on the page.

Expand Down Expand Up @@ -123,6 +155,17 @@ Calling `adobeDataLayer.getState()` in the browser console will return e.g.:
}
```

## Google State Structure

The GDL does not include a getState method, so to view the contents of the data layer, simply type `dataLayer` in the the browser console.
While this is not the *computed state* is is sufficient for debugging in most cases.

## Google Data Structure

If Google Tag Manager is used then the data will be in the same format in the data layer as the ACDL snippets above.

If Gtag is used the events will be in an array rather than object format. Both of these structures can be seen on [Google's Enhance e-Commerce demo](https://ga-dev-tools.google/ga4/enhanced-ecommerce/) site.

## Components supporting the Data Layer

The following table shows the components supporting the data layer:
Expand Down Expand Up @@ -355,6 +398,11 @@ Run the following code in your browser console:
```
adobeDataLayer.getState()
```
or, if a Google Data Layer is used, view the entire data layer object by typing:
```
dataLayer
```

The `HelloWorld` component does not yet write to the data layer.

#### Add HelloWorld data to the data layer
Expand Down Expand Up @@ -394,6 +442,11 @@ Deploy the changes to AEM (model and HTL script). Refresh the page and in your b
```
adobeDataLayer.getState()
```
or, if a Google Data Layer is used, view the entire data layer object by typing:
```
dataLayer
```


It displays something like:
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
@Configuration(label="Data Layer", description="Configure support for Adobe Client Data Layer")
public @interface DataLayerConfig {

String DATALAYER_OBJECT_NAME_ADOBE = "adobeDataLayer";

/**
*
* @return {@code true} if the data layer is enabled, {@code false} otherwise. It defaults to {@code false}.
Expand All @@ -39,4 +41,29 @@
@Property(label="Data Layer client library not included")
boolean skipClientlibInclude() default false;

/**
*
* @return the name of the datalayer
* Defaults to adobeDataLayer.
*/
@Property(label = "Data Layer object name")
String name() default DATALAYER_OBJECT_NAME_ADOBE;

/**
*
* @return the type of push function (push() or gtag()) of the datalayer
* Defaults to push.
*/
@Property(label = "Use gtag function to push data")
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't it be enough to know that the name of the datalayer is gtag?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, the datalayer can be called anything or normally dataLayer, regardless of whether GA (gtag) or GTM is used.

boolean pushFunctionUseGtag() default false;

/**
*
* @return ACDL library not included
* Defaults to false.
*/
@Property(label = "ACDL library not included")
boolean skipAcdlInclude() default false;
Copy link
Member

Choose a reason for hiding this comment

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

We already have skipClientlibInclude that serves the same purpose (to control if the ACDL scripts should be included).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see reply to Paga.java above



}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.Optional;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.caconfig.ConfigurationBuilder;
import org.apache.sling.models.annotations.Exporter;
Expand Down Expand Up @@ -65,4 +66,33 @@ protected NavigationItem newRedirectItem(@NotNull String redirectTarget, @NotNul
return new RedirectItemImpl(redirectTarget, request, linkManager);
}

@Override
@JsonIgnore
public String getDataLayerName() {
String name = getDataLayerConfig().map(DataLayerConfig::name)
.orElse("");
return StringUtils.isEmpty(name) ? DataLayerConfig.DATALAYER_OBJECT_NAME_ADOBE : name;
}


private Optional<DataLayerConfig> getDataLayerConfig() {
return Optional.ofNullable(resource.adaptTo(ConfigurationBuilder.class))
.map(builder -> builder.as(DataLayerConfig.class));
}

@Override
@JsonIgnore
public boolean isDataLayerUseGtag() {
return getDataLayerConfig().map(config -> config.pushFunctionUseGtag())
.orElse(false);
}

@Override
@JsonIgnore
public boolean isDataLayerSkipAcdlInclude() {
return getDataLayerConfig().map(config -> config.skipAcdlInclude())
.orElse(false);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ default String getId() {
default ComponentData getData() {
return null;
}

/**
* Returns the style system information associated with the component
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
@ConsumerType
public interface Page extends ContainerExporter, Component {

String DATALAYER_OBJECT_NAME_ADOBE = "adobeDataLayer";

/**
* Key used for the regular favicon file.
*
Expand Down Expand Up @@ -467,4 +469,27 @@ default List<String> getRobotsTags() {
*/
default boolean isDataLayerClientlibIncluded() {return true;}

/**
* Returns the name of the data layer.
*
* {@code true} if the data layer client library should be included.
* @since om.adobe.cq.wcm.core.components.models 12.24.0
*/
default String getDataLayerName() { return DATALAYER_OBJECT_NAME_ADOBE; }
Copy link
Member

Choose a reason for hiding this comment

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

This will require a package minor version bump


/**
* Checks if gtag() should be used to push to the data layer.
*
* {@code true} if gtag() should be used.
* @since om.adobe.cq.wcm.core.components.models 12.24.0
*/
default boolean isDataLayerUseGtag() { return false; };

/**
* Checks if the ACDL library should be included.
*
* {@code true} if ACDL library should be used.
* @since om.adobe.cq.wcm.core.components.models 12.24.0
*/
default boolean isDataLayerSkipAcdlInclude() { return false; };
Copy link
Member

Choose a reason for hiding this comment

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

We already have isDataLayerClientlibIncluded that serves the same purpose (to control if the ACDL scripts should be included).

Copy link
Contributor Author

@brobatr brobatr Nov 13, 2023

Choose a reason for hiding this comment

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

hi @vladbailescu

unfortunately isDataLayerClienlibIncluded removes the datalayer.js file not just the ACDL. This may be a bug: when the flag is set to true there is an odd state - some component datalayer interaction happens as before (accordian, text etc), but the components that are caught in a loop in datalayer.js are no longer pushed to the datalayer. In short we get a semi-enabled state.

This is the reason (clienlib.config.js):

assets: {
js: [
"src/scripts/datalayer/v1/polyfill.js",
"node_modules/@adobe/adobe-client-data-layer/dist/adobe-client-data-layer.min.js",
"src/scripts/datalayer/v1/datalayer.js"
]
}

For the GDL I need to keep datalayer.js. This has led to the ugly approach of "remove DL clientlib and include part of it again"

Perhaps it would be better to change the existing functionality so that only the ACDL code is removed by isDataLayerClientlibIncluded? That would allow me to make the whole thing cleaner, and seems more intuitive.

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

import com.adobe.cq.wcm.core.components.models.datalayer.AssetData;
import com.adobe.cq.wcm.core.components.models.datalayer.ContentFragmentData;
import com.adobe.cq.wcm.core.components.models.datalayer.EmbeddableData;

/**
* Data layer field value supplier.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,30 @@ public static String getTestDataModelJSONPath(String testBase, String testResour
* @param enabled {@code true} to enable the data layer, {@code false} to disable it
*/
public static void enableDataLayer(AemContext context, boolean enabled) {
configureDataLayer(context, enabled, false);
configureDataLayer(context, enabled, false, "adobeDataLayer", false);
}

/**
* Sets the data layer context aware configuration of the AEM test context to enable the data layer, including a configured name.
*
* @param context The AEM test context
* @param enabled {@code true} to enable the data layer, {@code false} to disable it
* @param name a configured name for the data layer
*/
public static void enableDataLayer(AemContext context, boolean enabled, String name) {
configureDataLayer(context, enabled, false, name, false);
}

/**
* Sets the data layer context aware configuration of the AEM test context to enable the data layer, including a configured name and choice of push function.
*
* @param context The AEM test context
* @param enabled {@code true} to enable the data layer, {@code false} to disable it
* @param name a configured name for the data layer
* @param useGtag {@code true} to push to the datalayer using gtag(), {@code false} to use push()
*/
public static void enableDataLayer(AemContext context, boolean enabled, String name, boolean useGtag) {
configureDataLayer(context, enabled, false, name, useGtag);
}

/**
Expand All @@ -166,14 +189,27 @@ public static void enableDataLayer(AemContext context, boolean enabled) {
* @param skip {@code true} to not include the data layer clientlib, {@code false} to include it
*/
public static void skipDataLayerInclude(AemContext context, boolean skip) {
configureDataLayer(context, true, skip);
configureDataLayer(context, true, skip, "adobeDataLayer", false);
}

/**
* Sets the data layer context aware configuration of the AEM test context to not include the data layer clientlib, including the option to skip the clientlib includes.
*
* @param context The AEM test context
* @param enabled {@code true} to enable the data layer, {@code false} to disable it
* @param skip {@code true} to not include the data layer clientlib, {@code false} to include it
*/
public static void configureDataLayer(AemContext context, boolean enabled, boolean skip) {
configureDataLayer(context,enabled,skip,"adobeDataLayer", false);
}

private static void configureDataLayer(AemContext context, boolean enabled, boolean skip) {
public static void configureDataLayer(AemContext context, boolean enabled, boolean skip, String name, boolean useGtag) {
ConfigurationBuilder builder = Mockito.mock(ConfigurationBuilder.class);
DataLayerConfig dataLayerConfig = Mockito.mock(DataLayerConfig.class);
lenient().when(dataLayerConfig.enabled()).thenReturn(enabled);
lenient().when(dataLayerConfig.skipClientlibInclude()).thenReturn(skip);
lenient().when(dataLayerConfig.name()).thenReturn(name);
lenient().when(dataLayerConfig.pushFunctionUseGtag()).thenReturn(useGtag);
lenient().when(builder.as(DataLayerConfig.class)).thenReturn(dataLayerConfig);
context.registerAdapter(Resource.class, ConfigurationBuilder.class, builder);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.adobe.cq.wcm.core.components.models.NavigationItem;
import com.adobe.cq.wcm.core.components.models.Page;

import static com.adobe.cq.wcm.core.components.Utils.configureDataLayer;
import static com.adobe.cq.wcm.core.components.Utils.skipDataLayerInclude;
import static com.adobe.cq.wcm.core.components.internal.link.LinkTestUtils.assertValidLink;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -111,4 +112,31 @@ protected void testIsDataLayerClientlibIncluded_caconfig_false() {
assertTrue(page.isDataLayerClientlibIncluded(), "The data layer clientlib should be included.");
}

@Test
protected void testIsDataLayerName_caconfig_empty() {
Page page = getPageUnderTest(PAGE);
configureDataLayer(context,false, true, "", false);
assertTrue(page.getDataLayerName().equals("adobeDataLayer"), "The data layer name should be the default adobeDataLayer.");
}

@Test
protected void testIsDataLayerName_caconfig_configured() {
Page page = getPageUnderTest(PAGE);
configureDataLayer(context,false, true, "dataLayer", false);
assertTrue(page.getDataLayerName().equals("dataLayer"), "The data layer name should be the configured 'dataLayer'.");
}

@Test
protected void testIsDataLayerGtag_caconfig_configured_true() {
Page page = getPageUnderTest(PAGE);
configureDataLayer(context,false, true, "", true);
assertTrue(page.isDataLayerUseGtag(), "The data layer push function should be gtag (true).");
}

@Test
protected void testIsDataLayerGtag_caconfig_configured_false() {
Page page = getPageUnderTest(PAGE);
configureDataLayer(context,false, true, "", false);
assertFalse(page.isDataLayerUseGtag(), "The data layer push function should not be gtag (false).");
}
}
12 changes: 12 additions & 0 deletions content/clientlib.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ module.exports = {
"src/scripts/datalayer/v1/datalayer.js"
]
}
},
{
name: "core.wcm.components.commons.datalayer.generic.v1",
serializationFormat: "xml",
allowProxy: true,
jsProcessor: ["default:none", "min:gcc;compilationLevel=whitespace"],
assets: {
js: [
"src/scripts/datalayer/v1/polyfill.js",
"src/scripts/datalayer/v1/datalayer.js"
]
}
}
]
};
Expand Down
1 change: 1 addition & 0 deletions content/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
<exclude>**/node_modules/**</exclude>
<!-- Ignore generated datalayer clientlib -->
<exclude>**/core.wcm.components.commons.datalayer.v1/**</exclude>
<exclude>**/core.wcm.components.commons.datalayer.generic.v1/**</exclude>
<!-- Ignore karma -->
<exclude>**/coverage/**</exclude>
</excludes>
Expand Down
Loading
Loading