-
Notifications
You must be signed in to change notification settings - Fork 24
Add slack analytics project #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
MajaCernja
wants to merge
10
commits into
dbt-labs:main
Choose a base branch
from
MajaCernja:add-slack-analytics-project
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
9dcce3c
Add slack analytics dbt project
MajaCernja 44a9eb9
Add helper script to mock slack reactions data
MajaCernja d423fa4
Add slack analytics task 1 recreating fct model
MajaCernja 2473009
Add slack analytics task 2 recreating custom macro
MajaCernja 2eb2549
Add slack analytics task 3 to fix type bug
MajaCernja a377e6a
Add slack analytics task 4 solving incorrect CTE reference
MajaCernja af740a7
Propagate built-in TimeoutError
MajaCernja c159a9f
Fix JSON <> VARCHAR duckdb comparison issue
MajaCernja 4f4e418
Add slack analytics task 5 fix dedupe bug
MajaCernja 647dec8
Add slack analytics task 6 build mart model
MajaCernja File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Generate mock reactions data for channel_messages CSV files. | ||
|
|
||
| This script takes an input CSV containing channel_messages data (without reactions) | ||
| and outputs a new CSV with a generated 'reactions' column containing realistic mock data. | ||
| """ | ||
|
|
||
| import argparse | ||
| import json | ||
| import random | ||
| from pathlib import Path | ||
|
|
||
| import pandas as pd | ||
|
|
||
| REACTION_NAMES = [ | ||
| "approved", | ||
| "merged", | ||
| "raised_hands", | ||
| "pray", | ||
| "laugh", | ||
| "ty", | ||
| "this", | ||
| "point_up", | ||
| "white_check_mark", | ||
| ] | ||
|
|
||
| MOCK_SLACK_USER_IDS = [ | ||
| "U1FM88J9J", | ||
| "UNC2V4ZD7", | ||
| "U7W96XW2A", | ||
| "US3QTX4Z1", | ||
| "UQY0NPYWN", | ||
| "UR257U86O", | ||
| "UG6UG9470", | ||
| "U0KG42Q0P", | ||
| "UEA8H90WN", | ||
| "UNNH8IW78", | ||
| "U3GY5K9EQ", | ||
| "UL9VWQ9S2", | ||
| "UORS33I12", | ||
| "U8S28EIM6", | ||
| "UWL5JX23M", | ||
| "UV77JEL7Q", | ||
| "U2M260029", | ||
| "UFF1HYHJ2", | ||
| "UDC0ZA535", | ||
| "UB81I8027", | ||
| "U3HY0BS3W", | ||
| "UL92954KA", | ||
| "UZ5125Y28", | ||
| "U168N7Y9G", | ||
| "UE83A3954", | ||
| "UP9T7VJBF", | ||
| "UK839UQ5R", | ||
| "UCL2RJ89X", | ||
| "UFUV7GPHM", | ||
| "UNVWFC5Q1", | ||
| "UY640917T", | ||
| "UQSF747QR", | ||
| "U34AX16M0", | ||
| "UWBXBIUB7", | ||
| "U2QXC4ZNG", | ||
| "UN979RN92", | ||
| "UH749DC04", | ||
| "UL2989703", | ||
| "U4095YWYD", | ||
| "U24SJD0U3", | ||
| "U7ZP08017", | ||
| "U0GBC0N90", | ||
| "UU6K92DUR", | ||
| "UNY6WDI60", | ||
| "UJ65P6F44", | ||
| "U3TE14ET2", | ||
| "U96N45CL2", | ||
| "UK4V3ON2O", | ||
| "UKA77QAYY", | ||
| "U81OT695K", | ||
| "UPT27H737", | ||
| "UOBK5OFZ0", | ||
| "U0EIW7DOX", | ||
| "UZ271WU40", | ||
| "UYVM94HE1", | ||
| "UEZMYV74R", | ||
| "UYEP6YY91", | ||
| "U9E255NN9", | ||
| "ULRS0HVJ3", | ||
| "U378U76S4", | ||
| "UK756E555", | ||
| "U6P582A60", | ||
| "UL96240J4", | ||
| "UU95TZPYP", | ||
| "U306GEBY8", | ||
| "U549234TU", | ||
| "UIX8TSI0Y", | ||
| "UE0WA7608", | ||
| "U9YFOYM78", | ||
| "UZ90K3JDW", | ||
| "U5V38TNV1", | ||
| "U0381889F", | ||
| "UF39WLXAT", | ||
| "UHGL04640", | ||
| "UA7M10C0O", | ||
| "UB51ZBPGJ", | ||
| "UNS22LGIY", | ||
| "UN0J0I7F0", | ||
| "UH1039P7I", | ||
| "UO513ARUP", | ||
| "U3O3NUM10", | ||
| "UKVN1ZI1Z", | ||
| "ULAN263E4", | ||
| "U7PX1XVTE", | ||
| "UIDQ8QXI5", | ||
| "UV4M04Y17", | ||
| "U0D6182AR", | ||
| "UC3Z15JC3", | ||
| "U84A3Q05B", | ||
| "UWW10VL06", | ||
| "USI1BH3K3", | ||
| "U2XQ3JJAN", | ||
| "UYJ494VWS", | ||
| "ULWL32B29", | ||
| "U91Q0A72O", | ||
| "U85PB3J63", | ||
| "UA3IV9419", | ||
| "UB9LLF16J", | ||
| "U3U77Y17K", | ||
| "U08ITMEA6", | ||
| ] | ||
|
|
||
|
|
||
| def generate_reactions() -> list[dict]: | ||
| """ | ||
| Generate a random list of reactions for a message. | ||
|
|
||
| Returns a list of reaction dicts, each containing: | ||
| - name: reaction name from REACTION_NAMES | ||
| - users: list of user IDs who reacted | ||
| - count: number of users (equals len(users)) | ||
| """ | ||
| # Randomly decide how many reaction types (0 to all available) | ||
| # Weight towards fewer reactions (more realistic) | ||
| num_reaction_types = random.choices( | ||
| range(len(REACTION_NAMES) + 1), | ||
| weights=[40, 25, 15, 10, 5, 3, 1, 0.5, 0.3, 0.2], # Weighted towards 0-2 reactions | ||
| k=1, | ||
| )[0] | ||
|
|
||
| if num_reaction_types == 0: | ||
| return [] | ||
|
|
||
| # Select which reaction types to include | ||
| selected_reactions = random.sample(REACTION_NAMES, num_reaction_types) | ||
|
|
||
| reactions = [] | ||
| for reaction_name in selected_reactions: | ||
| # Randomly decide how many users reacted (1 to ~10 typically) | ||
| # Weight towards fewer users per reaction | ||
| num_users = random.choices( | ||
| range(1, 11), weights=[40, 25, 15, 10, 5, 3, 1, 0.5, 0.3, 0.2], k=1 | ||
| )[0] | ||
|
|
||
| # Select random users | ||
| users = random.sample(MOCK_SLACK_USER_IDS, num_users) | ||
|
|
||
| reactions.append({"name": reaction_name, "users": users, "count": len(users)}) | ||
|
|
||
| return reactions | ||
|
|
||
|
|
||
| def process_csv(input_path: Path, output_path: Path) -> None: | ||
| """ | ||
| Read input CSV, generate reactions for each row, and write output CSV. | ||
| """ | ||
|
|
||
| df = pd.read_csv(input_path) | ||
| df["reactions"] = [json.dumps(generate_reactions()) for _ in range(len(df))] | ||
| df.to_csv(output_path, index=False) | ||
|
|
||
| print(f"Processed {len(df)} rows") | ||
| print(f"Output written to: {output_path}") | ||
|
|
||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser( | ||
| description="Generate mock reactions data for channel_messages CSV files." | ||
| ) | ||
| parser.add_argument( | ||
| "input_file", type=Path, help="Path to input CSV file containing channel_messages data" | ||
| ) | ||
| parser.add_argument( | ||
| "-o", | ||
| "--output", | ||
| type=Path, | ||
| default=None, | ||
| help="Path to output CSV file (default: input_file with '_with_reactions' suffix)", | ||
| ) | ||
| parser.add_argument( | ||
| "--seed", type=int, default=None, help="Random seed for reproducible results" | ||
| ) | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| # Validate input file exists | ||
| if not args.input_file.exists(): | ||
| print(f"Error: Input file not found: {args.input_file}") | ||
| return 1 | ||
|
|
||
| # Set output path | ||
| if args.output is None: | ||
| output_path = args.input_file.with_stem(f"{args.input_file.stem}_with_reactions") | ||
| else: | ||
| output_path = args.output | ||
|
|
||
| # Set random seed if provided | ||
| if args.seed is not None: | ||
| random.seed(args.seed) | ||
|
|
||
| # Process the CSV | ||
| process_csv(args.input_file, output_path) | ||
|
|
||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| exit(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| name: 'slack_analytics' | ||
| version: '1.0.0' | ||
| config-version: 2 | ||
|
|
||
| profile: 'slack-analytics-duckdb' | ||
|
|
||
| # These configurations specify where dbt should look for different types of files. | ||
| model-paths: | ||
| - models | ||
| analysis-paths: | ||
| - analyses | ||
| test-paths: | ||
| - tests | ||
| macro-paths: | ||
| - macros | ||
| snapshot-paths: | ||
| - snapshots | ||
|
|
||
| clean-targets: # Directories to be removed by `dbt clean` | ||
| - dbt_packages | ||
| - state | ||
| - target | ||
|
|
||
| # Configuring models | ||
| # Full documentation: https://docs.getdbt.com/docs/configuring-models | ||
|
|
||
| models: | ||
| slack_analytics: | ||
| +materialized: table | ||
|
|
||
| vars: | ||
| local_timezone: 'UTC' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| {% macro parse_json(column_name) %} | ||
| {{ return(adapter.dispatch('parse_json', 'slack_analytics')(column_name)) }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro default__parse_json(column_name) %} | ||
| {{ column_name }}::JSON | ||
| {% endmacro %} | ||
|
|
||
| {% macro databricks__parse_json(column_name) %} | ||
| FROM_JSON({{ column_name }}, 'STRING') | ||
| {% endmacro %} | ||
|
|
||
| {% macro snowflake__parse_json(column_name) %} | ||
| PARSE_JSON({{ column_name }}) | ||
| {% endmacro %} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| {% macro to_date(timestamp, localize=True, timezone=var('local_timezone')) %} | ||
| {{ return(adapter.dispatch('to_date', 'slack_analytics')(timestamp, localize, timezone)) }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro default__to_date(timestamp, localize, timezone) %} | ||
| {% if localize %} | ||
| TO_DATE(FROM_UTC_TIMESTAMP({{ timestamp }}, '{{ timezone }}')) | ||
| {% else %} | ||
| TO_DATE({{ timestamp }}) | ||
| {% endif %} | ||
| {% endmacro %} | ||
|
|
||
| {% macro duckdb__to_date(timestamp, localize, timezone) %} | ||
| {% if localize %} | ||
| CAST(timezone('{{ timezone }}', {{ timestamp }}::TIMESTAMPTZ) AS DATE) | ||
| {% else %} | ||
| CAST({{ timestamp }} AS DATE) | ||
| {% endif %} | ||
| {% endmacro %} | ||
|
|
||
| {% macro snowflake__to_date(timestamp, localize, timezone) %} | ||
| {% if localize %} | ||
| TO_DATE(CONVERT_TIMEZONE('UTC', '{{ timezone }}', {{ timestamp }})) | ||
| {% else %} | ||
| TO_DATE({{ timestamp }}) | ||
| {% endif %} | ||
| {% endmacro %} |
15 changes: 15 additions & 0 deletions
15
shared/projects/dbt/slack_analytics/macros/unnest_array.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| {% macro unnest_array(array_column, alias) %} | ||
| {{ return(adapter.dispatch('unnest_array', 'slack_analytics')(array_column, alias)) }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro databricks__unnest_array(array_column, alias) %} | ||
| lateral view explode({{ array_column }}) as {{ alias }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro snowflake__unnest_array(array_column, alias) %} | ||
| , lateral flatten(input => {{ array_column }}) as {{ alias }} | ||
| {% endmacro %} | ||
|
|
||
| {% macro duckdb__unnest_array(array_column, alias) %} | ||
| , unnest(from_json({{ array_column }}, '["JSON"]')) as t({{ alias }}) | ||
| {% endmacro %} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it's all that useful to commit this script given it served quite a niche purpose, but maybe it'd be helpful as an example for future mock data generation.
(The rest of the data I generated with an online tool called Mockaroo.)