From ce84d11859b9d99386bea71fc4f238aeef0dccb8 Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:19:17 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Source=20Google=20Analytics=20Da?= =?UTF-8?q?ta=20API:=20Add=20`CohortSpec`=20to=20custom=20report=20in=20sp?= =?UTF-8?q?ecification=20(#33802)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceptance-test-config.yml | 5 +- .../source-google-analytics-data-api/main.py | 3 +- .../metadata.yaml | 4 +- .../config_migrations.py | 83 +++++++++++- .../source.py | 4 +- .../spec.json | 126 ++++++++++++++++++ .../test_config.json | 59 ++++++++ .../test_config_migration_cohortspec.py | 45 +++++++ .../test_new_config.json | 63 +++++++++ .../sources/google-analytics-data-api.md | 85 +++++------- 10 files changed, 415 insertions(+), 62 deletions(-) create mode 100644 airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_config.json create mode 100644 airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_config_migration_cohortspec.py create mode 100644 airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_new_config.json diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/acceptance-test-config.yml b/airbyte-integrations/connectors/source-google-analytics-data-api/acceptance-test-config.yml index 56c4350f3bd80..4e07a4a4ce280 100644 --- a/airbyte-integrations/connectors/source-google-analytics-data-api/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/acceptance-test-config.yml @@ -7,9 +7,8 @@ acceptance_tests: tests: - spec_path: "source_google_analytics_data_api/spec.json" backward_compatibility_tests_config: - # changed the structure of `custom_reports` - # from `json string` to `list[reports]` - disable_for_version: 1.5.1 + # changed the structure of `custom_reports` -> `cohortSpec` + disable_for_version: 2.1.0 connection: tests: - config_path: "secrets/config.json" diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/main.py b/airbyte-integrations/connectors/source-google-analytics-data-api/main.py index ae4135b1396bb..02cbd41ab7d54 100644 --- a/airbyte-integrations/connectors/source-google-analytics-data-api/main.py +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/main.py @@ -7,10 +7,11 @@ from airbyte_cdk.entrypoint import launch from source_google_analytics_data_api import SourceGoogleAnalyticsDataApi -from source_google_analytics_data_api.config_migrations import MigrateCustomReports, MigratePropertyID +from source_google_analytics_data_api.config_migrations import MigrateCustomReports, MigrateCustomReportsCohortSpec, MigratePropertyID if __name__ == "__main__": source = SourceGoogleAnalyticsDataApi() MigratePropertyID.migrate(sys.argv[1:], source) MigrateCustomReports.migrate(sys.argv[1:], source) + MigrateCustomReportsCohortSpec.migrate(sys.argv[1:], source) launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/metadata.yaml b/airbyte-integrations/connectors/source-google-analytics-data-api/metadata.yaml index 23678df88e962..c9e768b077b49 100644 --- a/airbyte-integrations/connectors/source-google-analytics-data-api/metadata.yaml +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/metadata.yaml @@ -8,11 +8,11 @@ data: - www.googleapis.com - analyticsdata.googleapis.com connectorBuildOptions: - baseImage: docker.io/airbyte/python-connector-base:1.1.0@sha256:bd98f6505c6764b1b5f99d3aedc23dfc9e9af631a62533f60eb32b1d3dbab20c + baseImage: docker.io/airbyte/python-connector-base:1.2.0@sha256:c22a9d97464b69d6ef01898edf3f8612dc11614f05a84984451dde195f337db9 connectorSubtype: api connectorType: source definitionId: 3cc2eafd-84aa-4dca-93af-322d9dfeec1a - dockerImageTag: 2.0.3 + dockerImageTag: 2.1.0 dockerRepository: airbyte/source-google-analytics-data-api documentationUrl: https://docs.airbyte.com/integrations/sources/google-analytics-data-api githubIssueLabel: source-google-analytics-data-api diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/config_migrations.py b/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/config_migrations.py index 23b9fcd5f4f40..621b5bbafcaf4 100644 --- a/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/config_migrations.py +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/config_migrations.py @@ -6,6 +6,7 @@ import logging from typing import Any, List, Mapping +import dpath.util from airbyte_cdk.config_observation import create_connector_config_control_message from airbyte_cdk.entrypoint import AirbyteEntrypoint from airbyte_cdk.sources.message import InMemoryMessageRepository, MessageRepository @@ -33,9 +34,9 @@ class MigratePropertyID: @classmethod def _should_migrate(cls, config: Mapping[str, Any]) -> bool: """ - This method determines whether config require migration. + This method determines whether config requires migration. Returns: - > True, if the transformation is neccessary + > True, if the transformation is necessary > False, otherwise. """ if cls.migrate_from_key in config: @@ -72,7 +73,7 @@ def _emit_control_message(cls, migrated_config: Mapping[str, Any]) -> None: def migrate(cls, args: List[str], source: SourceGoogleAnalyticsDataApi) -> None: """ This method checks the input args, should the config be migrated, - transform if neccessary and emit the CONTROL message. + transform if necessary and emit the CONTROL message. """ # get config path config_path = AirbyteEntrypoint(source).extract_config(args) @@ -104,7 +105,7 @@ class MigrateCustomReports: @classmethod def _should_migrate(cls, config: Mapping[str, Any]) -> bool: """ - This method determines whether or not the config should be migrated to have the new structure for the `custom_reports`, + This method determines whether the config should be migrated to have the new structure for the `custom_reports`, based on the source spec. Returns: > True, if the transformation is necessary @@ -126,7 +127,7 @@ def _should_migrate(cls, config: Mapping[str, Any]) -> bool: def _transform_to_array(cls, config: Mapping[str, Any], source: SourceGoogleAnalyticsDataApi = None) -> Mapping[str, Any]: # assign old values to new property that will be used within the new version config[cls.migrate_to_key] = config[cls.migrate_from_key] - # transfom `json_str` to `list` of objects + # transform `json_str` to `list` of objects return source._validate_custom_reports(config) @classmethod @@ -150,7 +151,77 @@ def _emit_control_message(cls, migrated_config: Mapping[str, Any]) -> None: def migrate(cls, args: List[str], source: SourceGoogleAnalyticsDataApi) -> None: """ This method checks the input args, should the config be migrated, - transform if neccessary and emit the CONTROL message. + transform if necessary and emit the CONTROL message. + """ + # get config path + config_path = AirbyteEntrypoint(source).extract_config(args) + # proceed only if `--config` arg is provided + if config_path: + # read the existing config + config = source.read_config(config_path) + # migration check + if cls._should_migrate(config): + cls._emit_control_message( + cls._modify_and_save(config_path, source, config), + ) + + +class MigrateCustomReportsCohortSpec: + """ + This class stands for migrating the config at runtime, + Specifically, starting from `2.1.0`; the `cohortSpec` property will be added tp `custom_reports_array` with flag `enabled`: + > List([{name: my_report, "cohortSpec": { "enabled": "true" } }, ...]) + """ + + message_repository: MessageRepository = InMemoryMessageRepository() + + @classmethod + def _should_migrate(cls, config: Mapping[str, Any]) -> bool: + """ + This method determines whether the config should be migrated to have the new structure for the `cohortSpec` inside `custom_reports`, + based on the source spec. + Returns: + > True, if the transformation is necessary + > False, otherwise. + """ + + return not dpath.util.search(config, "custom_reports_array/**/cohortSpec/enabled") + + @classmethod + def _transform_custom_reports_cohort_spec( + cls, + config: Mapping[str, Any], + ) -> Mapping[str, Any]: + """Assign `enabled` property that will be used within the new version""" + for report in config.get("custom_reports_array", []): + if report.get("cohortSpec"): + report["cohortSpec"]["enabled"] = "true" + else: + report.setdefault("cohortSpec", {})["enabled"] = "false" + return config + + @classmethod + def _modify_and_save(cls, config_path: str, source: SourceGoogleAnalyticsDataApi, config: Mapping[str, Any]) -> Mapping[str, Any]: + # modify the config + migrated_config = cls._transform_custom_reports_cohort_spec(config) + # save the config + source.write_config(migrated_config, config_path) + # return modified config + return migrated_config + + @classmethod + def _emit_control_message(cls, migrated_config: Mapping[str, Any]) -> None: + # add the Airbyte Control Message to message repo + cls.message_repository.emit_message(create_connector_config_control_message(migrated_config)) + # emit the Airbyte Control Message from message queue to stdout + for message in cls.message_repository.consume_queue(): + print(message.json(exclude_unset=True)) + + @classmethod + def migrate(cls, args: List[str], source: SourceGoogleAnalyticsDataApi) -> None: + """ + This method checks the input args, should the config be migrated, + transform if necessary and emit the CONTROL message. """ # get config path config_path = AirbyteEntrypoint(source).extract_config(args) diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/source.py b/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/source.py index 41024fcdef5e8..149bc5e7a786c 100644 --- a/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/source.py +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/source.py @@ -545,7 +545,7 @@ def instantiate_report_streams(self, report: dict, config: Mapping[str, Any], ** def instantiate_report_class( report: dict, add_name_suffix: bool, config: Mapping[str, Any], **extra_kwargs ) -> GoogleAnalyticsDataApiBaseStream: - cohort_spec = report.get("cohortSpec") + cohort_spec = report.get("cohortSpec", {}) pivots = report.get("pivots") stream_config = { **config, @@ -558,7 +558,7 @@ def instantiate_report_class( if pivots: stream_config["pivots"] = pivots report_class_tuple = (PivotReport,) - if cohort_spec: + if cohort_spec.pop("enabled", "") == "true": stream_config["cohort_spec"] = cohort_spec report_class_tuple = (CohortReportMixin, *report_class_tuple) name = report["name"] diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/spec.json b/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/spec.json index 1b3c6077bd4ad..8efc2e7b13a5c 100644 --- a/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/spec.json +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/spec.json @@ -2095,6 +2095,132 @@ "required": ["field_name", "filter"] } ] + }, + "cohortSpec": { + "title": "Cohort Reports", + "description": "Cohort reports creates a time series of user retention for the cohort.", + "type": "object", + "order": 5, + "oneOf": [ + { + "title": "Disabled", + "type": "object", + "properties": { + "enabled": { + "type": "string", + "const": "false" + } + } + }, + { + "title": "Enabled", + "type": "object", + "properties": { + "enabled": { + "type": "string", + "const": "true" + }, + "cohorts": { + "name": "Cohorts", + "order": 0, + "type": "array", + "always_show": true, + "items": { + "title": "Cohorts", + "type": "object", + "required": ["dimension", "dateRange"], + "properties": { + "name": { + "title": "Name", + "type": "string", + "always_show": true, + "pattern": "^(?!(cohort_|RESERVED_)).*$", + "description": "Assigns a name to this cohort. If not set, cohorts are named by their zero based index cohort_0, cohort_1, etc.", + "order": 0 + }, + "dimension": { + "title": "Dimension", + "description": "Dimension used by the cohort. Required and only supports `firstSessionDate`", + "type": "string", + "enum": ["firstSessionDate"], + "order": 1 + }, + "dateRange": { + "type": "object", + "required": ["startDate", "endDate"], + "properties": { + "startDate": { + "title": "Start Date", + "type": "string", + "format": "date", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", + "pattern_descriptor": "YYYY-MM-DD", + "examples": ["2021-01-01"], + "order": 2 + }, + "endDate": { + "title": "End Date", + "type": "string", + "format": "date", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", + "pattern_descriptor": "YYYY-MM-DD", + "examples": ["2021-01-01"], + "order": 3 + } + } + } + } + } + }, + "cohortsRange": { + "type": "object", + "order": 1, + "required": ["granularity", "endOffset"], + "properties": { + "granularity": { + "title": "Granularity", + "description": "The granularity used to interpret the startOffset and endOffset for the extended reporting date range for a cohort report.", + "type": "string", + "enum": [ + "GRANULARITY_UNSPECIFIED", + "DAILY", + "WEEKLY", + "MONTHLY" + ], + "order": 0 + }, + "startOffset": { + "title": "Start Offset", + "description": "Specifies the start date of the extended reporting date range for a cohort report.", + "type": "integer", + "minimum": 0, + "order": 1 + }, + "endOffset": { + "title": "End Offset", + "description": "Specifies the end date of the extended reporting date range for a cohort report.", + "type": "integer", + "minimum": 0, + "order": 2 + } + } + }, + "cohortReportSettings": { + "type": "object", + "title": "Cohort Report Settings", + "description": "Optional settings for a cohort report.", + "properties": { + "accumulate": { + "always_show": true, + "title": "Accumulate", + "description": "If true, accumulates the result from first touch day to the end day", + "type": "boolean" + } + } + } + } + } + ] } }, "required": ["name", "dimensions", "metrics"] diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_config.json b/airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_config.json new file mode 100644 index 0000000000000..245a01f07016e --- /dev/null +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_config.json @@ -0,0 +1,59 @@ +{ + "credentials": { + "auth_type": "Service", + "credentials_json": "" + }, + "date_ranges_start_date": "2023-09-01", + "window_in_days": 30, + "property_ids": "314186564", + "custom_reports_array": [ + { + "name": "cohort_report", + "dimensions": ["cohort", "cohortNthDay"], + "metrics": ["cohortActiveUsers"], + "cohortSpec": { + "cohorts": [ + { + "dimension": "firstSessionDate", + "dateRange": { + "startDate": "2023-04-24", + "endDate": "2023-04-24" + } + } + ], + "cohortsRange": { + "endOffset": 100, + "granularity": "DAILY" + }, + "cohortReportSettings": { + "accumulate": false + } + } + }, + { + "name": "pivot_report", + "dateRanges": [ + { + "startDate": "2020-09-01", + "endDate": "2020-09-15" + } + ], + "dimensions": ["browser", "country", "language"], + "metrics": ["sessions"], + "pivots": [ + { + "fieldNames": ["browser"], + "limit": 5 + }, + { + "fieldNames": ["country"], + "limit": 250 + }, + { + "fieldNames": ["language"], + "limit": 15 + } + ] + } + ] +} diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_config_migration_cohortspec.py b/airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_config_migration_cohortspec.py new file mode 100644 index 0000000000000..de76bda4e8a4a --- /dev/null +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_config_migration_cohortspec.py @@ -0,0 +1,45 @@ +# +# Copyright (c) 2023 Airbyte, Inc., all rights reserved. +# + + +import json +import os +from typing import Any, Mapping + +import dpath.util +from airbyte_cdk.models import OrchestratorType, Type +from airbyte_cdk.sources import Source +from source_google_analytics_data_api.config_migrations import MigrateCustomReportsCohortSpec +from source_google_analytics_data_api.source import SourceGoogleAnalyticsDataApi + +# BASE ARGS +CMD = "check" +TEST_CONFIG_PATH = f"{os.path.dirname(__file__)}/test_config.json" +NEW_TEST_CONFIG_PATH = f"{os.path.dirname(__file__)}/test_new_config.json" +SOURCE_INPUT_ARGS = [CMD, "--config", TEST_CONFIG_PATH] +SOURCE: Source = SourceGoogleAnalyticsDataApi() + + +# HELPERS +def load_config(config_path: str = TEST_CONFIG_PATH) -> Mapping[str, Any]: + with open(config_path, "r") as config: + return json.load(config) + + +def test_migrate_config(capsys): + migration_instance = MigrateCustomReportsCohortSpec() + # migrate the test_config + migration_instance.migrate(SOURCE_INPUT_ARGS, SOURCE) + + control_msg = json.loads(capsys.readouterr().out) + assert control_msg["type"] == Type.CONTROL.value + assert control_msg["control"]["type"] == OrchestratorType.CONNECTOR_CONFIG.value + + assert control_msg["control"]["connectorConfig"]["config"]["custom_reports_array"][0]["cohortSpec"]["enabled"] == "true" + assert control_msg["control"]["connectorConfig"]["config"]["custom_reports_array"][1]["cohortSpec"]["enabled"] == "false" + + +def test_should_not_migrate_new_config(): + new_config = load_config(NEW_TEST_CONFIG_PATH) + assert not MigrateCustomReportsCohortSpec._should_migrate(new_config) diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_new_config.json b/airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_new_config.json new file mode 100644 index 0000000000000..fd7ddcd7ce9fd --- /dev/null +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/unit_tests/test_migration_cohortspec/test_new_config.json @@ -0,0 +1,63 @@ +{ + "credentials": { + "auth_type": "Service", + "credentials_json": "" + }, + "date_ranges_start_date": "2023-09-01", + "window_in_days": 30, + "property_ids": "314186564", + "custom_reports_array": [ + { + "name": "cohort_report", + "dimensions": ["cohort", "cohortNthDay"], + "metrics": ["cohortActiveUsers"], + "cohortSpec": { + "cohorts": [ + { + "dimension": "firstSessionDate", + "dateRange": { + "startDate": "2023-04-24", + "endDate": "2023-04-24" + } + } + ], + "cohortsRange": { + "endOffset": 100, + "granularity": "DAILY" + }, + "cohortReportSettings": { + "accumulate": false + }, + "enable": "true" + } + }, + { + "name": "pivot_report", + "dateRanges": [ + { + "startDate": "2020-09-01", + "endDate": "2020-09-15" + } + ], + "dimensions": ["browser", "country", "language"], + "metrics": ["sessions"], + "pivots": [ + { + "fieldNames": ["browser"], + "limit": 5 + }, + { + "fieldNames": ["country"], + "limit": 250 + }, + { + "fieldNames": ["language"], + "limit": 15 + } + ], + "cohortSpec": { + "enabled": "false" + } + } + ] +} diff --git a/docs/integrations/sources/google-analytics-data-api.md b/docs/integrations/sources/google-analytics-data-api.md index 03e569c0997f9..cbf18822357fb 100644 --- a/docs/integrations/sources/google-analytics-data-api.md +++ b/docs/integrations/sources/google-analytics-data-api.md @@ -30,7 +30,7 @@ If the Property Settings shows a "Tracking Id" such as "UA-123...-1", this denot ::: 7. (Optional) In the **Start Date** field, use the provided datepicker or enter a date programmatically in the format `YYYY-MM-DD`. All data added from this date onward will be replicated. Note that this setting is _not_ applied to custom Cohort reports. -8. (Optional) In the **Custom Reports** field, you may optionally provide a JSON array describing any custom reports you want to sync from Google Analytics. See the [Custom Reports](#custom-reports) section below for more information on formulating these reports. +8. (Optional) In the **Custom Reports** field, you may optionally describe any custom reports you want to sync from Google Analytics. See the [Custom Reports](#custom-reports) section below for more information on formulating these reports. 9. (Optional) In the **Data Request Interval (Days)** field, you can specify the interval in days (ranging from 1 to 364) used when requesting data from the Google Analytics API. The bigger this value is, the faster the sync will be, but the more likely that sampling will be applied to your data, potentially causing inaccuracies in the returned results. We recommend setting this to 1 unless you have a hard requirement to make the sync faster at the expense of accuracy. This field does not apply to custom Cohort reports. See the [Data Sampling](#data-sampling-and-data-request-intervals) section below for more context on this field. :::caution @@ -91,7 +91,7 @@ If the start date is not provided, the default value will be used, which is two Many analyses and data investigations may require 24-48 hours to process information from your website or app. To ensure the accuracy of the data, we subtract two days from the starting date. For more details, please refer to [Google's documentation](https://support.google.com/analytics/answer/9333790?hl=en). ::: -7. (Optional) In the **Custom Reports** field, you may optionally provide a JSON array describing any custom reports you want to sync from Google Analytics. See the [Custom Reports](#custom-reports) section below for more information on formulating these reports. +7. (Optional) In the **Custom Reports** field, you may optionally describe any custom reports you want to sync from Google Analytics. See the [Custom Reports](#custom-reports) section below for more information on formulating these reports. 8. (Optional) In the **Data Request Interval (Days)** field, you can specify the interval in days (ranging from 1 to 364) used when requesting data from the Google Analytics API. The bigger this value is, the faster the sync will be, but the more likely that sampling will be applied to your data, potentially causing inaccuracies in the returned results. We recommend setting this to 1 unless you have a hard requirement to make the sync faster at the expense of accuracy. This field does not apply to custom Cohort reports. See the [Data Sampling](#data-sampling-and-data-request-intervals) section below for more context on this field. :::caution @@ -192,19 +192,6 @@ Custom reports in Google Analytics allow for flexibility in querying specific da A full list of dimensions and metrics supported in the API can be found [here](https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema). To ensure your dimensions and metrics are compatible for your GA4 property, you can use the [GA4 Dimensions & Metrics Explorer](https://ga-dev-tools.google/ga4/dimensions-metrics-explorer/). -Custom reports should be constructed as an array of JSON objects in the following format: - -```json -[ - { - "name": "", - "dimensions": ["", ...], - "metrics": ["", ...], - "cohortSpec": {/* cohortSpec object */}, - "pivots": [{/* pivot object */}, ...] - } -] -``` The following is an example of a basic User Engagement report to track sessions and bounce rate, segmented by city: @@ -274,36 +261,38 @@ The Google Analytics connector is subject to Google Analytics Data API quotas. P ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------------------------------| -| 2.0.3 | 2023-11-03 | [32149](https://github.com/airbytehq/airbyte/pull/32149) | Fixed bug with missing `metadata` when the credentials are not valid | -| 2.0.2 | 2023-11-02 | [32094](https://github.com/airbytehq/airbyte/pull/32094) | Added handling for `JSONDecodeError` while checking for `api qouta` limits | -| 2.0.1 | 2023-10-18 | [31543](https://github.com/airbytehq/airbyte/pull/31543) | Base image migration: remove Dockerfile and use the python-connector-base image | -| 2.0.0 | 2023-09-29 | [30930](https://github.com/airbytehq/airbyte/pull/30930) | Use distinct stream naming in case there are multiple properties in the config. | -| 1.6.0 | 2023-09-19 | [30460](https://github.com/airbytehq/airbyte/pull/30460) | Migrated custom reports from string to array; add `FilterExpressions` support | -| 1.5.1 | 2023-09-20 | [30608](https://github.com/airbytehq/airbyte/pull/30608) | Revert `:` auto replacement name to underscore | -| 1.5.0 | 2023-09-18 | [30421](https://github.com/airbytehq/airbyte/pull/30421) | Add `yearWeek`, `yearMonth`, `year` dimensions cursor | -| 1.4.1 | 2023-09-17 | [30506](https://github.com/airbytehq/airbyte/pull/30506) | Fix None type error when metrics or dimensions response does not have name | -| 1.4.0 | 2023-09-15 | [30417](https://github.com/airbytehq/airbyte/pull/30417) | Change start date to optional; add suggested streams and update errors handling | -| 1.3.1 | 2023-09-14 | [30424](https://github.com/airbytehq/airbyte/pull/30424) | Fixed duplicated stream issue | -| 1.2.0 | 2023-09-11 | [30290](https://github.com/airbytehq/airbyte/pull/30290) | Add new preconfigured reports | -| 1.1.3 | 2023-08-04 | [29103](https://github.com/airbytehq/airbyte/pull/29103) | Update input field descriptions | -| 1.1.2 | 2023-07-03 | [27909](https://github.com/airbytehq/airbyte/pull/27909) | Limit the page size of custom report streams | -| 1.1.1 | 2023-06-26 | [27718](https://github.com/airbytehq/airbyte/pull/27718) | Limit the page size when calling `check()` | -| 1.1.0 | 2023-06-26 | [27738](https://github.com/airbytehq/airbyte/pull/27738) | License Update: Elv2 | -| 1.0.0 | 2023-06-22 | [26283](https://github.com/airbytehq/airbyte/pull/26283) | Added primary_key and lookback window | -| 0.2.7 | 2023-06-21 | [27531](https://github.com/airbytehq/airbyte/pull/27531) | Fix formatting | -| 0.2.6 | 2023-06-09 | [27207](https://github.com/airbytehq/airbyte/pull/27207) | Improve api rate limit messages | -| 0.2.5 | 2023-06-08 | [27175](https://github.com/airbytehq/airbyte/pull/27175) | Improve Error Messages | -| 0.2.4 | 2023-06-01 | [26887](https://github.com/airbytehq/airbyte/pull/26887) | Remove `authSpecification` from connector spec in favour of `advancedAuth` | -| 0.2.3 | 2023-05-16 | [26126](https://github.com/airbytehq/airbyte/pull/26126) | Fix pagination | -| 0.2.2 | 2023-05-12 | [25987](https://github.com/airbytehq/airbyte/pull/25987) | Categorized Config Errors Accurately | -| 0.2.1 | 2023-05-11 | [26008](https://github.com/airbytehq/airbyte/pull/26008) | Added handling for `429 - potentiallyThresholdedRequestsPerHour` error | -| 0.2.0 | 2023-04-13 | [25179](https://github.com/airbytehq/airbyte/pull/25179) | Implement support for custom Cohort and Pivot reports | -| 0.1.3 | 2023-03-10 | [23872](https://github.com/airbytehq/airbyte/pull/23872) | Fix parse + cursor for custom reports | -| 0.1.2 | 2023-03-07 | [23822](https://github.com/airbytehq/airbyte/pull/23822) | Improve `rate limits` customer faced error messages and retry logic for `429` | -| 0.1.1 | 2023-01-10 | [21169](https://github.com/airbytehq/airbyte/pull/21169) | Slicer updated, unit tests added | -| 0.1.0 | 2023-01-08 | [20889](https://github.com/airbytehq/airbyte/pull/20889) | Improved config validation, SAT | -| 0.0.3 | 2022-08-15 | [15229](https://github.com/airbytehq/airbyte/pull/15229) | Source Google Analytics Data Api: code refactoring | -| 0.0.2 | 2022-07-27 | [15087](https://github.com/airbytehq/airbyte/pull/15087) | fix documentationUrl | -| 0.0.1 | 2022-05-09 | [12701](https://github.com/airbytehq/airbyte/pull/12701) | Introduce Google Analytics Data API source | \ No newline at end of file +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:---------------------------------------------------------|:--------------------------------------------------------------------------------| +| 2.1.0 | 2023-12-28 | [33802](https://github.com/airbytehq/airbyte/pull/33802) | Add `CohortSpec` to custom report in specification | +| 2.0.3 | 2023-11-03 | [32149](https://github.com/airbytehq/airbyte/pull/32149) | Fixed bug with missing `metadata` when the credentials are not valid | +| 2.0.2 | 2023-11-02 | [32094](https://github.com/airbytehq/airbyte/pull/32094) | Added handling for `JSONDecodeError` while checking for `api qouta` limits | +| 2.0.1 | 2023-10-18 | [31543](https://github.com/airbytehq/airbyte/pull/31543) | Base image migration: remove Dockerfile and use the python-connector-base image | +| 2.0.0 | 2023-09-29 | [30930](https://github.com/airbytehq/airbyte/pull/30930) | Use distinct stream naming in case there are multiple properties in the config. | +| 1.6.0 | 2023-09-19 | [30460](https://github.com/airbytehq/airbyte/pull/30460) | Migrated custom reports from string to array; add `FilterExpressions` support | +| 1.5.1 | 2023-09-20 | [30608](https://github.com/airbytehq/airbyte/pull/30608) | Revert `:` auto replacement name to underscore | +| 1.5.0 | 2023-09-18 | [30421](https://github.com/airbytehq/airbyte/pull/30421) | Add `yearWeek`, `yearMonth`, `year` dimensions cursor | +| 1.4.1 | 2023-09-17 | [30506](https://github.com/airbytehq/airbyte/pull/30506) | Fix None type error when metrics or dimensions response does not have name | +| 1.4.0 | 2023-09-15 | [30417](https://github.com/airbytehq/airbyte/pull/30417) | Change start date to optional; add suggested streams and update errors handling | +| 1.3.1 | 2023-09-14 | [30424](https://github.com/airbytehq/airbyte/pull/30424) | Fixed duplicated stream issue | +| 1.3.0 | 2023-09-13 | [30152](https://github.com/airbytehq/airbyte/pull/30152) | Ability to add multiple property ids | +| 1.2.0 | 2023-09-11 | [30290](https://github.com/airbytehq/airbyte/pull/30290) | Add new preconfigured reports | +| 1.1.3 | 2023-08-04 | [29103](https://github.com/airbytehq/airbyte/pull/29103) | Update input field descriptions | +| 1.1.2 | 2023-07-03 | [27909](https://github.com/airbytehq/airbyte/pull/27909) | Limit the page size of custom report streams | +| 1.1.1 | 2023-06-26 | [27718](https://github.com/airbytehq/airbyte/pull/27718) | Limit the page size when calling `check()` | +| 1.1.0 | 2023-06-26 | [27738](https://github.com/airbytehq/airbyte/pull/27738) | License Update: Elv2 | +| 1.0.0 | 2023-06-22 | [26283](https://github.com/airbytehq/airbyte/pull/26283) | Added primary_key and lookback window | +| 0.2.7 | 2023-06-21 | [27531](https://github.com/airbytehq/airbyte/pull/27531) | Fix formatting | +| 0.2.6 | 2023-06-09 | [27207](https://github.com/airbytehq/airbyte/pull/27207) | Improve api rate limit messages | +| 0.2.5 | 2023-06-08 | [27175](https://github.com/airbytehq/airbyte/pull/27175) | Improve Error Messages | +| 0.2.4 | 2023-06-01 | [26887](https://github.com/airbytehq/airbyte/pull/26887) | Remove `authSpecification` from connector spec in favour of `advancedAuth` | +| 0.2.3 | 2023-05-16 | [26126](https://github.com/airbytehq/airbyte/pull/26126) | Fix pagination | +| 0.2.2 | 2023-05-12 | [25987](https://github.com/airbytehq/airbyte/pull/25987) | Categorized Config Errors Accurately | +| 0.2.1 | 2023-05-11 | [26008](https://github.com/airbytehq/airbyte/pull/26008) | Added handling for `429 - potentiallyThresholdedRequestsPerHour` error | +| 0.2.0 | 2023-04-13 | [25179](https://github.com/airbytehq/airbyte/pull/25179) | Implement support for custom Cohort and Pivot reports | +| 0.1.3 | 2023-03-10 | [23872](https://github.com/airbytehq/airbyte/pull/23872) | Fix parse + cursor for custom reports | +| 0.1.2 | 2023-03-07 | [23822](https://github.com/airbytehq/airbyte/pull/23822) | Improve `rate limits` customer faced error messages and retry logic for `429` | +| 0.1.1 | 2023-01-10 | [21169](https://github.com/airbytehq/airbyte/pull/21169) | Slicer updated, unit tests added | +| 0.1.0 | 2023-01-08 | [20889](https://github.com/airbytehq/airbyte/pull/20889) | Improved config validation, SAT | +| 0.0.3 | 2022-08-15 | [15229](https://github.com/airbytehq/airbyte/pull/15229) | Source Google Analytics Data Api: code refactoring | +| 0.0.2 | 2022-07-27 | [15087](https://github.com/airbytehq/airbyte/pull/15087) | fix documentationUrl | +| 0.0.1 | 2022-05-09 | [12701](https://github.com/airbytehq/airbyte/pull/12701) | Introduce Google Analytics Data API source | \ No newline at end of file