Skip to content

Commit c997cab

Browse files
michalslowikowski00michalslowikowski00
andauthored
[AIRFLOW-6724] Add Google Analytics 360 Accounts Retrieve Operator (#7630)
Co-authored-by: michalslowikowski00 <michal.slowikowski@polidea.com>
1 parent 289bc80 commit c997cab

File tree

7 files changed

+350
-0
lines changed

7 files changed

+350
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
"""
18+
Example Airflow DAG that shows how to use Google Analytics 360.
19+
"""
20+
21+
from airflow import models
22+
from airflow.providers.google.marketing_platform.operators.analytics import (
23+
GoogleAnalyticsListAccountsOperator,
24+
)
25+
from airflow.utils import dates
26+
27+
default_args = {"start_date": dates.days_ago(1)}
28+
29+
with models.DAG(
30+
"example_google_analytics",
31+
default_args=default_args,
32+
schedule_interval=None, # Override to match your needs
33+
) as dag:
34+
# [START howto_marketing_platform_list_accounts_operator]
35+
list_account = GoogleAnalyticsListAccountsOperator(task_id="list_account")
36+
# [END howto_marketing_platform_list_accounts_operator]
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
from typing import Any, Dict, List
19+
20+
from googleapiclient.discovery import Resource, build
21+
22+
from airflow.providers.google.cloud.hooks.base import CloudBaseHook
23+
24+
25+
class GoogleAnalyticsHook(CloudBaseHook):
26+
"""
27+
Hook for Google Analytics 360.
28+
"""
29+
30+
def __init__(
31+
self,
32+
api_version: str = "v3",
33+
gcp_connection_id: str = "google cloud default",
34+
*args,
35+
**kwargs
36+
):
37+
super().__init__(*args, **kwargs)
38+
self.api_version = api_version
39+
self.gcp_connection_is = gcp_connection_id
40+
self._conn = None
41+
42+
def get_conn(self) -> Resource:
43+
"""
44+
Retrieves connection to Google Analytics 360.
45+
"""
46+
if not self._conn:
47+
http_authorized = self._authorize()
48+
self._conn = build(
49+
"analytics",
50+
self.api_version,
51+
http=http_authorized,
52+
cache_discovery=False,
53+
)
54+
return self._conn
55+
56+
def list_accounts(self) -> List[Dict[str, Any]]:
57+
"""
58+
Lists accounts list from Google Analytics 360.
59+
"""
60+
61+
self.log.info("Retrieving accounts list...")
62+
result = [] # type: List[Dict]
63+
conn = self.get_conn()
64+
accounts = conn.management().accounts() # pylint: disable=no-member
65+
while True:
66+
# start index has value 1
67+
request = accounts.list(start_index=len(result) + 1)
68+
response = request.execute(num_retries=self.num_retries)
69+
result.extend(response.get('items', []))
70+
# result is the number of fetched accounts from Analytics
71+
# when all accounts will be add to the result
72+
# the loop will be break
73+
if response["totalResults"] <= len(result):
74+
break
75+
return result
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
"""
19+
This module contains Google Analytics 360 operators.
20+
"""
21+
22+
from airflow.models import BaseOperator
23+
from airflow.providers.google.marketing_platform.hooks.analytics import GoogleAnalyticsHook
24+
from airflow.utils.decorators import apply_defaults
25+
26+
27+
class GoogleAnalyticsListAccountsOperator(BaseOperator):
28+
"""
29+
Lists all accounts to which the user has access.
30+
31+
.. seealso::
32+
Check official API docs:
33+
https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference/management/accounts/list
34+
and for python client
35+
http://googleapis.github.io/google-api-python-client/docs/dyn/analytics_v3.management.accounts.html#list
36+
37+
.. seealso::
38+
For more information on how to use this operator, take a look at the guide:
39+
:ref:`howto/operator:GoogleAnalyticsListAccountsOperator`
40+
41+
:param api_version: The version of the api that will be requested for example 'v3'.
42+
:type api_version: str
43+
:param gcp_conn_id: The connection ID to use when fetching connection info.
44+
:type gcp_conn_id: str
45+
"""
46+
47+
template_fields = ("api_version", "gcp_connection_id",)
48+
49+
@apply_defaults
50+
def __init__(self,
51+
api_version: str = "v3",
52+
gcp_connection_id: str = "google_cloud_default",
53+
*args,
54+
**kwargs):
55+
super().__init__(*args, **kwargs)
56+
57+
self.api_version = api_version
58+
self.gcp_connection_id = gcp_connection_id
59+
60+
def execute(self, context):
61+
hook = GoogleAnalyticsHook(api_version=self.api_version,
62+
gcp_connection_id=self.gcp_connection_id)
63+
result = hook.list_accounts()
64+
return result
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
.. Licensed to the Apache Software Foundation (ASF) under one
2+
or more contributor license agreements. See the NOTICE file
3+
distributed with this work for additional information
4+
regarding copyright ownership. The ASF licenses this file
5+
to you under the Apache License, Version 2.0 (the
6+
"License"); you may not use this file except in compliance
7+
with the License. You may obtain a copy of the License at
8+
9+
.. http://www.apache.org/licenses/LICENSE-2.0
10+
11+
.. Unless required by applicable law or agreed to in writing,
12+
software distributed under the License is distributed on an
13+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
KIND, either express or implied. See the License for the
15+
specific language governing permissions and limitations
16+
under the License.
17+
18+
Google Analytics 360 Operators
19+
==============================
20+
21+
Google Analytics 360 operators allow you to lists all accounts to which the user has access.
22+
For more information about the Google Analytics 360 API check
23+
`official documentation <https://developers.google.com/analytics/devguides/config/mgmt/v3>`__.
24+
25+
26+
.. contents::
27+
:depth: 1
28+
:local:
29+
30+
Prerequisite Tasks
31+
^^^^^^^^^^^^^^^^^^
32+
33+
.. include:: _partials/prerequisite_tasks.rst
34+
35+
.. _howto/operator:GoogleAnalyticsListAccountsOperator:
36+
37+
List the Accounts
38+
^^^^^^^^^^^^^^^^^
39+
40+
To list accounts from Analytics you can use the
41+
:class:`~airflow.providers.google.marketing_platform.operators.analytics.GoogleAnalyticsListAccountsOperator`.
42+
43+
.. exampleinclude:: ../../../../airflow/providers/google/marketing_platform/example_dags/example_analytics.py
44+
:language: python
45+
:dedent: 4
46+
:start-after: [START howto_marketing_platform_list_accounts_operator]
47+
:end-before: [END howto_marketing_platform_list_accounts_operator]
48+
49+
You can use :ref:`Jinja templating <jinja-templating>` with
50+
:template-fields:`airflow.providers.google.marketing_platform.operators.analytics.GoogleAnalyticsListAccountsOperator`

β€Ždocs/operators-and-hooks-ref.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,12 @@ These integrations allow you to perform various operations within the Google Clo
554554
- Operators
555555
- Sensors
556556

557+
* - `Analytics360 <https://analytics.google.com/>`__
558+
- :doc:`How to use <howto/operator/gcp/analytics>`
559+
- :mod:`airflow.providers.google.marketing_platform.hooks.analytics`
560+
- :mod:`airflow.providers.google.marketing_platform.operators.analytics`
561+
-
562+
557563
* - `AutoML <https://cloud.google.com/automl/>`__
558564
- :doc:`How to use <howto/operator/gcp/automl>`
559565
- :mod:`airflow.providers.google.cloud.hooks.automl`
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
import unittest
19+
from unittest import mock
20+
21+
from airflow.providers.google.marketing_platform.hooks.analytics import GoogleAnalyticsHook
22+
from tests.providers.google.cloud.utils.base_gcp_mock import mock_base_gcp_hook_default_project_id
23+
24+
API_VERSION = "v3"
25+
GCP_CONN_ID = "google_cloud_default"
26+
27+
28+
class TestGoogleAnalyticsHook(unittest.TestCase):
29+
def setUp(self):
30+
with mock.patch(
31+
"airflow.providers.google.cloud.hooks.base.CloudBaseHook.__init__",
32+
new=mock_base_gcp_hook_default_project_id,
33+
):
34+
self.hook = GoogleAnalyticsHook(API_VERSION, GCP_CONN_ID)
35+
36+
@mock.patch(
37+
"airflow.providers.google.marketing_platform.hooks."
38+
"analytics.GoogleAnalyticsHook._authorize"
39+
)
40+
@mock.patch("airflow.providers.google.marketing_platform.hooks.analytics.build")
41+
def test_gen_conn(self, mock_build, mock_authorize):
42+
result = self.hook.get_conn()
43+
mock_build.assert_called_once_with(
44+
"analytics",
45+
API_VERSION,
46+
http=mock_authorize.return_value,
47+
cache_discovery=False,
48+
)
49+
self.assertEqual(mock_build.return_value, result)
50+
51+
@mock.patch(
52+
"airflow.providers.google.marketing_platform.hooks."
53+
"analytics.GoogleAnalyticsHook.get_conn"
54+
)
55+
def test_list_accounts(self, get_conn_mock):
56+
mock_accounts = get_conn_mock.return_value.management.return_value.accounts
57+
mock_list = mock_accounts.return_value.list
58+
mock_execute = mock_list.return_value.execute
59+
mock_execute.return_value = {"items": ["a", "b"], "totalResults": 2}
60+
list_accounts = self.hook.list_accounts()
61+
self.assertEqual(list_accounts, ["a", "b"])
62+
63+
@mock.patch(
64+
"airflow.providers.google.marketing_platform.hooks."
65+
"analytics.GoogleAnalyticsHook.get_conn"
66+
)
67+
def test_list_accounts_for_multiple_pages(self, get_conn_mock):
68+
mock_accounts = get_conn_mock.return_value.management.return_value.accounts
69+
mock_list = mock_accounts.return_value.list
70+
mock_execute = mock_list.return_value.execute
71+
mock_execute.side_effect = [
72+
{"items": ["a"], "totalResults": 2},
73+
{"items": ["b"], "totalResults": 2},
74+
]
75+
list_accounts = self.hook.list_accounts()
76+
self.assertEqual(list_accounts, ["a", "b"])
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
import unittest
19+
from unittest import mock
20+
21+
from airflow.providers.google.marketing_platform.operators.analytics import (
22+
GoogleAnalyticsListAccountsOperator,
23+
)
24+
25+
API_VERSION = "api_version"
26+
GCP_CONN_ID = "google_cloud_default"
27+
28+
29+
class TestGoogleAnalyticsListAccountsOperator(unittest.TestCase):
30+
@mock.patch(
31+
"airflow.providers.google.marketing_platform.operators."
32+
"analytics.GoogleAnalyticsHook"
33+
)
34+
def test_execute(self, hook_mock):
35+
36+
op = GoogleAnalyticsListAccountsOperator(
37+
api_version=API_VERSION,
38+
gcp_connection_id=GCP_CONN_ID,
39+
task_id="test_task",
40+
)
41+
op.execute(context=None)
42+
hook_mock.assert_called_once()
43+
hook_mock.return_value.list_accounts.assert_called_once()

0 commit comments

Comments
 (0)