spine-framework-cortex 0.1.19 → 0.2.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/{functions/custom_cortex-handler.ts → api/cortex-handler.ts} +9 -9
- package/components/CliInstancesCard.tsx +144 -0
- package/components/CortexSidebar.tsx +27 -53
- package/hooks/useTypeRegistry.ts +74 -0
- package/index.tsx +13 -24
- package/manifest.json +1 -13
- package/package.json +11 -20
- package/pages/courses/CoursesPage.tsx +14 -4
- package/pages/crm/AccountDetailPage.tsx +149 -194
- package/pages/crm/ContactsPage.tsx +7 -7
- package/pages/intelligence/IntelligencePage.tsx +24 -31
- package/pages/kb/KBEditorPage.tsx +9 -2
- package/pages/operations/AuditFunnelPage.tsx +378 -0
- package/pages/operations/InstallFunnelPage.tsx +410 -0
- package/pages/operations/OperationsDashboard.tsx +275 -0
- package/pages/support/RedactionReview.tsx +11 -2
- package/seed/link-types.json +8 -42
- package/seed/package.json +27 -0
- package/seed/roles.json +1 -1
- package/seed/types.json +2711 -596
- package/CHANGELOG.md +0 -46
- package/LICENSE.md +0 -223
- package/README.md +0 -69
- package/functions/custom_anonymous-sessions.ts +0 -356
- package/functions/custom_case_analysis.ts +0 -507
- package/functions/custom_community-escalation.ts +0 -234
- package/functions/custom_cortex-chunks.ts +0 -52
- package/functions/custom_funnel-scoring.ts +0 -256
- package/functions/custom_funnel-signal.ts +0 -446
- package/functions/custom_funnel-timers.ts +0 -449
- package/functions/custom_kb-chunker-test.ts +0 -364
- package/functions/custom_kb-chunker.ts +0 -576
- package/functions/custom_kb-embeddings.ts +0 -481
- package/functions/custom_kb-ingestion.ts +0 -448
- package/functions/custom_support-triage.ts +0 -649
- package/functions/custom_tag_management.ts +0 -314
- package/functions/webhook-handlers.ts +0 -29
- package/lib/resolveTypeId.ts +0 -16
- package/pages/crm/ContactDetailPage.tsx +0 -184
- package/pages/ops/AuditFunnelPage.tsx +0 -191
- package/pages/ops/CommandCenterPage.tsx +0 -377
- package/pages/ops/InstallFunnelPage.tsx +0 -226
- package/seed/accounts.json +0 -9
- package/seed/integrations.json +0 -24
- package/seed/pipelines.json +0 -59
- package/seed/triggers.json +0 -125
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react'
|
|
2
|
-
import { useNavigate } from 'react-router-dom'
|
|
3
|
-
import { apiFetch } from '@core/lib/api'
|
|
4
|
-
import { Badge } from '@core/components/ui/badge'
|
|
5
|
-
import { Button } from '@core/components/ui/button'
|
|
6
|
-
import { Input } from '@core/components/ui/input'
|
|
7
|
-
import { Skeleton } from '@core/components/ui/skeleton'
|
|
8
|
-
import { Download, Search, ChevronRight } from 'lucide-react'
|
|
9
|
-
|
|
10
|
-
type FilterTab = 'all' | 'unclaimed' | 'claimed' | 'hot'
|
|
11
|
-
|
|
12
|
-
interface InstallAccount {
|
|
13
|
-
id: string
|
|
14
|
-
slug: string
|
|
15
|
-
display_name?: string
|
|
16
|
-
created_at: string
|
|
17
|
-
data?: {
|
|
18
|
-
temperature?: 'cold' | 'warm' | 'hot'
|
|
19
|
-
lifecycle_stage?: string
|
|
20
|
-
lead_score?: number
|
|
21
|
-
last_signal_at?: string
|
|
22
|
-
claim_status?: string
|
|
23
|
-
instance_id?: string
|
|
24
|
-
ratings?: Record<string, { rating: number; raw_score: number; calculated_at?: string }>
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function scoreBar(rating: number) {
|
|
29
|
-
const val = Math.round((rating / 5) * 100)
|
|
30
|
-
const color = val >= 80 ? 'bg-green-500' : val >= 50 ? 'bg-yellow-500' : 'bg-gray-300'
|
|
31
|
-
return (
|
|
32
|
-
<div className="flex items-center gap-2">
|
|
33
|
-
<div className="w-24 h-1.5 bg-muted rounded-full overflow-hidden">
|
|
34
|
-
<div className={`h-full rounded-full ${color}`} style={{ width: `${val}%` }} />
|
|
35
|
-
</div>
|
|
36
|
-
<span className="text-xs font-medium tabular-nums">{val}</span>
|
|
37
|
-
</div>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function claimBadge(status?: string) {
|
|
42
|
-
if (status === 'claimed') return <Badge className="text-xs bg-green-100 text-green-700 border-green-200">Claimed</Badge>
|
|
43
|
-
return <Badge variant="outline" className="text-xs">Unclaimed</Badge>
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function stageBadge(stage?: string) {
|
|
47
|
-
return <Badge variant="secondary" className="text-xs capitalize">{stage || '—'}</Badge>
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function maskInstanceId(id?: string): string {
|
|
51
|
-
if (!id) return '—'
|
|
52
|
-
if (id.length <= 12) return id
|
|
53
|
-
return id.slice(0, 10) + '…'
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function daysSince(dateStr?: string): number | null {
|
|
57
|
-
if (!dateStr) return null
|
|
58
|
-
return Math.floor((Date.now() - new Date(dateStr).getTime()) / (1000 * 60 * 60 * 24))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export default function InstallFunnelPage() {
|
|
62
|
-
const navigate = useNavigate()
|
|
63
|
-
const [accounts, setAccounts] = useState<InstallAccount[]>([])
|
|
64
|
-
const [loading, setLoading] = useState(true)
|
|
65
|
-
const [search, setSearch] = useState('')
|
|
66
|
-
const [tab, setTab] = useState<FilterTab>('all')
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
apiFetch('/api/admin-data?action=list&entity=accounts&limit=500')
|
|
70
|
-
.then(r => r.json())
|
|
71
|
-
.then(json => {
|
|
72
|
-
const all: InstallAccount[] = Array.isArray(json?.data) ? json.data : Array.isArray(json) ? json : []
|
|
73
|
-
// Keep accounts in the technical funnel
|
|
74
|
-
const techAccounts = all.filter(a =>
|
|
75
|
-
a.data?.ratings?.['funnel-technical'] ||
|
|
76
|
-
a.data?.ratings?.['funnel-technical-installed'] ||
|
|
77
|
-
a.data?.ratings?.['funnel-technical-claimed'] ||
|
|
78
|
-
a.data?.lifecycle_stage === 'installed'
|
|
79
|
-
)
|
|
80
|
-
// Sort by technical rating desc, then by first signal date
|
|
81
|
-
techAccounts.sort((a, b) => {
|
|
82
|
-
const ra = a.data?.ratings?.['funnel-technical']?.rating || a.data?.ratings?.installed?.rating || 0
|
|
83
|
-
const rb = b.data?.ratings?.['funnel-technical']?.rating || b.data?.ratings?.installed?.rating || 0
|
|
84
|
-
if (rb !== ra) return rb - ra
|
|
85
|
-
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
|
86
|
-
})
|
|
87
|
-
setAccounts(techAccounts)
|
|
88
|
-
})
|
|
89
|
-
.catch(() => setAccounts([]))
|
|
90
|
-
.finally(() => setLoading(false))
|
|
91
|
-
}, [])
|
|
92
|
-
|
|
93
|
-
const tabFiltered = accounts.filter(a => {
|
|
94
|
-
if (tab === 'unclaimed') return a.data?.claim_status !== 'claimed'
|
|
95
|
-
if (tab === 'claimed') return a.data?.claim_status === 'claimed'
|
|
96
|
-
if (tab === 'hot') return a.data?.temperature === 'hot'
|
|
97
|
-
return true
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const filtered = tabFiltered.filter(a => {
|
|
101
|
-
const q = search.toLowerCase()
|
|
102
|
-
return !q || (a.display_name || a.slug || a.data?.instance_id || '').toLowerCase().includes(q)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
const counts = {
|
|
106
|
-
all: accounts.length,
|
|
107
|
-
unclaimed: accounts.filter(a => a.data?.claim_status !== 'claimed').length,
|
|
108
|
-
claimed: accounts.filter(a => a.data?.claim_status === 'claimed').length,
|
|
109
|
-
hot: accounts.filter(a => a.data?.temperature === 'hot').length,
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return (
|
|
113
|
-
<div className="p-6 space-y-6">
|
|
114
|
-
{/* Header */}
|
|
115
|
-
<div className="flex items-center justify-between">
|
|
116
|
-
<div>
|
|
117
|
-
<div className="flex items-center gap-2">
|
|
118
|
-
<Download className="h-5 w-5 text-muted-foreground" />
|
|
119
|
-
<h1 className="text-2xl font-bold">Install Funnel</h1>
|
|
120
|
-
</div>
|
|
121
|
-
<p className="text-sm text-muted-foreground mt-0.5">
|
|
122
|
-
Developer accounts progressing from npm install through claim — your technical pipeline.
|
|
123
|
-
</p>
|
|
124
|
-
</div>
|
|
125
|
-
<Button size="sm" onClick={() => navigate('/cortex/ops/command-center')}>
|
|
126
|
-
Command Center <ChevronRight className="h-3.5 w-3.5 ml-1" />
|
|
127
|
-
</Button>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
{/* Summary stats */}
|
|
131
|
-
<div className="grid grid-cols-4 gap-4">
|
|
132
|
-
{[
|
|
133
|
-
{ label: 'Total Installs', value: loading ? '…' : accounts.length },
|
|
134
|
-
{ label: 'Claimed', value: loading ? '…' : counts.claimed },
|
|
135
|
-
{ label: 'Unclaimed', value: loading ? '…' : counts.unclaimed },
|
|
136
|
-
{ label: 'Hot', value: loading ? '…' : counts.hot },
|
|
137
|
-
].map(s => (
|
|
138
|
-
<div key={s.label} className="border rounded-lg p-4">
|
|
139
|
-
<div className="text-xs text-muted-foreground mb-1">{s.label}</div>
|
|
140
|
-
{loading ? <Skeleton className="h-7 w-12" /> : <div className="text-2xl font-bold">{s.value}</div>}
|
|
141
|
-
</div>
|
|
142
|
-
))}
|
|
143
|
-
</div>
|
|
144
|
-
|
|
145
|
-
{/* Table */}
|
|
146
|
-
<div className="border rounded-lg">
|
|
147
|
-
<div className="flex items-center justify-between px-4 py-3 border-b">
|
|
148
|
-
{/* Filter tabs */}
|
|
149
|
-
<div className="flex gap-0.5">
|
|
150
|
-
{(['all', 'unclaimed', 'claimed', 'hot'] as FilterTab[]).map(t => (
|
|
151
|
-
<button
|
|
152
|
-
key={t}
|
|
153
|
-
onClick={() => setTab(t)}
|
|
154
|
-
className={`px-3 py-1 text-xs rounded-md capitalize transition-colors ${
|
|
155
|
-
tab === t ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
156
|
-
}`}
|
|
157
|
-
>
|
|
158
|
-
{t} ({counts[t]})
|
|
159
|
-
</button>
|
|
160
|
-
))}
|
|
161
|
-
</div>
|
|
162
|
-
<div className="relative w-56">
|
|
163
|
-
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
|
164
|
-
<Input
|
|
165
|
-
className="h-7 pl-8 text-xs"
|
|
166
|
-
placeholder="Search installs…"
|
|
167
|
-
value={search}
|
|
168
|
-
onChange={e => setSearch(e.target.value)}
|
|
169
|
-
/>
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
{loading ? (
|
|
174
|
-
<div className="p-4 space-y-3">
|
|
175
|
-
{Array.from({ length: 5 }).map((_, i) => <Skeleton key={i} className="h-10 w-full" />)}
|
|
176
|
-
</div>
|
|
177
|
-
) : filtered.length === 0 ? (
|
|
178
|
-
<div className="p-10 text-center text-sm text-muted-foreground">
|
|
179
|
-
<Download className="h-8 w-8 mx-auto mb-2 opacity-20" />
|
|
180
|
-
{search ? 'No installs match your search.' : 'No technical funnel activity yet. npm install signals will appear here.'}
|
|
181
|
-
</div>
|
|
182
|
-
) : (
|
|
183
|
-
<>
|
|
184
|
-
<div className="grid grid-cols-[2fr_1fr_1fr_1fr_1fr_1fr] gap-4 px-4 py-2 text-xs text-muted-foreground font-medium border-b bg-muted/30">
|
|
185
|
-
<span>Instance / Account</span>
|
|
186
|
-
<span>Signal Score</span>
|
|
187
|
-
<span>Stage</span>
|
|
188
|
-
<span>Last Action</span>
|
|
189
|
-
<span>Claim Status</span>
|
|
190
|
-
<span>Days Active</span>
|
|
191
|
-
</div>
|
|
192
|
-
<div className="divide-y">
|
|
193
|
-
{filtered.map(a => {
|
|
194
|
-
const rating = a.data?.ratings?.['funnel-technical']?.rating
|
|
195
|
-
|| a.data?.ratings?.['funnel-technical-claimed']?.rating
|
|
196
|
-
|| a.data?.ratings?.installed?.rating
|
|
197
|
-
|| 0
|
|
198
|
-
const days = daysSince(a.created_at)
|
|
199
|
-
const instanceId = a.data?.instance_id || a.slug
|
|
200
|
-
return (
|
|
201
|
-
<div
|
|
202
|
-
key={a.id}
|
|
203
|
-
className="grid grid-cols-[2fr_1fr_1fr_1fr_1fr_1fr] gap-4 px-4 py-3 items-center hover:bg-accent/50 cursor-pointer transition-colors text-sm"
|
|
204
|
-
onClick={() => navigate(`/cortex/crm/accounts/${a.id}`)}
|
|
205
|
-
>
|
|
206
|
-
<div className="min-w-0">
|
|
207
|
-
<div className="font-medium font-mono text-xs truncate">{maskInstanceId(instanceId)}</div>
|
|
208
|
-
{a.display_name && <div className="text-xs text-muted-foreground truncate">{a.display_name}</div>}
|
|
209
|
-
</div>
|
|
210
|
-
<div>{scoreBar(rating)}</div>
|
|
211
|
-
<div>{stageBadge(a.data?.lifecycle_stage)}</div>
|
|
212
|
-
<div className="text-xs text-muted-foreground">
|
|
213
|
-
{a.data?.last_signal_at ? new Date(a.data.last_signal_at).toLocaleDateString() : '—'}
|
|
214
|
-
</div>
|
|
215
|
-
<div>{claimBadge(a.data?.claim_status)}</div>
|
|
216
|
-
<div className="text-xs text-muted-foreground">{days !== null ? `${days}d` : '—'}</div>
|
|
217
|
-
</div>
|
|
218
|
-
)
|
|
219
|
-
})}
|
|
220
|
-
</div>
|
|
221
|
-
</>
|
|
222
|
-
)}
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
)
|
|
226
|
-
}
|
package/seed/accounts.json
DELETED
package/seed/integrations.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"name": "Funnel Signal: Marketing",
|
|
4
|
-
"slug": "funnel-signal-mar",
|
|
5
|
-
"provider": "webhook",
|
|
6
|
-
"status": "active",
|
|
7
|
-
"is_active": true,
|
|
8
|
-
"config": {
|
|
9
|
-
"handler": { "path": "funnel-signal" },
|
|
10
|
-
"description": "Marketing signal ingest via integration-routes"
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"name": "Funnel Signal: Usage",
|
|
15
|
-
"slug": "funnel-signal-use",
|
|
16
|
-
"provider": "webhook",
|
|
17
|
-
"status": "active",
|
|
18
|
-
"is_active": true,
|
|
19
|
-
"config": {
|
|
20
|
-
"handler": { "path": "funnel-signal" },
|
|
21
|
-
"description": "Usage signal ingest via integration-routes"
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
]
|
package/seed/pipelines.json
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"slug": "case-analysis",
|
|
4
|
-
"name": "Case Analysis Pipeline",
|
|
5
|
-
"description": "Automatically analyzes resolved support tickets to extract insights and create tags",
|
|
6
|
-
"stages": [
|
|
7
|
-
{
|
|
8
|
-
"stage_type": "agent_inference",
|
|
9
|
-
"config": {
|
|
10
|
-
"function": "case_analysis.analyze"
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"stage_type": "agent_inference",
|
|
15
|
-
"config": {
|
|
16
|
-
"function": "tag_management.generate"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
],
|
|
20
|
-
"config": {
|
|
21
|
-
"debounce_ms": 5000,
|
|
22
|
-
"retry_count": 3,
|
|
23
|
-
"retry_delay_ms": 1000
|
|
24
|
-
},
|
|
25
|
-
"ownership": "tenant",
|
|
26
|
-
"is_system": false,
|
|
27
|
-
"is_active": true
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"slug": "funnel-general",
|
|
31
|
-
"name": "Funnel: General",
|
|
32
|
-
"description": "Default funnel pipeline — fires for all signals without a specific pipeline_slug. Add stages to notify, create deals, or trigger outreach.",
|
|
33
|
-
"stages": [],
|
|
34
|
-
"config": {},
|
|
35
|
-
"ownership": "tenant",
|
|
36
|
-
"is_system": false,
|
|
37
|
-
"is_active": true
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"slug": "funnel-ai-audit",
|
|
41
|
-
"name": "Funnel: AI Audit",
|
|
42
|
-
"description": "Pipeline for the AI Audit funnel. Fires when a signal arrives with pipeline_slug='funnel-ai-audit'. Add stages to run your sales sequence.",
|
|
43
|
-
"stages": [],
|
|
44
|
-
"config": {},
|
|
45
|
-
"ownership": "tenant",
|
|
46
|
-
"is_system": false,
|
|
47
|
-
"is_active": true
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"slug": "funnel-implementation",
|
|
51
|
-
"name": "Funnel: Implementation",
|
|
52
|
-
"description": "Pipeline for the Implementation Contract funnel. Fires when a signal arrives with pipeline_slug='funnel-implementation'.",
|
|
53
|
-
"stages": [],
|
|
54
|
-
"config": {},
|
|
55
|
-
"ownership": "tenant",
|
|
56
|
-
"is_system": false,
|
|
57
|
-
"is_active": true
|
|
58
|
-
}
|
|
59
|
-
]
|
package/seed/triggers.json
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"name": "Case Resolution Analysis",
|
|
4
|
-
"description": "Automatically triggers case analysis when support tickets are resolved",
|
|
5
|
-
"trigger_type": "event",
|
|
6
|
-
"event_type": "item_updated",
|
|
7
|
-
"pipeline_slug": "case-analysis",
|
|
8
|
-
"config": {
|
|
9
|
-
"scope": "account",
|
|
10
|
-
"filters": [
|
|
11
|
-
{ "field": "status", "value": "resolved", "operator": "$eq" },
|
|
12
|
-
{ "field": "type_id", "type_slug": "support_ticket", "operator": "$eq" }
|
|
13
|
-
],
|
|
14
|
-
"debounce_ms": 5000,
|
|
15
|
-
"entity_type": "items",
|
|
16
|
-
"retry_count": 3,
|
|
17
|
-
"retry_delay_ms": 1000
|
|
18
|
-
},
|
|
19
|
-
"ownership": "tenant",
|
|
20
|
-
"is_system": false,
|
|
21
|
-
"is_active": true
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"name": "Funnel: General Signal Routing",
|
|
25
|
-
"description": "Fires the general funnel pipeline for signals with no specific pipeline_slug",
|
|
26
|
-
"trigger_type": "event",
|
|
27
|
-
"event_type": "item_created",
|
|
28
|
-
"pipeline_slug": "funnel-general",
|
|
29
|
-
"config": {
|
|
30
|
-
"scope": "account",
|
|
31
|
-
"type_slug": "funnel_signal",
|
|
32
|
-
"entity_type": "item",
|
|
33
|
-
"filters": {
|
|
34
|
-
"data.classification.pipeline_slug": { "$exists": false }
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
"ownership": "tenant",
|
|
38
|
-
"is_system": false,
|
|
39
|
-
"is_active": true
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"name": "Funnel: AI Audit Routing",
|
|
43
|
-
"description": "Fires the AI Audit pipeline when a signal arrives tagged with pipeline_slug='funnel-ai-audit'",
|
|
44
|
-
"trigger_type": "event",
|
|
45
|
-
"event_type": "item_created",
|
|
46
|
-
"pipeline_slug": "funnel-ai-audit",
|
|
47
|
-
"config": {
|
|
48
|
-
"scope": "account",
|
|
49
|
-
"type_slug": "funnel_signal",
|
|
50
|
-
"entity_type": "item",
|
|
51
|
-
"filters": {
|
|
52
|
-
"data.classification.pipeline_slug": "funnel-ai-audit"
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"ownership": "tenant",
|
|
56
|
-
"is_system": false,
|
|
57
|
-
"is_active": true
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"name": "Funnel: Implementation Routing",
|
|
61
|
-
"description": "Fires the Implementation pipeline when a signal arrives tagged with pipeline_slug='funnel-implementation'",
|
|
62
|
-
"trigger_type": "event",
|
|
63
|
-
"event_type": "item_created",
|
|
64
|
-
"pipeline_slug": "funnel-implementation",
|
|
65
|
-
"config": {
|
|
66
|
-
"scope": "account",
|
|
67
|
-
"type_slug": "funnel_signal",
|
|
68
|
-
"entity_type": "item",
|
|
69
|
-
"filters": {
|
|
70
|
-
"data.classification.pipeline_slug": "funnel-implementation"
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
"ownership": "tenant",
|
|
74
|
-
"is_system": false,
|
|
75
|
-
"is_active": true
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
"name": "Funnel: Aggregation",
|
|
79
|
-
"description": "Hourly dashboard metric aggregation",
|
|
80
|
-
"trigger_type": "cron",
|
|
81
|
-
"event_type": null,
|
|
82
|
-
"config": {
|
|
83
|
-
"scope": "account",
|
|
84
|
-
"function": "funnel-timers.aggregation",
|
|
85
|
-
"schedule": "0 * * * *",
|
|
86
|
-
"timezone": "UTC",
|
|
87
|
-
"description": "Hourly dashboard metric aggregation"
|
|
88
|
-
},
|
|
89
|
-
"ownership": "tenant",
|
|
90
|
-
"is_system": false,
|
|
91
|
-
"is_active": true
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"name": "Funnel: Score Decay",
|
|
95
|
-
"description": "Daily recalculation of account ratings for score decay",
|
|
96
|
-
"trigger_type": "cron",
|
|
97
|
-
"event_type": null,
|
|
98
|
-
"config": {
|
|
99
|
-
"scope": "account",
|
|
100
|
-
"function": "funnel-timers.scoreDecay",
|
|
101
|
-
"schedule": "59 23 * * *",
|
|
102
|
-
"timezone": "UTC",
|
|
103
|
-
"description": "Daily recalculation of account ratings for score decay"
|
|
104
|
-
},
|
|
105
|
-
"ownership": "tenant",
|
|
106
|
-
"is_system": false,
|
|
107
|
-
"is_active": true
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
"name": "Funnel: Session Cleanup",
|
|
111
|
-
"description": "Daily purge of expired anonymous sessions",
|
|
112
|
-
"trigger_type": "cron",
|
|
113
|
-
"event_type": null,
|
|
114
|
-
"config": {
|
|
115
|
-
"scope": "account",
|
|
116
|
-
"function": "funnel-timers.sessionCleanup",
|
|
117
|
-
"schedule": "0 2 * * *",
|
|
118
|
-
"timezone": "UTC",
|
|
119
|
-
"description": "Daily purge of expired anonymous sessions"
|
|
120
|
-
},
|
|
121
|
-
"ownership": "tenant",
|
|
122
|
-
"is_system": false,
|
|
123
|
-
"is_active": true
|
|
124
|
-
}
|
|
125
|
-
]
|