business-as-code 2.1.1 → 2.3.0
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/CHANGELOG.md +18 -0
- package/README.md +2 -0
- package/package.json +7 -4
- package/src/dollar.ts +5 -2
- package/src/entities/organization.ts +31 -18
- package/src/goals.ts +78 -12
- package/src/index.ts +48 -18
- package/src/kpis.ts +62 -8
- package/src/metrics.ts +92 -79
- package/src/okrs.ts +120 -20
- package/src/organization.ts +12 -15
- package/src/process.ts +11 -12
- package/src/product.ts +8 -9
- package/src/queries.ts +238 -75
- package/src/roles.ts +62 -61
- package/src/workflow.ts +22 -15
- package/test/business.test.ts +282 -0
- package/test/dollar.test.ts +270 -0
- package/test/entities.test.ts +628 -0
- package/test/financials.test.ts +539 -0
- package/test/goals.test.ts +451 -0
- package/{src → test}/index.test.ts +1 -1
- package/test/kpis.test.ts +440 -0
- package/test/metrics.test.ts +744 -0
- package/test/okrs.test.ts +741 -0
- package/test/organization.test.ts +548 -0
- package/test/process.test.ts +503 -0
- package/test/product.test.ts +430 -0
- package/test/queries.test.ts +556 -0
- package/test/roles.test.ts +546 -0
- package/test/service.test.ts +450 -0
- package/test/types.test.ts +1141 -0
- package/test/vision.test.ts +214 -0
- package/test/workflow.test.ts +501 -0
- package/vitest.config.ts +47 -0
- package/.turbo/turbo-build.log +0 -5
- package/dist/business.d.ts +0 -62
- package/dist/business.d.ts.map +0 -1
- package/dist/business.js +0 -109
- package/dist/business.js.map +0 -1
- package/dist/dollar.d.ts +0 -60
- package/dist/dollar.d.ts.map +0 -1
- package/dist/dollar.js +0 -107
- package/dist/dollar.js.map +0 -1
- package/dist/entities/assets.d.ts +0 -21
- package/dist/entities/assets.d.ts.map +0 -1
- package/dist/entities/assets.js +0 -323
- package/dist/entities/assets.js.map +0 -1
- package/dist/entities/business.d.ts +0 -36
- package/dist/entities/business.d.ts.map +0 -1
- package/dist/entities/business.js +0 -370
- package/dist/entities/business.js.map +0 -1
- package/dist/entities/communication.d.ts +0 -21
- package/dist/entities/communication.d.ts.map +0 -1
- package/dist/entities/communication.js +0 -255
- package/dist/entities/communication.js.map +0 -1
- package/dist/entities/customers.d.ts +0 -58
- package/dist/entities/customers.d.ts.map +0 -1
- package/dist/entities/customers.js +0 -989
- package/dist/entities/customers.js.map +0 -1
- package/dist/entities/financials.d.ts +0 -59
- package/dist/entities/financials.d.ts.map +0 -1
- package/dist/entities/financials.js +0 -932
- package/dist/entities/financials.js.map +0 -1
- package/dist/entities/goals.d.ts +0 -58
- package/dist/entities/goals.d.ts.map +0 -1
- package/dist/entities/goals.js +0 -800
- package/dist/entities/goals.js.map +0 -1
- package/dist/entities/index.d.ts +0 -299
- package/dist/entities/index.d.ts.map +0 -1
- package/dist/entities/index.js +0 -198
- package/dist/entities/index.js.map +0 -1
- package/dist/entities/legal.d.ts +0 -21
- package/dist/entities/legal.d.ts.map +0 -1
- package/dist/entities/legal.js +0 -301
- package/dist/entities/legal.js.map +0 -1
- package/dist/entities/market.d.ts +0 -21
- package/dist/entities/market.d.ts.map +0 -1
- package/dist/entities/market.js +0 -301
- package/dist/entities/market.js.map +0 -1
- package/dist/entities/marketing.d.ts +0 -67
- package/dist/entities/marketing.d.ts.map +0 -1
- package/dist/entities/marketing.js +0 -1157
- package/dist/entities/marketing.js.map +0 -1
- package/dist/entities/offerings.d.ts +0 -51
- package/dist/entities/offerings.d.ts.map +0 -1
- package/dist/entities/offerings.js +0 -727
- package/dist/entities/offerings.js.map +0 -1
- package/dist/entities/operations.d.ts +0 -58
- package/dist/entities/operations.d.ts.map +0 -1
- package/dist/entities/operations.js +0 -787
- package/dist/entities/operations.js.map +0 -1
- package/dist/entities/organization.d.ts +0 -57
- package/dist/entities/organization.d.ts.map +0 -1
- package/dist/entities/organization.js +0 -807
- package/dist/entities/organization.js.map +0 -1
- package/dist/entities/partnerships.d.ts +0 -21
- package/dist/entities/partnerships.d.ts.map +0 -1
- package/dist/entities/partnerships.js +0 -300
- package/dist/entities/partnerships.js.map +0 -1
- package/dist/entities/planning.d.ts +0 -87
- package/dist/entities/planning.d.ts.map +0 -1
- package/dist/entities/planning.js +0 -271
- package/dist/entities/planning.js.map +0 -1
- package/dist/entities/projects.d.ts +0 -25
- package/dist/entities/projects.d.ts.map +0 -1
- package/dist/entities/projects.js +0 -349
- package/dist/entities/projects.js.map +0 -1
- package/dist/entities/risk.d.ts +0 -21
- package/dist/entities/risk.d.ts.map +0 -1
- package/dist/entities/risk.js +0 -293
- package/dist/entities/risk.js.map +0 -1
- package/dist/entities/sales.d.ts +0 -72
- package/dist/entities/sales.d.ts.map +0 -1
- package/dist/entities/sales.js +0 -1248
- package/dist/entities/sales.js.map +0 -1
- package/dist/financials.d.ts +0 -130
- package/dist/financials.d.ts.map +0 -1
- package/dist/financials.js +0 -297
- package/dist/financials.js.map +0 -1
- package/dist/goals.d.ts +0 -87
- package/dist/goals.d.ts.map +0 -1
- package/dist/goals.js +0 -215
- package/dist/goals.js.map +0 -1
- package/dist/index.d.ts +0 -97
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -132
- package/dist/index.js.map +0 -1
- package/dist/kpis.d.ts +0 -118
- package/dist/kpis.d.ts.map +0 -1
- package/dist/kpis.js +0 -232
- package/dist/kpis.js.map +0 -1
- package/dist/metrics.d.ts +0 -448
- package/dist/metrics.d.ts.map +0 -1
- package/dist/metrics.js +0 -325
- package/dist/metrics.js.map +0 -1
- package/dist/okrs.d.ts +0 -123
- package/dist/okrs.d.ts.map +0 -1
- package/dist/okrs.js +0 -269
- package/dist/okrs.js.map +0 -1
- package/dist/organization.d.ts +0 -585
- package/dist/organization.d.ts.map +0 -1
- package/dist/organization.js +0 -173
- package/dist/organization.js.map +0 -1
- package/dist/process.d.ts +0 -112
- package/dist/process.d.ts.map +0 -1
- package/dist/process.js +0 -241
- package/dist/process.js.map +0 -1
- package/dist/product.d.ts +0 -85
- package/dist/product.d.ts.map +0 -1
- package/dist/product.js +0 -145
- package/dist/product.js.map +0 -1
- package/dist/queries.d.ts +0 -304
- package/dist/queries.d.ts.map +0 -1
- package/dist/queries.js +0 -415
- package/dist/queries.js.map +0 -1
- package/dist/roles.d.ts +0 -340
- package/dist/roles.d.ts.map +0 -1
- package/dist/roles.js +0 -255
- package/dist/roles.js.map +0 -1
- package/dist/service.d.ts +0 -61
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -140
- package/dist/service.js.map +0 -1
- package/dist/types.d.ts +0 -459
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/dist/vision.d.ts +0 -38
- package/dist/vision.d.ts.map +0 -1
- package/dist/vision.js +0 -68
- package/dist/vision.js.map +0 -1
- package/dist/workflow.d.ts +0 -115
- package/dist/workflow.d.ts.map +0 -1
- package/dist/workflow.js +0 -247
- package/dist/workflow.js.map +0 -1
- package/src/business.js +0 -108
- package/src/dollar.js +0 -106
- package/src/entities/assets.js +0 -322
- package/src/entities/business.js +0 -369
- package/src/entities/communication.js +0 -254
- package/src/entities/customers.js +0 -988
- package/src/entities/financials.js +0 -931
- package/src/entities/goals.js +0 -799
- package/src/entities/index.js +0 -197
- package/src/entities/legal.js +0 -300
- package/src/entities/market.js +0 -300
- package/src/entities/marketing.js +0 -1156
- package/src/entities/offerings.js +0 -726
- package/src/entities/operations.js +0 -786
- package/src/entities/organization.js +0 -806
- package/src/entities/partnerships.js +0 -299
- package/src/entities/planning.js +0 -270
- package/src/entities/projects.js +0 -348
- package/src/entities/risk.js +0 -292
- package/src/entities/sales.js +0 -1247
- package/src/financials.js +0 -296
- package/src/goals.js +0 -214
- package/src/index.js +0 -131
- package/src/index.test.js +0 -274
- package/src/kpis.js +0 -231
- package/src/metrics.js +0 -324
- package/src/okrs.js +0 -268
- package/src/organization.js +0 -172
- package/src/process.js +0 -240
- package/src/product.js +0 -144
- package/src/queries.js +0 -414
- package/src/roles.js +0 -254
- package/src/service.js +0 -139
- package/src/types.js +0 -4
- package/src/vision.js +0 -67
- package/src/workflow.js +0 -246
package/src/process.ts
CHANGED
|
@@ -89,7 +89,7 @@ export function getStepsByAutomationLevel(
|
|
|
89
89
|
process: ProcessDefinition,
|
|
90
90
|
level: ProcessStep['automationLevel']
|
|
91
91
|
): ProcessStep[] {
|
|
92
|
-
return process.steps?.filter(step => step.automationLevel === level) || []
|
|
92
|
+
return process.steps?.filter((step) => step.automationLevel === level) || []
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
@@ -160,7 +160,7 @@ export function calculateAutomationPercentage(process: ProcessDefinition): numbe
|
|
|
160
160
|
if (!process.steps || process.steps.length === 0) return 0
|
|
161
161
|
|
|
162
162
|
const automatedSteps = process.steps.filter(
|
|
163
|
-
step => step.automationLevel === 'automated' || step.automationLevel === 'semi-automated'
|
|
163
|
+
(step) => step.automationLevel === 'automated' || step.automationLevel === 'semi-automated'
|
|
164
164
|
).length
|
|
165
165
|
|
|
166
166
|
return (automatedSteps / process.steps.length) * 100
|
|
@@ -170,7 +170,7 @@ export function calculateAutomationPercentage(process: ProcessDefinition): numbe
|
|
|
170
170
|
* Get metric by name
|
|
171
171
|
*/
|
|
172
172
|
export function getMetric(process: ProcessDefinition, name: string): ProcessMetric | undefined {
|
|
173
|
-
return process.metrics?.find(m => m.name === name)
|
|
173
|
+
return process.metrics?.find((m) => m.name === name)
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/**
|
|
@@ -198,14 +198,13 @@ export function updateMetric(
|
|
|
198
198
|
metricName: string,
|
|
199
199
|
currentValue: number
|
|
200
200
|
): ProcessDefinition {
|
|
201
|
-
const metrics = process.metrics?.map(m =>
|
|
201
|
+
const metrics = process.metrics?.map((m) =>
|
|
202
202
|
m.name === metricName ? { ...m, current: currentValue } : m
|
|
203
203
|
)
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
205
|
+
const result: ProcessDefinition = { ...process }
|
|
206
|
+
if (metrics !== undefined) result.metrics = metrics
|
|
207
|
+
return result
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
/**
|
|
@@ -222,10 +221,10 @@ export function addStep(process: ProcessDefinition, step: ProcessStep): ProcessD
|
|
|
222
221
|
* Remove step from process
|
|
223
222
|
*/
|
|
224
223
|
export function removeStep(process: ProcessDefinition, stepOrder: number): ProcessDefinition {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
const steps = process.steps?.filter((s) => s.order !== stepOrder)
|
|
225
|
+
const result: ProcessDefinition = { ...process }
|
|
226
|
+
if (steps !== undefined) result.steps = steps
|
|
227
|
+
return result
|
|
229
228
|
}
|
|
230
229
|
|
|
231
230
|
/**
|
package/src/product.ts
CHANGED
|
@@ -82,7 +82,7 @@ export function getRoadmapByStatus(
|
|
|
82
82
|
product: ProductDefinition,
|
|
83
83
|
status: RoadmapItem['status']
|
|
84
84
|
): RoadmapItem[] {
|
|
85
|
-
return product.roadmap?.filter(item => item.status === status) || []
|
|
85
|
+
return product.roadmap?.filter((item) => item.status === status) || []
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
@@ -92,7 +92,7 @@ export function getRoadmapByPriority(
|
|
|
92
92
|
product: ProductDefinition,
|
|
93
93
|
priority: RoadmapItem['priority']
|
|
94
94
|
): RoadmapItem[] {
|
|
95
|
-
return product.roadmap?.filter(item => item.priority === priority) || []
|
|
95
|
+
return product.roadmap?.filter((item) => item.priority === priority) || []
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
@@ -102,7 +102,7 @@ export function getOverdueRoadmapItems(product: ProductDefinition): RoadmapItem[
|
|
|
102
102
|
const now = new Date()
|
|
103
103
|
return (
|
|
104
104
|
product.roadmap?.filter(
|
|
105
|
-
item =>
|
|
105
|
+
(item) =>
|
|
106
106
|
item.targetDate &&
|
|
107
107
|
item.targetDate < now &&
|
|
108
108
|
item.status !== 'completed' &&
|
|
@@ -119,14 +119,13 @@ export function updateRoadmapItem(
|
|
|
119
119
|
itemName: string,
|
|
120
120
|
updates: Partial<RoadmapItem>
|
|
121
121
|
): ProductDefinition {
|
|
122
|
-
const roadmap = product.roadmap?.map(item =>
|
|
122
|
+
const roadmap = product.roadmap?.map((item) =>
|
|
123
123
|
item.name === itemName ? { ...item, ...updates } : item
|
|
124
124
|
)
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
126
|
+
const result: ProductDefinition = { ...product }
|
|
127
|
+
if (roadmap !== undefined) result.roadmap = roadmap
|
|
128
|
+
return result
|
|
130
129
|
}
|
|
131
130
|
|
|
132
131
|
/**
|
|
@@ -145,7 +144,7 @@ export function addFeature(product: ProductDefinition, feature: string): Product
|
|
|
145
144
|
export function removeFeature(product: ProductDefinition, feature: string): ProductDefinition {
|
|
146
145
|
return {
|
|
147
146
|
...product,
|
|
148
|
-
features: product.features?.filter(f => f !== feature) || [],
|
|
147
|
+
features: product.features?.filter((f) => f !== feature) || [],
|
|
149
148
|
}
|
|
150
149
|
}
|
|
151
150
|
|
package/src/queries.ts
CHANGED
|
@@ -17,14 +17,7 @@ import type { TimePeriod } from './types.js'
|
|
|
17
17
|
/**
|
|
18
18
|
* Time granularity for aggregations
|
|
19
19
|
*/
|
|
20
|
-
export type Granularity =
|
|
21
|
-
| 'minute'
|
|
22
|
-
| 'hour'
|
|
23
|
-
| 'day'
|
|
24
|
-
| 'week'
|
|
25
|
-
| 'month'
|
|
26
|
-
| 'quarter'
|
|
27
|
-
| 'year'
|
|
20
|
+
export type Granularity = 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'
|
|
28
21
|
|
|
29
22
|
/**
|
|
30
23
|
* Aggregation function
|
|
@@ -50,18 +43,18 @@ export type AggregateFunction =
|
|
|
50
43
|
* Comparison operator
|
|
51
44
|
*/
|
|
52
45
|
export type Operator =
|
|
53
|
-
| 'eq'
|
|
54
|
-
| 'ne'
|
|
55
|
-
| 'gt'
|
|
56
|
-
| 'gte'
|
|
57
|
-
| 'lt'
|
|
58
|
-
| 'lte'
|
|
59
|
-
| 'in'
|
|
60
|
-
| 'notIn'
|
|
61
|
-
| 'like'
|
|
46
|
+
| 'eq' // =
|
|
47
|
+
| 'ne' // !=
|
|
48
|
+
| 'gt' // >
|
|
49
|
+
| 'gte' // >=
|
|
50
|
+
| 'lt' // <
|
|
51
|
+
| 'lte' // <=
|
|
52
|
+
| 'in' // IN
|
|
53
|
+
| 'notIn' // NOT IN
|
|
54
|
+
| 'like' // LIKE
|
|
62
55
|
| 'notLike' // NOT LIKE
|
|
63
56
|
| 'between' // BETWEEN
|
|
64
|
-
| 'isNull'
|
|
57
|
+
| 'isNull' // IS NULL
|
|
65
58
|
| 'isNotNull' // IS NOT NULL
|
|
66
59
|
|
|
67
60
|
/**
|
|
@@ -78,11 +71,11 @@ export type SortDirection = 'asc' | 'desc'
|
|
|
78
71
|
*/
|
|
79
72
|
export interface Dimension {
|
|
80
73
|
name: string
|
|
81
|
-
field: string
|
|
74
|
+
field: string // Source field in database
|
|
82
75
|
type: 'string' | 'number' | 'date' | 'boolean'
|
|
83
76
|
description?: string
|
|
84
|
-
granularity?: Granularity
|
|
85
|
-
format?: string
|
|
77
|
+
granularity?: Granularity // For time dimensions
|
|
78
|
+
format?: string // Display format
|
|
86
79
|
}
|
|
87
80
|
|
|
88
81
|
/**
|
|
@@ -90,7 +83,7 @@ export interface Dimension {
|
|
|
90
83
|
*/
|
|
91
84
|
export interface Measure {
|
|
92
85
|
name: string
|
|
93
|
-
field: string
|
|
86
|
+
field: string // Source field or expression
|
|
94
87
|
aggregate: AggregateFunction
|
|
95
88
|
type?: 'number' | 'currency' | 'percent'
|
|
96
89
|
description?: string
|
|
@@ -103,8 +96,8 @@ export interface Measure {
|
|
|
103
96
|
*/
|
|
104
97
|
export interface CalculatedMeasure {
|
|
105
98
|
name: string
|
|
106
|
-
expression: string
|
|
107
|
-
measures: string[]
|
|
99
|
+
expression: string // e.g., "revenue - cogs" or "revenue / customers"
|
|
100
|
+
measures: string[] // Dependencies
|
|
108
101
|
type?: 'number' | 'currency' | 'percent'
|
|
109
102
|
description?: string
|
|
110
103
|
format?: string
|
|
@@ -137,8 +130,8 @@ export interface Sort {
|
|
|
137
130
|
* Time range filter
|
|
138
131
|
*/
|
|
139
132
|
export interface TimeRange {
|
|
140
|
-
field: string
|
|
141
|
-
start?: Date | string
|
|
133
|
+
field: string // The timestamp field
|
|
134
|
+
start?: Date | string // Absolute or relative (e.g., '-30d')
|
|
142
135
|
end?: Date | string
|
|
143
136
|
granularity?: Granularity
|
|
144
137
|
}
|
|
@@ -155,11 +148,11 @@ export interface Query {
|
|
|
155
148
|
description?: string
|
|
156
149
|
|
|
157
150
|
// Data source
|
|
158
|
-
source: string
|
|
151
|
+
source: string // Table or view name
|
|
159
152
|
|
|
160
153
|
// What to select
|
|
161
|
-
dimensions?: string[]
|
|
162
|
-
measures?: string[]
|
|
154
|
+
dimensions?: string[] // Dimension names to group by
|
|
155
|
+
measures?: string[] // Measure names to aggregate
|
|
163
156
|
|
|
164
157
|
// Filtering
|
|
165
158
|
filters?: Filter[]
|
|
@@ -197,8 +190,8 @@ export interface View {
|
|
|
197
190
|
|
|
198
191
|
// Materialization options
|
|
199
192
|
materialized?: boolean
|
|
200
|
-
refreshInterval?: string
|
|
201
|
-
retention?: string
|
|
193
|
+
refreshInterval?: string // e.g., '5m', '1h', '1d'
|
|
194
|
+
retention?: string // How long to keep data
|
|
202
195
|
|
|
203
196
|
// Access
|
|
204
197
|
public?: boolean
|
|
@@ -244,16 +237,16 @@ export interface DashboardItem {
|
|
|
244
237
|
* Visualization type
|
|
245
238
|
*/
|
|
246
239
|
export type Visualization =
|
|
247
|
-
| 'number'
|
|
248
|
-
| 'trend'
|
|
249
|
-
| 'table'
|
|
250
|
-
| 'bar'
|
|
251
|
-
| 'line'
|
|
252
|
-
| 'area'
|
|
253
|
-
| 'pie'
|
|
254
|
-
| 'funnel'
|
|
255
|
-
| 'cohort'
|
|
256
|
-
| 'heatmap'
|
|
240
|
+
| 'number' // Single big number
|
|
241
|
+
| 'trend' // Number with sparkline
|
|
242
|
+
| 'table' // Data table
|
|
243
|
+
| 'bar' // Bar chart
|
|
244
|
+
| 'line' // Line chart
|
|
245
|
+
| 'area' // Area chart
|
|
246
|
+
| 'pie' // Pie chart
|
|
247
|
+
| 'funnel' // Funnel chart
|
|
248
|
+
| 'cohort' // Cohort matrix
|
|
249
|
+
| 'heatmap' // Heatmap
|
|
257
250
|
|
|
258
251
|
// =============================================================================
|
|
259
252
|
// Metric Definitions (Standard SaaS Metrics as Queries)
|
|
@@ -266,18 +259,39 @@ export const StandardDimensions: Record<string, Dimension> = {
|
|
|
266
259
|
// Time
|
|
267
260
|
date: { name: 'date', field: 'date', type: 'date', description: 'Event date' },
|
|
268
261
|
month: { name: 'month', field: 'date', type: 'date', granularity: 'month', description: 'Month' },
|
|
269
|
-
quarter: {
|
|
262
|
+
quarter: {
|
|
263
|
+
name: 'quarter',
|
|
264
|
+
field: 'date',
|
|
265
|
+
type: 'date',
|
|
266
|
+
granularity: 'quarter',
|
|
267
|
+
description: 'Quarter',
|
|
268
|
+
},
|
|
270
269
|
year: { name: 'year', field: 'date', type: 'date', granularity: 'year', description: 'Year' },
|
|
271
270
|
|
|
272
271
|
// Customer
|
|
273
|
-
customerId: {
|
|
274
|
-
|
|
272
|
+
customerId: {
|
|
273
|
+
name: 'customerId',
|
|
274
|
+
field: 'customer_id',
|
|
275
|
+
type: 'string',
|
|
276
|
+
description: 'Customer ID',
|
|
277
|
+
},
|
|
278
|
+
customerSegment: {
|
|
279
|
+
name: 'customerSegment',
|
|
280
|
+
field: 'customer_segment',
|
|
281
|
+
type: 'string',
|
|
282
|
+
description: 'Customer segment',
|
|
283
|
+
},
|
|
275
284
|
plan: { name: 'plan', field: 'plan', type: 'string', description: 'Subscription plan' },
|
|
276
285
|
cohort: { name: 'cohort', field: 'cohort', type: 'string', description: 'Customer cohort' },
|
|
277
286
|
|
|
278
287
|
// Product
|
|
279
288
|
productId: { name: 'productId', field: 'product_id', type: 'string', description: 'Product ID' },
|
|
280
|
-
productName: {
|
|
289
|
+
productName: {
|
|
290
|
+
name: 'productName',
|
|
291
|
+
field: 'product_name',
|
|
292
|
+
type: 'string',
|
|
293
|
+
description: 'Product name',
|
|
294
|
+
},
|
|
281
295
|
feature: { name: 'feature', field: 'feature', type: 'string', description: 'Feature name' },
|
|
282
296
|
|
|
283
297
|
// Geography
|
|
@@ -285,9 +299,19 @@ export const StandardDimensions: Record<string, Dimension> = {
|
|
|
285
299
|
region: { name: 'region', field: 'region', type: 'string', description: 'Region' },
|
|
286
300
|
|
|
287
301
|
// Channel
|
|
288
|
-
channel: {
|
|
302
|
+
channel: {
|
|
303
|
+
name: 'channel',
|
|
304
|
+
field: 'channel',
|
|
305
|
+
type: 'string',
|
|
306
|
+
description: 'Acquisition channel',
|
|
307
|
+
},
|
|
289
308
|
source: { name: 'source', field: 'source', type: 'string', description: 'Traffic source' },
|
|
290
|
-
campaign: {
|
|
309
|
+
campaign: {
|
|
310
|
+
name: 'campaign',
|
|
311
|
+
field: 'campaign',
|
|
312
|
+
type: 'string',
|
|
313
|
+
description: 'Marketing campaign',
|
|
314
|
+
},
|
|
291
315
|
}
|
|
292
316
|
|
|
293
317
|
/**
|
|
@@ -295,27 +319,117 @@ export const StandardDimensions: Record<string, Dimension> = {
|
|
|
295
319
|
*/
|
|
296
320
|
export const StandardMeasures: Record<string, Measure> = {
|
|
297
321
|
// Revenue
|
|
298
|
-
revenue: {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
322
|
+
revenue: {
|
|
323
|
+
name: 'revenue',
|
|
324
|
+
field: 'revenue',
|
|
325
|
+
aggregate: 'sum',
|
|
326
|
+
type: 'currency',
|
|
327
|
+
description: 'Total revenue',
|
|
328
|
+
},
|
|
329
|
+
mrr: {
|
|
330
|
+
name: 'mrr',
|
|
331
|
+
field: 'mrr',
|
|
332
|
+
aggregate: 'sum',
|
|
333
|
+
type: 'currency',
|
|
334
|
+
description: 'Monthly recurring revenue',
|
|
335
|
+
},
|
|
336
|
+
newMrr: {
|
|
337
|
+
name: 'newMrr',
|
|
338
|
+
field: 'new_mrr',
|
|
339
|
+
aggregate: 'sum',
|
|
340
|
+
type: 'currency',
|
|
341
|
+
description: 'New MRR',
|
|
342
|
+
},
|
|
343
|
+
expansionMrr: {
|
|
344
|
+
name: 'expansionMrr',
|
|
345
|
+
field: 'expansion_mrr',
|
|
346
|
+
aggregate: 'sum',
|
|
347
|
+
type: 'currency',
|
|
348
|
+
description: 'Expansion MRR',
|
|
349
|
+
},
|
|
350
|
+
contractionMrr: {
|
|
351
|
+
name: 'contractionMrr',
|
|
352
|
+
field: 'contraction_mrr',
|
|
353
|
+
aggregate: 'sum',
|
|
354
|
+
type: 'currency',
|
|
355
|
+
description: 'Contraction MRR',
|
|
356
|
+
},
|
|
357
|
+
churnedMrr: {
|
|
358
|
+
name: 'churnedMrr',
|
|
359
|
+
field: 'churned_mrr',
|
|
360
|
+
aggregate: 'sum',
|
|
361
|
+
type: 'currency',
|
|
362
|
+
description: 'Churned MRR',
|
|
363
|
+
},
|
|
304
364
|
|
|
305
365
|
// Customers
|
|
306
|
-
customers: {
|
|
307
|
-
|
|
308
|
-
|
|
366
|
+
customers: {
|
|
367
|
+
name: 'customers',
|
|
368
|
+
field: 'customer_id',
|
|
369
|
+
aggregate: 'countDistinct',
|
|
370
|
+
type: 'number',
|
|
371
|
+
description: 'Unique customers',
|
|
372
|
+
},
|
|
373
|
+
newCustomers: {
|
|
374
|
+
name: 'newCustomers',
|
|
375
|
+
field: 'new_customer_id',
|
|
376
|
+
aggregate: 'countDistinct',
|
|
377
|
+
type: 'number',
|
|
378
|
+
description: 'New customers',
|
|
379
|
+
},
|
|
380
|
+
churnedCustomers: {
|
|
381
|
+
name: 'churnedCustomers',
|
|
382
|
+
field: 'churned_customer_id',
|
|
383
|
+
aggregate: 'countDistinct',
|
|
384
|
+
type: 'number',
|
|
385
|
+
description: 'Churned customers',
|
|
386
|
+
},
|
|
309
387
|
|
|
310
388
|
// Usage
|
|
311
|
-
events: {
|
|
312
|
-
|
|
313
|
-
|
|
389
|
+
events: {
|
|
390
|
+
name: 'events',
|
|
391
|
+
field: 'event_id',
|
|
392
|
+
aggregate: 'count',
|
|
393
|
+
type: 'number',
|
|
394
|
+
description: 'Event count',
|
|
395
|
+
},
|
|
396
|
+
sessions: {
|
|
397
|
+
name: 'sessions',
|
|
398
|
+
field: 'session_id',
|
|
399
|
+
aggregate: 'countDistinct',
|
|
400
|
+
type: 'number',
|
|
401
|
+
description: 'Unique sessions',
|
|
402
|
+
},
|
|
403
|
+
activeUsers: {
|
|
404
|
+
name: 'activeUsers',
|
|
405
|
+
field: 'user_id',
|
|
406
|
+
aggregate: 'countDistinct',
|
|
407
|
+
type: 'number',
|
|
408
|
+
description: 'Active users',
|
|
409
|
+
},
|
|
314
410
|
|
|
315
411
|
// Costs
|
|
316
|
-
cogs: {
|
|
317
|
-
|
|
318
|
-
|
|
412
|
+
cogs: {
|
|
413
|
+
name: 'cogs',
|
|
414
|
+
field: 'cogs',
|
|
415
|
+
aggregate: 'sum',
|
|
416
|
+
type: 'currency',
|
|
417
|
+
description: 'Cost of goods sold',
|
|
418
|
+
},
|
|
419
|
+
salesSpend: {
|
|
420
|
+
name: 'salesSpend',
|
|
421
|
+
field: 'sales_spend',
|
|
422
|
+
aggregate: 'sum',
|
|
423
|
+
type: 'currency',
|
|
424
|
+
description: 'Sales spend',
|
|
425
|
+
},
|
|
426
|
+
marketingSpend: {
|
|
427
|
+
name: 'marketingSpend',
|
|
428
|
+
field: 'marketing_spend',
|
|
429
|
+
aggregate: 'sum',
|
|
430
|
+
type: 'currency',
|
|
431
|
+
description: 'Marketing spend',
|
|
432
|
+
},
|
|
319
433
|
}
|
|
320
434
|
|
|
321
435
|
/**
|
|
@@ -471,8 +585,17 @@ export class QueryBuilder {
|
|
|
471
585
|
return this
|
|
472
586
|
}
|
|
473
587
|
|
|
474
|
-
timeRange(
|
|
475
|
-
|
|
588
|
+
timeRange(
|
|
589
|
+
field: string,
|
|
590
|
+
start?: Date | string,
|
|
591
|
+
end?: Date | string,
|
|
592
|
+
granularity?: Granularity
|
|
593
|
+
): this {
|
|
594
|
+
const timeRange: TimeRange = { field }
|
|
595
|
+
if (start !== undefined) timeRange.start = start
|
|
596
|
+
if (end !== undefined) timeRange.end = end
|
|
597
|
+
if (granularity !== undefined) timeRange.granularity = granularity
|
|
598
|
+
this._query.timeRange = timeRange
|
|
476
599
|
return this
|
|
477
600
|
}
|
|
478
601
|
|
|
@@ -610,8 +733,8 @@ export class ViewBuilder {
|
|
|
610
733
|
|
|
611
734
|
materialize(refreshInterval?: string, retention?: string): this {
|
|
612
735
|
this._view.materialized = true
|
|
613
|
-
this._view.refreshInterval = refreshInterval
|
|
614
|
-
this._view.retention = retention
|
|
736
|
+
if (refreshInterval !== undefined) this._view.refreshInterval = refreshInterval
|
|
737
|
+
if (retention !== undefined) this._view.retention = retention
|
|
615
738
|
return this
|
|
616
739
|
}
|
|
617
740
|
|
|
@@ -661,17 +784,27 @@ export class DashboardBuilder {
|
|
|
661
784
|
return this
|
|
662
785
|
}
|
|
663
786
|
|
|
664
|
-
add(
|
|
787
|
+
add(
|
|
788
|
+
viewDef: View,
|
|
789
|
+
options?: {
|
|
790
|
+
x?: number
|
|
791
|
+
y?: number
|
|
792
|
+
width?: number
|
|
793
|
+
height?: number
|
|
794
|
+
visualization?: Visualization
|
|
795
|
+
}
|
|
796
|
+
): this {
|
|
665
797
|
this._dashboard.views.push(viewDef)
|
|
666
798
|
if (options && this._dashboard.layout) {
|
|
667
|
-
|
|
799
|
+
const item: DashboardItem = {
|
|
668
800
|
viewName: viewDef.name,
|
|
669
801
|
x: options.x || 0,
|
|
670
802
|
y: options.y || 0,
|
|
671
803
|
width: options.width || 1,
|
|
672
804
|
height: options.height || 1,
|
|
673
|
-
|
|
674
|
-
|
|
805
|
+
}
|
|
806
|
+
if (options.visualization !== undefined) item.visualization = options.visualization
|
|
807
|
+
this._dashboard.layout.items.push(item)
|
|
675
808
|
}
|
|
676
809
|
return this
|
|
677
810
|
}
|
|
@@ -711,11 +844,41 @@ export class DashboardBuilder {
|
|
|
711
844
|
export const ExecutiveDashboard = dashboard('executive')
|
|
712
845
|
.describe('Executive overview of key SaaS metrics')
|
|
713
846
|
.layout(4, 3)
|
|
714
|
-
.add(view('mrr', MrrOverview).build(), {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
847
|
+
.add(view('mrr', MrrOverview).build(), {
|
|
848
|
+
x: 0,
|
|
849
|
+
y: 0,
|
|
850
|
+
width: 2,
|
|
851
|
+
height: 1,
|
|
852
|
+
visualization: 'trend',
|
|
853
|
+
})
|
|
854
|
+
.add(view('arr_segments', ArrBySegment).build(), {
|
|
855
|
+
x: 2,
|
|
856
|
+
y: 0,
|
|
857
|
+
width: 2,
|
|
858
|
+
height: 1,
|
|
859
|
+
visualization: 'bar',
|
|
860
|
+
})
|
|
861
|
+
.add(view('unit_econ', UnitEconomics).build(), {
|
|
862
|
+
x: 0,
|
|
863
|
+
y: 1,
|
|
864
|
+
width: 2,
|
|
865
|
+
height: 1,
|
|
866
|
+
visualization: 'table',
|
|
867
|
+
})
|
|
868
|
+
.add(view('growth', GrowthMetrics).build(), {
|
|
869
|
+
x: 2,
|
|
870
|
+
y: 1,
|
|
871
|
+
width: 2,
|
|
872
|
+
height: 1,
|
|
873
|
+
visualization: 'line',
|
|
874
|
+
})
|
|
875
|
+
.add(view('cohorts', CohortRetention).build(), {
|
|
876
|
+
x: 0,
|
|
877
|
+
y: 2,
|
|
878
|
+
width: 4,
|
|
879
|
+
height: 1,
|
|
880
|
+
visualization: 'cohort',
|
|
881
|
+
})
|
|
719
882
|
.refresh('5m')
|
|
720
883
|
.tags('executive', 'saas', 'metrics')
|
|
721
884
|
.build()
|