python/ga4_setup/setup.py (193 lines of code) (raw):
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from google.analytics import admin_v1alpha
from google.analytics.admin import AnalyticsAdminServiceClient
from typing import List
def get_data_stream(property_id: str, stream_id: str, transport: str = None):
"""
Retrieves a data stream from Google Analytics 4.
Args:
property_id: The ID of the Google Analytics 4 property.
stream_id: The ID of the data stream.
transport: The transport to use for the request. Defaults to None.
Returns:
A DataStream object.
Raises:
GoogleAnalyticsAdminError: If an error occurs while retrieving the data stream.
"""
client = AnalyticsAdminServiceClient(transport=transport)
return client.get_data_stream(
name=f"properties/{property_id}/dataStreams/{stream_id}"
)
def get_measurement_protocol_secret_value(configuration: map, secret_display_name: str, transport: str = None):
"""
Retrieves the secret value for a given measurement protocol secret display name.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
secret_display_name: The display name of the measurement protocol secret.
transport: The transport to use for the request. Defaults to None.
Returns:
The secret value for the measurement protocol secret, or None if the secret is not found.
Raises:
GoogleAnalyticsAdminError: If an error occurs while retrieving the measurement protocol secret.
"""
client = AnalyticsAdminServiceClient(transport=transport)
results = client.list_measurement_protocol_secrets(
parent=f"properties/{configuration['property_id']}/dataStreams/{configuration['stream_id']}"
)
for measurement_protocol_secret in results:
if measurement_protocol_secret.display_name == secret_display_name:
return measurement_protocol_secret.secret_value
return None
def get_measurement_protocol_secret(configuration: map, secret_display_name: str):
"""
Retrieves the secret value for a given measurement protocol secret display name.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
secret_display_name: The display name of the measurement protocol secret.
Returns:
The secret value for the measurement protocol secret, or None if the secret is not found.
Raises:
GoogleAnalyticsAdminError: If an error occurs while retrieving the measurement protocol secret.
"""
measurement_protocol_secret = get_measurement_protocol_secret_value(
configuration, secret_display_name)
if measurement_protocol_secret:
return measurement_protocol_secret
else:
return create_measurement_protocol_secret(configuration, secret_display_name)
def get_measurement_id(configuration: map):
"""
Retrieves the measurement ID for a given Google Analytics 4 property and data stream.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
Returns:
The measurement ID for the given property and data stream.
Raises:
GoogleAnalyticsAdminError: If an error occurs while retrieving the measurement ID.
"""
return get_data_stream(configuration['property_id'], configuration['stream_id']).web_stream_data.measurement_id
def get_property(configuration: map, transport: str = None):
client = AnalyticsAdminServiceClient(transport=transport)
return client.get_property(
name=f"properties/{configuration['property_id']}"
)
def create_measurement_protocol_secret(configuration: map, secret_display_name: str, transport: str = None):
"""
Creates a new measurement protocol secret for a given Google Analytics 4 property and data stream.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
secret_display_name: The display name of the measurement protocol secret.
transport: The transport to use for the request. Defaults to None.
Returns:
The secret value for the measurement protocol secret.
Raises:
GoogleAnalyticsAdminError: If an error occurs while creating the measurement protocol secret.
"""
from google.analytics.admin_v1alpha import MeasurementProtocolSecret
client = AnalyticsAdminServiceClient(transport=transport)
measurement_protocol_secret = client.create_measurement_protocol_secret(
parent=f"properties/{configuration['property_id']}/dataStreams/{configuration['stream_id']}",
measurement_protocol_secret=MeasurementProtocolSecret(
display_name=secret_display_name
),
)
return measurement_protocol_secret.secret_value
def load_event_names():
"""
Loads the event names from the activation type configuration template file.
Returns:
A list of event names.
"""
fo = open('templates/activation_type_configuration_template.tpl')
activation_types_obj = json.load(fo)
event_names = []
for k in activation_types_obj:
event_names.append(activation_types_obj[k]['activation_event_name'])
return event_names
def create_custom_events(configuration: map):
"""
Creates custom events in Google Analytics 4 based on the event names defined in the activation type configuration template file.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
Raises:
GoogleAnalyticsAdminError: If an error occurs while creating the custom events.
"""
event_names = load_event_names()
existing_event_names = load_existing_ga4_custom_events(configuration)
for event_name in event_names:
if not event_name in existing_event_names:
print(f"Create custom event with name: {event_name}")
create_custom_event(configuration, event_name)
def load_existing_ga4_custom_events(configuration: map):
"""
Loads the existing custom events from Google Analytics 4 based on the provided configuration.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
Returns:
A list of existing custom event names.
Raises:
GoogleAnalyticsAdminError: If an error occurs while retrieving the custom events.
"""
response = load_existing_ga4_custom_event_objs(configuration)
existing_event_rules = []
for page in response.pages:
for event_rule_obj in page.event_create_rules:
existing_event_rules.append(event_rule_obj.destination_event)
return existing_event_rules
def load_existing_ga4_custom_event_objs(configuration: map):
"""
Loads the existing custom event objects from Google Analytics 4 based on the provided configuration.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
Returns:
A ListEventCreateRulesResponse object containing the existing custom event objects.
Raises:
GoogleAnalyticsAdminError: If an error occurs while retrieving the custom events.
"""
client = admin_v1alpha.AnalyticsAdminServiceClient()
request = admin_v1alpha.ListEventCreateRulesRequest(
parent=f"properties/{configuration['property_id']}/dataStreams/{configuration['stream_id']}",
)
return client.list_event_create_rules(request=request)
def create_custom_event(configuration: map, event_name: str):
"""
Creates a custom event in Google Analytics 4 based on the provided event name.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
event_name: The name of the custom event to be created.
Raises:
GoogleAnalyticsAdminError: If an error occurs while creating the custom event.
"""
client = admin_v1alpha.AnalyticsAdminServiceClient()
event_create_rule = admin_v1alpha.EventCreateRule()
condition = admin_v1alpha.MatchingCondition()
condition.field = "event_name"
condition.comparison_type = "EQUALS"
condition.value = event_name
event_create_rule.destination_event = event_name
event_create_rule.event_conditions.append(condition)
request = admin_v1alpha.CreateEventCreateRuleRequest(
parent=f"properties/{configuration['property_id']}/dataStreams/{configuration['stream_id']}",
event_create_rule=event_create_rule,
)
response = client.create_event_create_rule(request=request)
def create_custom_dimensions(configuration: map):
"""
Creates custom dimensions in Google Analytics 4 based on the provided configuration.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
Raises:
GoogleAnalyticsAdminError: If an error occurs while creating the custom dimensions.
"""
existing_dimensions = load_existing_ga4_custom_dimensions(configuration)
create_custom_dimensions_for('Audience Segmentation', ['a_s_prediction'], existing_dimensions, configuration)
create_custom_dimensions_for('Purchase Propensity', ['p_p_prediction', 'p_p_decile'], existing_dimensions, configuration)
create_custom_dimensions_for('CLTV', ['cltv_decile'], existing_dimensions, configuration)
create_custom_dimensions_for('Auto Audience Segmentation', ['a_a_s_prediction'], existing_dimensions, configuration)
create_custom_dimensions_for('Churn Propensity', ['c_p_prediction', 'c_p_decile'], existing_dimensions, configuration)
create_custom_dimensions_for('Lead Score Propensity', ['l_s_p_prediction', 'l_s_p_decile'], existing_dimensions, configuration)
def create_custom_dimensions_for(use_case: str, fields: List[str], existing_dimensions: List[str], configuration: map):
"""
Creates custom dimensions in Google Analytics 4 based on the provided configuration for a specific use case.
Args:
use_case: The use case for which the custom dimensions are being created.
fields: A list of field names to be used as custom dimensions.
existing_dimensions: A list of existing custom dimension names.
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
Raises:
GoogleAnalyticsAdminError: If an error occurs while creating the custom dimensions.
"""
for field in fields:
display_name = f'MAJ {use_case} {field}'
if not display_name in existing_dimensions:
print(f'Create custom dimension: {display_name}')
create_custom_dimension(configuration, field, display_name)
def create_custom_dimension(configuration: map, field_name: str, display_name: str):
"""
Creates a custom dimension in Google Analytics 4 based on the provided configuration.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID.
field_name: The name of the field to be used as the custom dimension.
display_name: The display name of the custom dimension.
Raises:
GoogleAnalyticsAdminError: If an error occurs while creating the custom dimension.
"""
client = admin_v1alpha.AnalyticsAdminServiceClient()
custom_dimension = admin_v1alpha.CustomDimension()
custom_dimension.parameter_name = field_name
custom_dimension.display_name = display_name
custom_dimension.scope = "USER"
request = admin_v1alpha.CreateCustomDimensionRequest(
parent=f"properties/{configuration['property_id']}",
custom_dimension=custom_dimension,
)
client.create_custom_dimension(request=request)
def load_existing_ga4_custom_dimension_objs(configuration: map):
"""
Loads the existing custom dimension objects from Google Analytics 4 based on the provided configuration.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID.
Returns:
A ListCustomDimensionsResponse object containing the existing custom dimension objects.
Raises:
GoogleAnalyticsAdminError: If an error occurs while retrieving the custom dimensions.
"""
client = admin_v1alpha.AnalyticsAdminServiceClient()
request = admin_v1alpha.ListCustomDimensionsRequest(
parent=f"properties/{configuration['property_id']}",
)
return client.list_custom_dimensions(request=request)
def load_existing_ga4_custom_dimensions(configuration: map):
"""
Loads the existing custom dimension objects from Google Analytics 4 based on the provided configuration.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID.
Returns:
A list of existing custom dimension names.
Raises:
GoogleAnalyticsAdminError: If an error occurs while retrieving the custom dimensions.
"""
page_result = load_existing_ga4_custom_dimension_objs(configuration)
existing_custom_dimensions = []
for page in page_result.pages:
for custom_dimension in page.custom_dimensions:
existing_custom_dimensions.append(custom_dimension.display_name)
return existing_custom_dimensions
def update_custom_event_with_new_prefix(event_create_rule, old_prefix, new_prefix):
"""
Updates an existing custom event in Google Analytics 4 with a new prefix.
Args:
event_create_rule: The custom event rule to be updated.
old_prefix: The old prefix to be replaced.
new_prefix: The new prefix to be used.
Raises:
GoogleAnalyticsAdminError: If an error occurs while updating the custom event.
"""
client = admin_v1alpha.AnalyticsAdminServiceClient()
event_create_rule.destination_event = event_create_rule.destination_event.replace(old_prefix, new_prefix)
event_create_rule.event_conditions[0].value = event_create_rule.event_conditions[0].value.replace(old_prefix, new_prefix)
request = admin_v1alpha.UpdateEventCreateRuleRequest(
event_create_rule=event_create_rule,
update_mask="destinationEvent,eventConditions"
)
client.update_event_create_rule(request=request)
def rename_existing_ga4_custom_events(configuration: map, old_prefix, new_prefix):
"""
Renames existing custom events in Google Analytics 4 by replacing the old prefix with the new prefix.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID and data stream ID.
old_prefix: The old prefix to be replaced.
new_prefix: The new prefix to be used.
Raises:
GoogleAnalyticsAdminError: If an error occurs while renaming the custom events.
"""
existing_event_rules = load_existing_ga4_custom_event_objs(configuration)
for page in existing_event_rules.pages:
for create_event_rule in page.event_create_rules:
if create_event_rule.destination_event.startswith(old_prefix):
update_custom_event_with_new_prefix(create_event_rule, old_prefix, new_prefix)
def update_custom_dimension_with_new_prefix(custom_dimension, old_prefix, new_prefix):
"""
Updates an existing custom dimension in Google Analytics 4 with a new prefix.
Args:
custom_dimension: The custom dimension to be updated.
old_prefix: The old prefix to be replaced.
new_prefix: The new prefix to be used.
Raises:
GoogleAnalyticsAdminError: If an error occurs while updating the custom dimension.
"""
client = admin_v1alpha.AnalyticsAdminServiceClient()
custom_dimension.display_name = custom_dimension.display_name.replace(old_prefix, new_prefix)
request = admin_v1alpha.UpdateCustomDimensionRequest(
custom_dimension=custom_dimension,
update_mask="displayName"
)
client.update_custom_dimension(request=request)
def rename_existing_ga4_custom_dimensions(configuration: map, old_prefix, new_prefix):
"""
Renames existing custom dimensions in Google Analytics 4 by replacing the old prefix with the new prefix.
Args:
configuration: A dictionary containing the Google Analytics 4 property ID.
old_prefix: The old prefix to be replaced.
new_prefix: The new prefix to be used.
Raises:
GoogleAnalyticsAdminError: If an error occurs while renaming the custom dimensions.
"""
page_result = load_existing_ga4_custom_dimension_objs(configuration)
for page in page_result.pages:
for custom_dimension in page.custom_dimensions:
if custom_dimension.display_name.startswith(old_prefix):
update_custom_dimension_with_new_prefix(custom_dimension, old_prefix, new_prefix)
def entry():
"""
This function is the entry point for the setup script. It takes three arguments:
Args:
ga4_resource: The Google Analytics 4 resource to be configured.
ga4_property_id: The Google Analytics 4 property ID.
ga4_stream_id: The Google Analytics 4 data stream ID.
Raises:
GoogleAnalyticsAdminError: If an error occurs while configuring the Google Analytics 4 resource.
"""
'''
Following Google API scopes are required to call Google Analytics Admin API:
https://www.googleapis.com/auth/analytics
https://www.googleapis.com/auth/analytics.edit
https://www.googleapis.com/auth/analytics.manage.users
https://www.googleapis.com/auth/analytics.manage.users.readonly
https://www.googleapis.com/auth/analytics.provision
https://www.googleapis.com/auth/analytics.readonly
https://www.googleapis.com/auth/analytics.user.deletion
'''
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--ga4_resource', type=str, required=True)
parser.add_argument('--ga4_property_id', type=str, required=True)
parser.add_argument('--ga4_stream_id', type=str, required=True)
args = parser.parse_args()
configuration = {
'property_id': args.ga4_property_id,
'stream_id': args.ga4_stream_id
}
# python setup.py --ga4_resource=measurement_properties
if args.ga4_resource == "measurement_properties":
secret_display_name = 'MAJ Activation'
properties = {
'measurement_id': get_measurement_id(configuration),
'measurement_secret': get_measurement_protocol_secret(configuration, secret_display_name)
}
print(json.dumps(properties))
if args.ga4_resource == "check_property_type":
property = get_property(configuration)
is_property_supported = set((property.property_type.PROPERTY_TYPE_ORDINARY, property.property_type.PROPERTY_TYPE_SUBPROPERTY, property.property_type.PROPERTY_TYPE_ROLLUP))
result = {}
if property.property_type in is_property_supported:
result = {'supported': "True"}
else:
result = {'supported': "False"}
print(json.dumps(result))
# python setup.py --ga4_resource=custom_events
if args.ga4_resource == "custom_events":
rename_existing_ga4_custom_events(configuration, "mas_", "maj_")
create_custom_events(configuration)
# python setup.py --ga4_resource=custom_dimensions
if args.ga4_resource == "custom_dimensions":
rename_existing_ga4_custom_dimensions(configuration, "MDE ", "MAJ ")
create_custom_dimensions(configuration)