@open-mercato/tillio 0.7.1-develop.7176.1.d229567a50

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.
Files changed (135) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/build.mjs +7 -0
  3. package/dist/index.js +5 -0
  4. package/dist/index.js.map +7 -0
  5. package/dist/modules/tillio/__integration__/TC-PHONE-001-pull-readiness.spec.js +17 -0
  6. package/dist/modules/tillio/__integration__/TC-PHONE-001-pull-readiness.spec.js.map +7 -0
  7. package/dist/modules/tillio/__integration__/TC-PHONE-002-pull-blocked.spec.js +27 -0
  8. package/dist/modules/tillio/__integration__/TC-PHONE-002-pull-blocked.spec.js.map +7 -0
  9. package/dist/modules/tillio/__integration__/TC-PHONE-003-operators.spec.js +47 -0
  10. package/dist/modules/tillio/__integration__/TC-PHONE-003-operators.spec.js.map +7 -0
  11. package/dist/modules/tillio/__integration__/TC-PHONE-004-pull-widget.spec.js +11 -0
  12. package/dist/modules/tillio/__integration__/TC-PHONE-004-pull-widget.spec.js.map +7 -0
  13. package/dist/modules/tillio/__integration__/TC-PHONE-005-operators-widget.spec.js +13 -0
  14. package/dist/modules/tillio/__integration__/TC-PHONE-005-operators-widget.spec.js.map +7 -0
  15. package/dist/modules/tillio/acl.js +9 -0
  16. package/dist/modules/tillio/acl.js.map +7 -0
  17. package/dist/modules/tillio/api/operators/[id]/route.js +78 -0
  18. package/dist/modules/tillio/api/operators/[id]/route.js.map +7 -0
  19. package/dist/modules/tillio/api/operators/route.js +171 -0
  20. package/dist/modules/tillio/api/operators/route.js.map +7 -0
  21. package/dist/modules/tillio/api/pull/route.js +180 -0
  22. package/dist/modules/tillio/api/pull/route.js.map +7 -0
  23. package/dist/modules/tillio/cli.js +108 -0
  24. package/dist/modules/tillio/cli.js.map +7 -0
  25. package/dist/modules/tillio/di.js +16 -0
  26. package/dist/modules/tillio/di.js.map +7 -0
  27. package/dist/modules/tillio/index.js +12 -0
  28. package/dist/modules/tillio/index.js.map +7 -0
  29. package/dist/modules/tillio/integration.js +62 -0
  30. package/dist/modules/tillio/integration.js.map +7 -0
  31. package/dist/modules/tillio/lib/adapter.js +107 -0
  32. package/dist/modules/tillio/lib/adapter.js.map +7 -0
  33. package/dist/modules/tillio/lib/client.js +249 -0
  34. package/dist/modules/tillio/lib/client.js.map +7 -0
  35. package/dist/modules/tillio/lib/environment.js +34 -0
  36. package/dist/modules/tillio/lib/environment.js.map +7 -0
  37. package/dist/modules/tillio/lib/error-codes.js +59 -0
  38. package/dist/modules/tillio/lib/error-codes.js.map +7 -0
  39. package/dist/modules/tillio/lib/errors.js +12 -0
  40. package/dist/modules/tillio/lib/errors.js.map +7 -0
  41. package/dist/modules/tillio/lib/health.js +41 -0
  42. package/dist/modules/tillio/lib/health.js.map +7 -0
  43. package/dist/modules/tillio/lib/locking.js +18 -0
  44. package/dist/modules/tillio/lib/locking.js.map +7 -0
  45. package/dist/modules/tillio/lib/normalizer.js +134 -0
  46. package/dist/modules/tillio/lib/normalizer.js.map +7 -0
  47. package/dist/modules/tillio/lib/operators-store.js +62 -0
  48. package/dist/modules/tillio/lib/operators-store.js.map +7 -0
  49. package/dist/modules/tillio/lib/operators.js +168 -0
  50. package/dist/modules/tillio/lib/operators.js.map +7 -0
  51. package/dist/modules/tillio/lib/preset.js +122 -0
  52. package/dist/modules/tillio/lib/preset.js.map +7 -0
  53. package/dist/modules/tillio/lib/pull-job.js +265 -0
  54. package/dist/modules/tillio/lib/pull-job.js.map +7 -0
  55. package/dist/modules/tillio/lib/pull-readiness.js +36 -0
  56. package/dist/modules/tillio/lib/pull-readiness.js.map +7 -0
  57. package/dist/modules/tillio/lib/queue.js +22 -0
  58. package/dist/modules/tillio/lib/queue.js.map +7 -0
  59. package/dist/modules/tillio/lib/tz.js +85 -0
  60. package/dist/modules/tillio/lib/tz.js.map +7 -0
  61. package/dist/modules/tillio/lib/url-guard.js +24 -0
  62. package/dist/modules/tillio/lib/url-guard.js.map +7 -0
  63. package/dist/modules/tillio/setup.js +45 -0
  64. package/dist/modules/tillio/setup.js.map +7 -0
  65. package/dist/modules/tillio/widgets/injection/operators-config/widget.client.js +208 -0
  66. package/dist/modules/tillio/widgets/injection/operators-config/widget.client.js.map +7 -0
  67. package/dist/modules/tillio/widgets/injection/operators-config/widget.js +17 -0
  68. package/dist/modules/tillio/widgets/injection/operators-config/widget.js.map +7 -0
  69. package/dist/modules/tillio/widgets/injection/pull-calls/widget.client.js +148 -0
  70. package/dist/modules/tillio/widgets/injection/pull-calls/widget.client.js.map +7 -0
  71. package/dist/modules/tillio/widgets/injection/pull-calls/widget.js +17 -0
  72. package/dist/modules/tillio/widgets/injection/pull-calls/widget.js.map +7 -0
  73. package/dist/modules/tillio/widgets/injection-table.js +21 -0
  74. package/dist/modules/tillio/widgets/injection-table.js.map +7 -0
  75. package/dist/modules/tillio/workers/tillio-pull.js +17 -0
  76. package/dist/modules/tillio/workers/tillio-pull.js.map +7 -0
  77. package/jest.config.cjs +35 -0
  78. package/package.json +92 -0
  79. package/src/index.ts +1 -0
  80. package/src/modules/tillio/__integration__/TC-PHONE-001-pull-readiness.spec.ts +38 -0
  81. package/src/modules/tillio/__integration__/TC-PHONE-002-pull-blocked.spec.ts +44 -0
  82. package/src/modules/tillio/__integration__/TC-PHONE-003-operators.spec.ts +76 -0
  83. package/src/modules/tillio/__integration__/TC-PHONE-004-pull-widget.spec.ts +22 -0
  84. package/src/modules/tillio/__integration__/TC-PHONE-005-operators-widget.spec.ts +27 -0
  85. package/src/modules/tillio/__tests__/adapter-fetch-calls.test.ts +148 -0
  86. package/src/modules/tillio/__tests__/client.test.ts +203 -0
  87. package/src/modules/tillio/__tests__/health.test.ts +122 -0
  88. package/src/modules/tillio/__tests__/normalizer.test.ts +143 -0
  89. package/src/modules/tillio/__tests__/operators-routes.test.ts +100 -0
  90. package/src/modules/tillio/__tests__/operators-store.test.ts +104 -0
  91. package/src/modules/tillio/__tests__/operators.test.ts +250 -0
  92. package/src/modules/tillio/__tests__/preset.test.ts +337 -0
  93. package/src/modules/tillio/__tests__/pull-body.test.ts +43 -0
  94. package/src/modules/tillio/__tests__/pull-job.test.ts +250 -0
  95. package/src/modules/tillio/__tests__/pull-readiness.test.ts +143 -0
  96. package/src/modules/tillio/__tests__/pull-route.test.ts +148 -0
  97. package/src/modules/tillio/__tests__/tz.test.ts +53 -0
  98. package/src/modules/tillio/__tests__/url-guard.test.ts +46 -0
  99. package/src/modules/tillio/acl.ts +5 -0
  100. package/src/modules/tillio/api/operators/[id]/route.ts +90 -0
  101. package/src/modules/tillio/api/operators/route.ts +191 -0
  102. package/src/modules/tillio/api/pull/route.ts +213 -0
  103. package/src/modules/tillio/cli.ts +125 -0
  104. package/src/modules/tillio/di.ts +16 -0
  105. package/src/modules/tillio/i18n/de.json +73 -0
  106. package/src/modules/tillio/i18n/en.json +73 -0
  107. package/src/modules/tillio/i18n/es.json +73 -0
  108. package/src/modules/tillio/i18n/ko.json +73 -0
  109. package/src/modules/tillio/i18n/pl.json +73 -0
  110. package/src/modules/tillio/index.ts +8 -0
  111. package/src/modules/tillio/integration.ts +58 -0
  112. package/src/modules/tillio/lib/adapter.ts +135 -0
  113. package/src/modules/tillio/lib/client.ts +329 -0
  114. package/src/modules/tillio/lib/environment.ts +53 -0
  115. package/src/modules/tillio/lib/error-codes.ts +67 -0
  116. package/src/modules/tillio/lib/errors.ts +11 -0
  117. package/src/modules/tillio/lib/health.ts +57 -0
  118. package/src/modules/tillio/lib/locking.ts +30 -0
  119. package/src/modules/tillio/lib/normalizer.ts +184 -0
  120. package/src/modules/tillio/lib/operators-store.ts +77 -0
  121. package/src/modules/tillio/lib/operators.ts +269 -0
  122. package/src/modules/tillio/lib/preset.ts +226 -0
  123. package/src/modules/tillio/lib/pull-job.ts +390 -0
  124. package/src/modules/tillio/lib/pull-readiness.ts +77 -0
  125. package/src/modules/tillio/lib/queue.ts +33 -0
  126. package/src/modules/tillio/lib/tz.ts +121 -0
  127. package/src/modules/tillio/lib/url-guard.ts +34 -0
  128. package/src/modules/tillio/setup.ts +49 -0
  129. package/src/modules/tillio/widgets/injection/operators-config/widget.client.tsx +304 -0
  130. package/src/modules/tillio/widgets/injection/operators-config/widget.ts +16 -0
  131. package/src/modules/tillio/widgets/injection/pull-calls/widget.client.tsx +214 -0
  132. package/src/modules/tillio/widgets/injection/pull-calls/widget.ts +16 -0
  133. package/src/modules/tillio/widgets/injection-table.ts +19 -0
  134. package/src/modules/tillio/workers/tillio-pull.ts +18 -0
  135. package/tsconfig.json +9 -0
@@ -0,0 +1,214 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'
5
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
6
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
7
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
8
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
9
+ import { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'
10
+ import { Button } from '@open-mercato/ui/primitives/button'
11
+ import {
12
+ Dialog,
13
+ DialogContent,
14
+ DialogDescription,
15
+ DialogFooter,
16
+ DialogHeader,
17
+ DialogTitle,
18
+ } from '@open-mercato/ui/primitives/dialog'
19
+ import { DateRangePicker } from '@open-mercato/ui/primitives/date-range-picker'
20
+ import { Spinner } from '@open-mercato/ui/primitives/spinner'
21
+ import type { DateRange } from '@open-mercato/ui/backend/date-range/dateRanges'
22
+ import { PULL_BLOCKER_COPY, tillioErrorCopy } from '../../../lib/error-codes'
23
+ import type { PullBlocker } from '../../../lib/pull-readiness'
24
+
25
+ const DEFAULT_RANGE_DAYS = 7
26
+
27
+ type PullReadiness = {
28
+ ok: boolean
29
+ environmentReady: boolean
30
+ operatorAttached: boolean
31
+ envDrift: boolean
32
+ blocker: PullBlocker | null
33
+ timeZone: string | null
34
+ }
35
+
36
+ type PullResult = {
37
+ ok: boolean
38
+ code?: string
39
+ progressJobId?: string
40
+ }
41
+
42
+ function toDayString(date: Date): string {
43
+ const pad = (value: number): string => String(value).padStart(2, '0')
44
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
45
+ }
46
+
47
+ function defaultRange(): DateRange {
48
+ const end = new Date()
49
+ const start = new Date(end)
50
+ start.setDate(start.getDate() - (DEFAULT_RANGE_DAYS - 1))
51
+ return { start, end }
52
+ }
53
+
54
+ export default function PullCallsWidget(
55
+ _props: InjectionWidgetComponentProps<Record<string, unknown>, Record<string, unknown>>,
56
+ ) {
57
+ const t = useT()
58
+ const [open, setOpen] = React.useState(false)
59
+ const [range, setRange] = React.useState<DateRange | null>(defaultRange)
60
+ const [pending, setPending] = React.useState(false)
61
+ const [readiness, setReadiness] = React.useState<PullReadiness | null>(null)
62
+ const [checking, setChecking] = React.useState(false)
63
+ const { runMutation, retryLastMutation } = useGuardedMutation({
64
+ contextId: 'tillio-pull-calls',
65
+ blockedMessage: t('tillio.pull.blocked', 'Pull blocked by validation.'),
66
+ })
67
+ const mutationContext = React.useMemo(
68
+ () => ({ providerKey: 'tillio', retryLastMutation }),
69
+ [retryLastMutation],
70
+ )
71
+
72
+ // Readiness is checked when the dialog opens rather than on mount, so the
73
+ // calls list does not pay for a request every visitor never needs.
74
+ const openDialog = React.useCallback(async () => {
75
+ setOpen(true)
76
+ setChecking(true)
77
+ try {
78
+ const response = await apiCall<PullReadiness>('/api/tillio/pull')
79
+ setReadiness(response.ok ? (response.result ?? null) : null)
80
+ } catch {
81
+ // A null readiness already renders the "could not check" notice, so the failure is
82
+ // handled here rather than escaping the void call as an unhandled rejection.
83
+ setReadiness(null)
84
+ } finally {
85
+ setChecking(false)
86
+ }
87
+ }, [])
88
+
89
+ const blockerMessage = React.useCallback(
90
+ (blocker: PullBlocker): string => {
91
+ // The lookup can still miss at runtime when this bundle is older than the server it talks to.
92
+ const copy = PULL_BLOCKER_COPY[blocker]
93
+ return copy ? t(copy.key, copy.fallback) : t('tillio.pull.blocked', 'Pull blocked by validation.')
94
+ },
95
+ [t],
96
+ )
97
+
98
+ const pull = React.useCallback(async () => {
99
+ if (pending) return
100
+ if (!range?.start || !range.end) {
101
+ flash(t('tillio.pull.rangeRequired', 'Pick a date range first.'), 'error')
102
+ return
103
+ }
104
+ const from = toDayString(range.start)
105
+ const to = toDayString(range.end)
106
+
107
+ setPending(true)
108
+ try {
109
+ const response = await runMutation({
110
+ context: mutationContext,
111
+ mutationPayload: { providerKey: 'tillio', from, to },
112
+ operation: () =>
113
+ apiCall<PullResult>('/api/tillio/pull', {
114
+ method: 'POST',
115
+ headers: { 'content-type': 'application/json' },
116
+ body: JSON.stringify({ from, to }),
117
+ }),
118
+ })
119
+ const body = response.result as PullResult | undefined
120
+ if (!response.ok) {
121
+ const copy = tillioErrorCopy(body?.code, 'pull_failed')
122
+ flash(t(copy.key, copy.fallback), 'error')
123
+ return
124
+ }
125
+ // The sweep runs in a worker, so the dialog only confirms the handover; the top bar owns
126
+ // the rest and the list picks the calls up on its next load.
127
+ flash(t('tillio.pull.queued', 'Pulling calls from Tillio. Track it in the progress bar.'), 'success')
128
+ setOpen(false)
129
+ } finally {
130
+ setPending(false)
131
+ }
132
+ }, [mutationContext, pending, range, runMutation, t])
133
+
134
+ const handleKeyDown = React.useCallback(
135
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
136
+ if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
137
+ event.preventDefault()
138
+ void pull()
139
+ }
140
+ },
141
+ [pull],
142
+ )
143
+
144
+ const blocker = readiness?.blocker ?? null
145
+ const blocked = checking || !readiness || Boolean(blocker)
146
+
147
+ return (
148
+ <>
149
+ <Button type="button" variant="outline" onClick={() => void openDialog()}>
150
+ {t('tillio.pull.action', 'Pull calls')}
151
+ </Button>
152
+
153
+ <Dialog open={open} onOpenChange={(next) => { if (!pending) setOpen(next) }}>
154
+ <DialogContent onKeyDown={handleKeyDown}>
155
+ <DialogHeader>
156
+ <DialogTitle>{t('tillio.pull.title', 'Pull calls from Tillio')}</DialogTitle>
157
+ <DialogDescription>
158
+ {t('tillio.pull.description', 'Calls in the selected range are ingested from the attached operator. Pulling the same range again updates existing calls instead of duplicating them.')}
159
+ </DialogDescription>
160
+ </DialogHeader>
161
+
162
+ {checking ? (
163
+ <div className="flex items-center gap-2 py-6 text-sm text-muted-foreground">
164
+ <Spinner />
165
+ {t('tillio.pull.checking', 'Checking the Tillio connection...')}
166
+ </div>
167
+ ) : (
168
+ <>
169
+ {!readiness ? (
170
+ <Alert status="error">
171
+ <AlertTitle>{t('tillio.pull.checkFailed', 'Could not check the Tillio connection.')}</AlertTitle>
172
+ <AlertDescription>
173
+ <Button type="button" variant="outline" size="sm" onClick={() => void openDialog()}>
174
+ {t('tillio.pull.retry', 'Retry')}
175
+ </Button>
176
+ </AlertDescription>
177
+ </Alert>
178
+ ) : null}
179
+
180
+ {blocker ? (
181
+ <Alert status="warning">
182
+ <AlertTitle>{t('tillio.pull.notReadyTitle', 'Tillio is not ready')}</AlertTitle>
183
+ <AlertDescription>{blockerMessage(blocker)}</AlertDescription>
184
+ </Alert>
185
+ ) : null}
186
+
187
+ <div className="grid gap-2 py-2">
188
+ <DateRangePicker value={range} onChange={setRange} disabled={pending || blocked} />
189
+ {readiness?.timeZone ? (
190
+ <span className="text-xs text-muted-foreground">
191
+ {t(
192
+ 'tillio.pull.timezoneHint',
193
+ 'Days are interpreted in the {{timeZone}} timezone that Tillio uses.',
194
+ { timeZone: readiness.timeZone },
195
+ )}
196
+ </span>
197
+ ) : null}
198
+ </div>
199
+ </>
200
+ )}
201
+
202
+ <DialogFooter>
203
+ <Button type="button" variant="outline" onClick={() => setOpen(false)} disabled={pending}>
204
+ {t('tillio.pull.cancel', 'Cancel')}
205
+ </Button>
206
+ <Button type="button" onClick={() => void pull()} disabled={pending || blocked}>
207
+ {pending ? t('tillio.pull.pulling', 'Pulling...') : t('tillio.pull.confirm', 'Pull calls')}
208
+ </Button>
209
+ </DialogFooter>
210
+ </DialogContent>
211
+ </Dialog>
212
+ </>
213
+ )
214
+ }
@@ -0,0 +1,16 @@
1
+ import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'
2
+ import PullCallsWidget from './widget.client'
3
+
4
+ const widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {
5
+ metadata: {
6
+ id: 'tillio.injection.pull-calls',
7
+ title: 'Pull calls from Tillio',
8
+ description: 'Manually ingest phone calls from the attached Tillio operator for a chosen date range.',
9
+ features: ['phone_calls.manage', 'integrations.manage'],
10
+ priority: 20,
11
+ enabled: true,
12
+ },
13
+ Widget: PullCallsWidget,
14
+ }
15
+
16
+ export default widget
@@ -0,0 +1,19 @@
1
+ import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'
2
+ import { tillioDetailWidgetSpotId } from '../integration'
3
+
4
+ export const injectionTable: ModuleInjectionTable = {
5
+ [tillioDetailWidgetSpotId]: [
6
+ {
7
+ widgetId: 'tillio.injection.operators',
8
+ kind: 'tab',
9
+ groupLabel: 'tillio.operators.tab',
10
+ priority: 100,
11
+ },
12
+ ],
13
+ 'data-table:phone_calls.calls:toolbar': {
14
+ widgetId: 'tillio.injection.pull-calls',
15
+ priority: 20,
16
+ },
17
+ }
18
+
19
+ export default injectionTable
@@ -0,0 +1,18 @@
1
+ import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'
2
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
3
+ import { runTillioPullJob, type TillioPullJobPayload } from '../lib/pull-job'
4
+ import { resolveTillioQueueConcurrency, TILLIO_PULL_QUEUE } from '../lib/queue'
5
+
6
+ export const metadata: WorkerMeta = {
7
+ queue: TILLIO_PULL_QUEUE,
8
+ id: 'tillio:pull-calls',
9
+ concurrency: resolveTillioQueueConcurrency(),
10
+ }
11
+
12
+ export default async function handle(
13
+ job: QueuedJob<TillioPullJobPayload>,
14
+ _ctx: JobContext,
15
+ ): Promise<void> {
16
+ const container = await createRequestContainer()
17
+ await runTillioPullJob({ container, payload: job.payload })
18
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "../../tsconfig.base.json",
4
+ "compilerOptions": {
5
+ "noEmit": true
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "**/__tests__/**"]
9
+ }