Add Stripe-based logical subscriptions ETLs (DENG-977) (#4166)

* Add `subscription_platform_derived.services_v1` ETL.

* Add `subscription_platform_derived.subplat_flow_events_v1` ETL.

* Add `subscription_platform_derived.subplat_attribution_impressions_v1` ETL.

* Add `mozilla_vpn_derived.users_attribution_v1` table.

* Add `subscription_platform_derived.stripe_logical_subscriptions_history_v1` ETL.

* Add `subscription_platform_derived.logical_subscriptions_history_v1` ETL.

* Add `subscription_platform_derived.daily_active_logical_subscriptions_v1` ETL.

* Add `subscription_platform_derived.monthly_active_logical_subscriptions_v1` ETL.

* Add `subscription_platform_derived.logical_subscription_events_v1` ETL.

* Add `subscription_platform.logical_subscriptions` view.

* Add `subscription_platform.daily_active_logical_subscriptions` view.

* Add `subscription_platform.monthly_active_logical_subscriptions` view.

* Add `subscription_platform.logical_subscription_events` view.
This commit is contained in:
Sean Rose 2023-08-18 14:07:05 -07:00 коммит произвёл GitHub
Родитель 8e751a26da
Коммит 8f4a06a462
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
34 изменённых файлов: 2941 добавлений и 3 удалений

Просмотреть файл

@ -98,7 +98,9 @@ dry_run:
- sql/moz-fx-data-shared-prod/subscription_platform_derived/nonprod_apple_subscriptions_v1/query.sql
- sql/moz-fx-data-shared-prod/subscription_platform_derived/nonprod_google_subscriptions_v1/query.sql
- sql/moz-fx-data-shared-prod/subscription_platform_derived/nonprod_stripe_subscriptions_history_v1/query.sql
- sql/moz-fx-data-shared-prod/subscription_platform_derived/services_v1/query.sql
- sql/moz-fx-data-shared-prod/subscription_platform_derived/stripe_customers_revised_changelog_v1/query.sql
- sql/moz-fx-data-shared-prod/subscription_platform_derived/stripe_logical_subscriptions_history_v1/query.sql
- sql/moz-fx-data-shared-prod/subscription_platform_derived/stripe_subscriptions_revised_changelog_v1/query.sql
- sql/moz-fx-data-shared-prod/subscription_platform_derived/stripe_subscriptions_history_v1/query.sql
- sql/moz-fx-data-shared-prod/stripe/itemized_payout_reconciliation/view.sql
@ -113,6 +115,7 @@ dry_run:
- sql/moz-fx-data-shared-prod/mozilla_vpn_derived/site_metrics_empty_check_v1/query.sql
- sql/moz-fx-data-shared-prod/mozilla_vpn_derived/site_metrics_summary_v1/query.sql
- sql/moz-fx-data-shared-prod/mozilla_vpn_derived/subscriptions_v1/query.sql
- sql/moz-fx-data-shared-prod/mozilla_vpn_derived/users_attribution_v1/query.sql
- sql/moz-fx-data-shared-prod/mozilla_vpn_derived/users_v1/query.sql
- sql/moz-fx-data-shared-prod/mozilla_vpn_external/devices_v1/query.sql
- sql/moz-fx-data-shared-prod/mozilla_vpn_external/subscriptions_v1/query.sql

Просмотреть файл

@ -963,6 +963,17 @@ with DAG(
task_concurrency=1,
)
subscription_platform_derived__daily_active_logical_subscriptions__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__daily_active_logical_subscriptions__v1",
destination_table="daily_active_logical_subscriptions_v1",
dataset_id="subscription_platform_derived",
project_id="moz-fx-data-shared-prod",
owner="srose@mozilla.com",
email=["srose@mozilla.com", "telemetry-alerts@mozilla.com"],
date_partition_parameter="date",
depends_on_past=False,
)
subscription_platform_derived__google_subscriptions__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__google_subscriptions__v1",
destination_table="google_subscriptions_v1",
@ -975,6 +986,43 @@ with DAG(
task_concurrency=1,
)
subscription_platform_derived__logical_subscription_events__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__logical_subscription_events__v1",
destination_table="logical_subscription_events_v1",
dataset_id="subscription_platform_derived",
project_id="moz-fx-data-shared-prod",
owner="srose@mozilla.com",
email=["srose@mozilla.com", "telemetry-alerts@mozilla.com"],
date_partition_parameter="date",
depends_on_past=False,
)
subscription_platform_derived__logical_subscriptions_history__v1 = (
bigquery_etl_query(
task_id="subscription_platform_derived__logical_subscriptions_history__v1",
destination_table="logical_subscriptions_history_v1",
dataset_id="subscription_platform_derived",
project_id="moz-fx-data-shared-prod",
owner="srose@mozilla.com",
email=["srose@mozilla.com", "telemetry-alerts@mozilla.com"],
date_partition_parameter=None,
depends_on_past=False,
task_concurrency=1,
)
)
subscription_platform_derived__monthly_active_logical_subscriptions__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__monthly_active_logical_subscriptions__v1",
destination_table="monthly_active_logical_subscriptions_v1",
dataset_id="subscription_platform_derived",
project_id="moz-fx-data-shared-prod",
owner="srose@mozilla.com",
email=["srose@mozilla.com", "telemetry-alerts@mozilla.com"],
date_partition_parameter="date",
table_partition_template='${{ dag_run.logical_date.strftime("%Y%m") }}',
depends_on_past=False,
)
subscription_platform_derived__nonprod_apple_subscriptions__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__nonprod_apple_subscriptions__v1",
destination_table="nonprod_apple_subscriptions_v1",
@ -1001,6 +1049,18 @@ with DAG(
)
)
subscription_platform_derived__services__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__services__v1",
destination_table="services_v1",
dataset_id="subscription_platform_derived",
project_id="moz-fx-data-shared-prod",
owner="srose@mozilla.com",
email=["srose@mozilla.com", "telemetry-alerts@mozilla.com"],
date_partition_parameter=None,
depends_on_past=False,
task_concurrency=1,
)
subscription_platform_derived__stripe_customers_history__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__stripe_customers_history__v1",
destination_table="stripe_customers_history_v1",
@ -1024,6 +1084,18 @@ with DAG(
depends_on_past=False,
)
subscription_platform_derived__stripe_logical_subscriptions_history__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__stripe_logical_subscriptions_history__v1",
destination_table="stripe_logical_subscriptions_history_v1",
dataset_id="subscription_platform_derived",
project_id="moz-fx-data-shared-prod",
owner="srose@mozilla.com",
email=["srose@mozilla.com", "telemetry-alerts@mozilla.com"],
date_partition_parameter=None,
depends_on_past=False,
task_concurrency=1,
)
subscription_platform_derived__stripe_subscriptions__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__stripe_subscriptions__v1",
destination_table="stripe_subscriptions_v1",
@ -1075,6 +1147,29 @@ with DAG(
depends_on_past=False,
)
subscription_platform_derived__subplat_attribution_impressions__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__subplat_attribution_impressions__v1",
destination_table="subplat_attribution_impressions_v1",
dataset_id="subscription_platform_derived",
project_id="moz-fx-data-shared-prod",
owner="srose@mozilla.com",
email=["srose@mozilla.com", "telemetry-alerts@mozilla.com"],
date_partition_parameter=None,
depends_on_past=True,
parameters=["date:DATE:{{ds}}"],
)
subscription_platform_derived__subplat_flow_events__v1 = bigquery_etl_query(
task_id="subscription_platform_derived__subplat_flow_events__v1",
destination_table="subplat_flow_events_v1",
dataset_id="subscription_platform_derived",
project_id="moz-fx-data-shared-prod",
owner="srose@mozilla.com",
email=["srose@mozilla.com", "telemetry-alerts@mozilla.com"],
date_partition_parameter="date",
depends_on_past=True,
)
wait_for_firefox_accounts_derived__fxa_auth_events__v1 = ExternalTaskSensor(
task_id="wait_for_firefox_accounts_derived__fxa_auth_events__v1",
external_dag_id="bqetl_fxa_events",
@ -1435,10 +1530,40 @@ with DAG(
mozilla_vpn_derived__guardian_apple_events__v1
)
subscription_platform_derived__daily_active_logical_subscriptions__v1.set_upstream(
subscription_platform_derived__logical_subscriptions_history__v1
)
subscription_platform_derived__logical_subscription_events__v1.set_upstream(
subscription_platform_derived__logical_subscriptions_history__v1
)
subscription_platform_derived__logical_subscriptions_history__v1.set_upstream(
mozilla_vpn_derived__users__v1
)
subscription_platform_derived__logical_subscriptions_history__v1.set_upstream(
subscription_platform_derived__stripe_logical_subscriptions_history__v1
)
subscription_platform_derived__logical_subscriptions_history__v1.set_upstream(
subscription_platform_derived__subplat_attribution_impressions__v1
)
subscription_platform_derived__monthly_active_logical_subscriptions__v1.set_upstream(
subscription_platform_derived__daily_active_logical_subscriptions__v1
)
subscription_platform_derived__nonprod_apple_subscriptions__v1.set_upstream(
mozilla_vpn_derived__guardian_apple_events__v1
)
subscription_platform_derived__services__v1.set_upstream(stripe_external__plan__v1)
subscription_platform_derived__services__v1.set_upstream(
stripe_external__product__v1
)
subscription_platform_derived__stripe_customers_history__v1.set_upstream(
subscription_platform_derived__stripe_customers_revised_changelog__v1
)
@ -1451,6 +1576,30 @@ with DAG(
stripe_external__subscriptions_changelog__v1
)
subscription_platform_derived__stripe_logical_subscriptions_history__v1.set_upstream(
stripe_external__card__v1
)
subscription_platform_derived__stripe_logical_subscriptions_history__v1.set_upstream(
stripe_external__charge__v1
)
subscription_platform_derived__stripe_logical_subscriptions_history__v1.set_upstream(
stripe_external__invoice__v1
)
subscription_platform_derived__stripe_logical_subscriptions_history__v1.set_upstream(
stripe_external__refund__v1
)
subscription_platform_derived__stripe_logical_subscriptions_history__v1.set_upstream(
subscription_platform_derived__services__v1
)
subscription_platform_derived__stripe_logical_subscriptions_history__v1.set_upstream(
subscription_platform_derived__stripe_subscriptions_history__v2
)
subscription_platform_derived__stripe_subscriptions__v1.set_upstream(
subscription_platform_derived__stripe_subscriptions_history__v1
)
@ -1522,3 +1671,25 @@ with DAG(
subscription_platform_derived__stripe_subscriptions_revised_changelog__v1.set_upstream(
stripe_external__subscriptions_changelog__v1
)
subscription_platform_derived__subplat_attribution_impressions__v1.set_upstream(
subscription_platform_derived__services__v1
)
subscription_platform_derived__subplat_attribution_impressions__v1.set_upstream(
subscription_platform_derived__subplat_flow_events__v1
)
subscription_platform_derived__subplat_flow_events__v1.set_upstream(
wait_for_firefox_accounts_derived__fxa_auth_events__v1
)
subscription_platform_derived__subplat_flow_events__v1.set_upstream(
wait_for_firefox_accounts_derived__fxa_content_events__v1
)
subscription_platform_derived__subplat_flow_events__v1.set_upstream(
wait_for_firefox_accounts_derived__fxa_stdout_events__v1
)
subscription_platform_derived__subplat_flow_events__v1.set_upstream(
subscription_platform_derived__services__v1
)

Просмотреть файл

@ -0,0 +1,15 @@
friendly_name: Mozilla VPN users attribution archive
description: |-
Archive of VPN users' attribution data between 2020-06-25 and 2022-03-23.
The attribution data was originally stored in the Guardian database's `users` table and synced into `mozilla_vpn_external.users_v1`.
It was later copied into the undocumented `mozilla_vpn_external.users_attribution_v1` table with the attribution data as JSON strings.
Now it's officially archived here in a fully structured table.
owners:
- srose@mozilla.com
labels:
incremental: false
bigquery:
time_partitioning: null
clustering: null
references: {}

Просмотреть файл

@ -0,0 +1,31 @@
WITH users_attribution AS (
SELECT
id AS user_id,
STRUCT(
NULLIF(JSON_VALUE(attribution, '$.referrer'), '') AS referrer,
JSON_VALUE(attribution, '$.entrypoint_experiment') AS entrypoint_experiment,
JSON_VALUE(attribution, '$.entrypoint_variation') AS entrypoint_variation,
JSON_VALUE(attribution, '$.utm_campaign') AS utm_campaign,
JSON_VALUE(attribution, '$.utm_content') AS utm_content,
NULLIF(NULLIF(JSON_VALUE(attribution, '$.utm_medium'), ''), '(not set)') AS utm_medium,
NULLIF(NULLIF(JSON_VALUE(attribution, '$.utm_source'), ''), '(not set)') AS utm_source,
JSON_VALUE(attribution, '$.utm_term') AS utm_term,
JSON_VALUE(attribution, '$.data_cta_position') AS data_cta_position
) AS attribution
FROM
`moz-fx-data-shared-prod.mozilla_vpn_external.users_attribution_v1`
)
SELECT
*
FROM
users_attribution
WHERE
attribution.referrer IS NOT NULL
OR attribution.entrypoint_experiment IS NOT NULL
OR attribution.entrypoint_variation IS NOT NULL
OR attribution.utm_campaign IS NOT NULL
OR attribution.utm_content IS NOT NULL
OR attribution.utm_medium IS NOT NULL
OR attribution.utm_source IS NOT NULL
OR attribution.utm_term IS NOT NULL
OR attribution.data_cta_position IS NOT NULL

Просмотреть файл

@ -0,0 +1,35 @@
fields:
- name: user_id
type: INTEGER
mode: NULLABLE
- name: attribution
type: RECORD
mode: NULLABLE
fields:
- name: referrer
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: data_cta_position
type: STRING
mode: NULLABLE

Просмотреть файл

@ -0,0 +1,7 @@
CREATE OR REPLACE VIEW
`moz-fx-data-shared-prod.subscription_platform.daily_active_logical_subscriptions`
AS
SELECT
*
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.daily_active_logical_subscriptions_v1`

Просмотреть файл

@ -0,0 +1,7 @@
CREATE OR REPLACE VIEW
`moz-fx-data-shared-prod.subscription_platform.logical_subscription_events`
AS
SELECT
*
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.logical_subscription_events_v1`

Просмотреть файл

@ -0,0 +1,9 @@
CREATE OR REPLACE VIEW
`moz-fx-data-shared-prod.subscription_platform.logical_subscriptions`
AS
SELECT
subscription.*
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.logical_subscriptions_history_v1`
WHERE
valid_to = '9999-12-31 23:59:59.999999'

Просмотреть файл

@ -0,0 +1,7 @@
CREATE OR REPLACE VIEW
`moz-fx-data-shared-prod.subscription_platform.monthly_active_logical_subscriptions`
AS
SELECT
*
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.monthly_active_logical_subscriptions_v1`

Просмотреть файл

@ -0,0 +1,22 @@
friendly_name: Daily active logical subscriptions
description: |-
Daily snapshots of logical subscriptions that were active at any point during each day.
The latest state of the subscription during the day is saved.
Logical subscriptions are a continuous active period for a particular provider subscription.
owners:
- srose@mozilla.com
labels:
incremental: true
schedule: daily
scheduling:
dag_name: bqetl_subplat
date_partition_parameter: date
bigquery:
time_partitioning:
type: day
field: date
require_partition_filter: false
expiration_days: null
clustering: null
references: {}

Просмотреть файл

@ -0,0 +1,61 @@
WITH dates AS (
{% if is_init() %}
SELECT
`date`,
(`date` + 1) AS next_date
FROM
UNNEST(
GENERATE_DATE_ARRAY(
(
SELECT
DATE(MIN(started_at))
FROM
`moz-fx-data-shared-prod.subscription_platform.logical_subscriptions`
),
CURRENT_DATE() - 1
)
) AS `date`
{% else %}
SELECT
@date AS `date`,
(@date + 1) AS next_date
{% endif %}
),
daily_active_subscriptions_history AS (
SELECT
CONCAT(subscriptions_history.subscription.id, '-', FORMAT_DATE('%F', dates.date)) AS id,
dates.date,
MIN_BY(
subscriptions_history,
subscriptions_history.valid_from
) AS earliest_subscription_history,
MAX_BY(subscriptions_history, subscriptions_history.valid_from) AS latest_subscription_history
FROM
dates
JOIN
`moz-fx-data-shared-prod.subscription_platform_derived.logical_subscriptions_history_v1` AS subscriptions_history
ON
TIMESTAMP(dates.next_date) > subscriptions_history.valid_from
AND TIMESTAMP(dates.date) < subscriptions_history.valid_to
AND (
TIMESTAMP(dates.date) < subscriptions_history.subscription.ended_at
OR subscriptions_history.subscription.ended_at IS NULL
)
GROUP BY
dates.date,
subscriptions_history.subscription.id
HAVING
LOGICAL_OR(subscriptions_history.subscription.is_active)
)
SELECT
id,
`date`,
latest_subscription_history.id AS logical_subscriptions_history_id,
latest_subscription_history.subscription,
(
earliest_subscription_history.subscription.is_active
AND earliest_subscription_history.valid_from <= TIMESTAMP(`date`)
) AS was_active_at_day_start,
latest_subscription_history.subscription.is_active AS was_active_at_day_end
FROM
daily_active_subscriptions_history

Просмотреть файл

@ -0,0 +1,200 @@
fields:
- name: id
type: STRING
mode: NULLABLE
- name: date
type: DATE
mode: NULLABLE
- name: logical_subscriptions_history_id
type: STRING
mode: NULLABLE
- name: subscription
type: RECORD
mode: NULLABLE
fields:
- name: id
type: STRING
mode: NULLABLE
- name: provider
type: STRING
mode: NULLABLE
- name: payment_provider
type: STRING
mode: NULLABLE
- name: provider_subscription_id
type: STRING
mode: NULLABLE
- name: provider_subscription_item_id
type: STRING
mode: NULLABLE
- name: provider_subscription_created_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_subscription_updated_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_customer_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id_sha256
type: STRING
mode: NULLABLE
- name: customer_subscription_number
type: INTEGER
mode: NULLABLE
- name: country_code
type: STRING
mode: NULLABLE
- name: country_name
type: STRING
mode: NULLABLE
- name: services
type: RECORD
mode: REPEATED
fields:
- name: id
type: STRING
mode: NULLABLE
- name: name
type: STRING
mode: NULLABLE
- name: tier
type: STRING
mode: NULLABLE
- name: provider_product_id
type: STRING
mode: NULLABLE
- name: product_name
type: STRING
mode: NULLABLE
- name: provider_plan_id
type: STRING
mode: NULLABLE
- name: plan_summary
type: STRING
mode: NULLABLE
- name: plan_interval
type: STRING
mode: NULLABLE
- name: plan_interval_type
type: STRING
mode: NULLABLE
- name: plan_interval_count
type: INTEGER
mode: NULLABLE
- name: plan_interval_months
type: INTEGER
mode: NULLABLE
- name: plan_currency
type: STRING
mode: NULLABLE
- name: plan_amount
type: NUMERIC
mode: NULLABLE
- name: is_bundle
type: BOOLEAN
mode: NULLABLE
- name: is_trial
type: BOOLEAN
mode: NULLABLE
- name: is_active
type: BOOLEAN
mode: NULLABLE
- name: provider_status
type: STRING
mode: NULLABLE
- name: started_at
type: TIMESTAMP
mode: NULLABLE
- name: ended_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_started_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_ends_at
type: TIMESTAMP
mode: NULLABLE
- name: auto_renew
type: BOOLEAN
mode: NULLABLE
- name: auto_renew_disabled_at
type: TIMESTAMP
mode: NULLABLE
- name: has_refunds
type: BOOLEAN
mode: NULLABLE
- name: has_fraudulent_charges
type: BOOLEAN
mode: NULLABLE
- name: first_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: last_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: was_active_at_day_start
type: BOOLEAN
mode: NULLABLE
- name: was_active_at_day_end
type: BOOLEAN
mode: NULLABLE

Просмотреть файл

@ -0,0 +1,24 @@
friendly_name: Logical subscriptions events
description: |-
Logical subscription events such as "Subscription Start", "Plan Change", "Auto-Renew Change", and "Subscription End".
Logical subscriptions are a continuous active period for a particular provider subscription.
owners:
- srose@mozilla.com
labels:
incremental: true
schedule: daily
scheduling:
dag_name: bqetl_subplat
date_partition_parameter: date
bigquery:
time_partitioning:
type: day
field: timestamp
require_partition_filter: false
expiration_days: null
clustering:
fields:
- type
- reason
references: {}

Просмотреть файл

@ -0,0 +1,175 @@
WITH subscription_changes AS (
SELECT
id AS logical_subscriptions_history_id,
valid_from AS `timestamp`,
subscription,
LAG(subscription) OVER (PARTITION BY subscription.id ORDER BY valid_from) AS old_subscription
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.logical_subscriptions_history_v1`
WHERE
{% if is_init() %}
DATE(valid_from) < CURRENT_DATE()
{% else %}
DATE(valid_from) = @date
OR DATE(valid_to) = @date
{% endif %}
),
subscription_start_events AS (
SELECT
subscription.started_at AS `timestamp`,
'Subscription Start' AS type,
CONCAT(
IF(subscription.customer_subscription_number = 1, 'New Customer', 'Returning Customer'),
IF(subscription.is_trial, ' Trial', '')
) AS reason,
logical_subscriptions_history_id,
subscription,
old_subscription
FROM
subscription_changes
QUALIFY
1 = ROW_NUMBER() OVER (PARTITION BY subscription.id ORDER BY `timestamp`)
{% if not is_init() %}
AND DATE(subscription.started_at) = @date
{% endif %}
),
subscription_end_events AS (
SELECT
subscription.ended_at AS `timestamp`,
'Subscription End' AS type,
-- TODO: rather than "Unknown", determine if the user cancelled intentionally or their payment failed
IF(NOT subscription.auto_renew, 'Auto-Renew Disabled', 'Unknown') AS reason,
logical_subscriptions_history_id,
subscription,
old_subscription
FROM
subscription_changes
WHERE
subscription.ended_at IS NOT NULL
QUALIFY
1 = ROW_NUMBER() OVER (PARTITION BY subscription.id ORDER BY `timestamp`)
{% if not is_init() %}
AND DATE(subscription.ended_at) = @date
{% endif %}
),
mozilla_account_change_events AS (
SELECT
`timestamp`,
'Mozilla Account Change' AS type,
CASE
WHEN old_subscription.mozilla_account_id_sha256 IS NULL
THEN 'Mozilla Account Added'
WHEN subscription.mozilla_account_id_sha256 IS NULL
THEN 'Mozilla Account Removed'
ELSE 'Mozilla Account Changed'
END AS reason,
logical_subscriptions_history_id,
subscription,
old_subscription
FROM
subscription_changes
WHERE
old_subscription IS NOT NULL
AND subscription.mozilla_account_id_sha256 IS DISTINCT FROM old_subscription.mozilla_account_id_sha256
),
plan_change_events AS (
SELECT
`timestamp`,
'Plan Change' AS type,
CASE
WHEN (SELECT STRING_AGG(id ORDER BY id) FROM UNNEST(subscription.services)) != (
SELECT
STRING_AGG(id ORDER BY id)
FROM
UNNEST(old_subscription.services)
)
THEN 'Services Changed'
WHEN (SELECT STRING_AGG(tier ORDER BY id) FROM UNNEST(subscription.services)) != (
SELECT
STRING_AGG(tier ORDER BY id)
FROM
UNNEST(old_subscription.services)
)
THEN 'Service Tier Changed'
WHEN subscription.plan_interval != old_subscription.plan_interval
OR subscription.plan_interval_count != old_subscription.plan_interval_count
THEN 'Plan Interval Changed'
END AS reason,
logical_subscriptions_history_id,
subscription,
old_subscription
FROM
subscription_changes
WHERE
subscription.provider_plan_id != old_subscription.provider_plan_id
),
trial_change_events AS (
SELECT
`timestamp`,
'Trial Change' AS type,
IF(subscription.is_trial, 'Trial Started', 'Trial Converted') AS reason,
logical_subscriptions_history_id,
subscription,
old_subscription
FROM
subscription_changes
WHERE
subscription.is_trial != old_subscription.is_trial
AND subscription.ended_at IS NULL
),
auto_renew_change_events AS (
SELECT
`timestamp`,
'Auto-Renew Change' AS type,
IF(subscription.auto_renew, 'Auto-Renew Enabled', 'Auto-Renew Disabled') AS reason,
logical_subscriptions_history_id,
subscription,
old_subscription
FROM
subscription_changes
WHERE
subscription.auto_renew != old_subscription.auto_renew
AND subscription.ended_at IS NULL
),
all_events AS (
SELECT
*
FROM
subscription_start_events
UNION ALL
SELECT
*
FROM
subscription_end_events
UNION ALL
SELECT
*
FROM
mozilla_account_change_events
UNION ALL
SELECT
*
FROM
plan_change_events
UNION ALL
SELECT
*
FROM
trial_change_events
UNION ALL
SELECT
*
FROM
auto_renew_change_events
)
SELECT
CONCAT(
subscription.id,
'-',
FORMAT_TIMESTAMP('%FT%H:%M:%E6S', `timestamp`),
'-',
REPLACE(type, ' ', '-')
) AS id,
*
FROM
all_events

Просмотреть файл

@ -0,0 +1,384 @@
fields:
- name: id
type: STRING
mode: NULLABLE
- name: timestamp
type: TIMESTAMP
mode: NULLABLE
- name: type
type: STRING
mode: NULLABLE
- name: reason
type: STRING
mode: NULLABLE
- name: logical_subscriptions_history_id
type: STRING
mode: NULLABLE
- name: subscription
type: RECORD
mode: NULLABLE
fields:
- name: id
type: STRING
mode: NULLABLE
- name: provider
type: STRING
mode: NULLABLE
- name: payment_provider
type: STRING
mode: NULLABLE
- name: provider_subscription_id
type: STRING
mode: NULLABLE
- name: provider_subscription_item_id
type: STRING
mode: NULLABLE
- name: provider_subscription_created_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_subscription_updated_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_customer_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id_sha256
type: STRING
mode: NULLABLE
- name: customer_subscription_number
type: INTEGER
mode: NULLABLE
- name: country_code
type: STRING
mode: NULLABLE
- name: country_name
type: STRING
mode: NULLABLE
- name: services
type: RECORD
mode: REPEATED
fields:
- name: id
type: STRING
mode: NULLABLE
- name: name
type: STRING
mode: NULLABLE
- name: tier
type: STRING
mode: NULLABLE
- name: provider_product_id
type: STRING
mode: NULLABLE
- name: product_name
type: STRING
mode: NULLABLE
- name: provider_plan_id
type: STRING
mode: NULLABLE
- name: plan_summary
type: STRING
mode: NULLABLE
- name: plan_interval
type: STRING
mode: NULLABLE
- name: plan_interval_type
type: STRING
mode: NULLABLE
- name: plan_interval_count
type: INTEGER
mode: NULLABLE
- name: plan_interval_months
type: INTEGER
mode: NULLABLE
- name: plan_currency
type: STRING
mode: NULLABLE
- name: plan_amount
type: NUMERIC
mode: NULLABLE
- name: is_bundle
type: BOOLEAN
mode: NULLABLE
- name: is_trial
type: BOOLEAN
mode: NULLABLE
- name: is_active
type: BOOLEAN
mode: NULLABLE
- name: provider_status
type: STRING
mode: NULLABLE
- name: started_at
type: TIMESTAMP
mode: NULLABLE
- name: ended_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_started_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_ends_at
type: TIMESTAMP
mode: NULLABLE
- name: auto_renew
type: BOOLEAN
mode: NULLABLE
- name: auto_renew_disabled_at
type: TIMESTAMP
mode: NULLABLE
- name: has_refunds
type: BOOLEAN
mode: NULLABLE
- name: has_fraudulent_charges
type: BOOLEAN
mode: NULLABLE
- name: first_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: last_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: old_subscription
type: RECORD
mode: NULLABLE
fields:
- name: id
type: STRING
mode: NULLABLE
- name: provider
type: STRING
mode: NULLABLE
- name: payment_provider
type: STRING
mode: NULLABLE
- name: provider_subscription_id
type: STRING
mode: NULLABLE
- name: provider_subscription_item_id
type: STRING
mode: NULLABLE
- name: provider_subscription_created_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_subscription_updated_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_customer_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id_sha256
type: STRING
mode: NULLABLE
- name: customer_subscription_number
type: INTEGER
mode: NULLABLE
- name: country_code
type: STRING
mode: NULLABLE
- name: country_name
type: STRING
mode: NULLABLE
- name: services
type: RECORD
mode: REPEATED
fields:
- name: id
type: STRING
mode: NULLABLE
- name: name
type: STRING
mode: NULLABLE
- name: tier
type: STRING
mode: NULLABLE
- name: provider_product_id
type: STRING
mode: NULLABLE
- name: product_name
type: STRING
mode: NULLABLE
- name: provider_plan_id
type: STRING
mode: NULLABLE
- name: plan_summary
type: STRING
mode: NULLABLE
- name: plan_interval
type: STRING
mode: NULLABLE
- name: plan_interval_type
type: STRING
mode: NULLABLE
- name: plan_interval_count
type: INTEGER
mode: NULLABLE
- name: plan_interval_months
type: INTEGER
mode: NULLABLE
- name: plan_currency
type: STRING
mode: NULLABLE
- name: plan_amount
type: NUMERIC
mode: NULLABLE
- name: is_bundle
type: BOOLEAN
mode: NULLABLE
- name: is_trial
type: BOOLEAN
mode: NULLABLE
- name: is_active
type: BOOLEAN
mode: NULLABLE
- name: provider_status
type: STRING
mode: NULLABLE
- name: started_at
type: TIMESTAMP
mode: NULLABLE
- name: ended_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_started_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_ends_at
type: TIMESTAMP
mode: NULLABLE
- name: auto_renew
type: BOOLEAN
mode: NULLABLE
- name: auto_renew_disabled_at
type: TIMESTAMP
mode: NULLABLE
- name: has_refunds
type: BOOLEAN
mode: NULLABLE
- name: has_fraudulent_charges
type: BOOLEAN
mode: NULLABLE
- name: first_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: last_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE

Просмотреть файл

@ -0,0 +1,26 @@
friendly_name: Logical subscriptions history
description: |-
History of changes to logical subscriptions, which are a continuous active period for a particular provider subscription.
To get the historical state at a particular point in time use a condition like the following:
valid_from <= {timestamp}
AND valid_to > {timestamp}
owners:
- srose@mozilla.com
labels:
incremental: false
schedule: daily
scheduling:
dag_name: bqetl_subplat
# The whole table is overwritten every time, not a specific date partition.
date_partition_parameter: null
bigquery:
time_partitioning:
type: day
field: valid_to
require_partition_filter: false
expiration_days: null
clustering:
fields:
- valid_from
references: {}

Просмотреть файл

@ -0,0 +1,213 @@
WITH history AS (
SELECT
*
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.stripe_logical_subscriptions_history_v1`
),
countries AS (
SELECT
code,
name
FROM
`moz-fx-data-shared-prod.static.country_codes_v1`
),
customer_attribution_impressions AS (
SELECT
mozilla_account_id_sha256,
impression_at,
entrypoint,
entrypoint_experiment,
entrypoint_variation,
utm_campaign,
utm_content,
utm_medium,
utm_source,
utm_term,
service_ids
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.subplat_attribution_impressions_v1`
CROSS JOIN
UNNEST(mozilla_account_ids_sha256) AS mozilla_account_id_sha256
UNION ALL
-- Include historical VPN attributions from before VPN's SubPlat funnel was implemented on 2021-08-25.
SELECT
users.fxa_uid AS mozilla_account_id_sha256,
users.created_at AS impression_at,
CAST(NULL AS STRING) AS entrypoint,
users_attribution.attribution.entrypoint_experiment,
users_attribution.attribution.entrypoint_variation,
users_attribution.attribution.utm_campaign,
users_attribution.attribution.utm_content,
users_attribution.attribution.utm_medium,
users_attribution.attribution.utm_source,
users_attribution.attribution.utm_term,
['VPN'] AS service_ids
FROM
`moz-fx-data-shared-prod.mozilla_vpn_derived.users_attribution_v1` AS users_attribution
JOIN
`moz-fx-data-shared-prod.mozilla_vpn_derived.users_v1` AS users
ON
users_attribution.user_id = users.id
WHERE
DATE(users.created_at) <= '2021-08-25'
AND (
users_attribution.attribution.entrypoint_experiment IS NOT NULL
OR users_attribution.attribution.entrypoint_variation IS NOT NULL
OR users_attribution.attribution.utm_campaign IS NOT NULL
OR users_attribution.attribution.utm_content IS NOT NULL
OR users_attribution.attribution.utm_medium IS NOT NULL
OR users_attribution.attribution.utm_source IS NOT NULL
OR users_attribution.attribution.utm_term IS NOT NULL
)
),
subscription_starts AS (
SELECT
subscription.id AS subscription_id,
subscription.started_at,
subscription.mozilla_account_id_sha256,
subscription.services
FROM
history
QUALIFY
1 = ROW_NUMBER() OVER (PARTITION BY subscription.id ORDER BY valid_from)
),
subscription_attributions AS (
SELECT
subscription_starts.subscription_id,
MIN_BY(
STRUCT(
customer_attribution_impressions.impression_at,
customer_attribution_impressions.entrypoint,
customer_attribution_impressions.entrypoint_experiment,
customer_attribution_impressions.entrypoint_variation,
customer_attribution_impressions.utm_campaign,
customer_attribution_impressions.utm_content,
customer_attribution_impressions.utm_medium,
customer_attribution_impressions.utm_source,
customer_attribution_impressions.utm_term
-- TODO: calculate normalized attribution values like `mozfun.norm.vpn_attribution()` does
),
customer_attribution_impressions.impression_at
) AS first_touch_attribution,
MAX_BY(
STRUCT(
customer_attribution_impressions.impression_at,
customer_attribution_impressions.entrypoint,
customer_attribution_impressions.entrypoint_experiment,
customer_attribution_impressions.entrypoint_variation,
customer_attribution_impressions.utm_campaign,
customer_attribution_impressions.utm_content,
customer_attribution_impressions.utm_medium,
customer_attribution_impressions.utm_source,
customer_attribution_impressions.utm_term
-- TODO: calculate normalized attribution values like `mozfun.norm.vpn_attribution()` does
),
customer_attribution_impressions.impression_at
) AS last_touch_attribution
FROM
subscription_starts
CROSS JOIN
UNNEST(subscription_starts.services) AS service
JOIN
customer_attribution_impressions
ON
subscription_starts.mozilla_account_id_sha256 = customer_attribution_impressions.mozilla_account_id_sha256
AND service.id IN UNNEST(customer_attribution_impressions.service_ids)
AND subscription_starts.started_at >= customer_attribution_impressions.impression_at
GROUP BY
subscription_id
)
SELECT
history.id,
history.valid_from,
history.valid_to,
history.provider_subscriptions_history_id,
STRUCT(
history.subscription.id,
history.subscription.provider,
history.subscription.payment_provider,
history.subscription.provider_subscription_id,
history.subscription.provider_subscription_item_id,
history.subscription.provider_subscription_created_at,
history.valid_from AS provider_subscription_updated_at,
history.subscription.provider_customer_id,
history.subscription.mozilla_account_id,
history.subscription.mozilla_account_id_sha256,
DENSE_RANK() OVER (
PARTITION BY
-- We don't have unhashed Mozilla Account IDs for some historical customers, so we use the hashed IDs instead,
-- and if we don't have any Mozilla Account ID data we fall back to the provider's customer/subscription IDs.
COALESCE(
history.subscription.mozilla_account_id_sha256,
history.subscription.provider_customer_id,
history.subscription.provider_subscription_id
)
ORDER BY
history.subscription.started_at,
history.subscription.id
) AS customer_subscription_number,
history.subscription.country_code,
COALESCE(countries.name, history.subscription.country_code, 'Unknown') AS country_name,
history.subscription.services,
history.subscription.provider_product_id,
history.subscription.product_name,
history.subscription.provider_plan_id,
CONCAT(
history.subscription.plan_interval_count,
' ',
history.subscription.plan_interval_type,
IF(history.subscription.plan_interval_count > 1, 's', ''),
IF(
history.subscription.plan_amount IS NOT NULL,
CONCAT(
' ',
history.subscription.plan_currency,
' ',
FORMAT('%.2f', history.subscription.plan_amount)
),
''
),
IF(history.subscription.is_bundle, ' bundle', '')
) AS plan_summary,
CONCAT(
history.subscription.plan_interval_count,
' ',
history.subscription.plan_interval_type,
IF(history.subscription.plan_interval_count > 1, 's', '')
) AS plan_interval,
history.subscription.plan_interval_type,
history.subscription.plan_interval_count,
CASE
history.subscription.plan_interval_type
WHEN 'month'
THEN history.subscription.plan_interval_count
WHEN 'year'
THEN history.subscription.plan_interval_count * 12
END AS plan_interval_months,
history.subscription.plan_currency,
history.subscription.plan_amount,
history.subscription.is_bundle,
history.subscription.is_trial,
history.subscription.is_active,
history.subscription.provider_status,
history.subscription.started_at,
history.subscription.ended_at,
history.subscription.current_period_started_at,
history.subscription.current_period_ends_at,
history.subscription.auto_renew,
history.subscription.auto_renew_disabled_at,
history.subscription.has_refunds,
history.subscription.has_fraudulent_charges,
subscription_attributions.first_touch_attribution,
subscription_attributions.last_touch_attribution
) AS subscription
FROM
history
LEFT JOIN
countries
ON
history.subscription.country_code = countries.code
LEFT JOIN
subscription_attributions
ON
history.subscription.id = subscription_attributions.subscription_id

Просмотреть файл

@ -0,0 +1,197 @@
fields:
- name: id
type: STRING
mode: NULLABLE
- name: valid_from
type: TIMESTAMP
mode: NULLABLE
- name: valid_to
type: TIMESTAMP
mode: NULLABLE
- name: provider_subscriptions_history_id
type: STRING
mode: NULLABLE
- name: subscription
type: RECORD
mode: NULLABLE
fields:
- name: id
type: STRING
mode: NULLABLE
- name: provider
type: STRING
mode: NULLABLE
- name: payment_provider
type: STRING
mode: NULLABLE
- name: provider_subscription_id
type: STRING
mode: NULLABLE
- name: provider_subscription_item_id
type: STRING
mode: NULLABLE
- name: provider_subscription_created_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_subscription_updated_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_customer_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id_sha256
type: STRING
mode: NULLABLE
- name: customer_subscription_number
type: INTEGER
mode: NULLABLE
- name: country_code
type: STRING
mode: NULLABLE
- name: country_name
type: STRING
mode: NULLABLE
- name: services
type: RECORD
mode: REPEATED
fields:
- name: id
type: STRING
mode: NULLABLE
- name: name
type: STRING
mode: NULLABLE
- name: tier
type: STRING
mode: NULLABLE
- name: provider_product_id
type: STRING
mode: NULLABLE
- name: product_name
type: STRING
mode: NULLABLE
- name: provider_plan_id
type: STRING
mode: NULLABLE
- name: plan_summary
type: STRING
mode: NULLABLE
- name: plan_interval
type: STRING
mode: NULLABLE
- name: plan_interval_type
type: STRING
mode: NULLABLE
- name: plan_interval_count
type: INTEGER
mode: NULLABLE
- name: plan_interval_months
type: INTEGER
mode: NULLABLE
- name: plan_currency
type: STRING
mode: NULLABLE
- name: plan_amount
type: NUMERIC
mode: NULLABLE
- name: is_bundle
type: BOOLEAN
mode: NULLABLE
- name: is_trial
type: BOOLEAN
mode: NULLABLE
- name: is_active
type: BOOLEAN
mode: NULLABLE
- name: provider_status
type: STRING
mode: NULLABLE
- name: started_at
type: TIMESTAMP
mode: NULLABLE
- name: ended_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_started_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_ends_at
type: TIMESTAMP
mode: NULLABLE
- name: auto_renew
type: BOOLEAN
mode: NULLABLE
- name: auto_renew_disabled_at
type: TIMESTAMP
mode: NULLABLE
- name: has_refunds
type: BOOLEAN
mode: NULLABLE
- name: has_fraudulent_charges
type: BOOLEAN
mode: NULLABLE
- name: first_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: last_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE

Просмотреть файл

@ -0,0 +1,22 @@
friendly_name: Monthly active logical subscriptions
description: |-
Monthly snapshots of logical subscriptions that were active at any point during each month.
The latest state of the subscription during the month is saved.
Logical subscriptions are a continuous active period for a particular provider subscription.
owners:
- srose@mozilla.com
labels:
incremental: true
schedule: daily
scheduling:
dag_name: bqetl_subplat
date_partition_parameter: date
bigquery:
time_partitioning:
type: month
field: month_start_date
require_partition_filter: false
expiration_days: null
clustering: null
references: {}

Просмотреть файл

@ -0,0 +1,59 @@
WITH months AS (
{% if is_init() %}
SELECT
month_start_date,
LAST_DAY(month_start_date, MONTH) AS month_end_date
FROM
UNNEST(
GENERATE_DATE_ARRAY(
(
SELECT
DATE_TRUNC(DATE(MIN(started_at)), MONTH)
FROM
`moz-fx-data-shared-prod.subscription_platform.logical_subscriptions`
),
CURRENT_DATE() - 1,
INTERVAL 1 MONTH
)
) AS month_start_date
{% else %}
SELECT
DATE_TRUNC(@date, MONTH) AS month_start_date,
LAST_DAY(@date, MONTH) AS month_end_date
{% endif %}
),
monthly_active_subscriptions AS (
SELECT
CONCAT(
daily_subscriptions.subscription.id,
'-',
FORMAT_DATE('%Y-%m', months.month_start_date)
) AS id,
months.month_start_date,
months.month_end_date,
MIN_BY(daily_subscriptions, daily_subscriptions.date) AS earliest_daily_subscription,
MAX_BY(daily_subscriptions, daily_subscriptions.date) AS latest_daily_subscription
FROM
months
JOIN
`moz-fx-data-shared-prod.subscription_platform_derived.daily_active_logical_subscriptions_v1` AS daily_subscriptions
ON
(daily_subscriptions.date BETWEEN months.month_start_date AND months.month_end_date)
GROUP BY
months.month_start_date,
months.month_end_date,
daily_subscriptions.subscription.id
)
SELECT
id,
month_start_date,
month_end_date,
latest_daily_subscription.logical_subscriptions_history_id,
latest_daily_subscription.subscription,
(
earliest_daily_subscription.was_active_at_day_start
AND earliest_daily_subscription.date = month_start_date
) AS was_active_at_month_start,
latest_daily_subscription.subscription.is_active AS was_active_at_month_end
FROM
monthly_active_subscriptions

Просмотреть файл

@ -0,0 +1,203 @@
fields:
- name: id
type: STRING
mode: NULLABLE
- name: month_start_date
type: DATE
mode: NULLABLE
- name: month_end_date
type: DATE
mode: NULLABLE
- name: logical_subscriptions_history_id
type: STRING
mode: NULLABLE
- name: subscription
type: RECORD
mode: NULLABLE
fields:
- name: id
type: STRING
mode: NULLABLE
- name: provider
type: STRING
mode: NULLABLE
- name: payment_provider
type: STRING
mode: NULLABLE
- name: provider_subscription_id
type: STRING
mode: NULLABLE
- name: provider_subscription_item_id
type: STRING
mode: NULLABLE
- name: provider_subscription_created_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_subscription_updated_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_customer_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id_sha256
type: STRING
mode: NULLABLE
- name: customer_subscription_number
type: INTEGER
mode: NULLABLE
- name: country_code
type: STRING
mode: NULLABLE
- name: country_name
type: STRING
mode: NULLABLE
- name: services
type: RECORD
mode: REPEATED
fields:
- name: id
type: STRING
mode: NULLABLE
- name: name
type: STRING
mode: NULLABLE
- name: tier
type: STRING
mode: NULLABLE
- name: provider_product_id
type: STRING
mode: NULLABLE
- name: product_name
type: STRING
mode: NULLABLE
- name: provider_plan_id
type: STRING
mode: NULLABLE
- name: plan_summary
type: STRING
mode: NULLABLE
- name: plan_interval
type: STRING
mode: NULLABLE
- name: plan_interval_type
type: STRING
mode: NULLABLE
- name: plan_interval_count
type: INTEGER
mode: NULLABLE
- name: plan_interval_months
type: INTEGER
mode: NULLABLE
- name: plan_currency
type: STRING
mode: NULLABLE
- name: plan_amount
type: NUMERIC
mode: NULLABLE
- name: is_bundle
type: BOOLEAN
mode: NULLABLE
- name: is_trial
type: BOOLEAN
mode: NULLABLE
- name: is_active
type: BOOLEAN
mode: NULLABLE
- name: provider_status
type: STRING
mode: NULLABLE
- name: started_at
type: TIMESTAMP
mode: NULLABLE
- name: ended_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_started_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_ends_at
type: TIMESTAMP
mode: NULLABLE
- name: auto_renew
type: BOOLEAN
mode: NULLABLE
- name: auto_renew_disabled_at
type: TIMESTAMP
mode: NULLABLE
- name: has_refunds
type: BOOLEAN
mode: NULLABLE
- name: has_fraudulent_charges
type: BOOLEAN
mode: NULLABLE
- name: first_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: last_touch_attribution
type: RECORD
mode: NULLABLE
fields:
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: was_active_at_month_start
type: BOOLEAN
mode: NULLABLE
- name: was_active_at_month_end
type: BOOLEAN
mode: NULLABLE

Просмотреть файл

@ -0,0 +1,16 @@
friendly_name: Subscription services
description: |-
Metadata about subscription services and their tiers.
owners:
- srose@mozilla.com
labels:
incremental: false
schedule: daily
scheduling:
dag_name: bqetl_subplat
# The whole table is overwritten every time, not a specific date partition.
date_partition_parameter: null
bigquery:
time_partitioning: null
clustering: null
references: {}

Просмотреть файл

@ -0,0 +1,207 @@
WITH services AS (
SELECT
*
FROM
UNNEST(
[
STRUCT(
'VPN' AS id,
'Mozilla VPN' AS name,
ARRAY<STRUCT<name STRING, subplat_capabilities ARRAY<STRING>>>[] AS tiers,
['guardian_vpn_1'] AS subplat_capabilities,
[STRUCT('e6eb0d1e856335fc' AS id, 'guardian-vpn' AS name)] AS subplat_oauth_clients
),
STRUCT(
'FPN' AS id,
'Firefox Private Network' AS name,
ARRAY<STRUCT<name STRING, subplat_capabilities ARRAY<STRING>>>[] AS tiers,
['fpn-browser'] AS subplat_capabilities,
[STRUCT('565585c1745a144d' AS id, 'fx-priv-network' AS name)] AS subplat_oauth_clients
),
STRUCT(
'Relay' AS id,
'Relay Premium' AS name,
[
STRUCT('Email & Phone Masking' AS name, ['relay-phones'] AS subplat_capabilities),
STRUCT('Email Masking' AS name, ['premium-relay'] AS subplat_capabilities)
] AS tiers,
['relay-phones', 'premium-relay'] AS subplat_capabilities,
[STRUCT('9ebfe2c2f9ea3c58' AS id, 'fx-private-relay' AS name)] AS subplat_oauth_clients
),
STRUCT(
'MDN' AS id,
'MDN Plus' AS name,
[
STRUCT(
'Supporter 10' AS name,
['mdn_plus_10m', 'mdn_plus_10y'] AS subplat_capabilities
),
STRUCT('Plus 5' AS name, ['mdn_plus_5m', 'mdn_plus_5y'] AS subplat_capabilities)
] AS tiers,
[
'mdn_plus',
'mdn_plus_10m',
'mdn_plus_10y',
'mdn_plus_5m',
'mdn_plus_5y'
] AS subplat_capabilities,
[STRUCT('720bc80adfa6988d' AS id, 'mdn-plus' AS name)] AS subplat_oauth_clients
),
STRUCT(
'Hubs' AS id,
'Hubs' AS name,
[
STRUCT('Professional' AS name, ['hubs-professional'] AS subplat_capabilities),
STRUCT('Personal' AS name, ['managed-hubs'] AS subplat_capabilities)
] AS tiers,
['hubs-professional', 'managed-hubs'] AS subplat_capabilities,
[
STRUCT('8c3e3e6de4ee9731' AS id, 'mozilla-hubs' AS name),
STRUCT('34bc0d0a6add7329' AS id, 'mozilla-hubs-dev' AS name)
] AS subplat_oauth_clients
)
]
)
),
stripe_products AS (
SELECT
id,
PARSE_JSON(metadata) AS metadata
FROM
`moz-fx-data-shared-prod.stripe_external.product_v1`
),
stripe_plans AS (
SELECT
id,
PARSE_JSON(metadata) AS metadata,
product_id
FROM
`moz-fx-data-shared-prod.stripe_external.plan_v1`
),
service_stripe_product_ids AS (
SELECT
services.id AS service_id,
ARRAY_AGG(DISTINCT stripe_products.id ORDER BY stripe_products.id) AS stripe_product_ids
FROM
services
CROSS JOIN
UNNEST(services.subplat_oauth_clients) AS subplat_oauth_client
CROSS JOIN
stripe_products
JOIN
UNNEST(
SPLIT(JSON_VALUE(stripe_products.metadata['capabilities:' || subplat_oauth_client.id]), ',')
) AS capability
ON
TRIM(capability) IN UNNEST(services.subplat_capabilities)
GROUP BY
service_id
),
service_stripe_plan_capabilities AS (
SELECT DISTINCT
services.id AS service_id,
stripe_plans.id AS stripe_plan_id,
TRIM(capability) AS capability
FROM
services
CROSS JOIN
UNNEST(services.subplat_oauth_clients) AS subplat_oauth_client
CROSS JOIN
stripe_plans
LEFT JOIN
stripe_products
ON
stripe_plans.product_id = stripe_products.id
JOIN
UNNEST(
SPLIT(
COALESCE(
JSON_VALUE(stripe_plans.metadata['capabilities:' || subplat_oauth_client.id]),
JSON_VALUE(stripe_products.metadata['capabilities:' || subplat_oauth_client.id])
),
','
)
) AS capability
ON
TRIM(capability) IN UNNEST(services.subplat_capabilities)
),
service_stripe_plan_ids AS (
SELECT
service_id,
ARRAY_AGG(DISTINCT stripe_plan_id ORDER BY stripe_plan_id) AS stripe_plan_ids
FROM
service_stripe_plan_capabilities
GROUP BY
service_id
),
service_stripe_plan_tier_names AS (
SELECT
service_stripe_plan_capabilities.service_id,
service_stripe_plan_capabilities.stripe_plan_id,
-- Pick the first tier that matches (tiers should be in order of precedence).
ARRAY_AGG(tier.name ORDER BY tier_order LIMIT 1)[ORDINAL(1)] tier_name
FROM
service_stripe_plan_capabilities
JOIN
services
ON
service_stripe_plan_capabilities.service_id = services.id
JOIN
UNNEST(services.tiers) AS tier
WITH OFFSET AS tier_order
ON
service_stripe_plan_capabilities.capability IN UNNEST(tier.subplat_capabilities)
GROUP BY
service_id,
stripe_plan_id
),
service_tier_stripe_plan_ids AS (
SELECT
service_id,
tier_name,
ARRAY_AGG(DISTINCT stripe_plan_id ORDER BY stripe_plan_id) AS stripe_plan_ids
FROM
service_stripe_plan_tier_names
GROUP BY
service_id,
tier_name
),
service_tiers AS (
SELECT
services.id AS service_id,
ARRAY_AGG(
STRUCT(tier.name, tier.subplat_capabilities, service_tier_stripe_plan_ids.stripe_plan_ids)
ORDER BY
tier_order
) AS tiers
FROM
services
CROSS JOIN
UNNEST(services.tiers) AS tier
WITH OFFSET AS tier_order
LEFT JOIN
service_tier_stripe_plan_ids
ON
services.id = service_tier_stripe_plan_ids.service_id
AND tier.name = service_tier_stripe_plan_ids.tier_name
GROUP BY
service_id
)
SELECT
services.* REPLACE (service_tiers.tiers AS tiers),
service_stripe_product_ids.stripe_product_ids,
service_stripe_plan_ids.stripe_plan_ids
FROM
services
LEFT JOIN
service_tiers
ON
services.id = service_tiers.service_id
LEFT JOIN
service_stripe_product_ids
ON
services.id = service_stripe_product_ids.service_id
LEFT JOIN
service_stripe_plan_ids
ON
services.id = service_stripe_plan_ids.service_id

Просмотреть файл

@ -0,0 +1,54 @@
fields:
- name: id
type: STRING
mode: NULLABLE
description: Short, unique, human-readable ID for the service.
- name: name
type: STRING
mode: NULLABLE
description: Proper name for the service.
- name: tiers
type: RECORD
mode: REPEATED
description: Tiers for the service in order of precedence (i.e. highest tier first).
fields:
- name: name
type: STRING
mode: NULLABLE
description: Human-readable name for the tier.
- name: subplat_capabilities
type: STRING
mode: REPEATED
description: SubPlat capabilities specific to the tier. Used to correlate with
products/plans based on Stripe metadata.
- name: stripe_plan_ids
type: STRING
mode: REPEATED
description: IDs of Stripe plans with any of the tier's SubPlat capabilities.
- name: subplat_capabilities
type: STRING
mode: REPEATED
description: All SubPlat capabilities associated with the service. Used to correlate
with products/plans based on Stripe metadata.
- name: subplat_oauth_clients
type: RECORD
mode: REPEATED
description: SubPlat OAuth clients associated with the service. Used to correlate
with products/plans based on Stripe metadata, and SubPlat logs.
fields:
- name: id
type: STRING
mode: NULLABLE
description: OAuth client ID.
- name: name
type: STRING
mode: NULLABLE
description: OAuth client name.
- name: stripe_product_ids
type: STRING
mode: REPEATED
description: IDs of Stripe products with any of the service's SubPlat capabilities.
- name: stripe_plan_ids
type: STRING
mode: REPEATED
description: IDs of Stripe plans with any of the service's SubPlat capabilities.

Просмотреть файл

@ -0,0 +1,26 @@
friendly_name: Stripe logical subscriptions history
description: |-
History of changes to Stripe logical subscriptions, which are a continuous active period for a particular subscription.
To get the historical state at a particular point in time use a condition like the following:
valid_from <= {timestamp}
AND valid_to > {timestamp}
owners:
- srose@mozilla.com
labels:
incremental: false
schedule: daily
scheduling:
dag_name: bqetl_subplat
# The whole table is overwritten every time, not a specific date partition.
date_partition_parameter: null
bigquery:
time_partitioning:
type: day
field: valid_to
require_partition_filter: false
expiration_days: null
clustering:
fields:
- valid_from
references: {}

Просмотреть файл

@ -0,0 +1,211 @@
WITH subscriptions_history AS (
SELECT
id,
valid_from,
valid_to,
subscription,
customer,
-- This should be kept in agreement with what SubPlat considers an active Stripe subscription.
-- https://github.com/mozilla/fxa/blob/56026cd08e60525823c60c4f4116f705e79d6124/packages/fxa-shared/subscriptions/stripe.ts#L19-L24
subscription.status IN ('active', 'past_due', 'trialing') AS subscription_is_active
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.stripe_subscriptions_history_v2`
),
active_subscriptions_history AS (
-- Only include a subscription's history once it becomes active.
SELECT
*,
FIRST_VALUE(
IF(subscription_is_active, valid_from, NULL) IGNORE NULLS
) OVER subscription_history_to_date_asc AS subscription_first_active_at
FROM
subscriptions_history
QUALIFY
LOGICAL_OR(subscription_is_active) OVER subscription_history_to_date_asc
WINDOW
subscription_history_to_date_asc AS (
PARTITION BY
subscription.id
ORDER BY
valid_from
ROWS BETWEEN
UNBOUNDED PRECEDING
AND CURRENT ROW
)
),
plan_services AS (
SELECT
plan_id,
ARRAY_AGG(
STRUCT(services.id, services.name, tier.name AS tier)
ORDER BY
services.id
) AS services
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.services_v1` AS services
CROSS JOIN
UNNEST(services.stripe_plan_ids) AS plan_id
LEFT JOIN
UNNEST(services.tiers) AS tier
ON
plan_id IN UNNEST(tier.stripe_plan_ids)
GROUP BY
plan_id
),
paypal_subscriptions AS (
SELECT DISTINCT
subscription_id
FROM
`moz-fx-data-shared-prod.stripe_external.invoice_v1`
WHERE
JSON_VALUE(metadata, '$.paypalTransactionId') IS NOT NULL
),
subscriptions_history_charge_summaries AS (
SELECT
history.id AS subscriptions_history_id,
ARRAY_AGG(
cards.country IGNORE NULLS
ORDER BY
-- Prefer charges that succeeded.
IF(charges.status = 'succeeded', 1, 2),
charges.created DESC
LIMIT
1
)[SAFE_ORDINAL(1)] AS latest_card_country,
LOGICAL_OR(refunds.status = 'succeeded') AS has_refunds,
LOGICAL_OR(
charges.fraud_details_user_report = 'fraudulent'
OR (
charges.fraud_details_stripe_report = 'fraudulent'
AND charges.fraud_details_user_report IS DISTINCT FROM 'safe'
)
OR (refunds.reason = 'fraudulent' AND refunds.status = 'succeeded')
) AS has_fraudulent_charges
FROM
`moz-fx-data-shared-prod.stripe_external.charge_v1` AS charges
JOIN
`moz-fx-data-shared-prod.stripe_external.invoice_v1` AS invoices
ON
charges.invoice_id = invoices.id
JOIN
active_subscriptions_history AS history
ON
invoices.subscription_id = history.subscription.id
AND charges.created < history.valid_to
LEFT JOIN
`moz-fx-data-shared-prod.stripe_external.card_v1` AS cards
ON
charges.card_id = cards.id
LEFT JOIN
`moz-fx-data-shared-prod.stripe_external.refund_v1` AS refunds
ON
charges.id = refunds.charge_id
GROUP BY
subscriptions_history_id
)
SELECT
CONCAT(
'Stripe-',
history.subscription.id,
'-',
FORMAT_TIMESTAMP('%FT%H:%M:%E6S', history.subscription_first_active_at),
'-',
FORMAT_TIMESTAMP('%FT%H:%M:%E6S', history.valid_from)
) AS id,
history.valid_from,
history.valid_to,
history.id AS provider_subscriptions_history_id,
STRUCT(
CONCAT(
'Stripe-',
history.subscription.id,
'-',
FORMAT_TIMESTAMP('%FT%H:%M:%E6S', history.subscription_first_active_at)
) AS id,
'Stripe' AS provider,
IF(paypal_subscriptions.subscription_id IS NOT NULL, 'PayPal', 'Stripe') AS payment_provider,
history.subscription.id AS provider_subscription_id,
subscription_item.id AS provider_subscription_item_id,
history.subscription.created AS provider_subscription_created_at,
history.subscription.customer_id AS provider_customer_id,
history.customer.metadata.userid AS mozilla_account_id,
history.customer.metadata.userid_sha256 AS mozilla_account_id_sha256,
CASE
-- Use the same address hierarchy as Stripe Tax after we enabled Stripe Tax (FXA-5457).
-- https://stripe.com/docs/tax/customer-locations#address-hierarchy
WHEN DATE(history.valid_to) >= '2022-12-01'
AND (
DATE(history.subscription.ended_at) >= '2022-12-01'
OR history.subscription.ended_at IS NULL
)
THEN COALESCE(
NULLIF(history.customer.shipping.address.country, ''),
NULLIF(history.customer.address.country, ''),
charge_summaries.latest_card_country
)
-- SubPlat copies the PayPal billing agreement country to the customer's address.
WHEN paypal_subscriptions.subscription_id IS NOT NULL
THEN NULLIF(history.customer.address.country, '')
ELSE charge_summaries.latest_card_country
END AS country_code,
plan_services.services,
subscription_item.plan.product.id AS provider_product_id,
subscription_item.plan.product.name AS product_name,
subscription_item.plan.id AS provider_plan_id,
subscription_item.plan.`interval` AS plan_interval_type,
subscription_item.plan.interval_count AS plan_interval_count,
UPPER(subscription_item.plan.currency) AS plan_currency,
(CAST(subscription_item.plan.amount AS DECIMAL) / 100) AS plan_amount,
IF(ARRAY_LENGTH(plan_services.services) > 1, TRUE, FALSE) AS is_bundle,
IF(
history.subscription.status = 'trialing'
OR (
history.subscription.ended_at
BETWEEN history.subscription.trial_start
AND history.subscription.trial_end
),
TRUE,
FALSE
) AS is_trial,
history.subscription_is_active AS is_active,
history.subscription.status AS provider_status,
history.subscription_first_active_at AS started_at,
history.subscription.ended_at,
-- TODO: ended_reason
IF(
history.subscription.ended_at IS NULL,
history.subscription.current_period_start,
NULL
) AS current_period_started_at,
IF(
history.subscription.ended_at IS NULL,
history.subscription.current_period_end,
NULL
) AS current_period_ends_at,
history.subscription.cancel_at_period_end IS NOT TRUE AS auto_renew,
IF(
history.subscription.cancel_at_period_end,
history.subscription.canceled_at,
NULL
) AS auto_renew_disabled_at,
-- TODO: promotion_codes
-- TODO: promotion_discounts_amount
COALESCE(charge_summaries.has_refunds, FALSE) AS has_refunds,
COALESCE(charge_summaries.has_fraudulent_charges, FALSE) AS has_fraudulent_charges
) AS subscription
FROM
active_subscriptions_history AS history
CROSS JOIN
UNNEST(history.subscription.items) AS subscription_item
LEFT JOIN
plan_services
ON
subscription_item.plan.id = plan_services.plan_id
LEFT JOIN
paypal_subscriptions
ON
history.subscription.id = paypal_subscriptions.subscription_id
LEFT JOIN
subscriptions_history_charge_summaries AS charge_summaries
ON
history.id = charge_summaries.subscriptions_history_id

Просмотреть файл

@ -0,0 +1,117 @@
fields:
- name: id
type: STRING
mode: NULLABLE
- name: valid_from
type: TIMESTAMP
mode: NULLABLE
- name: valid_to
type: TIMESTAMP
mode: NULLABLE
- name: provider_subscriptions_history_id
type: STRING
mode: NULLABLE
- name: subscription
type: RECORD
mode: NULLABLE
fields:
- name: id
type: STRING
mode: NULLABLE
- name: provider
type: STRING
mode: NULLABLE
- name: payment_provider
type: STRING
mode: NULLABLE
- name: provider_subscription_id
type: STRING
mode: NULLABLE
- name: provider_subscription_item_id
type: STRING
mode: NULLABLE
- name: provider_subscription_created_at
type: TIMESTAMP
mode: NULLABLE
- name: provider_customer_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id_sha256
type: STRING
mode: NULLABLE
- name: country_code
type: STRING
mode: NULLABLE
- name: services
type: RECORD
mode: REPEATED
fields:
- name: id
type: STRING
mode: NULLABLE
- name: name
type: STRING
mode: NULLABLE
- name: tier
type: STRING
mode: NULLABLE
- name: provider_product_id
type: STRING
mode: NULLABLE
- name: product_name
type: STRING
mode: NULLABLE
- name: provider_plan_id
type: STRING
mode: NULLABLE
- name: plan_interval_type
type: STRING
mode: NULLABLE
- name: plan_interval_count
type: INTEGER
mode: NULLABLE
- name: plan_currency
type: STRING
mode: NULLABLE
- name: plan_amount
type: NUMERIC
mode: NULLABLE
- name: is_bundle
type: BOOLEAN
mode: NULLABLE
- name: is_trial
type: BOOLEAN
mode: NULLABLE
- name: is_active
type: BOOLEAN
mode: NULLABLE
- name: provider_status
type: STRING
mode: NULLABLE
- name: started_at
type: TIMESTAMP
mode: NULLABLE
- name: ended_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_started_at
type: TIMESTAMP
mode: NULLABLE
- name: current_period_ends_at
type: TIMESTAMP
mode: NULLABLE
- name: auto_renew
type: BOOLEAN
mode: NULLABLE
- name: auto_renew_disabled_at
type: TIMESTAMP
mode: NULLABLE
- name: has_refunds
type: BOOLEAN
mode: NULLABLE
- name: has_fraudulent_charges
type: BOOLEAN
mode: NULLABLE

Просмотреть файл

@ -223,6 +223,7 @@ questionable_subscription_plans_history AS (
plans.trial_period_days,
plans.usage_type
) AS plan,
ROW_NUMBER() OVER subscription_plan_changes_asc AS subscription_plan_number,
LAG(plan_changes.plan_id) OVER subscription_plan_changes_asc AS previous_plan_id,
plan_changes.subscription_plan_start AS valid_from,
COALESCE(
@ -265,8 +266,7 @@ synthetic_subscription_start_changelog AS (
questionable_subscription_plans_history AS plans_history
ON
changelog.subscription.id = plans_history.subscription_id
AND changelog.subscription.start_date >= plans_history.valid_from
AND changelog.subscription.start_date < plans_history.valid_to
AND plans_history.subscription_plan_number = 1
),
synthetic_plan_change_changelog AS (
SELECT
@ -287,7 +287,8 @@ synthetic_plan_change_changelog AS (
ON
plans_history.subscription_id = changelog.subscription.id
WHERE
plans_history.valid_from > changelog.subscription.start_date
plans_history.subscription_plan_number > 1
AND plans_history.valid_from > changelog.subscription.start_date
),
synthetic_trial_start_changelog AS (
SELECT

Просмотреть файл

@ -0,0 +1,25 @@
friendly_name: SubPlat attribution impressions
description: |-
Subscription service attribution impressions from SubPlat logs.
owners:
- srose@mozilla.com
labels:
incremental: true
schedule: daily
scheduling:
dag_name: bqetl_subplat
depends_on_past: true
# The whole table is overwritten every time, not a specific date partition.
date_partition_parameter: null
parameters:
- 'date:DATE:{{ds}}'
bigquery:
time_partitioning:
type: day
field: impression_at
require_partition_filter: false
expiration_days: null
clustering:
fields:
- flow_id
references: {}

Просмотреть файл

@ -0,0 +1,158 @@
WITH services AS (
SELECT
id,
ARRAY(SELECT id FROM UNNEST(subplat_oauth_clients)) AS subplat_oauth_client_ids,
ARRAY(SELECT name FROM UNNEST(subplat_oauth_clients)) AS subplat_oauth_client_names,
stripe_product_ids,
stripe_plan_ids
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.services_v1`
),
new_service_flow_events AS (
SELECT
events.flow_id,
MIN(events.event_time) OVER (PARTITION BY events.flow_id) AS flow_min_event_time,
events.event_time,
events.event_type,
events.entrypoint,
events.entrypoint_experiment,
events.entrypoint_variation,
events.utm_campaign,
events.utm_content,
events.utm_medium,
events.utm_source,
events.utm_term,
events.mozilla_account_id_sha256,
events.oauth_client_id,
events.oauth_client_name,
events.product_id,
events.plan_id,
services.id AS service_id
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.subplat_flow_events_v1` AS events
-- If multiple services match this join it can cause fan-outs, but the way we aggregate things this doesn't matter.
LEFT JOIN
services
ON
events.oauth_client_id IN UNNEST(services.subplat_oauth_client_ids)
OR events.oauth_client_name IN UNNEST(services.subplat_oauth_client_names)
-- For a while Bedrock incorrectly passed VPN's OAuth client name as the OAuth client ID.
OR events.oauth_client_id IN UNNEST(services.subplat_oauth_client_names)
OR events.product_id IN UNNEST(services.stripe_product_ids)
OR events.plan_id IN UNNEST(services.stripe_plan_ids)
WHERE
{% if is_init() %}
DATE(events.log_timestamp) < CURRENT_DATE()
{% else %}
-- Reprocess the previous day's events as well in case a flow spanned multiple days
-- but wasn't saved here on the initial day due to not having attribution values yet.
(DATE(events.log_timestamp) BETWEEN (@date - 1) AND @date)
{% endif %}
),
service_flow_events AS (
{% if is_init() %}
SELECT
*
FROM
new_service_flow_events
{% else %}
SELECT
*
FROM
new_service_flow_events
UNION ALL
SELECT
flow_id,
flow_started_at AS flow_min_event_time,
impression_at AS event_time,
event_type,
entrypoint,
entrypoint_experiment,
entrypoint_variation,
utm_campaign,
utm_content,
utm_medium,
utm_source,
utm_term,
mozilla_account_id_sha256,
oauth_client_id,
oauth_client_name,
product_id,
plan_id,
service_id
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.subplat_attribution_impressions_v1`
-- Unnesting these arrays can cause fan-outs, but the way we reaggregate things this doesn't matter.
LEFT JOIN
UNNEST(mozilla_account_ids_sha256) AS mozilla_account_id_sha256
LEFT JOIN
UNNEST(oauth_client_ids) AS oauth_client_id
LEFT JOIN
UNNEST(oauth_client_names) AS oauth_client_name
LEFT JOIN
UNNEST(product_ids) AS product_id
LEFT JOIN
UNNEST(plan_ids) AS plan_id
LEFT JOIN
UNNEST(service_ids) AS service_id
{% endif %}
)
SELECT
flow_id,
MIN(flow_min_event_time) AS flow_started_at,
ARRAY_AGG(
IF(
entrypoint_experiment IS NOT NULL
OR entrypoint_variation IS NOT NULL
OR utm_campaign IS NOT NULL
OR utm_content IS NOT NULL
OR utm_medium IS NOT NULL
OR utm_source IS NOT NULL
OR utm_term IS NOT NULL,
STRUCT(
event_time AS impression_at,
event_type,
entrypoint,
entrypoint_experiment,
entrypoint_variation,
utm_campaign,
utm_content,
utm_medium,
utm_source,
utm_term
),
NULL
) IGNORE NULLS
ORDER BY
event_time
LIMIT
1
)[ORDINAL(1)].*,
ARRAY_AGG(
DISTINCT mozilla_account_id_sha256 IGNORE NULLS
ORDER BY
mozilla_account_id_sha256
) AS mozilla_account_ids_sha256,
ARRAY_AGG(DISTINCT oauth_client_id IGNORE NULLS ORDER BY oauth_client_id) AS oauth_client_ids,
ARRAY_AGG(
DISTINCT oauth_client_name IGNORE NULLS
ORDER BY
oauth_client_name
) AS oauth_client_names,
ARRAY_AGG(DISTINCT product_id IGNORE NULLS ORDER BY product_id) AS product_ids,
ARRAY_AGG(DISTINCT plan_id IGNORE NULLS ORDER BY plan_id) AS plan_ids,
ARRAY_AGG(DISTINCT service_id IGNORE NULLS ORDER BY service_id) AS service_ids
FROM
service_flow_events AS events
GROUP BY
flow_id
HAVING
LOGICAL_OR(
events.entrypoint_experiment IS NOT NULL
OR events.entrypoint_variation IS NOT NULL
OR events.utm_campaign IS NOT NULL
OR events.utm_content IS NOT NULL
OR events.utm_medium IS NOT NULL
OR events.utm_source IS NOT NULL
OR events.utm_term IS NOT NULL
)

Просмотреть файл

@ -0,0 +1,55 @@
fields:
- name: flow_id
type: STRING
mode: NULLABLE
- name: flow_started_at
type: TIMESTAMP
mode: NULLABLE
- name: impression_at
type: TIMESTAMP
mode: NULLABLE
- name: event_type
type: STRING
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: mozilla_account_ids_sha256
type: STRING
mode: REPEATED
- name: oauth_client_ids
type: STRING
mode: REPEATED
- name: oauth_client_names
type: STRING
mode: REPEATED
- name: product_ids
type: STRING
mode: REPEATED
- name: plan_ids
type: STRING
mode: REPEATED
- name: service_ids
type: STRING
mode: REPEATED

Просмотреть файл

@ -0,0 +1,22 @@
friendly_name: SubPlat flow events
description: |-
Subscription service flow events from SubPlat logs.
owners:
- srose@mozilla.com
labels:
incremental: true
schedule: daily
scheduling:
dag_name: bqetl_subplat
depends_on_past: true
date_partition_parameter: date
bigquery:
time_partitioning:
type: day
field: log_timestamp
require_partition_filter: false
expiration_days: null
clustering:
fields:
- flow_id
references: {}

Просмотреть файл

@ -0,0 +1,84 @@
WITH new_flow_events AS (
SELECT
logger,
`timestamp` AS log_timestamp,
COALESCE(event_time, `timestamp`) AS event_time,
event_type,
flow_id,
user_id AS mozilla_account_id_sha256,
oauth_client_id,
service AS oauth_client_name,
checkout_type,
payment_provider,
subscription_id,
product_id,
plan_id,
entrypoint,
entrypoint_experiment,
entrypoint_variation,
utm_campaign,
utm_content,
utm_medium,
utm_source,
utm_term,
promotion_code,
country_code_source,
country_code,
country,
`language`,
os_name,
os_version,
ua_browser,
ua_version,
FROM
`moz-fx-data-shared-prod.firefox_accounts.fxa_all_events`
WHERE
fxa_log IN ('content', 'auth', 'stdout')
AND flow_id IS NOT NULL
{% if is_init() %}
AND DATE(`timestamp`) < CURRENT_DATE()
{% else %}
AND DATE(`timestamp`) = @date
{% endif %}
),
services_metadata AS (
SELECT
ARRAY_AGG(DISTINCT subplat_oauth_client.id IGNORE NULLS) AS subplat_oauth_client_ids,
ARRAY_AGG(DISTINCT subplat_oauth_client.name IGNORE NULLS) AS subplat_oauth_client_names
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.services_v1`
LEFT JOIN
UNNEST(subplat_oauth_clients) AS subplat_oauth_client
),
existing_flow_ids AS (
{% if is_init() %}
SELECT
CAST(NULL AS STRING) AS flow_id
{% else %}
SELECT DISTINCT
flow_id
FROM
`moz-fx-data-shared-prod.subscription_platform_derived.subplat_flow_events_v1`
{% endif %}
)
SELECT
new_flow_events.*
FROM
new_flow_events
CROSS JOIN
services_metadata
LEFT JOIN
existing_flow_ids
ON
new_flow_events.flow_id = existing_flow_ids.flow_id
QUALIFY
LOGICAL_OR(
new_flow_events.oauth_client_id IN UNNEST(services_metadata.subplat_oauth_client_ids)
OR new_flow_events.oauth_client_name IN UNNEST(services_metadata.subplat_oauth_client_names)
-- For a while Bedrock incorrectly passed VPN's OAuth client name as the OAuth client ID.
OR new_flow_events.oauth_client_id IN UNNEST(services_metadata.subplat_oauth_client_names)
OR new_flow_events.subscription_id IS NOT NULL
OR new_flow_events.product_id IS NOT NULL
OR new_flow_events.plan_id IS NOT NULL
) OVER (PARTITION BY new_flow_events.flow_id)
OR existing_flow_ids.flow_id IS NOT NULL

Просмотреть файл

@ -0,0 +1,91 @@
fields:
- name: logger
type: STRING
mode: NULLABLE
- name: log_timestamp
type: TIMESTAMP
mode: NULLABLE
- name: event_time
type: TIMESTAMP
mode: NULLABLE
- name: event_type
type: STRING
mode: NULLABLE
- name: flow_id
type: STRING
mode: NULLABLE
- name: mozilla_account_id_sha256
type: STRING
mode: NULLABLE
- name: oauth_client_id
type: STRING
mode: NULLABLE
- name: oauth_client_name
type: STRING
mode: NULLABLE
- name: checkout_type
type: STRING
mode: NULLABLE
- name: payment_provider
type: STRING
mode: NULLABLE
- name: subscription_id
type: STRING
mode: NULLABLE
- name: product_id
type: STRING
mode: NULLABLE
- name: plan_id
type: STRING
mode: NULLABLE
- name: entrypoint
type: STRING
mode: NULLABLE
- name: entrypoint_experiment
type: STRING
mode: NULLABLE
- name: entrypoint_variation
type: STRING
mode: NULLABLE
- name: utm_campaign
type: STRING
mode: NULLABLE
- name: utm_content
type: STRING
mode: NULLABLE
- name: utm_medium
type: STRING
mode: NULLABLE
- name: utm_source
type: STRING
mode: NULLABLE
- name: utm_term
type: STRING
mode: NULLABLE
- name: promotion_code
type: STRING
mode: NULLABLE
- name: country_code_source
type: STRING
mode: NULLABLE
- name: country_code
type: STRING
mode: NULLABLE
- name: country
type: STRING
mode: NULLABLE
- name: language
type: STRING
mode: NULLABLE
- name: os_name
type: STRING
mode: NULLABLE
- name: os_version
type: STRING
mode: NULLABLE
- name: ua_browser
type: STRING
mode: NULLABLE
- name: ua_version
type: STRING
mode: NULLABLE