|
| 1 | +# Copyright 2025 Google LLC All Rights Reserved. |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +"""Tools for running funnel reports using the Data API (Alpha).""" |
| 16 | + |
| 17 | +from typing import Any, Dict, List |
| 18 | + |
| 19 | +from analytics_mcp.tools.reporting.metadata import ( |
| 20 | + get_date_ranges_hints, |
| 21 | + get_funnel_steps_hints, |
| 22 | +) |
| 23 | +from analytics_mcp.tools.utils import ( |
| 24 | + construct_property_rn, |
| 25 | + create_data_api_alpha_client, |
| 26 | + proto_to_dict, |
| 27 | +) |
| 28 | +from google.analytics import data_v1alpha |
| 29 | + |
| 30 | + |
| 31 | +def _run_funnel_report_description() -> str: |
| 32 | + """Returns the description for the `run_funnel_report` tool.""" |
| 33 | + return f""" |
| 34 | + {run_funnel_report.__doc__} |
| 35 | +
|
| 36 | + ## Hints for arguments |
| 37 | +
|
| 38 | + Here are some hints that outline the expected format and requirements |
| 39 | + for arguments. |
| 40 | +
|
| 41 | + ### Hints for `funnel_breakdown` |
| 42 | +
|
| 43 | + The `funnel_breakdown` parameter allows you to segment funnel results by a dimension: |
| 44 | + ```json |
| 45 | + {{ |
| 46 | + "breakdown_dimension": "deviceCategory" |
| 47 | + }} |
| 48 | + ``` |
| 49 | + Common breakdown dimensions include: |
| 50 | + - `deviceCategory` - Desktop, Mobile, Tablet |
| 51 | + - `country` - User's country |
| 52 | + - `operatingSystem` - User's operating system |
| 53 | + - `browser` - User's browser |
| 54 | +
|
| 55 | + ### Hints for `funnel_next_action` |
| 56 | +
|
| 57 | + The `funnel_next_action` parameter analyzes what users do after completing or dropping off from the funnel: |
| 58 | + ```json |
| 59 | + {{ |
| 60 | + "next_action_dimension": "eventName", |
| 61 | + "limit": 5 |
| 62 | + }} |
| 63 | + ``` |
| 64 | + Common next action dimensions include: |
| 65 | + - `eventName` - Next events users trigger |
| 66 | + - `pagePath` - Next pages users visit |
| 67 | +
|
| 68 | + ### Hints for `segments` |
| 69 | +
|
| 70 | + The `segments` parameter allows you to segment funnel results by user criteria. |
| 71 | + Each segment is a dictionary passed directly to `data_v1alpha.Segment()`. |
| 72 | + See https://developers.google.com/analytics/devguides/reporting/data/v1/funnels#segments |
| 73 | + for details and examples. |
| 74 | +
|
| 75 | + ### Hints for `date_ranges`: |
| 76 | + {get_date_ranges_hints()} |
| 77 | +
|
| 78 | + ### Hints for `funnel_steps` |
| 79 | + {get_funnel_steps_hints()} |
| 80 | +
|
| 81 | + """ |
| 82 | + |
| 83 | + |
| 84 | +async def run_funnel_report( |
| 85 | + property_id: int | str, |
| 86 | + funnel_steps: List[Dict[str, Any]], |
| 87 | + date_ranges: List[Dict[str, str]] = None, |
| 88 | + funnel_breakdown: Dict[str, str] = None, |
| 89 | + funnel_next_action: Dict[str, str] = None, |
| 90 | + segments: List[Dict[str, Any]] = None, |
| 91 | + return_property_quota: bool = False, |
| 92 | +) -> Dict[str, Any]: |
| 93 | + """Run a Google Analytics Data API funnel report. |
| 94 | +
|
| 95 | + See the funnel report guide at |
| 96 | + https://developers.google.com/analytics/devguides/reporting/data/v1/funnels |
| 97 | + for details and examples. |
| 98 | +
|
| 99 | + Args: |
| 100 | + property_id: The Google Analytics property ID. Accepted formats are: |
| 101 | + - A number |
| 102 | + - A string consisting of 'properties/' followed by a number |
| 103 | + funnel_steps: A list of funnel steps. Each step should be a dictionary |
| 104 | + containing: |
| 105 | + - 'name': (str) Display name for the step |
| 106 | + - 'filter_expression': (Dict) Complete filter expression for the step |
| 107 | + OR for simple event-based steps: |
| 108 | + - 'name': (str) Display name for the step |
| 109 | + - 'event': (str) Event name to filter on |
| 110 | + date_ranges: A list of date ranges |
| 111 | + (https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/DateRange) |
| 112 | + to include in the report. |
| 113 | + funnel_breakdown: Optional breakdown dimension to segment the funnel. |
| 114 | + This creates separate funnel results for each value of the dimension. |
| 115 | + Example: {"breakdown_dimension": "deviceCategory"} |
| 116 | + funnel_next_action: Optional next action analysis configuration. |
| 117 | + This analyzes what users do after completing or dropping off from |
| 118 | + the funnel. |
| 119 | + Example: {"next_action_dimension": "eventName", "limit": 5} |
| 120 | + segments: Optional list of segments to apply to the funnel. |
| 121 | + return_property_quota: Whether to return current property quota |
| 122 | + information. |
| 123 | +
|
| 124 | + Returns: |
| 125 | + Dict containing the funnel report response with funnel results |
| 126 | + including: |
| 127 | + - funnel_table: Table showing progression through funnel steps |
| 128 | + - funnel_visualization: Data for visualizing the funnel |
| 129 | + - property_quota: (if requested) Current quota usage information |
| 130 | +
|
| 131 | + Raises: |
| 132 | + ValueError: If funnel_steps is empty or contains invalid configurations |
| 133 | + Exception: If the API request fails |
| 134 | + """ |
| 135 | + if not funnel_steps: |
| 136 | + raise ValueError("funnel_steps must contain at least one step") |
| 137 | + |
| 138 | + steps = [] |
| 139 | + for i, step in enumerate(funnel_steps): |
| 140 | + if not isinstance(step, dict): |
| 141 | + raise ValueError(f"Step {i+1} must be a dictionary") |
| 142 | + |
| 143 | + step_name = step.get("name", f"Step {i+1}") |
| 144 | + |
| 145 | + if "filter_expression" in step: |
| 146 | + filter_expr = data_v1alpha.FunnelFilterExpression( |
| 147 | + step["filter_expression"] |
| 148 | + ) |
| 149 | + elif "event" in step: |
| 150 | + filter_expr = data_v1alpha.FunnelFilterExpression( |
| 151 | + funnel_event_filter=data_v1alpha.FunnelEventFilter( |
| 152 | + event_name=step["event"] |
| 153 | + ) |
| 154 | + ) |
| 155 | + else: |
| 156 | + raise ValueError( |
| 157 | + f"Step {i+1} must contain either 'filter_expression' or 'event' key" |
| 158 | + ) |
| 159 | + |
| 160 | + funnel_step = data_v1alpha.FunnelStep( |
| 161 | + name=step_name, filter_expression=filter_expr |
| 162 | + ) |
| 163 | + steps.append(funnel_step) |
| 164 | + |
| 165 | + request = data_v1alpha.RunFunnelReportRequest( |
| 166 | + property=construct_property_rn(property_id), |
| 167 | + funnel=data_v1alpha.Funnel(steps=steps), |
| 168 | + date_ranges=[data_v1alpha.DateRange(dr) for dr in (date_ranges or [])], |
| 169 | + return_property_quota=return_property_quota, |
| 170 | + ) |
| 171 | + |
| 172 | + if funnel_breakdown and "breakdown_dimension" in funnel_breakdown: |
| 173 | + request.funnel_breakdown = data_v1alpha.FunnelBreakdown( |
| 174 | + breakdown_dimension=data_v1alpha.Dimension( |
| 175 | + name=funnel_breakdown["breakdown_dimension"] |
| 176 | + ) |
| 177 | + ) |
| 178 | + |
| 179 | + if funnel_next_action and "next_action_dimension" in funnel_next_action: |
| 180 | + next_action_config = data_v1alpha.FunnelNextAction( |
| 181 | + next_action_dimension=data_v1alpha.Dimension( |
| 182 | + name=funnel_next_action["next_action_dimension"] |
| 183 | + ) |
| 184 | + ) |
| 185 | + if "limit" in funnel_next_action: |
| 186 | + next_action_config.limit = funnel_next_action["limit"] |
| 187 | + request.funnel_next_action = next_action_config |
| 188 | + |
| 189 | + if segments: |
| 190 | + request.segments = [ |
| 191 | + data_v1alpha.Segment(segment) for segment in segments |
| 192 | + ] |
| 193 | + |
| 194 | + response = await create_data_api_alpha_client().run_funnel_report(request) |
| 195 | + return proto_to_dict(response) |
0 commit comments