hinge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hinge/__init__.py +3 -0
- hinge/config/__init__.py +0 -0
- hinge/config/logging_setup.py +63 -0
- hinge/config/settings.py +23 -0
- hinge/config/types.yaml +42 -0
- hinge/dbt/.user.yml +1 -0
- hinge/dbt/dbt_project.yml +18 -0
- hinge/dbt/macros/apply_time_window.sql +11 -0
- hinge/dbt/macros/assert_recipe_supported.sql +59 -0
- hinge/dbt/macros/build_incidence.sql +46 -0
- hinge/dbt/macros/collapse_path.sql +26 -0
- hinge/dbt/macros/filter_bots.sql +23 -0
- hinge/dbt/macros/network_edges.sql +39 -0
- hinge/dbt/macros/project_bipartite.sql +33 -0
- hinge/dbt/macros/slice_edges.sql +41 -0
- hinge/dbt/models/hin/hin_accounts.sql +14 -0
- hinge/dbt/models/hin/hin_artifacts.sql +23 -0
- hinge/dbt/models/hin/hin_capabilities.sql +42 -0
- hinge/dbt/models/hin/hin_edges.sql +57 -0
- hinge/dbt/models/hin/hin_nodes.sql +44 -0
- hinge/dbt/models/hin/hin_repositories.sql +15 -0
- hinge/dbt/models/intermediate/int_developer_repo_affiliation.sql +27 -0
- hinge/dbt/models/intermediate/int_user_artifact_incidence.sql +21 -0
- hinge/dbt/models/networks/artifact_reference.sql +27 -0
- hinge/dbt/models/networks/co_commit_user_user.sql +48 -0
- hinge/dbt/models/networks/co_edit_file_user_user.sql +48 -0
- hinge/dbt/models/networks/co_edit_line_user_user.sql +48 -0
- hinge/dbt/models/networks/dev_interaction.sql +98 -0
- hinge/dbt/models/networks/developer_repo_affiliation.sql +20 -0
- hinge/dbt/models/networks/follow_user_user.sql +27 -0
- hinge/dbt/models/networks/fork_repo_repo.sql +27 -0
- hinge/dbt/models/networks/issue_co_participation.sql +50 -0
- hinge/dbt/models/networks/issue_participation.sql +40 -0
- hinge/dbt/models/networks/pr_author_reviewer.sql +66 -0
- hinge/dbt/models/networks/pr_participation.sql +40 -0
- hinge/dbt/models/networks/pr_reviewer_coreview.sql +48 -0
- hinge/dbt/models/networks/repo_shared_contributors.sql +41 -0
- hinge/dbt/models/networks/star_user_repo.sql +27 -0
- hinge/dbt/models/networks/user_mention_user.sql +60 -0
- hinge/dbt/models/networks/watch_user_repo.sql +27 -0
- hinge/dbt/models/sources/sources.yml +63 -0
- hinge/dbt/models/staging/stg_accounts.sql +4 -0
- hinge/dbt/models/staging/stg_adapter_manifest.sql +4 -0
- hinge/dbt/models/staging/stg_artifacts.sql +4 -0
- hinge/dbt/models/staging/stg_relations.sql +4 -0
- hinge/dbt/models/staging/stg_repositories.sql +4 -0
- hinge/dbt/profiles.yml +9 -0
- hinge/dbt/seeds/ref_recipe_requirements.csv +35 -0
- hinge/frontends/__init__.py +0 -0
- hinge/frontends/cli/__init__.py +3 -0
- hinge/frontends/cli/app.py +189 -0
- hinge/frontends/lib.py +148 -0
- hinge/frontends/tui/__init__.py +7 -0
- hinge/frontends/web/__init__.py +6 -0
- hinge/kernel/__init__.py +0 -0
- hinge/kernel/projection/__init__.py +9 -0
- hinge/kernel/projection/projected_graph.py +32 -0
- hinge/kernel/projection/projection_spec.py +35 -0
- hinge/kernel/protocols/__init__.py +24 -0
- hinge/kernel/protocols/dataset_view.py +30 -0
- hinge/kernel/protocols/exporter_stage.py +24 -0
- hinge/kernel/protocols/projection_stage.py +36 -0
- hinge/kernel/protocols/reader_stage.py +52 -0
- hinge/kernel/protocols/store_stage.py +71 -0
- hinge/kernel/registry.py +71 -0
- hinge/kernel/runner.py +236 -0
- hinge/kernel/schema/__init__.py +12 -0
- hinge/kernel/schema/hin_schema.py +38 -0
- hinge/kernel/schema/schema_violation.py +21 -0
- hinge/kernel/schema/typed_edge.py +20 -0
- hinge/kernel/schema/typed_node.py +17 -0
- hinge/stages/__init__.py +0 -0
- hinge/stages/exporters/__init__.py +0 -0
- hinge/stages/exporters/csv_exporter.py +77 -0
- hinge/stages/exporters/dot_exporter.py +13 -0
- hinge/stages/exporters/gexf_exporter.py +13 -0
- hinge/stages/exporters/gml_exporter.py +79 -0
- hinge/stages/exporters/graphml_exporter.py +13 -0
- hinge/stages/exporters/jsonld_exporter.py +13 -0
- hinge/stages/exporters/networkx_exporter.py +57 -0
- hinge/stages/exporters/parquet_exporter.py +13 -0
- hinge/stages/projection/__init__.py +0 -0
- hinge/stages/projection/dbt_projection.py +342 -0
- hinge/stages/projection/specs/__init__.py +0 -0
- hinge/stages/projection/specs/artifact_reference.py +14 -0
- hinge/stages/projection/specs/co_commit_user_user.py +14 -0
- hinge/stages/projection/specs/co_edit_file_user_user.py +14 -0
- hinge/stages/projection/specs/co_edit_line_user_user.py +14 -0
- hinge/stages/projection/specs/dev_interaction.py +26 -0
- hinge/stages/projection/specs/developer_repo_affiliation.py +15 -0
- hinge/stages/projection/specs/follow_user_user.py +14 -0
- hinge/stages/projection/specs/fork_repo_repo.py +14 -0
- hinge/stages/projection/specs/issue_co_participation.py +14 -0
- hinge/stages/projection/specs/issue_participation.py +14 -0
- hinge/stages/projection/specs/pr_author_reviewer.py +14 -0
- hinge/stages/projection/specs/pr_participation.py +14 -0
- hinge/stages/projection/specs/pr_reviewer_coreview.py +14 -0
- hinge/stages/projection/specs/repo_shared_contributors.py +14 -0
- hinge/stages/projection/specs/star_user_repo.py +15 -0
- hinge/stages/projection/specs/user_mention_user.py +14 -0
- hinge/stages/projection/specs/watch_user_repo.py +14 -0
- hinge/stages/readers/__init__.py +0 -0
- hinge/stages/readers/_numfocus_contract_adapter.py +314 -0
- hinge/stages/readers/numfocus_reader.py +69 -0
- hinge/stages/store/__init__.py +0 -0
- hinge/stages/store/duckdb_store.py +466 -0
- hinge-0.1.0.dist-info/METADATA +309 -0
- hinge-0.1.0.dist-info/RECORD +111 -0
- hinge-0.1.0.dist-info/WHEEL +4 -0
- hinge-0.1.0.dist-info/entry_points.txt +35 -0
- hinge-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ build_incidence(
|
|
4
|
+
left_type='user',
|
|
5
|
+
right_type='artifact',
|
|
6
|
+
edge_types=[
|
|
7
|
+
'authored',
|
|
8
|
+
'committed',
|
|
9
|
+
'coauthored',
|
|
10
|
+
'opened',
|
|
11
|
+
'reviewed',
|
|
12
|
+
'merged',
|
|
13
|
+
'closed',
|
|
14
|
+
'reopened',
|
|
15
|
+
'commented_on',
|
|
16
|
+
'review_commented_on',
|
|
17
|
+
'pushed',
|
|
18
|
+
'touched'
|
|
19
|
+
],
|
|
20
|
+
weight_mode='event_count'
|
|
21
|
+
) }}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('artifact_reference') }}
|
|
4
|
+
|
|
5
|
+
WITH reference_edges AS (
|
|
6
|
+
{{ slice_edges(
|
|
7
|
+
edge_types=['references', 'closes', 'fixes', 'duplicates', 'relates_to'],
|
|
8
|
+
source_types=['artifact'],
|
|
9
|
+
target_types=['artifact']
|
|
10
|
+
) }}
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
{{ network_edges(
|
|
14
|
+
relation='reference_edges',
|
|
15
|
+
recipe_name='artifact_reference',
|
|
16
|
+
source_node_id='source_node_id',
|
|
17
|
+
source_node_type="'artifact'",
|
|
18
|
+
target_node_id='target_node_id',
|
|
19
|
+
target_node_type="'artifact'",
|
|
20
|
+
edge_type='edge_type',
|
|
21
|
+
directed='true',
|
|
22
|
+
weight='coalesce(weight, 1.0)',
|
|
23
|
+
weight_kind="'binary'",
|
|
24
|
+
first_seen_at='occurred_at',
|
|
25
|
+
last_seen_at='occurred_at',
|
|
26
|
+
properties="to_json({'source_record_id': source_record_id, 'reference_type': edge_type})"
|
|
27
|
+
) }}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('co_commit_user_user') }}
|
|
4
|
+
|
|
5
|
+
WITH commit_events AS (
|
|
6
|
+
SELECT *
|
|
7
|
+
FROM {{ ref('int_user_artifact_incidence') }}
|
|
8
|
+
WHERE right_node_subtype = 'commit'
|
|
9
|
+
AND role IN ('authored', 'committed', 'coauthored')
|
|
10
|
+
),
|
|
11
|
+
|
|
12
|
+
user_commit AS (
|
|
13
|
+
SELECT
|
|
14
|
+
left_node_id AS user_node_id,
|
|
15
|
+
right_node_id AS commit_node_id,
|
|
16
|
+
min(occurred_at) AS first_seen_at,
|
|
17
|
+
max(occurred_at) AS last_seen_at,
|
|
18
|
+
sum(weight)::DOUBLE AS weight
|
|
19
|
+
FROM commit_events
|
|
20
|
+
GROUP BY left_node_id, right_node_id
|
|
21
|
+
),
|
|
22
|
+
|
|
23
|
+
co_commits AS (
|
|
24
|
+
{{ project_bipartite(
|
|
25
|
+
incidence_relation='user_commit',
|
|
26
|
+
left_col='user_node_id',
|
|
27
|
+
right_col='commit_node_id',
|
|
28
|
+
directed=false,
|
|
29
|
+
weight_mode='shared_count'
|
|
30
|
+
) }}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
{{ network_edges(
|
|
34
|
+
relation='co_commits',
|
|
35
|
+
recipe_name='co_commit_user_user',
|
|
36
|
+
source_node_id='source_node_id',
|
|
37
|
+
source_node_type="'user'",
|
|
38
|
+
target_node_id='target_node_id',
|
|
39
|
+
target_node_type="'user'",
|
|
40
|
+
edge_type="'co_committed'",
|
|
41
|
+
directed='false',
|
|
42
|
+
weight='weight',
|
|
43
|
+
weight_kind='weight_kind',
|
|
44
|
+
n_contexts='n_contexts',
|
|
45
|
+
first_seen_at='first_seen_at',
|
|
46
|
+
last_seen_at='last_seen_at',
|
|
47
|
+
properties="to_json({'shared_commits': n_contexts, 'commits': context_node_ids})"
|
|
48
|
+
) }}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('co_edit_file_user_user') }}
|
|
4
|
+
|
|
5
|
+
WITH file_touch_events AS (
|
|
6
|
+
SELECT *
|
|
7
|
+
FROM {{ ref('int_user_artifact_incidence') }}
|
|
8
|
+
WHERE right_node_subtype = 'file'
|
|
9
|
+
AND role = 'touched'
|
|
10
|
+
),
|
|
11
|
+
|
|
12
|
+
user_file AS (
|
|
13
|
+
SELECT
|
|
14
|
+
left_node_id AS user_node_id,
|
|
15
|
+
right_node_id AS file_node_id,
|
|
16
|
+
min(occurred_at) AS first_seen_at,
|
|
17
|
+
max(occurred_at) AS last_seen_at,
|
|
18
|
+
sum(weight)::DOUBLE AS weight
|
|
19
|
+
FROM file_touch_events
|
|
20
|
+
GROUP BY left_node_id, right_node_id
|
|
21
|
+
),
|
|
22
|
+
|
|
23
|
+
co_edits AS (
|
|
24
|
+
{{ project_bipartite(
|
|
25
|
+
incidence_relation='user_file',
|
|
26
|
+
left_col='user_node_id',
|
|
27
|
+
right_col='file_node_id',
|
|
28
|
+
directed=false,
|
|
29
|
+
weight_mode='shared_count'
|
|
30
|
+
) }}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
{{ network_edges(
|
|
34
|
+
relation='co_edits',
|
|
35
|
+
recipe_name='co_edit_file_user_user',
|
|
36
|
+
source_node_id='source_node_id',
|
|
37
|
+
source_node_type="'user'",
|
|
38
|
+
target_node_id='target_node_id',
|
|
39
|
+
target_node_type="'user'",
|
|
40
|
+
edge_type="'co_edited_file'",
|
|
41
|
+
directed='false',
|
|
42
|
+
weight='weight',
|
|
43
|
+
weight_kind='weight_kind',
|
|
44
|
+
n_contexts='n_contexts',
|
|
45
|
+
first_seen_at='first_seen_at',
|
|
46
|
+
last_seen_at='last_seen_at',
|
|
47
|
+
properties="to_json({'shared_files': n_contexts, 'files': context_node_ids})"
|
|
48
|
+
) }}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('co_edit_line_user_user') }}
|
|
4
|
+
|
|
5
|
+
WITH line_touch_events AS (
|
|
6
|
+
SELECT *
|
|
7
|
+
FROM {{ ref('int_user_artifact_incidence') }}
|
|
8
|
+
WHERE right_node_subtype = 'line_span'
|
|
9
|
+
AND role = 'touched'
|
|
10
|
+
),
|
|
11
|
+
|
|
12
|
+
user_line AS (
|
|
13
|
+
SELECT
|
|
14
|
+
left_node_id AS user_node_id,
|
|
15
|
+
right_node_id AS line_node_id,
|
|
16
|
+
min(occurred_at) AS first_seen_at,
|
|
17
|
+
max(occurred_at) AS last_seen_at,
|
|
18
|
+
sum(weight)::DOUBLE AS weight
|
|
19
|
+
FROM line_touch_events
|
|
20
|
+
GROUP BY left_node_id, right_node_id
|
|
21
|
+
),
|
|
22
|
+
|
|
23
|
+
co_edits AS (
|
|
24
|
+
{{ project_bipartite(
|
|
25
|
+
incidence_relation='user_line',
|
|
26
|
+
left_col='user_node_id',
|
|
27
|
+
right_col='line_node_id',
|
|
28
|
+
directed=false,
|
|
29
|
+
weight_mode='shared_count'
|
|
30
|
+
) }}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
{{ network_edges(
|
|
34
|
+
relation='co_edits',
|
|
35
|
+
recipe_name='co_edit_line_user_user',
|
|
36
|
+
source_node_id='source_node_id',
|
|
37
|
+
source_node_type="'user'",
|
|
38
|
+
target_node_id='target_node_id',
|
|
39
|
+
target_node_type="'user'",
|
|
40
|
+
edge_type="'co_edited_line'",
|
|
41
|
+
directed='false',
|
|
42
|
+
weight='weight',
|
|
43
|
+
weight_kind='weight_kind',
|
|
44
|
+
n_contexts='n_contexts',
|
|
45
|
+
first_seen_at='first_seen_at',
|
|
46
|
+
last_seen_at='last_seen_at',
|
|
47
|
+
properties="to_json({'shared_lines': n_contexts, 'line_spans': context_node_ids})"
|
|
48
|
+
) }}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- dev_interaction.sql — developer collaboration network
|
|
3
|
+
--
|
|
4
|
+
-- READ THIS BEFORE WRITING A NEW PROJECTION. Every .sql file in this folder
|
|
5
|
+
-- must satisfy the contract documented in the header below.
|
|
6
|
+
-- ============================================================================
|
|
7
|
+
--
|
|
8
|
+
-- WHAT THIS PROJECTION DOES
|
|
9
|
+
-- Builds an undirected user-to-user "collaborator" graph. Two developers
|
|
10
|
+
-- are collaborators if they both made code contributions to the same
|
|
11
|
+
-- repository. A contribution is any of: opening a PR or issue, reviewing,
|
|
12
|
+
-- merging, commenting on a PR or issue, or pushing commits.
|
|
13
|
+
--
|
|
14
|
+
-- "Shared a repo" is derived from the HIN structure using two hops:
|
|
15
|
+
-- user → <contribution edge> → artifact ← contains ← repo
|
|
16
|
+
-- The self-join on repo then finds all pairs of contributors per repo.
|
|
17
|
+
--
|
|
18
|
+
-- Starring, forking, and wiki edits are intentionally excluded — they
|
|
19
|
+
-- do not constitute active development collaboration.
|
|
20
|
+
--
|
|
21
|
+
-- ─── INPUT CONTRACT ─────────────────────────────────────────────────────────
|
|
22
|
+
-- Every projection reads from exactly two dbt sources:
|
|
23
|
+
--
|
|
24
|
+
-- {{ ref('hin_nodes') }} canonical HIN node model
|
|
25
|
+
-- {{ ref('hin_edges') }} canonical HIN edge model
|
|
26
|
+
--
|
|
27
|
+
-- The store (DuckDBStore) rewrites these views before each projection run
|
|
28
|
+
-- to point at the requested dataset_id. Do NOT touch any other table.
|
|
29
|
+
-- If you need helper logic, put it in a CTE — do not create intermediate
|
|
30
|
+
-- dbt models unless the projection genuinely needs to be split.
|
|
31
|
+
--
|
|
32
|
+
-- ─── OUTPUT CONTRACT ────────────────────────────────────────────────────────
|
|
33
|
+
-- Every projection model should use the network_edges macro. It emits the
|
|
34
|
+
-- standard cookbook shape:
|
|
35
|
+
--
|
|
36
|
+
-- recipe_name, recipe_version,
|
|
37
|
+
-- source_node_id, source_node_type, target_node_id, target_node_type,
|
|
38
|
+
-- directed, edge_type,
|
|
39
|
+
-- weight, weight_kind, n_contexts, n_events,
|
|
40
|
+
-- first_seen_at, last_seen_at, time_bin, bot_policy, properties
|
|
41
|
+
--
|
|
42
|
+
-- DbtProjection converts this richer table into TypedEdge objects for the
|
|
43
|
+
-- current exporter API, merging standard fields and properties into attrs.
|
|
44
|
+
--
|
|
45
|
+
-- The DbtProjection stage discovers the result table by its dbt model
|
|
46
|
+
-- name (this file's stem, `dev_interaction`). dbt's default
|
|
47
|
+
-- materialisation is `table`, set globally in dbt_project.yml.
|
|
48
|
+
--
|
|
49
|
+
-- ─── HOW TO REGISTER THIS FILE ──────────────────────────────────────────────
|
|
50
|
+
-- 1. Save the .sql file in hinge/dbt/models/networks/<name>.sql.
|
|
51
|
+
-- 2. Add a sibling Python module under
|
|
52
|
+
-- hinge/stages/projection/specs/<name>.py exposing a `SPEC` constant
|
|
53
|
+
-- (see specs/dev_interaction.py for the template).
|
|
54
|
+
-- 3. Register the entry-point in pyproject.toml under
|
|
55
|
+
-- [project.entry-points."hinge.projection_specs"].
|
|
56
|
+
-- 4. Write a test in tests/stages/projection/test_<name>.py against a
|
|
57
|
+
-- seeded :memory: DuckDB fixture.
|
|
58
|
+
--
|
|
59
|
+
-- ─── PARAMETERS ─────────────────────────────────────────────────────────────
|
|
60
|
+
-- Projections can accept run-time parameters via dbt vars (passed by
|
|
61
|
+
-- DbtProjection from the ProjectionParams object). Read them with
|
|
62
|
+
-- {{ var('name', default) }}.
|
|
63
|
+
-- This template ignores all vars — keep it that way until a real use case
|
|
64
|
+
-- appears.
|
|
65
|
+
-- ============================================================================
|
|
66
|
+
|
|
67
|
+
{{ assert_recipe_supported('dev_interaction') }}
|
|
68
|
+
|
|
69
|
+
WITH
|
|
70
|
+
|
|
71
|
+
-- Collaborator pairs: users who share at least one active development repo.
|
|
72
|
+
-- This is the generic bipartite projection pattern: user -> repo <- user.
|
|
73
|
+
collaborators AS (
|
|
74
|
+
{{ project_bipartite(
|
|
75
|
+
incidence_relation=ref('int_developer_repo_affiliation'),
|
|
76
|
+
left_col='user_node_id',
|
|
77
|
+
right_col='repo_node_id',
|
|
78
|
+
directed=false,
|
|
79
|
+
weight_mode='shared_count'
|
|
80
|
+
) }}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
{{ network_edges(
|
|
84
|
+
relation='collaborators',
|
|
85
|
+
recipe_name='dev_interaction',
|
|
86
|
+
source_node_id='source_node_id',
|
|
87
|
+
source_node_type="'user'",
|
|
88
|
+
target_node_id='target_node_id',
|
|
89
|
+
target_node_type="'user'",
|
|
90
|
+
edge_type="'collaborates_with'",
|
|
91
|
+
directed='false',
|
|
92
|
+
weight='weight',
|
|
93
|
+
weight_kind='weight_kind',
|
|
94
|
+
n_contexts='n_contexts',
|
|
95
|
+
first_seen_at='first_seen_at',
|
|
96
|
+
last_seen_at='last_seen_at',
|
|
97
|
+
properties="to_json({'shared_repos': n_contexts, 'repos': context_node_ids})"
|
|
98
|
+
) }}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('developer_repo_affiliation') }}
|
|
4
|
+
|
|
5
|
+
{{ network_edges(
|
|
6
|
+
relation=ref('int_developer_repo_affiliation'),
|
|
7
|
+
recipe_name='developer_repo_affiliation',
|
|
8
|
+
source_node_id='user_node_id',
|
|
9
|
+
source_node_type="'user'",
|
|
10
|
+
target_node_id='repo_node_id',
|
|
11
|
+
target_node_type="'repo'",
|
|
12
|
+
edge_type="'affiliated_with'",
|
|
13
|
+
directed='true',
|
|
14
|
+
weight='weight',
|
|
15
|
+
weight_kind="'event_count'",
|
|
16
|
+
n_events='n_events',
|
|
17
|
+
first_seen_at='first_seen_at',
|
|
18
|
+
last_seen_at='last_seen_at',
|
|
19
|
+
properties="to_json({'roles': roles})"
|
|
20
|
+
) }}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('follow_user_user') }}
|
|
4
|
+
|
|
5
|
+
WITH follow_edges AS (
|
|
6
|
+
{{ slice_edges(
|
|
7
|
+
edge_types=['follows'],
|
|
8
|
+
source_types=['user'],
|
|
9
|
+
target_types=['user']
|
|
10
|
+
) }}
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
{{ network_edges(
|
|
14
|
+
relation='follow_edges',
|
|
15
|
+
recipe_name='follow_user_user',
|
|
16
|
+
source_node_id='source_node_id',
|
|
17
|
+
source_node_type="'user'",
|
|
18
|
+
target_node_id='target_node_id',
|
|
19
|
+
target_node_type="'user'",
|
|
20
|
+
edge_type="'follows'",
|
|
21
|
+
directed='true',
|
|
22
|
+
weight='coalesce(weight, 1.0)',
|
|
23
|
+
weight_kind="'binary'",
|
|
24
|
+
first_seen_at='occurred_at',
|
|
25
|
+
last_seen_at='occurred_at',
|
|
26
|
+
properties="to_json({'source_record_id': source_record_id})"
|
|
27
|
+
) }}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('fork_repo_repo') }}
|
|
4
|
+
|
|
5
|
+
WITH fork_edges AS (
|
|
6
|
+
{{ slice_edges(
|
|
7
|
+
edge_types=['fork_of'],
|
|
8
|
+
source_types=['repo'],
|
|
9
|
+
target_types=['repo']
|
|
10
|
+
) }}
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
{{ network_edges(
|
|
14
|
+
relation='fork_edges',
|
|
15
|
+
recipe_name='fork_repo_repo',
|
|
16
|
+
source_node_id='source_node_id',
|
|
17
|
+
source_node_type="'repo'",
|
|
18
|
+
target_node_id='target_node_id',
|
|
19
|
+
target_node_type="'repo'",
|
|
20
|
+
edge_type="'fork_of'",
|
|
21
|
+
directed='true',
|
|
22
|
+
weight='coalesce(weight, 1.0)',
|
|
23
|
+
weight_kind="'binary'",
|
|
24
|
+
first_seen_at='occurred_at',
|
|
25
|
+
last_seen_at='occurred_at',
|
|
26
|
+
properties="to_json({'source_record_id': source_record_id})"
|
|
27
|
+
) }}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('issue_co_participation') }}
|
|
4
|
+
|
|
5
|
+
WITH issue_events AS (
|
|
6
|
+
SELECT *
|
|
7
|
+
FROM {{ ref('int_user_artifact_incidence') }}
|
|
8
|
+
WHERE right_node_subtype = 'issue'
|
|
9
|
+
AND role IN ('opened', 'commented_on', 'closed')
|
|
10
|
+
),
|
|
11
|
+
|
|
12
|
+
issue_participation AS (
|
|
13
|
+
SELECT
|
|
14
|
+
left_node_id AS user_node_id,
|
|
15
|
+
right_node_id AS issue_node_id,
|
|
16
|
+
min(occurred_at) AS first_seen_at,
|
|
17
|
+
max(occurred_at) AS last_seen_at,
|
|
18
|
+
sum(weight)::DOUBLE AS weight,
|
|
19
|
+
count(DISTINCT evidence_edge_id) AS n_events,
|
|
20
|
+
list_distinct(list(role)) AS roles
|
|
21
|
+
FROM issue_events
|
|
22
|
+
GROUP BY left_node_id, right_node_id
|
|
23
|
+
),
|
|
24
|
+
|
|
25
|
+
co_participation AS (
|
|
26
|
+
{{ project_bipartite(
|
|
27
|
+
incidence_relation='issue_participation',
|
|
28
|
+
left_col='user_node_id',
|
|
29
|
+
right_col='issue_node_id',
|
|
30
|
+
directed=false,
|
|
31
|
+
weight_mode='shared_count'
|
|
32
|
+
) }}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
{{ network_edges(
|
|
36
|
+
relation='co_participation',
|
|
37
|
+
recipe_name='issue_co_participation',
|
|
38
|
+
source_node_id='source_node_id',
|
|
39
|
+
source_node_type="'user'",
|
|
40
|
+
target_node_id='target_node_id',
|
|
41
|
+
target_node_type="'user'",
|
|
42
|
+
edge_type="'co_participates_issue'",
|
|
43
|
+
directed='false',
|
|
44
|
+
weight='weight',
|
|
45
|
+
weight_kind='weight_kind',
|
|
46
|
+
n_contexts='n_contexts',
|
|
47
|
+
first_seen_at='first_seen_at',
|
|
48
|
+
last_seen_at='last_seen_at',
|
|
49
|
+
properties="to_json({'shared_issues': n_contexts, 'issues': context_node_ids})"
|
|
50
|
+
) }}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('issue_participation') }}
|
|
4
|
+
|
|
5
|
+
WITH issue_events AS (
|
|
6
|
+
SELECT *
|
|
7
|
+
FROM {{ ref('int_user_artifact_incidence') }}
|
|
8
|
+
WHERE right_node_subtype = 'issue'
|
|
9
|
+
AND role IN ('opened', 'commented_on', 'closed')
|
|
10
|
+
),
|
|
11
|
+
|
|
12
|
+
participation AS (
|
|
13
|
+
SELECT
|
|
14
|
+
left_node_id AS user_node_id,
|
|
15
|
+
right_node_id AS issue_node_id,
|
|
16
|
+
min(occurred_at) AS first_seen_at,
|
|
17
|
+
max(occurred_at) AS last_seen_at,
|
|
18
|
+
sum(weight)::DOUBLE AS weight,
|
|
19
|
+
count(DISTINCT evidence_edge_id) AS n_events,
|
|
20
|
+
list_distinct(list(role)) AS roles
|
|
21
|
+
FROM issue_events
|
|
22
|
+
GROUP BY left_node_id, right_node_id
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
{{ network_edges(
|
|
26
|
+
relation='participation',
|
|
27
|
+
recipe_name='issue_participation',
|
|
28
|
+
source_node_id='user_node_id',
|
|
29
|
+
source_node_type="'user'",
|
|
30
|
+
target_node_id='issue_node_id',
|
|
31
|
+
target_node_type="'artifact'",
|
|
32
|
+
edge_type="'participated_in_issue'",
|
|
33
|
+
directed='true',
|
|
34
|
+
weight='weight',
|
|
35
|
+
weight_kind="'event_count'",
|
|
36
|
+
n_events='n_events',
|
|
37
|
+
first_seen_at='first_seen_at',
|
|
38
|
+
last_seen_at='last_seen_at',
|
|
39
|
+
properties="to_json({'roles': roles})"
|
|
40
|
+
) }}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('pr_author_reviewer') }}
|
|
4
|
+
|
|
5
|
+
WITH pr_events AS (
|
|
6
|
+
SELECT *
|
|
7
|
+
FROM {{ ref('int_user_artifact_incidence') }}
|
|
8
|
+
WHERE right_node_subtype = 'pull_request'
|
|
9
|
+
AND role IN ('opened', 'reviewed')
|
|
10
|
+
),
|
|
11
|
+
|
|
12
|
+
openers AS (
|
|
13
|
+
SELECT
|
|
14
|
+
left_node_id AS author_node_id,
|
|
15
|
+
right_node_id AS pr_node_id,
|
|
16
|
+
min(occurred_at) AS first_seen_at
|
|
17
|
+
FROM pr_events
|
|
18
|
+
WHERE role = 'opened'
|
|
19
|
+
GROUP BY left_node_id, right_node_id
|
|
20
|
+
),
|
|
21
|
+
|
|
22
|
+
reviewers AS (
|
|
23
|
+
SELECT
|
|
24
|
+
left_node_id AS reviewer_node_id,
|
|
25
|
+
right_node_id AS pr_node_id,
|
|
26
|
+
min(occurred_at) AS first_seen_at,
|
|
27
|
+
max(occurred_at) AS last_seen_at,
|
|
28
|
+
count(DISTINCT evidence_edge_id) AS n_reviews
|
|
29
|
+
FROM pr_events
|
|
30
|
+
WHERE role = 'reviewed'
|
|
31
|
+
GROUP BY left_node_id, right_node_id
|
|
32
|
+
),
|
|
33
|
+
|
|
34
|
+
author_reviewer AS (
|
|
35
|
+
SELECT
|
|
36
|
+
openers.author_node_id,
|
|
37
|
+
reviewers.reviewer_node_id,
|
|
38
|
+
count(DISTINCT openers.pr_node_id) AS n_contexts,
|
|
39
|
+
sum(reviewers.n_reviews) AS n_events,
|
|
40
|
+
list_distinct(list(openers.pr_node_id)) AS pull_requests,
|
|
41
|
+
min(least(openers.first_seen_at, reviewers.first_seen_at)) AS first_seen_at,
|
|
42
|
+
max(reviewers.last_seen_at) AS last_seen_at
|
|
43
|
+
FROM openers
|
|
44
|
+
JOIN reviewers
|
|
45
|
+
ON reviewers.pr_node_id = openers.pr_node_id
|
|
46
|
+
AND reviewers.reviewer_node_id != openers.author_node_id
|
|
47
|
+
GROUP BY openers.author_node_id, reviewers.reviewer_node_id
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
{{ network_edges(
|
|
51
|
+
relation='author_reviewer',
|
|
52
|
+
recipe_name='pr_author_reviewer',
|
|
53
|
+
source_node_id='author_node_id',
|
|
54
|
+
source_node_type="'user'",
|
|
55
|
+
target_node_id='reviewer_node_id',
|
|
56
|
+
target_node_type="'user'",
|
|
57
|
+
edge_type="'reviewed_pr_from'",
|
|
58
|
+
directed='true',
|
|
59
|
+
weight='n_contexts',
|
|
60
|
+
weight_kind="'shared_count'",
|
|
61
|
+
n_contexts='n_contexts',
|
|
62
|
+
n_events='n_events',
|
|
63
|
+
first_seen_at='first_seen_at',
|
|
64
|
+
last_seen_at='last_seen_at',
|
|
65
|
+
properties="to_json({'pull_requests': pull_requests})"
|
|
66
|
+
) }}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('pr_participation') }}
|
|
4
|
+
|
|
5
|
+
WITH pr_events AS (
|
|
6
|
+
SELECT *
|
|
7
|
+
FROM {{ ref('int_user_artifact_incidence') }}
|
|
8
|
+
WHERE right_node_subtype = 'pull_request'
|
|
9
|
+
AND role IN ('opened', 'reviewed', 'merged', 'commented_on', 'review_commented_on')
|
|
10
|
+
),
|
|
11
|
+
|
|
12
|
+
participation AS (
|
|
13
|
+
SELECT
|
|
14
|
+
left_node_id AS user_node_id,
|
|
15
|
+
right_node_id AS pr_node_id,
|
|
16
|
+
min(occurred_at) AS first_seen_at,
|
|
17
|
+
max(occurred_at) AS last_seen_at,
|
|
18
|
+
sum(weight)::DOUBLE AS weight,
|
|
19
|
+
count(DISTINCT evidence_edge_id) AS n_events,
|
|
20
|
+
list_distinct(list(role)) AS roles
|
|
21
|
+
FROM pr_events
|
|
22
|
+
GROUP BY left_node_id, right_node_id
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
{{ network_edges(
|
|
26
|
+
relation='participation',
|
|
27
|
+
recipe_name='pr_participation',
|
|
28
|
+
source_node_id='user_node_id',
|
|
29
|
+
source_node_type="'user'",
|
|
30
|
+
target_node_id='pr_node_id',
|
|
31
|
+
target_node_type="'artifact'",
|
|
32
|
+
edge_type="'participated_in_pr'",
|
|
33
|
+
directed='true',
|
|
34
|
+
weight='weight',
|
|
35
|
+
weight_kind="'event_count'",
|
|
36
|
+
n_events='n_events',
|
|
37
|
+
first_seen_at='first_seen_at',
|
|
38
|
+
last_seen_at='last_seen_at',
|
|
39
|
+
properties="to_json({'roles': roles})"
|
|
40
|
+
) }}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('pr_reviewer_coreview') }}
|
|
4
|
+
|
|
5
|
+
WITH review_events AS (
|
|
6
|
+
SELECT *
|
|
7
|
+
FROM {{ ref('int_user_artifact_incidence') }}
|
|
8
|
+
WHERE right_node_subtype = 'pull_request'
|
|
9
|
+
AND role = 'reviewed'
|
|
10
|
+
),
|
|
11
|
+
|
|
12
|
+
reviewer_pr AS (
|
|
13
|
+
SELECT
|
|
14
|
+
left_node_id AS reviewer_node_id,
|
|
15
|
+
right_node_id AS pr_node_id,
|
|
16
|
+
min(occurred_at) AS first_seen_at,
|
|
17
|
+
max(occurred_at) AS last_seen_at,
|
|
18
|
+
sum(weight)::DOUBLE AS weight
|
|
19
|
+
FROM review_events
|
|
20
|
+
GROUP BY left_node_id, right_node_id
|
|
21
|
+
),
|
|
22
|
+
|
|
23
|
+
co_reviews AS (
|
|
24
|
+
{{ project_bipartite(
|
|
25
|
+
incidence_relation='reviewer_pr',
|
|
26
|
+
left_col='reviewer_node_id',
|
|
27
|
+
right_col='pr_node_id',
|
|
28
|
+
directed=false,
|
|
29
|
+
weight_mode='shared_count'
|
|
30
|
+
) }}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
{{ network_edges(
|
|
34
|
+
relation='co_reviews',
|
|
35
|
+
recipe_name='pr_reviewer_coreview',
|
|
36
|
+
source_node_id='source_node_id',
|
|
37
|
+
source_node_type="'user'",
|
|
38
|
+
target_node_id='target_node_id',
|
|
39
|
+
target_node_type="'user'",
|
|
40
|
+
edge_type="'co_reviewed_pr'",
|
|
41
|
+
directed='false',
|
|
42
|
+
weight='weight',
|
|
43
|
+
weight_kind='weight_kind',
|
|
44
|
+
n_contexts='n_contexts',
|
|
45
|
+
first_seen_at='first_seen_at',
|
|
46
|
+
last_seen_at='last_seen_at',
|
|
47
|
+
properties="to_json({'shared_pull_requests': n_contexts, 'pull_requests': context_node_ids})"
|
|
48
|
+
) }}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{{ config(materialized='table') }}
|
|
2
|
+
|
|
3
|
+
{{ assert_recipe_supported('repo_shared_contributors') }}
|
|
4
|
+
|
|
5
|
+
WITH contributor_repo AS (
|
|
6
|
+
SELECT
|
|
7
|
+
repo_node_id,
|
|
8
|
+
user_node_id,
|
|
9
|
+
min(first_seen_at) AS first_seen_at,
|
|
10
|
+
max(last_seen_at) AS last_seen_at,
|
|
11
|
+
sum(weight)::DOUBLE AS weight
|
|
12
|
+
FROM {{ ref('int_developer_repo_affiliation') }}
|
|
13
|
+
GROUP BY repo_node_id, user_node_id
|
|
14
|
+
),
|
|
15
|
+
|
|
16
|
+
shared AS (
|
|
17
|
+
{{ project_bipartite(
|
|
18
|
+
incidence_relation='contributor_repo',
|
|
19
|
+
left_col='repo_node_id',
|
|
20
|
+
right_col='user_node_id',
|
|
21
|
+
directed=false,
|
|
22
|
+
weight_mode='shared_count'
|
|
23
|
+
) }}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
{{ network_edges(
|
|
27
|
+
relation='shared',
|
|
28
|
+
recipe_name='repo_shared_contributors',
|
|
29
|
+
source_node_id='source_node_id',
|
|
30
|
+
source_node_type="'repo'",
|
|
31
|
+
target_node_id='target_node_id',
|
|
32
|
+
target_node_type="'repo'",
|
|
33
|
+
edge_type="'shared_contributors'",
|
|
34
|
+
directed='false',
|
|
35
|
+
weight='weight',
|
|
36
|
+
weight_kind='weight_kind',
|
|
37
|
+
n_contexts='n_contexts',
|
|
38
|
+
first_seen_at='first_seen_at',
|
|
39
|
+
last_seen_at='last_seen_at',
|
|
40
|
+
properties="to_json({'shared_contributors': n_contexts, 'contributors': context_node_ids})"
|
|
41
|
+
) }}
|