From d8ea779d2beafc64417f70e945482ea531983dae Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Tue, 30 Jan 2024 22:00:16 +0000 Subject: [PATCH] feat(billing): Add views for monthly active user counts --- .../82/08_hcp_user_billing_view.up.sql | 67 +++ .../hcp/billing/monthly_active_users.sql | 470 ++++++++++++++++++ .../hcp/billing/monthly_active_users_all.sql | 100 ++++ 3 files changed, 637 insertions(+) create mode 100644 internal/db/schema/migrations/oss/postgres/82/08_hcp_user_billing_view.up.sql create mode 100644 internal/db/sqltest/tests/hcp/billing/monthly_active_users.sql create mode 100644 internal/db/sqltest/tests/hcp/billing/monthly_active_users_all.sql diff --git a/internal/db/schema/migrations/oss/postgres/82/08_hcp_user_billing_view.up.sql b/internal/db/schema/migrations/oss/postgres/82/08_hcp_user_billing_view.up.sql new file mode 100644 index 0000000000..0c363f8788 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/82/08_hcp_user_billing_view.up.sql @@ -0,0 +1,67 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + create view hcp_billing_monthly_active_users_last_2_months as + with + monthly_range (month) as ( + select date_trunc('month', time) as month + from generate_series( + date_trunc('month', now() - interval '1 month'), + now(), + '1 month'::interval + ) as time + ), + final (start_time, end_time, active_users_count) as ( + select monthly_range.month, -- start + case when monthly_range.month = date_trunc('month', now()) + then date_trunc('hour', now()) + else monthly_range.month + interval '1 month' + end, + count(distinct(user_id)) + from monthly_range + left join wh_auth_token_accumulating_fact as wh_ataf + on wh_ataf.auth_token_approximate_active_time_range && tstzrange(monthly_range.month, monthly_range.month + interval '1 month', '[)') + group by monthly_range.month + ) + select start_time, end_time, active_users_count + from final + order by start_time desc; + comment on view hcp_billing_monthly_active_users_last_2_months is + 'hcp_billing_monthly_active_users_last_2_months is a view that contains ' + 'the count of active users for the current month and the previous month. ' + 'The current month is a sum from the beginning of the current month ' + 'until the start of the current hour (exclusive).'; + + create view hcp_billing_monthly_active_users_all as + with + monthly_range (month) as ( + select date_trunc('month', time) as month + from generate_series( + date_trunc('month', (select min(auth_token_issued_time) + from wh_auth_token_accumulating_fact)), + now(), + '1 month'::interval + ) as time + ), + final (start_time, end_time, active_users_count) as ( + select monthly_range.month, -- start + case when monthly_range.month = date_trunc('month', now()) + then date_trunc('hour', now()) + else monthly_range.month + interval '1 month' + end, + count(distinct(user_id)) + from monthly_range + left join wh_auth_token_accumulating_fact as wh_ataf + on wh_ataf.auth_token_approximate_active_time_range && tstzrange(monthly_range.month, monthly_range.month + interval '1 month', '[)') + group by monthly_range.month + ) + select start_time, end_time, active_users_count + from final + order by start_time desc; + comment on view hcp_billing_monthly_active_users_all is + 'hcp_billing_monthly_active_users_all is a view that contains ' + 'the count of active users for the all months.' + 'The current month is a sum from the beginning of the current month ' + 'until the start of the current hour (exclusive).'; +commit; diff --git a/internal/db/sqltest/tests/hcp/billing/monthly_active_users.sql b/internal/db/sqltest/tests/hcp/billing/monthly_active_users.sql new file mode 100644 index 0000000000..7613692352 --- /dev/null +++ b/internal/db/sqltest/tests/hcp/billing/monthly_active_users.sql @@ -0,0 +1,470 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + select plan(39); + + select lives_ok('truncate wh_auth_token_accumulating_fact, + wh_user_dimension, + wh_session_accumulating_fact, + wh_session_connection_accumulating_fact;', + 'Truncate tables in preparation for testing'); + + -- validate the warehouse fact tables are empty + select is(count(*), 0::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 0::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + + -- Create a user dimension for corresponding auth token's user. + insert into wh_user_dimension ( + user_id, user_name, user_description, + auth_account_id, auth_account_type, auth_account_name, auth_account_description, + auth_method_id, auth_method_type, auth_method_name, auth_method_description, + user_organization_id, user_organization_name, user_organization_description, + current_row_indicator, + row_effective_time, row_expiration_time, + auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email + ) values ( + 'u_____user1', 'None', 'None', + 'a______acc1', 'None', 'None', 'None', + 'am______am1', 'None', 'None', 'None', + 'o______org1', 'None', 'None', + 'current', + now(), 'infinity'::timestamptz, + 'None', 'None', 'None', 'None' + ); + + -- validate view returns 0 active users for last two months + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 0::bigint) + $$); + + + -- Now insert some edge cases to make sure we count them correctly + + -- active_time_range spans start of a month + with month_range (previous) as ( + select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)') + ), + range (active, valid) as ( + select tstzrange(lower(previous) - interval '5 minutes', lower(previous) + interval '5 minutes', '[]'), + tstzrange(lower(previous) - interval '5 minutes', 'infinity'::timestamptz) + from month_range + ), + user_key (key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'at____atok1', user_key.key, + wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid), + coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid), + wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active), + range.active, + range.valid, + 1 + from range, user_key; + + select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint) + $$); + + -- active_time_range within start/end of month + select lives_ok('truncate wh_auth_token_accumulating_fact'); + with month_range (previous) as ( + select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)') + ), + range (active, valid) as ( + select tstzrange(lower(previous) + interval '5 minutes', lower(previous) + interval '10 minutes', '[]'), + tstzrange(lower(previous) + interval '5 minutes', 'infinity'::timestamptz) + from month_range + ), + user_key (key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'at____atok1', user_key.key, + wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid), + coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid), + wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active), + range.active, + range.valid, + 1 + from range, user_key; + + select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint) + $$); + + -- active_time_range spances end of month + select lives_ok('truncate wh_auth_token_accumulating_fact'); + with month_range (previous) as ( + select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)') + ), + range (active, valid) as ( + select tstzrange(upper(previous) - interval '5 minutes', upper(previous) + interval '5 minutes', '[]'), + tstzrange(upper(previous) - interval '5 minutes', 'infinity'::timestamptz) + from month_range + ), + user_key (key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'at____atok1', user_key.key, + wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid), + coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid), + wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active), + range.active, + range.valid, + 1 + from range, user_key; + + select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 1::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint) + $$); + + -- active_time_range starts on start of month + select lives_ok('truncate wh_auth_token_accumulating_fact'); + with month_range (previous) as ( + select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)') + ), + range (active, valid) as ( + select tstzrange(lower(previous), lower(previous) + interval '5 minutes', '[]'), + tstzrange(lower(previous), 'infinity'::timestamptz) + from month_range + ), + user_key (key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'at____atok1', user_key.key, + wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid), + coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid), + wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active), + range.active, + range.valid, + 1 + from range, user_key; + + select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint) + $$); + + -- active_time_range ends on end of month + select lives_ok('truncate wh_auth_token_accumulating_fact'); + with month_range (previous) as ( + select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)') + ), + range (active, valid) as ( + -- the resolution on a timestamptz is 1 microsecond, so subtracting one microsecond from the upper bound of + -- the month range puts us as close to the "end of month" as possible, wthout going over. + -- https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME + select tstzrange(lower(previous) + interval '5 minutes', upper(previous) - interval '1 microsecond', '[]'), + tstzrange(lower(previous) + interval '5 minutes', 'infinity'::timestamptz) + from month_range + ), + user_key (key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'at____atok1', user_key.key, + wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid), + coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid), + wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active), + range.active, + range.valid, + 1 + from range, user_key; + + select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint) + $$); + + -- active_time_range starts on end of month + select lives_ok('truncate wh_auth_token_accumulating_fact'); + with month_range (previous) as ( + select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)') + ), + range (active, valid) as ( + -- the resolution on a timestamptz is 1 microsecond, so subtracting one microsecond from the upper bound of + -- the month range puts us as close to the "end of month" as possible, wthout going over. + -- https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME + select tstzrange(upper(previous) - interval '1 microsecond', upper(previous) + '5 minutes', '[]'), + tstzrange(upper(previous) - interval '1 microsecond', 'infinity'::timestamptz) + from month_range + ), + user_key (key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'at____atok1', user_key.key, + wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid), + coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid), + wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active), + range.active, + range.valid, + 1 + from range, user_key; + + select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 1::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint) + $$); + + -- active_time_range ends on start of month + select lives_ok('truncate wh_auth_token_accumulating_fact'); + with month_range (previous) as ( + select tstzrange(date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), '[)') + ), + range (active, valid) as ( + select tstzrange(upper(previous) - interval '5 minutes', upper(previous), '[]'), + tstzrange(upper(previous) - interval '5 minutes', 'infinity'::timestamptz) + from month_range + ), + user_key (key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'at____atok1', user_key.key, + wh_date_key(lower(range.valid)), wh_time_key(lower(range.valid)), lower(range.valid), + coalesce(wh_date_key(upper(range.valid)), -1), coalesce(wh_time_key(upper(range.valid)), -1), upper(range.valid), + wh_date_key(upper(range.active)), wh_time_key(upper(range.active)), upper(range.active), + range.active, + range.valid, + 1 + from range, user_key; + + select is(count(*), 1::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 1::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint) + $$); + + -- multiple tokens for a user within month count as single user + select lives_ok('truncate wh_auth_token_accumulating_fact'); + with token (public_id, active) as ( + select * + from (values + ('at____atok1', tstzrange(date_trunc('month', now() - interval '1 month'), + date_trunc('month', now() - interval '1 month') + interval '5 minutes')), + ('at____atok2', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes', + date_trunc('month', now() - interval '1 month') + interval '15 minutes')) + ) as t (public_id, active) + ), + user_key (key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select token.public_id, user_key.key, + wh_date_key(lower(token.active)), wh_time_key(lower(token.active)), lower(token.active), + coalesce(wh_date_key(upper(token.active)), -1), coalesce(wh_time_key(upper(token.active)), -1), upper(token.active), + wh_date_key(upper(token.active)), wh_time_key(upper(token.active)), upper(token.active), + token.active, + token.active, + 1 + from token, user_key; + + select is(count(*), 2::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 1::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 1::bigint) + $$); + + -- multiple tokens for multiple users should get counted correctly + -- Create a user dimension for corresponding auth token's user. + insert into wh_user_dimension ( + user_id, user_name, user_description, + auth_account_id, auth_account_type, auth_account_name, auth_account_description, + auth_method_id, auth_method_type, auth_method_name, auth_method_description, + user_organization_id, user_organization_name, user_organization_description, + current_row_indicator, + row_effective_time, row_expiration_time, + auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email + ) values ( + 'u_____user2', 'None', 'None', + 'a______acc2', 'None', 'None', 'None', + 'am______am1', 'None', 'None', 'None', + 'o______org1', 'None', 'None', + 'current', + now(), 'infinity'::timestamptz, + 'None', 'None', 'None', 'None' + ),( + 'u_____user3', 'None', 'None', + 'a______acc3', 'None', 'None', 'None', + 'am______am1', 'None', 'None', 'None', + 'o______org1', 'None', 'None', + 'current', + now(), 'infinity'::timestamptz, + 'None', 'None', 'None', 'None' + ); + select lives_ok('truncate wh_auth_token_accumulating_fact'); + with token (user_id, public_id, active) as ( + select * + from (values + ('u_____user1', 'at___u1tok1', tstzrange(date_trunc('month', now() - interval '1 month'), + date_trunc('month', now() - interval '1 month') + interval '5 minutes')), + ('u_____user1', 'at___u1tok2', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes', + date_trunc('month', now() - interval '1 month') + interval '15 minutes')), + ('u_____user2', 'at___u2tok1', tstzrange(date_trunc('month', now() - interval '1 month'), + date_trunc('month', now() - interval '1 month') + interval '5 minutes')), + ('u_____user2', 'at___u2tok2', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes', + date_trunc('month', now() - interval '1 month') + interval '15 minutes')), + ('u_____user3', 'at___u3tok1', tstzrange(date_trunc('month', now() - interval '1 month'), + date_trunc('month', now() - interval '1 month') + interval '5 minutes')), + ('u_____user3', 'at___u3tok2', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes', + date_trunc('month', now() - interval '1 month') + interval '15 minutes')), + ('u_____user3', 'at___u3tok3', tstzrange(date_trunc('month', now() - interval '1 month') + interval '10 minutes', + date_trunc('month', now() - interval '1 month') + interval '15 minutes')) + ) as t (user_id, public_id, active) + ), + token_key (user_key, public_id, active) as ( + select whud.key, token.public_id, token.active + from wh_user_dimension as whud + join token + on whud.user_id = token.user_id + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select token_key.public_id, token_key.user_key, + wh_date_key(lower(token_key.active)), wh_time_key(lower(token_key.active)), lower(token_key.active), + coalesce(wh_date_key(upper(token_key.active)), -1), coalesce(wh_time_key(upper(token_key.active)), -1), upper(token_key.active), + wh_date_key(upper(token_key.active)), wh_time_key(upper(token_key.active)), upper(token_key.active), + token_key.active, + token_key.active, + 1 + from token_key; + + select is(count(*), 7::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 3::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_last_2_months', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 3::bigint) + $$); + + select * from finish(); +rollback; diff --git a/internal/db/sqltest/tests/hcp/billing/monthly_active_users_all.sql b/internal/db/sqltest/tests/hcp/billing/monthly_active_users_all.sql new file mode 100644 index 0000000000..87fdbe1bfa --- /dev/null +++ b/internal/db/sqltest/tests/hcp/billing/monthly_active_users_all.sql @@ -0,0 +1,100 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + select plan(4); + select lives_ok('truncate wh_auth_token_accumulating_fact, + wh_user_dimension, + wh_session_accumulating_fact, + wh_session_connection_accumulating_fact;', + 'Truncate tables in preparation for testing'); + + with + month_range (date_key, time_key, month) as ( + select wh_date_key(s), wh_time_key(s), s + from generate_series(date_trunc('month', now()) - interval '1 year', + date_trunc('month', now()) - interval '1 month', + interval '1 month') as s + ), + users (user_id, u) as ( + select 'u_____user'||u, u + from generate_series(1, 6, 1) as u + ), + user_key (key, user_id) as ( + insert into wh_user_dimension ( + user_id, user_name, user_description, + auth_account_id, auth_account_type, auth_account_name, auth_account_description, + auth_method_id, auth_method_type, auth_method_name, auth_method_description, + user_organization_id, user_organization_name, user_organization_description, + current_row_indicator, + row_effective_time, row_expiration_time, + auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email) + select users.user_id, 'None', 'None', + 'a______acc1', 'None', 'None', 'None', + 'am______am1', 'None', 'None', 'None', + 'o______org1', 'None', 'None', + 'current', + now(), 'infinity'::timestamptz, + 'None', 'None', 'None', 'None' + from users + returning key, user_id + ), + tokens (date_key, time_key, user_id, token_id) as ( + select wh_date_key(s), wh_time_key(s), users.user_id, 't_____u'||users.u||'tok'||s as token_id + from users, + generate_series(date_trunc('month', now()) - interval '1 year', + date_trunc('month', now()) - interval '1 month', + interval '1 month') as s + ), + tokens_user_keys (date_key, time_key, user_id, token_id, user_key) as ( + select tokens.date_key, tokens.time_key, tokens.user_id, tokens.token_id, user_key.key + from tokens + join user_key + on user_key.user_id = tokens.user_id + ), + auth_tokens (user_key, user_id, token_id, valid_range) as ( + select tokens_user_keys.user_key, tokens_user_keys.user_id, tokens_user_keys.token_id, tstzrange(month_range.month, month_range.month + interval '5 minutes', '[)') + from tokens_user_keys + join month_range + on month_range.date_key = tokens_user_keys.date_key + and month_range.time_key = tokens_user_keys.time_key + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, user_key, + auth_token_issued_date_key, auth_token_issued_time_key, auth_token_issued_time, + auth_token_deleted_date_key, auth_token_deleted_time_key, auth_token_deleted_time, + auth_token_approximate_last_access_date_key, auth_token_approximate_last_access_time_key, auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select auth_tokens.token_id, auth_tokens.user_key, + wh_date_key(lower(auth_tokens.valid_range)), wh_time_key(lower(auth_tokens.valid_range)), lower(auth_tokens.valid_range), + coalesce(wh_date_key(upper(auth_tokens.valid_range)), -1), coalesce(wh_time_key(upper(auth_tokens.valid_range)), -1), upper(auth_tokens.valid_range), + wh_date_key(upper(auth_tokens.valid_range)), wh_time_key(upper(auth_tokens.valid_range)), upper(auth_tokens.valid_range), + auth_tokens.valid_range, + auth_tokens.valid_range, + 1 + from auth_tokens; + select is(count(*), 72::bigint, 'wh_auth_token_accumulating_fact is not empty') from wh_auth_token_accumulating_fact; + select is(count(*), 6::bigint, 'wh_user_dimension is not empty') from wh_user_dimension; + select results_eq( + 'select * from hcp_billing_monthly_active_users_all', + $$ + values (date_trunc('month', now()), date_trunc('hour', now()), 0::bigint), + (date_trunc('month', now() - interval '1 month'), date_trunc('month', now()), 6::bigint), + (date_trunc('month', now() - interval '2 month'), date_trunc('month', now() - interval '1 month'), 6::bigint), + (date_trunc('month', now() - interval '3 month'), date_trunc('month', now() - interval '2 month'), 6::bigint), + (date_trunc('month', now() - interval '4 month'), date_trunc('month', now() - interval '3 month'), 6::bigint), + (date_trunc('month', now() - interval '5 month'), date_trunc('month', now() - interval '4 month'), 6::bigint), + (date_trunc('month', now() - interval '6 month'), date_trunc('month', now() - interval '5 month'), 6::bigint), + (date_trunc('month', now() - interval '7 month'), date_trunc('month', now() - interval '6 month'), 6::bigint), + (date_trunc('month', now() - interval '8 month'), date_trunc('month', now() - interval '7 month'), 6::bigint), + (date_trunc('month', now() - interval '9 month'), date_trunc('month', now() - interval '8 month'), 6::bigint), + (date_trunc('month', now() - interval '10 month'), date_trunc('month', now() - interval '9 month'), 6::bigint), + (date_trunc('month', now() - interval '11 month'), date_trunc('month', now() - interval '10 month'), 6::bigint), + (date_trunc('month', now() - interval '12 month'), date_trunc('month', now() - interval '11 month'), 6::bigint) + $$); + + select * from finish(); +rollback;