@revos/cli 0.2.0 → 0.2.2
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.
- package/LICENSE +9 -0
- package/README.md +286 -41
- package/dist/adapters/oclif/commands/action-runs/get.mjs +1 -1
- package/dist/adapters/oclif/commands/action-runs/list.mjs +8 -2
- package/dist/adapters/oclif/commands/actions/get-input-schema.mjs +2 -2
- package/dist/adapters/oclif/commands/actions/get-params-schema.mjs +2 -2
- package/dist/adapters/oclif/commands/actions/get.mjs +1 -1
- package/dist/adapters/oclif/commands/actions/list.mjs +8 -4
- package/dist/adapters/oclif/commands/ai-instructions/create.mjs +1 -1
- package/dist/adapters/oclif/commands/ai-instructions/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/ai-instructions/get.mjs +1 -1
- package/dist/adapters/oclif/commands/ai-instructions/list.mjs +8 -2
- package/dist/adapters/oclif/commands/ai-instructions/update.mjs +1 -1
- package/dist/adapters/oclif/commands/api.d.mts +11 -0
- package/dist/adapters/oclif/commands/api.mjs +112 -0
- package/dist/adapters/oclif/commands/apply.d.mts +28 -0
- package/dist/adapters/oclif/commands/apply.mjs +77 -0
- package/dist/adapters/oclif/commands/auth/login.d.mts +5 -4
- package/dist/adapters/oclif/commands/auth/login.mjs +22 -11
- package/dist/adapters/oclif/commands/auth/logout.d.mts +1 -1
- package/dist/adapters/oclif/commands/auth/logout.mjs +7 -3
- package/dist/adapters/oclif/commands/auth/status.d.mts +2 -2
- package/dist/adapters/oclif/commands/auth/status.mjs +2 -2
- package/dist/adapters/oclif/commands/connections/create.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/create.mjs +8 -0
- package/dist/adapters/oclif/commands/connections/delete.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/delete.mjs +8 -0
- package/dist/adapters/oclif/commands/connections/get.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/get.mjs +8 -0
- package/dist/adapters/oclif/commands/connections/list.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/list.mjs +14 -0
- package/dist/adapters/oclif/commands/connections/update.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/update.mjs +8 -0
- package/dist/adapters/oclif/commands/cubes/create.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/create.mjs +8 -0
- package/dist/adapters/oclif/commands/cubes/delete.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/delete.mjs +8 -0
- package/dist/adapters/oclif/commands/cubes/get.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/get.mjs +8 -0
- package/dist/adapters/oclif/commands/cubes/list.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/list.mjs +13 -0
- package/dist/adapters/oclif/commands/cubes/update.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/update.mjs +8 -0
- package/dist/adapters/oclif/commands/diff.d.mts +27 -0
- package/dist/adapters/oclif/commands/diff.mjs +66 -0
- package/dist/adapters/oclif/commands/gservice-account-keys/get.mjs +1 -1
- package/dist/adapters/oclif/commands/gservice-account-keys/reveal.mjs +2 -2
- package/dist/adapters/oclif/commands/gservice-accounts/create.mjs +1 -1
- package/dist/adapters/oclif/commands/gservice-accounts/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/gservice-accounts/get.mjs +1 -1
- package/dist/adapters/oclif/commands/gservice-accounts/list.mjs +7 -2
- package/dist/adapters/oclif/commands/init.d.mts +2 -1
- package/dist/adapters/oclif/commands/init.mjs +28 -24
- package/dist/adapters/oclif/commands/org/create.mjs +1 -1
- package/dist/adapters/oclif/commands/org/current.d.mts +2 -2
- package/dist/adapters/oclif/commands/org/current.mjs +2 -2
- package/dist/adapters/oclif/commands/org/get.mjs +1 -1
- package/dist/adapters/oclif/commands/org/list.d.mts +3 -11
- package/dist/adapters/oclif/commands/org/list.mjs +26 -26
- package/dist/adapters/oclif/commands/org/switch.d.mts +3 -2
- package/dist/adapters/oclif/commands/org/switch.mjs +13 -5
- package/dist/adapters/oclif/commands/pull.d.mts +28 -0
- package/dist/adapters/oclif/commands/pull.mjs +88 -0
- package/dist/adapters/oclif/commands/score-groups/create.mjs +3 -2
- package/dist/adapters/oclif/commands/score-groups/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/score-groups/get.mjs +1 -1
- package/dist/adapters/oclif/commands/score-groups/list.mjs +3 -2
- package/dist/adapters/oclif/commands/score-groups/update.mjs +1 -1
- package/dist/adapters/oclif/commands/scores/create.mjs +3 -2
- package/dist/adapters/oclif/commands/scores/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/scores/list.mjs +3 -2
- package/dist/adapters/oclif/commands/scores/update.mjs +1 -1
- package/dist/adapters/oclif/commands/segments/create.mjs +1 -1
- package/dist/adapters/oclif/commands/segments/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/segments/evaluate.mjs +2 -2
- package/dist/adapters/oclif/commands/segments/get-evaluation-history.mjs +2 -2
- package/dist/adapters/oclif/commands/segments/get-version.mjs +2 -2
- package/dist/adapters/oclif/commands/segments/get.mjs +1 -1
- package/dist/adapters/oclif/commands/segments/list-versions.mjs +16 -5
- package/dist/adapters/oclif/commands/segments/list.mjs +9 -2
- package/dist/adapters/oclif/commands/segments/restore-version.mjs +2 -2
- package/dist/adapters/oclif/commands/segments/update.mjs +1 -1
- package/dist/adapters/oclif/commands/sources/create.d.mts +11 -0
- package/dist/adapters/oclif/commands/sources/create.mjs +16 -0
- package/dist/adapters/oclif/commands/sources/delete.d.mts +6 -0
- package/dist/adapters/oclif/commands/sources/delete.mjs +8 -0
- package/dist/adapters/oclif/commands/sources/get.d.mts +6 -0
- package/dist/adapters/oclif/commands/sources/get.mjs +8 -0
- package/dist/adapters/oclif/commands/sources/list-streams.d.mts +6 -0
- package/dist/adapters/oclif/commands/sources/list-streams.mjs +31 -0
- package/dist/adapters/oclif/commands/sources/list.d.mts +6 -0
- package/dist/adapters/oclif/commands/sources/list.mjs +13 -0
- package/dist/adapters/oclif/commands/sources/update.d.mts +15 -0
- package/dist/adapters/oclif/commands/sources/update.mjs +21 -0
- package/dist/adapters/oclif/commands/status.d.mts +26 -0
- package/dist/adapters/oclif/commands/status.mjs +77 -0
- package/dist/adapters/oclif/commands/table-views/create.mjs +3 -2
- package/dist/adapters/oclif/commands/table-views/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/table-views/list.mjs +3 -2
- package/dist/adapters/oclif/commands/table-views/update.mjs +1 -1
- package/dist/adapters/oclif/commands/tables/create.mjs +1 -1
- package/dist/adapters/oclif/commands/tables/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/tables/get.mjs +1 -1
- package/dist/adapters/oclif/commands/tables/list.mjs +3 -2
- package/dist/adapters/oclif/commands/tables/update.mjs +1 -1
- package/dist/{base.command-d7VW6WTp.d.mts → base.command-D7X3ZNtY.d.mts} +0 -1
- package/dist/{base.command-DlVQ9Cqa.mjs → base.command-cV5d65r8.mjs} +15 -12
- package/dist/chunk-CfYAbeIz.mjs +13 -0
- package/dist/core-CMrP5BQS.mjs +2378 -0
- package/dist/{factory-D9sR_S_g.mjs → factory-C6XLqhT9.mjs} +44 -10
- package/dist/iac-render-BSZZEP0n.mjs +17 -0
- package/dist/index-BqKwXXAo.d.mts +598 -0
- package/dist/index.d.mts +3 -4
- package/dist/index.mjs +2 -2
- package/dist/{presets-Cvazkjmu.mjs → presets-CJbFbHlw.mjs} +35 -8
- package/dist/templates/.claude/settings.json +39 -0
- package/dist/templates/.devcontainer/devcontainer.json +2 -2
- package/dist/templates/.devcontainer/setup.sh +3 -0
- package/dist/templates/AGENTS.md +36 -13
- package/dist/templates/dbt/dbt_project.yml +2 -2
- package/dist/templates/skills/create-connections/SKILL.md +210 -0
- package/dist/templates/skills/create-connections/references/mappers.md +152 -0
- package/dist/templates/skills/{create-semantic-model → create-cubes}/SKILL.md +28 -26
- package/dist/templates/skills/create-cubes/references/bq-pk-fk-conventions.md +183 -0
- package/dist/templates/skills/{create-semantic-model → create-cubes}/references/cube-examples.md +85 -7
- package/dist/templates/skills/create-cubes/references/hubspot-entities.md +289 -0
- package/dist/templates/skills/create-cubes/references/jira-entities.md +201 -0
- package/dist/templates/skills/create-cubes/references/netsuite-entities.md +121 -0
- package/dist/templates/skills/create-cubes/references/stripe-entities.md +114 -0
- package/dist/templates/skills/create-dbt-transformations/SKILL.md +62 -33
- package/dist/templates/skills/create-dbt-transformations/references/edge-cases.md +21 -3
- package/dist/templates/skills/create-dbt-transformations/references/schema-conventions.md +21 -7
- package/dist/templates/skills/create-dbt-transformations/references/sql-templates.md +34 -20
- package/dist/templates/skills/explore-lakehouse/SKILL.md +8 -4
- package/dist/templates/skills/load-sample-data/SKILL.md +119 -0
- package/dist/templates/skills/visualize-semantic-model/SKILL.md +159 -0
- package/dist/templates/skills/visualize-semantic-model/scripts/render_graph.py +186 -0
- package/dist/{types-Y_ht_ja5.d.mts → types-CGjxcj4L.d.mts} +3 -0
- package/package.json +48 -6
- package/dist/adapters/oclif/commands/overlays/diff.d.mts +0 -19
- package/dist/adapters/oclif/commands/overlays/diff.mjs +0 -80
- package/dist/adapters/oclif/commands/overlays/pull.d.mts +0 -15
- package/dist/adapters/oclif/commands/overlays/pull.mjs +0 -44
- package/dist/adapters/oclif/commands/overlays/push.d.mts +0 -18
- package/dist/adapters/oclif/commands/overlays/push.mjs +0 -59
- package/dist/adapters/oclif/commands/overlays/status.d.mts +0 -18
- package/dist/adapters/oclif/commands/overlays/status.mjs +0 -53
- package/dist/core-gKJ_V-K5.mjs +0 -973
- package/dist/index-KAzwt5vr.d.mts +0 -190
- package/dist/types-C_p_6rkj.d.mts +0 -69
- /package/dist/templates/skills/{create-semantic-model → create-cubes}/references/key-patterns.md +0 -0
- /package/dist/templates/skills/{create-semantic-model → create-cubes}/references/validation-queries.md +0 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# HubSpot Entities Reference
|
|
2
|
+
|
|
3
|
+
## Table naming
|
|
4
|
+
|
|
5
|
+
Airbyte syncs HubSpot tables with a configurable prefix (default: `hubspot_`).
|
|
6
|
+
Inspect the BigQuery dataset to identify the actual prefix:
|
|
7
|
+
|
|
8
|
+
```sql
|
|
9
|
+
SELECT table_name FROM `<dataset>.INFORMATION_SCHEMA.TABLES`
|
|
10
|
+
WHERE table_name LIKE '%companies%' OR table_name LIKE '%deals%'
|
|
11
|
+
ORDER BY table_name LIMIT 20;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Throughout this document `<prefix>` is a placeholder for that prefix (e.g. `hubspot_`).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Primary entities
|
|
19
|
+
|
|
20
|
+
| Cube name | BigQuery table | PK | Notes |
|
|
21
|
+
| ------------------------ | ------------------------ | ------------ | -------------------------------------------------- | --- | --- | --- | --------- |
|
|
22
|
+
| `<prefix>companies` | `<prefix>companies` | `id` | `properties_name` is the display name |
|
|
23
|
+
| `<prefix>contacts` | `<prefix>contacts` | `id` | `properties_hs_full_name_or_email` is display name |
|
|
24
|
+
| `<prefix>deals` | `<prefix>deals` | `id` | `properties_dealname` is display name |
|
|
25
|
+
| `<prefix>tickets` | `<prefix>tickets` | `id` | — |
|
|
26
|
+
| `<prefix>owners` | `<prefix>owners` | `id` | Display name: `firstName | | ' ' | | lastName` |
|
|
27
|
+
| `<prefix>engagements` | `<prefix>engagements` | `id` | See engagement sub-types below |
|
|
28
|
+
| `<prefix>deal_pipelines` | `<prefix>deal_pipelines` | `pipelineId` | Stages stored as JSON array |
|
|
29
|
+
| `<prefix>line_items` | `<prefix>line_items` | `id` | `properties_name` |
|
|
30
|
+
| `<prefix>products` | `<prefix>products` | `id` | `properties_name` |
|
|
31
|
+
|
|
32
|
+
**Owner join pattern (shared by companies, contacts, deals, tickets):**
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
joins:
|
|
36
|
+
<prefix>owners:
|
|
37
|
+
relationship: many_to_one
|
|
38
|
+
sql: "${CUBE}.properties_hubspot_owner_id = ${<prefix>owners.id}"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Bridge / junction cubes (public: false)
|
|
44
|
+
|
|
45
|
+
HubSpot stores many-to-many associations as JSON arrays on the primary object.
|
|
46
|
+
Bridge cubes are required to join across these associations. They must be
|
|
47
|
+
`public: false` and use a composite PK.
|
|
48
|
+
|
|
49
|
+
### Association columns
|
|
50
|
+
|
|
51
|
+
| Source table | Column | Contains |
|
|
52
|
+
| --------------------- | ------------------------- | --------------------------------------- |
|
|
53
|
+
| `<prefix>deals` | `companies` | JSON array of company IDs |
|
|
54
|
+
| `<prefix>deals` | `contacts` | JSON array of contact IDs |
|
|
55
|
+
| `<prefix>deals` | `line_items` | JSON array of line item IDs |
|
|
56
|
+
| `<prefix>deals` | `deals` | JSON array (for tickets→deals) |
|
|
57
|
+
| `<prefix>tickets` | `companies` | JSON array of company IDs |
|
|
58
|
+
| `<prefix>tickets` | `contacts` | JSON array of contact IDs |
|
|
59
|
+
| `<prefix>tickets` | `deals` | JSON array of deal IDs (CAST to STRING) |
|
|
60
|
+
| `<prefix>companies` | `contacts` | JSON array of contact IDs |
|
|
61
|
+
| `<prefix>engagements` | `associations.contactIds` | JSON array of contact IDs |
|
|
62
|
+
| `<prefix>engagements` | `associations.companyIds` | JSON array of company IDs |
|
|
63
|
+
| `<prefix>engagements` | `associations.dealIds` | JSON array of deal IDs |
|
|
64
|
+
|
|
65
|
+
### Bridge cube: companies_to_deals
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
name: <prefix>companies_to_deals
|
|
69
|
+
sql: >
|
|
70
|
+
SELECT DISTINCT d.id as deal_id, company_id
|
|
71
|
+
FROM `<dataset>.<prefix>deals` d,
|
|
72
|
+
UNNEST(JSON_VALUE_ARRAY(d.companies)) company_id
|
|
73
|
+
public: false
|
|
74
|
+
dimensions:
|
|
75
|
+
id:
|
|
76
|
+
sql: "${CUBE.company_id} || ${CUBE.deal_id}"
|
|
77
|
+
type: string
|
|
78
|
+
primary_key: true
|
|
79
|
+
company_id:
|
|
80
|
+
sql: "${CUBE}.company_id"
|
|
81
|
+
type: string
|
|
82
|
+
deal_id:
|
|
83
|
+
sql: "${CUBE}.deal_id"
|
|
84
|
+
type: string
|
|
85
|
+
joins:
|
|
86
|
+
<prefix>companies:
|
|
87
|
+
relationship: many_to_one
|
|
88
|
+
sql: "${CUBE}.company_id = ${<prefix>companies.id}"
|
|
89
|
+
<prefix>deals:
|
|
90
|
+
relationship: many_to_one
|
|
91
|
+
sql: "${CUBE}.deal_id = ${<prefix>deals.id}"
|
|
92
|
+
refresh_key:
|
|
93
|
+
sql: "SELECT MAX(_airbyte_extracted_at) FROM `<dataset>.<prefix>deals`"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Bridge cube: companies_to_tickets
|
|
97
|
+
|
|
98
|
+
Same pattern — UNNEST `tickets.companies`:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
name: <prefix>companies_to_tickets
|
|
102
|
+
sql: >
|
|
103
|
+
SELECT DISTINCT t.id as ticket_id, company_id
|
|
104
|
+
FROM `<dataset>.<prefix>tickets` t,
|
|
105
|
+
UNNEST(JSON_VALUE_ARRAY(t.companies)) company_id
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Bridge cube: deals_to_tickets
|
|
109
|
+
|
|
110
|
+
Note: ticket `deals` column values are numbers — cast to STRING:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
name: <prefix>deals_to_tickets
|
|
114
|
+
sql: >
|
|
115
|
+
SELECT DISTINCT t.id AS ticket_id, CAST(deal_id AS STRING) AS deal_id
|
|
116
|
+
FROM `<dataset>.<prefix>tickets` t,
|
|
117
|
+
UNNEST(JSON_VALUE_ARRAY(t.deals)) AS deal_id
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Bridge cube: deals_to_line_items
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
name: <prefix>deals_to_line_items
|
|
124
|
+
sql: >
|
|
125
|
+
SELECT DISTINCT d.id AS deal_id, line_item_id
|
|
126
|
+
FROM `<dataset>.<prefix>deals` d,
|
|
127
|
+
UNNEST(JSON_VALUE_ARRAY(d.line_items)) AS line_item_id
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Bridge cube: contacts_to_deals
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
name: <prefix>contacts_to_deals
|
|
134
|
+
sql: >
|
|
135
|
+
SELECT DISTINCT d.id AS deal_id, contact_id
|
|
136
|
+
FROM `<dataset>.<prefix>deals` d,
|
|
137
|
+
UNNEST(JSON_VALUE_ARRAY(d.contacts)) contact_id
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Bridge cube: contacts_to_tickets
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
name: <prefix>contacts_to_tickets
|
|
144
|
+
sql: >
|
|
145
|
+
SELECT DISTINCT t.id AS ticket_id, contact_id
|
|
146
|
+
FROM `<dataset>.<prefix>tickets` t,
|
|
147
|
+
UNNEST(JSON_VALUE_ARRAY(t.contacts)) contact_id
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Bridge cube: contacts_to_companies
|
|
151
|
+
|
|
152
|
+
Note: this uses `SAFE_CAST` on both sides — IDs can have type mismatches:
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
name: <prefix>contacts_to_companies
|
|
156
|
+
sql: >
|
|
157
|
+
SELECT DISTINCT c.id AS company_id, contact_id
|
|
158
|
+
FROM `<dataset>.<prefix>companies` c,
|
|
159
|
+
UNNEST(JSON_VALUE_ARRAY(c.contacts)) AS contact_id
|
|
160
|
+
joins:
|
|
161
|
+
<prefix>contacts:
|
|
162
|
+
relationship: many_to_one
|
|
163
|
+
sql: "SAFE_CAST(${CUBE}.contact_id AS STRING) = SAFE_CAST(${<prefix>contacts.id} AS STRING)"
|
|
164
|
+
<prefix>companies:
|
|
165
|
+
relationship: many_to_one
|
|
166
|
+
sql: "SAFE_CAST(${CUBE}.company_id AS STRING) = SAFE_CAST(${<prefix>companies.id} AS STRING)"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Bridge cubes: engagements_to_contacts / companies / deals
|
|
170
|
+
|
|
171
|
+
Engagement IDs are integers — always CAST to STRING:
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
name: <prefix>engagements_to_contacts
|
|
175
|
+
sql: >
|
|
176
|
+
SELECT DISTINCT
|
|
177
|
+
CAST(e.id AS STRING) AS engagement_id,
|
|
178
|
+
CAST(contact_id AS STRING) AS contact_id
|
|
179
|
+
FROM `<dataset>.<prefix>engagements` e,
|
|
180
|
+
UNNEST(JSON_VALUE_ARRAY(e.associations.contactIds)) AS contact_id
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Same pattern for `companyIds` → `engagements_to_companies` and `dealIds` → `engagements_to_deals`.
|
|
184
|
+
|
|
185
|
+
Engagement join:
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
joins:
|
|
189
|
+
<prefix>engagements:
|
|
190
|
+
relationship: many_to_one
|
|
191
|
+
sql: "CAST(${CUBE}.engagement_id AS STRING) = CAST(${<prefix>engagements.id} AS STRING)"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Special cubes
|
|
197
|
+
|
|
198
|
+
### deal_pipeline_stages
|
|
199
|
+
|
|
200
|
+
Derived from `deal_pipelines.stages` JSON array. Not a raw table — uses `sql:` not `sql_table:`.
|
|
201
|
+
|
|
202
|
+
```yaml
|
|
203
|
+
name: <prefix>deal_pipeline_stages
|
|
204
|
+
sql: >
|
|
205
|
+
SELECT
|
|
206
|
+
JSON_VALUE(elem, '$.stageId') AS stage_id,
|
|
207
|
+
JSON_VALUE(elem, '$.label') AS label
|
|
208
|
+
FROM `<dataset>.<prefix>deal_pipelines`,
|
|
209
|
+
UNNEST(JSON_QUERY_ARRAY(stages)) AS elem
|
|
210
|
+
dimensions:
|
|
211
|
+
stage_id:
|
|
212
|
+
sql: "${CUBE}.stage_id"
|
|
213
|
+
type: string
|
|
214
|
+
primary_key: true
|
|
215
|
+
label:
|
|
216
|
+
sql: "${CUBE}.label"
|
|
217
|
+
type: string
|
|
218
|
+
joins:
|
|
219
|
+
<prefix>deals:
|
|
220
|
+
relationship: one_to_many
|
|
221
|
+
sql: "${CUBE}.stage_id = ${<prefix>deals.properties_dealstage}"
|
|
222
|
+
refresh_key:
|
|
223
|
+
sql: "SELECT MAX(_airbyte_extracted_at) FROM `<dataset>.<prefix>deal_pipelines`"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Deals join to stages and pipelines:
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
joins:
|
|
230
|
+
<prefix>deal_pipeline_stages:
|
|
231
|
+
relationship: many_to_one
|
|
232
|
+
sql: "${CUBE}.properties_dealstage = ${<prefix>deal_pipeline_stages.stage_id}"
|
|
233
|
+
<prefix>deal_pipelines:
|
|
234
|
+
relationship: many_to_one
|
|
235
|
+
sql: "${CUBE}.properties_pipeline = ${<prefix>deal_pipelines.pipelineId}"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### engagements sub-types
|
|
239
|
+
|
|
240
|
+
`engagements` table has sub-type tables: `engagements_calls`, `engagements_emails`,
|
|
241
|
+
`engagements_meetings`, `engagements_tasks`, `engagements_notes`.
|
|
242
|
+
|
|
243
|
+
Join pattern (one-to-one by ID with CAST):
|
|
244
|
+
|
|
245
|
+
```yaml
|
|
246
|
+
# On the engagements cube:
|
|
247
|
+
joins:
|
|
248
|
+
<prefix>engagements_calls:
|
|
249
|
+
relationship: one_to_one
|
|
250
|
+
sql: "CAST(${CUBE}.id AS STRING) = ${<prefix>engagements_calls.id}"
|
|
251
|
+
|
|
252
|
+
# On each sub-type cube:
|
|
253
|
+
joins:
|
|
254
|
+
<prefix>engagements:
|
|
255
|
+
relationship: many_to_one
|
|
256
|
+
sql: "${CUBE}.id = CAST(${<prefix>engagements.id} AS STRING)"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Deals measures
|
|
262
|
+
|
|
263
|
+
```yaml
|
|
264
|
+
measures:
|
|
265
|
+
count_closed:
|
|
266
|
+
type: count
|
|
267
|
+
filters:
|
|
268
|
+
- sql: "${CUBE}.properties_hs_is_closed = TRUE"
|
|
269
|
+
count_closed_won:
|
|
270
|
+
type: count
|
|
271
|
+
filters:
|
|
272
|
+
- sql: "${CUBE}.properties_hs_is_closed_won = TRUE"
|
|
273
|
+
count_closed_lost:
|
|
274
|
+
type: count
|
|
275
|
+
filters:
|
|
276
|
+
- sql: >
|
|
277
|
+
${CUBE}.properties_hs_is_closed = TRUE
|
|
278
|
+
AND ${CUBE}.properties_hs_is_closed_won = FALSE
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Common pitfalls
|
|
284
|
+
|
|
285
|
+
1. **ID type mismatches** — HubSpot IDs are sometimes integers, sometimes strings. Use `SAFE_CAST` when unsure (especially contacts_to_companies). Engagement IDs are always integers → always CAST to STRING.
|
|
286
|
+
2. **JSON_VALUE_ARRAY vs JSON_QUERY_ARRAY** — use `JSON_VALUE_ARRAY` when the array contains scalar strings/ints (association IDs); use `JSON_QUERY_ARRAY` when the array contains JSON objects (deal_pipelines stages).
|
|
287
|
+
3. **deal_pipeline_stages is derived** — uses `sql:` not `sql_table:`. Cannot be used in `revos cubes preview` diff against Airbyte-generated cubes.
|
|
288
|
+
4. **engagements bridge refresh_key** — use the parent engagement table timestamp, not the contact/company/deal table.
|
|
289
|
+
5. **Prefix varies** — always confirm the actual prefix from BigQuery before writing cube files.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Jira Entities Reference
|
|
2
|
+
|
|
3
|
+
## Table naming
|
|
4
|
+
|
|
5
|
+
Airbyte syncs Jira tables with a configurable prefix (default: `jira_`).
|
|
6
|
+
Inspect the BigQuery dataset to find the actual prefix:
|
|
7
|
+
|
|
8
|
+
```sql
|
|
9
|
+
SELECT table_name FROM `<dataset>.INFORMATION_SCHEMA.TABLES`
|
|
10
|
+
WHERE table_name LIKE '%issues%' OR table_name LIKE '%projects%'
|
|
11
|
+
ORDER BY table_name LIMIT 20;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Throughout this document `<prefix>` is a placeholder for that prefix.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Primary entities
|
|
19
|
+
|
|
20
|
+
| Cube name | BigQuery table | PK | Notes |
|
|
21
|
+
| ---------------------------- | ---------------------------- | ----------- | ------------------------------------------------ |
|
|
22
|
+
| `<prefix>issues` | `<prefix>issues` | `id` | `key` is display name; metadata in `fields` JSON |
|
|
23
|
+
| `<prefix>projects` | `<prefix>projects` | `id` | `name` |
|
|
24
|
+
| `<prefix>issue_types` | `<prefix>issue_types` | `id` | `name` |
|
|
25
|
+
| `<prefix>issue_priorities` | `<prefix>issue_priorities` | `id` | `name` |
|
|
26
|
+
| `<prefix>users` | `<prefix>users` | `accountId` | `displayName`; aliased 4× (see below) |
|
|
27
|
+
| `<prefix>sprints` | `<prefix>sprints` | `id` | `name`; FK `boardId` |
|
|
28
|
+
| `<prefix>boards` | `<prefix>boards` | `id` | `name`; FK `projectId` |
|
|
29
|
+
| `<prefix>issue_comments` | `<prefix>issue_comments` | — | FK `issueId`; `author` is JSON object |
|
|
30
|
+
| `<prefix>issue_worklogs` | `<prefix>issue_worklogs` | — | FK `issueId`; `author` is JSON object |
|
|
31
|
+
| `<prefix>project_components` | `<prefix>project_components` | — | FK `projectId` as INT64 |
|
|
32
|
+
| `<prefix>project_versions` | `<prefix>project_versions` | — | FK `projectId` as INT64 |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Users table aliasing
|
|
37
|
+
|
|
38
|
+
The single `<prefix>users` table must be exposed as **separate cubes** for each
|
|
39
|
+
role because Cube.js does not support joining the same table twice with
|
|
40
|
+
different conditions. Each alias has its own cube name and `sql_table` pointing
|
|
41
|
+
to the same physical table.
|
|
42
|
+
|
|
43
|
+
| Cube name | Role | Join condition on `<prefix>issues` |
|
|
44
|
+
| ------------------------------ | -------------- | -------------------------------------------------- |
|
|
45
|
+
| `<prefix>users_assignee` | Assignee | `JSON_VALUE(fields, '$.assignee.accountId')` |
|
|
46
|
+
| `<prefix>users_reporter` | Reporter | `JSON_VALUE(fields, '$.reporter.accountId')` |
|
|
47
|
+
| `<prefix>users_creator` | Creator | `JSON_VALUE(fields, '$.creator.accountId')` |
|
|
48
|
+
| `<prefix>users_comment_author` | Comment author | `JSON_VALUE(issue_comments.author, '$.accountId')` |
|
|
49
|
+
| `<prefix>users_worklog_author` | Worklog author | `JSON_VALUE(issue_worklogs.author, '$.accountId')` |
|
|
50
|
+
| `<prefix>users` | Direct queries | (no joins defined) |
|
|
51
|
+
|
|
52
|
+
Template for each alias:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
name: <prefix>users_assignee
|
|
56
|
+
sql_table: "`<dataset>.<prefix>users`"
|
|
57
|
+
joins:
|
|
58
|
+
<prefix>issues:
|
|
59
|
+
relationship: one_to_many
|
|
60
|
+
sql: >
|
|
61
|
+
${CUBE}.accountId =
|
|
62
|
+
JSON_VALUE(${<prefix>issues.fields}, '$.assignee.accountId')
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Corresponding join on `<prefix>issues`:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
joins:
|
|
69
|
+
<prefix>users_assignee:
|
|
70
|
+
relationship: many_to_one
|
|
71
|
+
sql: >
|
|
72
|
+
JSON_VALUE(${CUBE}.fields, '$.assignee.accountId') =
|
|
73
|
+
${<prefix>users_assignee.accountId}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Issues: fields JSON column
|
|
79
|
+
|
|
80
|
+
Issue metadata lives in a single `fields` JSON column. Extract with `JSON_VALUE`:
|
|
81
|
+
|
|
82
|
+
| Field | JSON path |
|
|
83
|
+
| ------------------- | ----------------------------------------- |
|
|
84
|
+
| Issue type ID | `$.issuetype.id` |
|
|
85
|
+
| Priority ID | `$.priority.id` |
|
|
86
|
+
| Assignee account ID | `$.assignee.accountId` |
|
|
87
|
+
| Reporter account ID | `$.reporter.accountId` |
|
|
88
|
+
| Creator account ID | `$.creator.accountId` |
|
|
89
|
+
| Status | `$.status.name` |
|
|
90
|
+
| Summary | `$.summary` |
|
|
91
|
+
| Story points | `$.story_points` or `$.customfield_10016` |
|
|
92
|
+
|
|
93
|
+
Example dimension:
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
dimensions:
|
|
97
|
+
status:
|
|
98
|
+
sql: "JSON_VALUE(${CUBE}.fields, '$.status.name')"
|
|
99
|
+
type: string
|
|
100
|
+
issue_type_name:
|
|
101
|
+
sql: "JSON_VALUE(${CUBE}.fields, '$.issuetype.name')"
|
|
102
|
+
type: string
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Bridge / junction cubes (public: false)
|
|
108
|
+
|
|
109
|
+
### sprint_issues
|
|
110
|
+
|
|
111
|
+
Sprints and issues are many-to-many. The `sprint_issues` table has columns
|
|
112
|
+
`issueId` (STRING) and `sprintId` (INT64). Composite PK required.
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
name: <prefix>sprint_issues
|
|
116
|
+
sql_table: "`<dataset>.<prefix>sprint_issues`"
|
|
117
|
+
public: false
|
|
118
|
+
dimensions:
|
|
119
|
+
id:
|
|
120
|
+
sql: "${CUBE}.issueId || '_' || CAST(${CUBE}.sprintId AS STRING)"
|
|
121
|
+
type: string
|
|
122
|
+
primary_key: true
|
|
123
|
+
joins:
|
|
124
|
+
<prefix>issues:
|
|
125
|
+
relationship: many_to_one
|
|
126
|
+
sql: "${CUBE}.issueId = ${<prefix>issues.id}"
|
|
127
|
+
<prefix>sprints:
|
|
128
|
+
relationship: many_to_one
|
|
129
|
+
sql: "${CUBE}.sprintId = ${<prefix>sprints.id}"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Issues and sprints join through this bridge:
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
# On <prefix>issues:
|
|
136
|
+
joins:
|
|
137
|
+
<prefix>sprint_issues:
|
|
138
|
+
relationship: one_to_many
|
|
139
|
+
sql: "${CUBE}.id = ${<prefix>sprint_issues.issueId}"
|
|
140
|
+
|
|
141
|
+
# On <prefix>sprints:
|
|
142
|
+
joins:
|
|
143
|
+
<prefix>sprint_issues:
|
|
144
|
+
relationship: one_to_many
|
|
145
|
+
sql: "${CUBE}.id = ${<prefix>sprint_issues.sprintId}"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### board_issues
|
|
149
|
+
|
|
150
|
+
Board issues (`board_issues` table) link boards to issues. Composite PK uses `id` + `boardId`.
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
name: <prefix>board_issues
|
|
154
|
+
sql_table: "`<dataset>.<prefix>board_issues`"
|
|
155
|
+
public: false
|
|
156
|
+
dimensions:
|
|
157
|
+
composite_id:
|
|
158
|
+
sql: "${CUBE}.id || '_' || CAST(${CUBE}.boardId AS STRING)"
|
|
159
|
+
type: string
|
|
160
|
+
primary_key: true
|
|
161
|
+
joins:
|
|
162
|
+
<prefix>issues:
|
|
163
|
+
relationship: many_to_one
|
|
164
|
+
sql: "${CUBE}.id = ${<prefix>issues.id}"
|
|
165
|
+
<prefix>boards:
|
|
166
|
+
relationship: many_to_one
|
|
167
|
+
sql: "${CUBE}.boardId = ${<prefix>boards.id}"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Type casting pitfalls
|
|
173
|
+
|
|
174
|
+
### project_components / project_versions → projects
|
|
175
|
+
|
|
176
|
+
`project_components.projectId` and `project_versions.projectId` are INT64 but
|
|
177
|
+
`projects.id` is STRING. Always cast:
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
180
|
+
# On <prefix>project_components:
|
|
181
|
+
joins:
|
|
182
|
+
<prefix>projects:
|
|
183
|
+
relationship: many_to_one
|
|
184
|
+
sql: "CAST(${CUBE}.projectId AS STRING) = ${<prefix>projects.id}"
|
|
185
|
+
|
|
186
|
+
# On <prefix>projects:
|
|
187
|
+
joins:
|
|
188
|
+
<prefix>project_components:
|
|
189
|
+
relationship: one_to_many
|
|
190
|
+
sql: "SAFE_CAST(${CUBE}.id AS INT64) = ${<prefix>project_components.projectId}"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Common pitfalls
|
|
196
|
+
|
|
197
|
+
1. **`fields` JSON column** — most issue attributes live here, not as top-level columns. Always check `INFORMATION_SCHEMA` before assuming a column exists at the top level.
|
|
198
|
+
2. **User aliases must be separate cubes** — do not try to join `users` twice from `issues`; Cube.js requires distinct cube names per join target.
|
|
199
|
+
3. **`sprintId` is INT64** — cast to STRING in the composite PK to avoid type errors.
|
|
200
|
+
4. **`issue_comments.author` is a JSON object** — extract `accountId` with `JSON_VALUE`, not a direct column reference.
|
|
201
|
+
5. **`boards.projectId` vs `projects.id`** — both are strings here; no cast needed. But `project_components.projectId` is INT64 — always cast.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# NetSuite Entities Reference
|
|
2
|
+
|
|
3
|
+
## Table naming
|
|
4
|
+
|
|
5
|
+
Airbyte syncs NetSuite tables with a configurable prefix (default: `netsuite_`).
|
|
6
|
+
Inspect the BigQuery dataset to confirm:
|
|
7
|
+
|
|
8
|
+
```sql
|
|
9
|
+
SELECT table_name FROM `<dataset>.INFORMATION_SCHEMA.TABLES`
|
|
10
|
+
WHERE table_name LIKE '%customer%' OR table_name LIKE '%salesorder%'
|
|
11
|
+
ORDER BY table_name LIMIT 20;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Throughout this document `<prefix>` is a placeholder for that prefix.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Primary entities
|
|
19
|
+
|
|
20
|
+
| Cube name | BigQuery table | PK | Notes |
|
|
21
|
+
| --------------------- | --------------------- | ---- | ------------------------------ |
|
|
22
|
+
| `<prefix>customer` | `<prefix>customer` | `id` | FK `subsidiary` is JSON object |
|
|
23
|
+
| `<prefix>contact` | `<prefix>contact` | `id` | FK `company` is JSON object |
|
|
24
|
+
| `<prefix>opportunity` | `<prefix>opportunity` | `id` | FK `entity` is JSON object |
|
|
25
|
+
| `<prefix>salesorder` | `<prefix>salesorder` | `id` | FK `entity` is JSON object |
|
|
26
|
+
| `<prefix>employee` | `<prefix>employee` | `id` | — |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## FK extraction pattern
|
|
31
|
+
|
|
32
|
+
NetSuite stores foreign keys as **JSON objects** with an `id` field rather than
|
|
33
|
+
as flat FK columns. Use `JSON_VALUE` to extract the ID and expose it as a
|
|
34
|
+
computed dimension.
|
|
35
|
+
|
|
36
|
+
### contact → customer
|
|
37
|
+
|
|
38
|
+
`contact.company` is a JSON object: `{"id": "123", "refName": "Acme Corp"}`.
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
name: <prefix>contact
|
|
42
|
+
sql_table: "`<dataset>.<prefix>contact`"
|
|
43
|
+
dimensions:
|
|
44
|
+
id:
|
|
45
|
+
sql: "id"
|
|
46
|
+
type: string
|
|
47
|
+
primary_key: true
|
|
48
|
+
customer_id:
|
|
49
|
+
sql: "JSON_VALUE(${CUBE}.company, '$.id')"
|
|
50
|
+
type: string
|
|
51
|
+
joins:
|
|
52
|
+
<prefix>customer:
|
|
53
|
+
relationship: many_to_one
|
|
54
|
+
sql: "${CUBE.customer_id} = ${<prefix>customer.id}"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### customer → subsidiary
|
|
58
|
+
|
|
59
|
+
`customer.subsidiary` is a JSON object: `{"id": "1", "refName": "Main Subsidiary"}`.
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
name: <prefix>customer
|
|
63
|
+
sql_table: "`<dataset>.<prefix>customer`"
|
|
64
|
+
dimensions:
|
|
65
|
+
subsidiary_id:
|
|
66
|
+
sql: "JSON_VALUE(${CUBE}.subsidiary, '$.id')"
|
|
67
|
+
type: string
|
|
68
|
+
joins:
|
|
69
|
+
<prefix>contact:
|
|
70
|
+
relationship: one_to_many
|
|
71
|
+
sql: "${CUBE}.id = ${<prefix>contact.customer_id}"
|
|
72
|
+
<prefix>salesorder:
|
|
73
|
+
relationship: one_to_many
|
|
74
|
+
sql: "${CUBE}.id = ${<prefix>salesorder.customer_id}"
|
|
75
|
+
<prefix>opportunity:
|
|
76
|
+
relationship: one_to_many
|
|
77
|
+
sql: "${CUBE}.id = ${<prefix>opportunity.customer_id}"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### opportunity / salesorder → customer
|
|
81
|
+
|
|
82
|
+
Both `opportunity` and `salesorder` use `entity` (not `company`) as the FK column:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
name: <prefix>opportunity
|
|
86
|
+
sql_table: "`<dataset>.<prefix>opportunity`"
|
|
87
|
+
dimensions:
|
|
88
|
+
id:
|
|
89
|
+
sql: "id"
|
|
90
|
+
type: string
|
|
91
|
+
primary_key: true
|
|
92
|
+
customer_id:
|
|
93
|
+
sql: "JSON_VALUE(${CUBE}.entity, '$.id')"
|
|
94
|
+
type: string
|
|
95
|
+
joins:
|
|
96
|
+
<prefix>customer:
|
|
97
|
+
relationship: many_to_one
|
|
98
|
+
sql: "${CUBE.customer_id} = ${<prefix>customer.id}"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Same pattern for `<prefix>salesorder`.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Relationship graph
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
customer ──< contact
|
|
109
|
+
customer ──< opportunity
|
|
110
|
+
customer ──< salesorder
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Common pitfalls
|
|
116
|
+
|
|
117
|
+
1. **FK columns are JSON objects** — `contact.company`, `opportunity.entity`, `salesorder.entity`, `customer.subsidiary` are all JSON objects. Never join directly; always extract with `JSON_VALUE(..., '$.id')` and expose as a computed dimension.
|
|
118
|
+
2. **`entity` vs `company`** — contacts use `company`, but opportunities and sales orders use `entity` as the customer FK column.
|
|
119
|
+
3. **PK column name is `id` (lowercase)** — not `internalId` or similar. Verify in INFORMATION_SCHEMA.
|
|
120
|
+
4. **Subsidiary** — `customer.subsidiary` holds the subsidiary FK. If the schema has multiple subsidiaries, you may need a `subsidiary` cube joined via `subsidiary_id`.
|
|
121
|
+
5. **No `_airbyte_extracted_at` guarantee** — some NetSuite streams use `lastModifiedDate` instead. Check actual column names before writing `refresh_key`.
|