deepline 0.3.108 → 0.3.110
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/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +18 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +38 -40
- package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +129 -0
- package/dist/bundling-sources/shared_libs/product-notifications/content.ts +13 -1
- package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +1 -1
- package/dist/bundling-sources/shared_libs/product-notifications/email.ts +50 -8
- package/dist/bundling-sources/shared_libs/product-notifications/events.ts +39 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/index.d.mts +149 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
|
|
|
200
200
|
// getters keep their established compatibility behavior.
|
|
201
201
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
202
202
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
203
|
-
version: '0.3.
|
|
203
|
+
version: '0.3.110',
|
|
204
204
|
updateSummary:
|
|
205
205
|
'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
|
|
206
206
|
packageCapabilities: {
|
|
@@ -903,6 +903,24 @@
|
|
|
903
903
|
"failureAlert": "convex_action_wrapper",
|
|
904
904
|
"domainReportRequired": true
|
|
905
905
|
},
|
|
906
|
+
{
|
|
907
|
+
"id": "convex.process-notification-usage-refreshes",
|
|
908
|
+
"name": "Process product notification usage refreshes",
|
|
909
|
+
"scheduler": "convex_cron",
|
|
910
|
+
"cadence": "every 60s",
|
|
911
|
+
"timezone": "UTC",
|
|
912
|
+
"source": "packages/convex/crons.ts",
|
|
913
|
+
"locator": "process notification usage refreshes",
|
|
914
|
+
"owner": "platform",
|
|
915
|
+
"criticality": "critical",
|
|
916
|
+
"purpose": "Recover missed usage-notification wakes and coalesce latest-state observations per organization.",
|
|
917
|
+
"successDefinition": "Queued usage wakes are observed and retired after their notification transaction commits; an empty queue is a valid no-op.",
|
|
918
|
+
"failureDefinition": "The worker cannot read or commit a batch, leaving usage wakes for the next retry and reporting the failure.",
|
|
919
|
+
"startGraceMs": 30000,
|
|
920
|
+
"runTimeoutMs": 1800000,
|
|
921
|
+
"failureAlert": "convex_action_wrapper",
|
|
922
|
+
"domainReportRequired": true
|
|
923
|
+
},
|
|
906
924
|
{
|
|
907
925
|
"id": "convex.fail-stale-play-deployments",
|
|
908
926
|
"name": "Fail stale play deployments",
|
|
@@ -18,6 +18,7 @@ export const MANUALLY_RUNNABLE_CONVEX_CRON_JOB_IDS = [
|
|
|
18
18
|
'convex.monitor-deployment-job-tick',
|
|
19
19
|
'convex.provider-monitor-global-audit-daily',
|
|
20
20
|
'convex.recover-notification-delivery-work',
|
|
21
|
+
'convex.process-notification-usage-refreshes',
|
|
21
22
|
'convex.observe-operational-queue-health',
|
|
22
23
|
'convex.cleanup-expired-notification-audit-records',
|
|
23
24
|
'convex.fail-stale-play-deployments',
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts
CHANGED
|
@@ -409,26 +409,26 @@ function absurdProjectionQuery(queueTable: string): string {
|
|
|
409
409
|
FROM ${table}
|
|
410
410
|
WHERE state = 'pending' AND available_at <= $1::timestamptz
|
|
411
411
|
ORDER BY available_at, run_id
|
|
412
|
-
LIMIT $
|
|
412
|
+
LIMIT $2)
|
|
413
413
|
UNION ALL
|
|
414
414
|
(SELECT 'delayed'::text, available_at
|
|
415
415
|
FROM ${table}
|
|
416
416
|
WHERE state = 'sleeping'
|
|
417
417
|
ORDER BY available_at, run_id
|
|
418
|
-
LIMIT $
|
|
418
|
+
LIMIT $2)
|
|
419
419
|
UNION ALL
|
|
420
420
|
(SELECT 'delayed'::text, available_at
|
|
421
421
|
FROM ${table}
|
|
422
422
|
WHERE state = 'pending' AND available_at > $1::timestamptz
|
|
423
423
|
ORDER BY available_at, run_id
|
|
424
|
-
LIMIT $
|
|
424
|
+
LIMIT $2)
|
|
425
425
|
UNION ALL
|
|
426
426
|
(SELECT 'in_flight'::text, coalesce(started_at, created_at)
|
|
427
427
|
FROM ${table}
|
|
428
428
|
WHERE state = 'running'
|
|
429
429
|
AND (claim_expires_at IS NULL OR claim_expires_at > $1::timestamptz)
|
|
430
430
|
ORDER BY available_at, run_id
|
|
431
|
-
LIMIT $
|
|
431
|
+
LIMIT $2)
|
|
432
432
|
UNION ALL
|
|
433
433
|
(SELECT 'blocked'::text, coalesce(claim_expires_at, started_at, created_at)
|
|
434
434
|
FROM ${table}
|
|
@@ -436,30 +436,30 @@ function absurdProjectionQuery(queueTable: string): string {
|
|
|
436
436
|
AND claim_expires_at IS NOT NULL
|
|
437
437
|
AND claim_expires_at <= $1::timestamptz
|
|
438
438
|
ORDER BY claim_expires_at, run_id
|
|
439
|
-
LIMIT $
|
|
439
|
+
LIMIT $2)
|
|
440
440
|
UNION ALL
|
|
441
441
|
(SELECT 'dead_lettered'::text, coalesce(failed_at, created_at)
|
|
442
442
|
FROM ${table}
|
|
443
443
|
WHERE state = 'failed'
|
|
444
444
|
ORDER BY available_at, run_id
|
|
445
|
-
LIMIT $
|
|
445
|
+
LIMIT $2)
|
|
446
446
|
)
|
|
447
447
|
SELECT
|
|
448
|
-
least(count(*) FILTER (WHERE bucket = 'ready'), $
|
|
449
|
-
least(count(*) FILTER (WHERE bucket = 'delayed'), $
|
|
450
|
-
least(count(*) FILTER (WHERE bucket = 'in_flight'), $
|
|
451
|
-
least(count(*) FILTER (WHERE bucket = 'blocked'), $
|
|
452
|
-
least(count(*) FILTER (WHERE bucket = 'dead_lettered'), $
|
|
448
|
+
least(count(*) FILTER (WHERE bucket = 'ready'), $3) AS ready,
|
|
449
|
+
least(count(*) FILTER (WHERE bucket = 'delayed'), $3) AS delayed,
|
|
450
|
+
least(count(*) FILTER (WHERE bucket = 'in_flight'), $3) AS in_flight,
|
|
451
|
+
least(count(*) FILTER (WHERE bucket = 'blocked'), $3) AS blocked,
|
|
452
|
+
least(count(*) FILTER (WHERE bucket = 'dead_lettered'), $3) AS dead_lettered,
|
|
453
453
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'ready') AS oldest_ready_ms,
|
|
454
454
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'delayed') AS oldest_delayed_ms,
|
|
455
455
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'in_flight') AS oldest_in_flight_ms,
|
|
456
456
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'blocked') AS oldest_blocked_ms,
|
|
457
457
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'dead_lettered') AS oldest_dead_lettered_ms,
|
|
458
|
-
count(*) FILTER (WHERE bucket = 'ready') > $
|
|
459
|
-
count(*) FILTER (WHERE bucket = 'delayed') > $
|
|
460
|
-
count(*) FILTER (WHERE bucket = 'in_flight') > $
|
|
461
|
-
count(*) FILTER (WHERE bucket = 'blocked') > $
|
|
462
|
-
count(*) FILTER (WHERE bucket = 'dead_lettered') > $
|
|
458
|
+
count(*) FILTER (WHERE bucket = 'ready') > $3 AS ready_truncated,
|
|
459
|
+
count(*) FILTER (WHERE bucket = 'delayed') > $3 AS delayed_truncated,
|
|
460
|
+
count(*) FILTER (WHERE bucket = 'in_flight') > $3 AS in_flight_truncated,
|
|
461
|
+
count(*) FILTER (WHERE bucket = 'blocked') > $3 AS blocked_truncated,
|
|
462
|
+
count(*) FILTER (WHERE bucket = 'dead_lettered') > $3 AS dead_lettered_truncated,
|
|
463
463
|
0::bigint AS admitted,
|
|
464
464
|
0::bigint AS started,
|
|
465
465
|
0::bigint AS completed,
|
|
@@ -493,34 +493,34 @@ function outboxProjectionQuery(schema: string): string {
|
|
|
493
493
|
FROM ${table}
|
|
494
494
|
WHERE delivered_at IS NULL AND failed_at IS NULL
|
|
495
495
|
ORDER BY coalesce(next_attempt_at, created_at), created_at, event_id
|
|
496
|
-
LIMIT $
|
|
496
|
+
LIMIT $2
|
|
497
497
|
), dead_sample AS (
|
|
498
498
|
SELECT 'dead_lettered'::text AS bucket, coalesce(failed_at, created_at) AS age_at
|
|
499
499
|
FROM ${table}
|
|
500
500
|
WHERE delivered_at IS NULL AND failed_at IS NOT NULL
|
|
501
501
|
ORDER BY failed_at, event_id
|
|
502
|
-
LIMIT $
|
|
502
|
+
LIMIT $2
|
|
503
503
|
), sampled AS (
|
|
504
504
|
SELECT * FROM pending_sample
|
|
505
505
|
UNION ALL
|
|
506
506
|
SELECT * FROM dead_sample
|
|
507
507
|
)
|
|
508
508
|
SELECT
|
|
509
|
-
least(count(*) FILTER (WHERE bucket = 'ready'), $
|
|
510
|
-
least(count(*) FILTER (WHERE bucket = 'delayed'), $
|
|
511
|
-
least(count(*) FILTER (WHERE bucket = 'in_flight'), $
|
|
512
|
-
least(count(*) FILTER (WHERE bucket = 'blocked'), $
|
|
513
|
-
least(count(*) FILTER (WHERE bucket = 'dead_lettered'), $
|
|
509
|
+
least(count(*) FILTER (WHERE bucket = 'ready'), $3) AS ready,
|
|
510
|
+
least(count(*) FILTER (WHERE bucket = 'delayed'), $3) AS delayed,
|
|
511
|
+
least(count(*) FILTER (WHERE bucket = 'in_flight'), $3) AS in_flight,
|
|
512
|
+
least(count(*) FILTER (WHERE bucket = 'blocked'), $3) AS blocked,
|
|
513
|
+
least(count(*) FILTER (WHERE bucket = 'dead_lettered'), $3) AS dead_lettered,
|
|
514
514
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'ready') AS oldest_ready_ms,
|
|
515
515
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'delayed') AS oldest_delayed_ms,
|
|
516
516
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'in_flight') AS oldest_in_flight_ms,
|
|
517
517
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'blocked') AS oldest_blocked_ms,
|
|
518
518
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'dead_lettered') AS oldest_dead_lettered_ms,
|
|
519
|
-
count(*) FILTER (WHERE bucket <> 'dead_lettered') > $
|
|
520
|
-
count(*) FILTER (WHERE bucket <> 'dead_lettered') > $
|
|
521
|
-
count(*) FILTER (WHERE bucket <> 'dead_lettered') > $
|
|
522
|
-
count(*) FILTER (WHERE bucket <> 'dead_lettered') > $
|
|
523
|
-
count(*) FILTER (WHERE bucket = 'dead_lettered') > $
|
|
519
|
+
count(*) FILTER (WHERE bucket <> 'dead_lettered') > $3 AS ready_truncated,
|
|
520
|
+
count(*) FILTER (WHERE bucket <> 'dead_lettered') > $3 AS delayed_truncated,
|
|
521
|
+
count(*) FILTER (WHERE bucket <> 'dead_lettered') > $3 AS in_flight_truncated,
|
|
522
|
+
count(*) FILTER (WHERE bucket <> 'dead_lettered') > $3 AS blocked_truncated,
|
|
523
|
+
count(*) FILTER (WHERE bucket = 'dead_lettered') > $3 AS dead_lettered_truncated,
|
|
524
524
|
0::bigint AS admitted,
|
|
525
525
|
0::bigint AS started,
|
|
526
526
|
0::bigint AS completed,
|
|
@@ -556,23 +556,23 @@ function cleanupProjectionQuery(schema: string): string {
|
|
|
556
556
|
FROM ${table}
|
|
557
557
|
WHERE status <> 'deleted'
|
|
558
558
|
ORDER BY available_at, created_at, provider, sandbox_id
|
|
559
|
-
LIMIT $
|
|
559
|
+
LIMIT $2
|
|
560
560
|
)
|
|
561
561
|
SELECT
|
|
562
|
-
least(count(*) FILTER (WHERE bucket = 'ready'), $
|
|
563
|
-
least(count(*) FILTER (WHERE bucket = 'delayed'), $
|
|
564
|
-
least(count(*) FILTER (WHERE bucket = 'in_flight'), $
|
|
565
|
-
least(count(*) FILTER (WHERE bucket = 'blocked'), $
|
|
562
|
+
least(count(*) FILTER (WHERE bucket = 'ready'), $3) AS ready,
|
|
563
|
+
least(count(*) FILTER (WHERE bucket = 'delayed'), $3) AS delayed,
|
|
564
|
+
least(count(*) FILTER (WHERE bucket = 'in_flight'), $3) AS in_flight,
|
|
565
|
+
least(count(*) FILTER (WHERE bucket = 'blocked'), $3) AS blocked,
|
|
566
566
|
0::bigint AS dead_lettered,
|
|
567
567
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'ready') AS oldest_ready_ms,
|
|
568
568
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'delayed') AS oldest_delayed_ms,
|
|
569
569
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'in_flight') AS oldest_in_flight_ms,
|
|
570
570
|
max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'blocked') AS oldest_blocked_ms,
|
|
571
571
|
NULL::bigint AS oldest_dead_lettered_ms,
|
|
572
|
-
count(*) > $
|
|
573
|
-
count(*) > $
|
|
574
|
-
count(*) > $
|
|
575
|
-
count(*) > $
|
|
572
|
+
count(*) > $3 AS ready_truncated,
|
|
573
|
+
count(*) > $3 AS delayed_truncated,
|
|
574
|
+
count(*) > $3 AS in_flight_truncated,
|
|
575
|
+
count(*) > $3 AS blocked_truncated,
|
|
576
576
|
false AS dead_lettered_truncated,
|
|
577
577
|
0::bigint AS admitted,
|
|
578
578
|
0::bigint AS started,
|
|
@@ -658,7 +658,7 @@ export async function readRuntimeQueueHealthFromClient(
|
|
|
658
658
|
for (const queueName of queueNames) {
|
|
659
659
|
const result = await client.query<CountRow>(
|
|
660
660
|
absurdProjectionQuery(`r_${queueName}`),
|
|
661
|
-
[now,
|
|
661
|
+
[now, sampleLimit + 1, sampleLimit],
|
|
662
662
|
);
|
|
663
663
|
projections.push(
|
|
664
664
|
projectionFromRow({
|
|
@@ -673,13 +673,11 @@ export async function readRuntimeQueueHealthFromClient(
|
|
|
673
673
|
}
|
|
674
674
|
const [outbox, cleanup] = await Promise.all([
|
|
675
675
|
client.query<CountRow>(outboxProjectionQuery(schema), [
|
|
676
|
-
now,
|
|
677
676
|
now,
|
|
678
677
|
sampleLimit + 1,
|
|
679
678
|
sampleLimit,
|
|
680
679
|
]),
|
|
681
680
|
client.query<CountRow>(cleanupProjectionQuery(schema), [
|
|
682
|
-
now,
|
|
683
681
|
now,
|
|
684
682
|
sampleLimit + 1,
|
|
685
683
|
sampleLimit,
|
|
@@ -68,6 +68,8 @@ export type ProductNotificationPresentation = {
|
|
|
68
68
|
subjectKind: string;
|
|
69
69
|
subjectId: string;
|
|
70
70
|
severity?: ProductNotificationSeverity;
|
|
71
|
+
/** Selects an alternate email layout; absent means the standard one. */
|
|
72
|
+
emailFormat?: 'thank_you';
|
|
71
73
|
};
|
|
72
74
|
|
|
73
75
|
type ProductNotificationRuleInput = {
|
|
@@ -233,6 +235,31 @@ function formatCredits(value: number): string {
|
|
|
233
235
|
return value.toLocaleString('en-US', { maximumFractionDigits: 2 });
|
|
234
236
|
}
|
|
235
237
|
|
|
238
|
+
export const DEEPLINE_DOCS_URL = 'https://deepline.com/docs';
|
|
239
|
+
|
|
240
|
+
/** Points a newly paying workspace at the docs to get value from Deepline. */
|
|
241
|
+
const LEARN_DEEPLINE_DETAIL: NotificationText = [
|
|
242
|
+
'To learn more about how to use Deepline, ',
|
|
243
|
+
{ link: 'check out the docs', href: DEEPLINE_DOCS_URL },
|
|
244
|
+
'.',
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
export const DEEPLINE_DEMO_URL =
|
|
248
|
+
'https://calendly.com/d/csdb-85z-t6z/deepline-demo';
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Growth welcomes lead with a demo call. One detail per line so the links read
|
|
252
|
+
* as a short list with the app's Lucide icons.
|
|
253
|
+
*/
|
|
254
|
+
const GROWTH_LEARN_MORE_DETAILS: NotificationText[] = [
|
|
255
|
+
'To learn more about Deepline:',
|
|
256
|
+
[{ icon: 'calendar' }, { link: 'Schedule a demo', href: DEEPLINE_DEMO_URL }],
|
|
257
|
+
[
|
|
258
|
+
{ icon: 'book-open' },
|
|
259
|
+
{ link: 'Check out the docs', href: DEEPLINE_DOCS_URL },
|
|
260
|
+
],
|
|
261
|
+
];
|
|
262
|
+
|
|
236
263
|
/** One canonical registry owns every notification type's runtime and UI metadata. */
|
|
237
264
|
const PRODUCT_NOTIFICATION_TYPE_REGISTRY = [
|
|
238
265
|
{
|
|
@@ -546,6 +573,108 @@ const PRODUCT_NOTIFICATION_TYPE_REGISTRY = [
|
|
|
546
573
|
},
|
|
547
574
|
},
|
|
548
575
|
},
|
|
576
|
+
{
|
|
577
|
+
id: 'credits.purchased',
|
|
578
|
+
eventName: ProductNotificationEvent.CreditsPurchaseCompleted,
|
|
579
|
+
requiredFilter: {},
|
|
580
|
+
allowedTriggers: ['threshold_crossing'],
|
|
581
|
+
catalog: {
|
|
582
|
+
name: 'Credit purchase thank-you',
|
|
583
|
+
category: 'credits',
|
|
584
|
+
description:
|
|
585
|
+
'Thank the workspace when a credit purchase of at least the chosen size completes.',
|
|
586
|
+
allowMultiple: false,
|
|
587
|
+
editor: {
|
|
588
|
+
kind: 'threshold',
|
|
589
|
+
label: 'Minimum credits',
|
|
590
|
+
defaultThreshold: 500,
|
|
591
|
+
minimum: 0,
|
|
592
|
+
suffix: 'credits',
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
rule: {
|
|
596
|
+
default: { ruleKey: 'default.credits_purchased', groupKey: 'default' },
|
|
597
|
+
create: (input, base, editor) => ({
|
|
598
|
+
...base,
|
|
599
|
+
repeatMode: 'every_match',
|
|
600
|
+
trigger: {
|
|
601
|
+
kind: 'threshold_crossing',
|
|
602
|
+
direction: 'at_or_above',
|
|
603
|
+
threshold: input.threshold ?? defaultThreshold(editor),
|
|
604
|
+
unit: 'credits',
|
|
605
|
+
},
|
|
606
|
+
}),
|
|
607
|
+
validate: (trigger) => {
|
|
608
|
+
if (
|
|
609
|
+
trigger.kind !== 'threshold_crossing' ||
|
|
610
|
+
trigger.direction !== 'at_or_above' ||
|
|
611
|
+
trigger.unit !== 'credits'
|
|
612
|
+
) {
|
|
613
|
+
throw new Error(
|
|
614
|
+
'Credit purchase rules must reach a purchased credit amount.',
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
if (!Number.isFinite(trigger.threshold) || trigger.threshold < 0) {
|
|
618
|
+
throw new Error(
|
|
619
|
+
'Credit purchase thresholds must be finite and nonnegative.',
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
present: ({ payload }) => ({
|
|
624
|
+
title: 'Thank you for your credit purchase',
|
|
625
|
+
summary: [
|
|
626
|
+
'Your ',
|
|
627
|
+
{ workspace: true },
|
|
628
|
+
' added ',
|
|
629
|
+
{ strong: `${formatCredits(Number(payload.credits))} credits` },
|
|
630
|
+
'. Thank you for supporting Deepline.',
|
|
631
|
+
],
|
|
632
|
+
details: [LEARN_DEEPLINE_DETAIL],
|
|
633
|
+
emailFormat: 'thank_you',
|
|
634
|
+
targetUrl: String(payload.targetUrl),
|
|
635
|
+
subjectKind: 'workspace',
|
|
636
|
+
subjectId: String(payload.accountId),
|
|
637
|
+
}),
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
id: 'plan.purchased',
|
|
642
|
+
eventName: ProductNotificationEvent.BillingPlanPurchased,
|
|
643
|
+
requiredFilter: {},
|
|
644
|
+
allowedTriggers: ['each_match'],
|
|
645
|
+
catalog: {
|
|
646
|
+
name: 'Plan welcome',
|
|
647
|
+
category: 'credits',
|
|
648
|
+
description: 'Thank the workspace when it purchases a plan.',
|
|
649
|
+
allowMultiple: false,
|
|
650
|
+
editor: { kind: 'none' },
|
|
651
|
+
},
|
|
652
|
+
rule: {
|
|
653
|
+
default: { ruleKey: 'default.plan_purchased', groupKey: 'default' },
|
|
654
|
+
create: (_input, base) => ({
|
|
655
|
+
...base,
|
|
656
|
+
repeatMode: 'every_match',
|
|
657
|
+
trigger: { kind: 'each_match' },
|
|
658
|
+
}),
|
|
659
|
+
present: ({ payload }) => ({
|
|
660
|
+
title: `Welcome to ${payload.planName}`,
|
|
661
|
+
summary: [
|
|
662
|
+
'Your ',
|
|
663
|
+
{ workspace: true },
|
|
664
|
+
` is now on the ${payload.planName} plan. Thank you for supporting Deepline.`,
|
|
665
|
+
],
|
|
666
|
+
// No credit-lifetime claims: they differ by plan.
|
|
667
|
+
details:
|
|
668
|
+
payload.offerKey === 'growth-v1'
|
|
669
|
+
? GROWTH_LEARN_MORE_DETAILS
|
|
670
|
+
: [LEARN_DEEPLINE_DETAIL],
|
|
671
|
+
emailFormat: 'thank_you',
|
|
672
|
+
targetUrl: String(payload.targetUrl),
|
|
673
|
+
subjectKind: 'workspace',
|
|
674
|
+
subjectId: String(payload.accountId),
|
|
675
|
+
}),
|
|
676
|
+
},
|
|
677
|
+
},
|
|
549
678
|
{
|
|
550
679
|
id: 'play.failed',
|
|
551
680
|
eventName: ProductNotificationEvent.PlayRunCompleted,
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
/** Type-owned content, independent of email markup and persisted audit strings. */
|
|
2
2
|
export type NotificationText =
|
|
3
3
|
| string
|
|
4
|
-
| readonly (
|
|
4
|
+
| readonly (
|
|
5
|
+
| string
|
|
6
|
+
| { strong: string }
|
|
7
|
+
| { workspace: true }
|
|
8
|
+
/** An absolute https link authored in code, never customer input. */
|
|
9
|
+
| { link: string; href: string }
|
|
10
|
+
/** A decorative brand icon; plain text omits it. */
|
|
11
|
+
| { icon: NotificationIcon }
|
|
12
|
+
)[];
|
|
13
|
+
|
|
14
|
+
export type NotificationIcon = 'calendar' | 'book-open';
|
|
5
15
|
|
|
6
16
|
export function notificationText(
|
|
7
17
|
content: NotificationText,
|
|
@@ -12,6 +22,8 @@ export function notificationText(
|
|
|
12
22
|
.map((part) => {
|
|
13
23
|
if (typeof part === 'string') return part;
|
|
14
24
|
if ('strong' in part) return part.strong;
|
|
25
|
+
if ('link' in part) return `${part.link} (${part.href})`;
|
|
26
|
+
if ('icon' in part) return '';
|
|
15
27
|
return workspaceName ? `workspace ${workspaceName}` : 'workspace';
|
|
16
28
|
})
|
|
17
29
|
.join('');
|
|
@@ -32,10 +32,24 @@ type ProductNotificationEmailContent = {
|
|
|
32
32
|
details: NotificationText[];
|
|
33
33
|
targetUrl?: string;
|
|
34
34
|
severity?: 'success' | 'warning' | 'error';
|
|
35
|
+
/**
|
|
36
|
+
* `thank_you` is the standard layout without the notification group header,
|
|
37
|
+
* with details shown as a compact list instead of divided rows. Kept as its
|
|
38
|
+
* own format so these emails can diverge later.
|
|
39
|
+
*/
|
|
40
|
+
emailFormat?: 'thank_you';
|
|
35
41
|
};
|
|
36
42
|
|
|
37
43
|
const MAX_RECIPIENTS = 50;
|
|
38
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Lucide icons rasterized to 48px PNGs in the light-theme muted foreground
|
|
47
|
+
* color, served from `apps/deepline-api/public/brand/email`. Email clients do
|
|
48
|
+
* not render inline SVG.
|
|
49
|
+
*/
|
|
50
|
+
export const NOTIFICATION_ICON_BASE_URL =
|
|
51
|
+
'https://code.deepline.com/brand/email';
|
|
52
|
+
|
|
39
53
|
const SEVERITY_STYLE: Record<
|
|
40
54
|
NonNullable<ProductNotificationEmailContent['severity']>,
|
|
41
55
|
{ bg: string; text: string; label: string }
|
|
@@ -74,6 +88,14 @@ function unsubscribeUrl(value: string): string {
|
|
|
74
88
|
return url.toString();
|
|
75
89
|
}
|
|
76
90
|
|
|
91
|
+
function externalLinkUrl(value: string): string {
|
|
92
|
+
const url = new URL(value);
|
|
93
|
+
if (url.protocol !== 'https:') {
|
|
94
|
+
throw new Error('Notification content links must use HTTPS.');
|
|
95
|
+
}
|
|
96
|
+
return url.toString();
|
|
97
|
+
}
|
|
98
|
+
|
|
77
99
|
function contentHtml(
|
|
78
100
|
content: NotificationText,
|
|
79
101
|
workspaceName?: string,
|
|
@@ -84,6 +106,10 @@ function contentHtml(
|
|
|
84
106
|
if (typeof part === 'string') return escapeHtml(part);
|
|
85
107
|
if ('strong' in part)
|
|
86
108
|
return `<strong>${escapeHtml(part.strong)}</strong>`;
|
|
109
|
+
if ('link' in part)
|
|
110
|
+
return `<a href="${escapeHtml(externalLinkUrl(part.href))}" style="color:#0a0a0a;text-decoration:underline">${escapeHtml(part.link)}</a>`;
|
|
111
|
+
if ('icon' in part)
|
|
112
|
+
return `<img src="${NOTIFICATION_ICON_BASE_URL}/icon-${part.icon}.png" width="16" height="16" alt="" style="display:inline-block;vertical-align:-3px;margin-right:8px;border:0">`;
|
|
87
113
|
return workspaceName
|
|
88
114
|
? `workspace <strong>${escapeHtml(workspaceName)}</strong>`
|
|
89
115
|
: 'workspace';
|
|
@@ -95,8 +121,12 @@ function detailHtml(
|
|
|
95
121
|
detail: NotificationText,
|
|
96
122
|
workspaceName: string | undefined,
|
|
97
123
|
borderBottom: boolean,
|
|
124
|
+
/** List-style rows: no dividers, tighter spacing. */
|
|
125
|
+
compact = false,
|
|
98
126
|
): string {
|
|
99
|
-
const border =
|
|
127
|
+
const border =
|
|
128
|
+
borderBottom && !compact ? ';border-bottom:1px solid #f0f0f0' : '';
|
|
129
|
+
const padding = compact ? '5px 0' : '12px 0';
|
|
100
130
|
if (
|
|
101
131
|
Array.isArray(detail) &&
|
|
102
132
|
detail.length === 2 &&
|
|
@@ -109,7 +139,7 @@ function detailHtml(
|
|
|
109
139
|
const value = typeof detail[1] === 'string' ? detail[1] : detail[1].strong;
|
|
110
140
|
return `<table role="presentation" width="100%" cellspacing="0" cellpadding="0"><tr><td style="padding:12px 0${border};font-size:14px;color:#0a0a0a">${escapeHtml(detail[0].trimEnd())}</td><td align="right" style="padding:12px 0${border};font-size:14px;color:#0a0a0a${typeof detail[1] === 'string' ? '' : ';font-weight:600'}">${escapeHtml(value)}</td></tr></table>`;
|
|
111
141
|
}
|
|
112
|
-
return `<p style="margin:0;padding
|
|
142
|
+
return `<p style="margin:0;padding:${padding}${border};font-size:14px;color:#0a0a0a">${contentHtml(detail, workspaceName)}</p>`;
|
|
113
143
|
}
|
|
114
144
|
|
|
115
145
|
export function renderProductNotificationEmail(
|
|
@@ -155,9 +185,12 @@ export function renderProductNotificationEmail(
|
|
|
155
185
|
const oneClickOptOutUrl = input.oneClickUnsubscribeUrl
|
|
156
186
|
? unsubscribeUrl(input.oneClickUnsubscribeUrl)
|
|
157
187
|
: optOutUrl;
|
|
188
|
+
const showGroupHeader = input.content.emailFormat !== 'thank_you';
|
|
158
189
|
const text = [
|
|
159
190
|
input.content.title,
|
|
160
|
-
|
|
191
|
+
...(showGroupHeader
|
|
192
|
+
? [`Notification group: ${input.content.groupName}`]
|
|
193
|
+
: []),
|
|
161
194
|
...(targetUrl ? [`Open Deepline: ${targetUrl}`] : []),
|
|
162
195
|
'',
|
|
163
196
|
notificationText(input.content.summary, input.workspaceName),
|
|
@@ -184,16 +217,25 @@ export function renderProductNotificationEmail(
|
|
|
184
217
|
`<td align="left" valign="middle"><a href="${escapeHtml(logoUrl)}" style="display:inline-block;text-decoration:none"><img src="https://code.deepline.com/brand/logo_full_dark.png" width="120" height="auto" alt="Deepline" style="display:block;border:0"></a></td>`,
|
|
185
218
|
`<td align="right" valign="middle">${severityBadge}</td>`,
|
|
186
219
|
'</tr></table>',
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
220
|
+
...(showGroupHeader
|
|
221
|
+
? [
|
|
222
|
+
'<p style="margin:0 0 16px">',
|
|
223
|
+
`<span style="font-size:12px;color:#a3a3a3;letter-spacing:.06em;text-transform:uppercase">${escapeHtml(input.content.groupName)}</span>`,
|
|
224
|
+
'</p>',
|
|
225
|
+
]
|
|
226
|
+
: []),
|
|
190
227
|
`<h1 style="margin:0 0 8px;font-size:22px;font-weight:600;color:#0a0a0a;line-height:1.3;letter-spacing:-0.02em">${escapeHtml(input.content.title)}</h1>`,
|
|
191
228
|
`<p style="margin:0 0 24px;font-size:15px;line-height:1.6;color:#525252">${contentHtml(input.content.summary, input.workspaceName)}</p>`,
|
|
192
229
|
...(detailCount > 0
|
|
193
230
|
? [
|
|
194
|
-
|
|
231
|
+
`<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border:1px solid #e5e5e5;border-radius:10px;margin:0 0 28px"><tr><td style="padding:${showGroupHeader ? '4px' : '11px'} 18px">`,
|
|
195
232
|
...input.content.details.map((detail, index) =>
|
|
196
|
-
detailHtml(
|
|
233
|
+
detailHtml(
|
|
234
|
+
detail,
|
|
235
|
+
input.workspaceName,
|
|
236
|
+
index < detailCount - 1,
|
|
237
|
+
!showGroupHeader,
|
|
238
|
+
),
|
|
197
239
|
),
|
|
198
240
|
'</td></tr></table>',
|
|
199
241
|
]
|
|
@@ -15,7 +15,9 @@ export const PRODUCT_NOTIFICATION_MAX_SOURCE_CLOCK_SKEW_MS = 5 * 60 * 1_000;
|
|
|
15
15
|
|
|
16
16
|
export const ProductNotificationEvent = {
|
|
17
17
|
CreditsBalanceChanged: 'credits.balance.changed',
|
|
18
|
+
CreditsPurchaseCompleted: 'credits.purchase.completed',
|
|
18
19
|
CreditsUsageRecorded: 'credits.usage.recorded',
|
|
20
|
+
BillingPlanPurchased: 'billing.plan.purchased',
|
|
19
21
|
MonitorLifecycleChanged: 'monitor.lifecycle.changed',
|
|
20
22
|
PlayLifecycleChanged: 'play.lifecycle.changed',
|
|
21
23
|
PlayRunCompleted: 'play.run.completed',
|
|
@@ -31,6 +33,7 @@ export type ProductNotificationEvent =
|
|
|
31
33
|
|
|
32
34
|
export const ProductNotificationProducerKey = {
|
|
33
35
|
BillingCredits: 'billing.credits',
|
|
36
|
+
BillingPlans: 'billing.plans',
|
|
34
37
|
BillingUsage: 'billing.usage',
|
|
35
38
|
MonitorsLifecycle: 'monitors.lifecycle',
|
|
36
39
|
PlaysLifecycle: 'plays.lifecycle',
|
|
@@ -53,6 +56,8 @@ export const PRODUCT_NOTIFICATION_EVENT_NAMES = [
|
|
|
53
56
|
ProductNotificationEvent.PlayRunCompleted,
|
|
54
57
|
ProductNotificationEvent.WebhookDeliveryCompleted,
|
|
55
58
|
ProductNotificationEvent.CreditsUsageRecorded,
|
|
59
|
+
ProductNotificationEvent.CreditsPurchaseCompleted,
|
|
60
|
+
ProductNotificationEvent.BillingPlanPurchased,
|
|
56
61
|
] as const;
|
|
57
62
|
|
|
58
63
|
export const PRODUCT_NOTIFICATION_COMPATIBILITY_EVENT_NAMES = [
|
|
@@ -175,6 +180,33 @@ export const PRODUCT_NOTIFICATION_EVENT_DEFINITIONS: readonly ProductNotificatio
|
|
|
175
180
|
allowedTriggers: ['threshold_crossing'],
|
|
176
181
|
allowedThresholdUnits: ['percent', 'usage'],
|
|
177
182
|
},
|
|
183
|
+
{
|
|
184
|
+
// One paid credit purchase (manual or automatic recharge) settled.
|
|
185
|
+
eventName: ProductNotificationEvent.CreditsPurchaseCompleted,
|
|
186
|
+
producerKey: ProductNotificationProducerKey.BillingCredits,
|
|
187
|
+
requiredFacts: {
|
|
188
|
+
accountId: 'string',
|
|
189
|
+
credits: 'number',
|
|
190
|
+
targetUrl: 'string',
|
|
191
|
+
},
|
|
192
|
+
orderingSubjectFact: 'accountId',
|
|
193
|
+
allowedTriggers: ['threshold_crossing'],
|
|
194
|
+
allowedThresholdUnits: ['credits'],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
// The workspace moved onto a paid acquisition offer.
|
|
198
|
+
eventName: ProductNotificationEvent.BillingPlanPurchased,
|
|
199
|
+
producerKey: ProductNotificationProducerKey.BillingPlans,
|
|
200
|
+
requiredFacts: {
|
|
201
|
+
accountId: 'string',
|
|
202
|
+
offerKey: 'string',
|
|
203
|
+
planName: 'string',
|
|
204
|
+
targetUrl: 'string',
|
|
205
|
+
},
|
|
206
|
+
optionalFacts: { previousOfferKey: 'string' },
|
|
207
|
+
orderingSubjectFact: 'accountId',
|
|
208
|
+
allowedTriggers: ['each_match'],
|
|
209
|
+
},
|
|
178
210
|
...PRODUCT_NOTIFICATION_COMPATIBILITY_EVENT_NAMES.map((eventName) => ({
|
|
179
211
|
eventName,
|
|
180
212
|
producerKey: ProductNotificationProducerKey.LegacyProductNotifications,
|
|
@@ -396,6 +428,13 @@ export function adaptProductNotificationObservation(input: {
|
|
|
396
428
|
unit: 'credits',
|
|
397
429
|
};
|
|
398
430
|
}
|
|
431
|
+
if (event.eventName === ProductNotificationEvent.CreditsPurchaseCompleted) {
|
|
432
|
+
return {
|
|
433
|
+
kind: 'threshold_state',
|
|
434
|
+
currentValue: Number(event.payload.credits),
|
|
435
|
+
unit: 'credits',
|
|
436
|
+
};
|
|
437
|
+
}
|
|
399
438
|
const limit = Number(event.payload.limit);
|
|
400
439
|
if (event.eventName === ProductNotificationEvent.CreditsUsageRecorded) {
|
|
401
440
|
if (!(limit > 0)) throw new Error('Monthly usage limit must be positive.');
|
package/dist/cli/index.js
CHANGED
|
@@ -1298,7 +1298,7 @@ var SDK_RELEASE = {
|
|
|
1298
1298
|
// getters keep their established compatibility behavior.
|
|
1299
1299
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
1300
1300
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
1301
|
-
version: "0.3.
|
|
1301
|
+
version: "0.3.110",
|
|
1302
1302
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
1303
1303
|
packageCapabilities: {
|
|
1304
1304
|
updatePreferences: 1
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1271,7 +1271,7 @@ var SDK_RELEASE = {
|
|
|
1271
1271
|
// getters keep their established compatibility behavior.
|
|
1272
1272
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
1273
1273
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
1274
|
-
version: "0.3.
|
|
1274
|
+
version: "0.3.110",
|
|
1275
1275
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
1276
1276
|
packageCapabilities: {
|
|
1277
1277
|
updatePreferences: 1
|
package/dist/index.d.mts
CHANGED
|
@@ -318,7 +318,17 @@ type NotificationText = string | readonly (string | {
|
|
|
318
318
|
strong: string;
|
|
319
319
|
} | {
|
|
320
320
|
workspace: true;
|
|
321
|
+
}
|
|
322
|
+
/** An absolute https link authored in code, never customer input. */
|
|
323
|
+
| {
|
|
324
|
+
link: string;
|
|
325
|
+
href: string;
|
|
326
|
+
}
|
|
327
|
+
/** A decorative brand icon; plain text omits it. */
|
|
328
|
+
| {
|
|
329
|
+
icon: NotificationIcon;
|
|
321
330
|
})[];
|
|
331
|
+
type NotificationIcon = 'calendar' | 'book-open';
|
|
322
332
|
|
|
323
333
|
/**
|
|
324
334
|
* The sections the configuration surface skims by. Declared here so UI, API,
|
|
@@ -368,6 +378,8 @@ type ProductNotificationPresentation = {
|
|
|
368
378
|
subjectKind: string;
|
|
369
379
|
subjectId: string;
|
|
370
380
|
severity?: ProductNotificationSeverity;
|
|
381
|
+
/** Selects an alternate email layout; absent means the standard one. */
|
|
382
|
+
emailFormat?: 'thank_you';
|
|
371
383
|
};
|
|
372
384
|
type ProductNotificationRuleInput = {
|
|
373
385
|
ruleKey: string;
|
|
@@ -698,6 +710,143 @@ declare const PRODUCT_NOTIFICATION_TYPE_REGISTRY: readonly [{
|
|
|
698
710
|
severity: "warning";
|
|
699
711
|
};
|
|
700
712
|
};
|
|
713
|
+
}, {
|
|
714
|
+
readonly id: "credits.purchased";
|
|
715
|
+
readonly eventName: "credits.purchase.completed";
|
|
716
|
+
readonly requiredFilter: {};
|
|
717
|
+
readonly allowedTriggers: ["threshold_crossing"];
|
|
718
|
+
readonly catalog: {
|
|
719
|
+
readonly name: "Credit purchase thank-you";
|
|
720
|
+
readonly category: "credits";
|
|
721
|
+
readonly description: "Thank the workspace when a credit purchase of at least the chosen size completes.";
|
|
722
|
+
readonly allowMultiple: false;
|
|
723
|
+
readonly editor: {
|
|
724
|
+
readonly kind: "threshold";
|
|
725
|
+
readonly label: "Minimum credits";
|
|
726
|
+
readonly defaultThreshold: 500;
|
|
727
|
+
readonly minimum: 0;
|
|
728
|
+
readonly suffix: "credits";
|
|
729
|
+
};
|
|
730
|
+
};
|
|
731
|
+
readonly rule: {
|
|
732
|
+
readonly default: {
|
|
733
|
+
readonly ruleKey: "default.credits_purchased";
|
|
734
|
+
readonly groupKey: "default";
|
|
735
|
+
};
|
|
736
|
+
readonly create: (input: ProductNotificationRuleInput, base: ProductNotificationRuleBase, editor: {
|
|
737
|
+
kind: "threshold";
|
|
738
|
+
label: string;
|
|
739
|
+
defaultThreshold: number;
|
|
740
|
+
minimum: number;
|
|
741
|
+
maximum?: number;
|
|
742
|
+
suffix?: string;
|
|
743
|
+
/**
|
|
744
|
+
* When set, editors show and accept `remainingOf - threshold` (for
|
|
745
|
+
* example percent remaining) while the stored trigger stays in the
|
|
746
|
+
* runtime's "used" terms. Bounds and defaults are stored values.
|
|
747
|
+
*/
|
|
748
|
+
remainingOf?: number;
|
|
749
|
+
} | {
|
|
750
|
+
kind: "play_failure";
|
|
751
|
+
defaultFailureCount: number;
|
|
752
|
+
} | {
|
|
753
|
+
kind: "play_scope";
|
|
754
|
+
} | {
|
|
755
|
+
kind: "none";
|
|
756
|
+
}) => {
|
|
757
|
+
repeatMode: "every_match";
|
|
758
|
+
trigger: {
|
|
759
|
+
kind: "threshold_crossing";
|
|
760
|
+
direction: "at_or_above";
|
|
761
|
+
threshold: number;
|
|
762
|
+
unit: string;
|
|
763
|
+
};
|
|
764
|
+
groupKey: string;
|
|
765
|
+
enabled: boolean;
|
|
766
|
+
conditionGeneration?: number | undefined;
|
|
767
|
+
ruleKey: string;
|
|
768
|
+
notificationType: string;
|
|
769
|
+
eventName: string;
|
|
770
|
+
eventFilter: ProductNotificationFacts;
|
|
771
|
+
};
|
|
772
|
+
readonly validate: (trigger: ProductNotificationTrigger) => void;
|
|
773
|
+
readonly present: ({ payload }: {
|
|
774
|
+
payload: ProductNotificationFacts;
|
|
775
|
+
trigger: ProductNotificationTrigger;
|
|
776
|
+
occurredAt?: number;
|
|
777
|
+
}) => {
|
|
778
|
+
title: string;
|
|
779
|
+
summary: (string | {
|
|
780
|
+
workspace: true;
|
|
781
|
+
strong?: undefined;
|
|
782
|
+
} | {
|
|
783
|
+
strong: string;
|
|
784
|
+
workspace?: undefined;
|
|
785
|
+
})[];
|
|
786
|
+
details: (readonly (string | {
|
|
787
|
+
strong: string;
|
|
788
|
+
} | {
|
|
789
|
+
workspace: true;
|
|
790
|
+
} | {
|
|
791
|
+
link: string;
|
|
792
|
+
href: string;
|
|
793
|
+
} | {
|
|
794
|
+
icon: NotificationIcon;
|
|
795
|
+
})[])[];
|
|
796
|
+
emailFormat: "thank_you";
|
|
797
|
+
targetUrl: string;
|
|
798
|
+
subjectKind: string;
|
|
799
|
+
subjectId: string;
|
|
800
|
+
};
|
|
801
|
+
};
|
|
802
|
+
}, {
|
|
803
|
+
readonly id: "plan.purchased";
|
|
804
|
+
readonly eventName: "billing.plan.purchased";
|
|
805
|
+
readonly requiredFilter: {};
|
|
806
|
+
readonly allowedTriggers: ["each_match"];
|
|
807
|
+
readonly catalog: {
|
|
808
|
+
readonly name: "Plan welcome";
|
|
809
|
+
readonly category: "credits";
|
|
810
|
+
readonly description: "Thank the workspace when it purchases a plan.";
|
|
811
|
+
readonly allowMultiple: false;
|
|
812
|
+
readonly editor: {
|
|
813
|
+
readonly kind: "none";
|
|
814
|
+
};
|
|
815
|
+
};
|
|
816
|
+
readonly rule: {
|
|
817
|
+
readonly default: {
|
|
818
|
+
readonly ruleKey: "default.plan_purchased";
|
|
819
|
+
readonly groupKey: "default";
|
|
820
|
+
};
|
|
821
|
+
readonly create: (_input: ProductNotificationRuleInput, base: ProductNotificationRuleBase) => {
|
|
822
|
+
repeatMode: "every_match";
|
|
823
|
+
trigger: {
|
|
824
|
+
kind: "each_match";
|
|
825
|
+
};
|
|
826
|
+
groupKey: string;
|
|
827
|
+
enabled: boolean;
|
|
828
|
+
conditionGeneration?: number | undefined;
|
|
829
|
+
ruleKey: string;
|
|
830
|
+
notificationType: string;
|
|
831
|
+
eventName: string;
|
|
832
|
+
eventFilter: ProductNotificationFacts;
|
|
833
|
+
};
|
|
834
|
+
readonly present: ({ payload }: {
|
|
835
|
+
payload: ProductNotificationFacts;
|
|
836
|
+
trigger: ProductNotificationTrigger;
|
|
837
|
+
occurredAt?: number;
|
|
838
|
+
}) => {
|
|
839
|
+
title: string;
|
|
840
|
+
summary: (string | {
|
|
841
|
+
workspace: true;
|
|
842
|
+
})[];
|
|
843
|
+
details: NotificationText[];
|
|
844
|
+
emailFormat: "thank_you";
|
|
845
|
+
targetUrl: string;
|
|
846
|
+
subjectKind: string;
|
|
847
|
+
subjectId: string;
|
|
848
|
+
};
|
|
849
|
+
};
|
|
701
850
|
}, {
|
|
702
851
|
readonly id: "play.failed";
|
|
703
852
|
readonly eventName: "play.run.completed";
|
package/dist/index.d.ts
CHANGED
|
@@ -318,7 +318,17 @@ type NotificationText = string | readonly (string | {
|
|
|
318
318
|
strong: string;
|
|
319
319
|
} | {
|
|
320
320
|
workspace: true;
|
|
321
|
+
}
|
|
322
|
+
/** An absolute https link authored in code, never customer input. */
|
|
323
|
+
| {
|
|
324
|
+
link: string;
|
|
325
|
+
href: string;
|
|
326
|
+
}
|
|
327
|
+
/** A decorative brand icon; plain text omits it. */
|
|
328
|
+
| {
|
|
329
|
+
icon: NotificationIcon;
|
|
321
330
|
})[];
|
|
331
|
+
type NotificationIcon = 'calendar' | 'book-open';
|
|
322
332
|
|
|
323
333
|
/**
|
|
324
334
|
* The sections the configuration surface skims by. Declared here so UI, API,
|
|
@@ -368,6 +378,8 @@ type ProductNotificationPresentation = {
|
|
|
368
378
|
subjectKind: string;
|
|
369
379
|
subjectId: string;
|
|
370
380
|
severity?: ProductNotificationSeverity;
|
|
381
|
+
/** Selects an alternate email layout; absent means the standard one. */
|
|
382
|
+
emailFormat?: 'thank_you';
|
|
371
383
|
};
|
|
372
384
|
type ProductNotificationRuleInput = {
|
|
373
385
|
ruleKey: string;
|
|
@@ -698,6 +710,143 @@ declare const PRODUCT_NOTIFICATION_TYPE_REGISTRY: readonly [{
|
|
|
698
710
|
severity: "warning";
|
|
699
711
|
};
|
|
700
712
|
};
|
|
713
|
+
}, {
|
|
714
|
+
readonly id: "credits.purchased";
|
|
715
|
+
readonly eventName: "credits.purchase.completed";
|
|
716
|
+
readonly requiredFilter: {};
|
|
717
|
+
readonly allowedTriggers: ["threshold_crossing"];
|
|
718
|
+
readonly catalog: {
|
|
719
|
+
readonly name: "Credit purchase thank-you";
|
|
720
|
+
readonly category: "credits";
|
|
721
|
+
readonly description: "Thank the workspace when a credit purchase of at least the chosen size completes.";
|
|
722
|
+
readonly allowMultiple: false;
|
|
723
|
+
readonly editor: {
|
|
724
|
+
readonly kind: "threshold";
|
|
725
|
+
readonly label: "Minimum credits";
|
|
726
|
+
readonly defaultThreshold: 500;
|
|
727
|
+
readonly minimum: 0;
|
|
728
|
+
readonly suffix: "credits";
|
|
729
|
+
};
|
|
730
|
+
};
|
|
731
|
+
readonly rule: {
|
|
732
|
+
readonly default: {
|
|
733
|
+
readonly ruleKey: "default.credits_purchased";
|
|
734
|
+
readonly groupKey: "default";
|
|
735
|
+
};
|
|
736
|
+
readonly create: (input: ProductNotificationRuleInput, base: ProductNotificationRuleBase, editor: {
|
|
737
|
+
kind: "threshold";
|
|
738
|
+
label: string;
|
|
739
|
+
defaultThreshold: number;
|
|
740
|
+
minimum: number;
|
|
741
|
+
maximum?: number;
|
|
742
|
+
suffix?: string;
|
|
743
|
+
/**
|
|
744
|
+
* When set, editors show and accept `remainingOf - threshold` (for
|
|
745
|
+
* example percent remaining) while the stored trigger stays in the
|
|
746
|
+
* runtime's "used" terms. Bounds and defaults are stored values.
|
|
747
|
+
*/
|
|
748
|
+
remainingOf?: number;
|
|
749
|
+
} | {
|
|
750
|
+
kind: "play_failure";
|
|
751
|
+
defaultFailureCount: number;
|
|
752
|
+
} | {
|
|
753
|
+
kind: "play_scope";
|
|
754
|
+
} | {
|
|
755
|
+
kind: "none";
|
|
756
|
+
}) => {
|
|
757
|
+
repeatMode: "every_match";
|
|
758
|
+
trigger: {
|
|
759
|
+
kind: "threshold_crossing";
|
|
760
|
+
direction: "at_or_above";
|
|
761
|
+
threshold: number;
|
|
762
|
+
unit: string;
|
|
763
|
+
};
|
|
764
|
+
groupKey: string;
|
|
765
|
+
enabled: boolean;
|
|
766
|
+
conditionGeneration?: number | undefined;
|
|
767
|
+
ruleKey: string;
|
|
768
|
+
notificationType: string;
|
|
769
|
+
eventName: string;
|
|
770
|
+
eventFilter: ProductNotificationFacts;
|
|
771
|
+
};
|
|
772
|
+
readonly validate: (trigger: ProductNotificationTrigger) => void;
|
|
773
|
+
readonly present: ({ payload }: {
|
|
774
|
+
payload: ProductNotificationFacts;
|
|
775
|
+
trigger: ProductNotificationTrigger;
|
|
776
|
+
occurredAt?: number;
|
|
777
|
+
}) => {
|
|
778
|
+
title: string;
|
|
779
|
+
summary: (string | {
|
|
780
|
+
workspace: true;
|
|
781
|
+
strong?: undefined;
|
|
782
|
+
} | {
|
|
783
|
+
strong: string;
|
|
784
|
+
workspace?: undefined;
|
|
785
|
+
})[];
|
|
786
|
+
details: (readonly (string | {
|
|
787
|
+
strong: string;
|
|
788
|
+
} | {
|
|
789
|
+
workspace: true;
|
|
790
|
+
} | {
|
|
791
|
+
link: string;
|
|
792
|
+
href: string;
|
|
793
|
+
} | {
|
|
794
|
+
icon: NotificationIcon;
|
|
795
|
+
})[])[];
|
|
796
|
+
emailFormat: "thank_you";
|
|
797
|
+
targetUrl: string;
|
|
798
|
+
subjectKind: string;
|
|
799
|
+
subjectId: string;
|
|
800
|
+
};
|
|
801
|
+
};
|
|
802
|
+
}, {
|
|
803
|
+
readonly id: "plan.purchased";
|
|
804
|
+
readonly eventName: "billing.plan.purchased";
|
|
805
|
+
readonly requiredFilter: {};
|
|
806
|
+
readonly allowedTriggers: ["each_match"];
|
|
807
|
+
readonly catalog: {
|
|
808
|
+
readonly name: "Plan welcome";
|
|
809
|
+
readonly category: "credits";
|
|
810
|
+
readonly description: "Thank the workspace when it purchases a plan.";
|
|
811
|
+
readonly allowMultiple: false;
|
|
812
|
+
readonly editor: {
|
|
813
|
+
readonly kind: "none";
|
|
814
|
+
};
|
|
815
|
+
};
|
|
816
|
+
readonly rule: {
|
|
817
|
+
readonly default: {
|
|
818
|
+
readonly ruleKey: "default.plan_purchased";
|
|
819
|
+
readonly groupKey: "default";
|
|
820
|
+
};
|
|
821
|
+
readonly create: (_input: ProductNotificationRuleInput, base: ProductNotificationRuleBase) => {
|
|
822
|
+
repeatMode: "every_match";
|
|
823
|
+
trigger: {
|
|
824
|
+
kind: "each_match";
|
|
825
|
+
};
|
|
826
|
+
groupKey: string;
|
|
827
|
+
enabled: boolean;
|
|
828
|
+
conditionGeneration?: number | undefined;
|
|
829
|
+
ruleKey: string;
|
|
830
|
+
notificationType: string;
|
|
831
|
+
eventName: string;
|
|
832
|
+
eventFilter: ProductNotificationFacts;
|
|
833
|
+
};
|
|
834
|
+
readonly present: ({ payload }: {
|
|
835
|
+
payload: ProductNotificationFacts;
|
|
836
|
+
trigger: ProductNotificationTrigger;
|
|
837
|
+
occurredAt?: number;
|
|
838
|
+
}) => {
|
|
839
|
+
title: string;
|
|
840
|
+
summary: (string | {
|
|
841
|
+
workspace: true;
|
|
842
|
+
})[];
|
|
843
|
+
details: NotificationText[];
|
|
844
|
+
emailFormat: "thank_you";
|
|
845
|
+
targetUrl: string;
|
|
846
|
+
subjectKind: string;
|
|
847
|
+
subjectId: string;
|
|
848
|
+
};
|
|
849
|
+
};
|
|
701
850
|
}, {
|
|
702
851
|
readonly id: "play.failed";
|
|
703
852
|
readonly eventName: "play.run.completed";
|
package/dist/index.js
CHANGED
|
@@ -860,7 +860,7 @@ var SDK_RELEASE = {
|
|
|
860
860
|
// getters keep their established compatibility behavior.
|
|
861
861
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
862
862
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
863
|
-
version: "0.3.
|
|
863
|
+
version: "0.3.110",
|
|
864
864
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
865
865
|
packageCapabilities: {
|
|
866
866
|
updatePreferences: 1
|
package/dist/index.mjs
CHANGED
|
@@ -768,7 +768,7 @@ var SDK_RELEASE = {
|
|
|
768
768
|
// getters keep their established compatibility behavior.
|
|
769
769
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
770
770
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
771
|
-
version: "0.3.
|
|
771
|
+
version: "0.3.110",
|
|
772
772
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
773
773
|
packageCapabilities: {
|
|
774
774
|
updatePreferences: 1
|
package/dist/release.d.mts
CHANGED
|
@@ -149,7 +149,7 @@ type SdkRelease = {
|
|
|
149
149
|
supportPolicy: SdkSupportPolicy;
|
|
150
150
|
};
|
|
151
151
|
declare const SDK_RELEASE: {
|
|
152
|
-
readonly version: "0.3.
|
|
152
|
+
readonly version: "0.3.110";
|
|
153
153
|
readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
|
|
154
154
|
readonly packageCapabilities: {
|
|
155
155
|
readonly updatePreferences: 1;
|
package/dist/release.d.ts
CHANGED
|
@@ -149,7 +149,7 @@ type SdkRelease = {
|
|
|
149
149
|
supportPolicy: SdkSupportPolicy;
|
|
150
150
|
};
|
|
151
151
|
declare const SDK_RELEASE: {
|
|
152
|
-
readonly version: "0.3.
|
|
152
|
+
readonly version: "0.3.110";
|
|
153
153
|
readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
|
|
154
154
|
readonly packageCapabilities: {
|
|
155
155
|
readonly updatePreferences: 1;
|
package/dist/release.js
CHANGED
|
@@ -74,7 +74,7 @@ var SDK_RELEASE = {
|
|
|
74
74
|
// getters keep their established compatibility behavior.
|
|
75
75
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
76
76
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
77
|
-
version: "0.3.
|
|
77
|
+
version: "0.3.110",
|
|
78
78
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
79
79
|
packageCapabilities: {
|
|
80
80
|
updatePreferences: 1
|
package/dist/release.mjs
CHANGED
|
@@ -48,7 +48,7 @@ var SDK_RELEASE = {
|
|
|
48
48
|
// getters keep their established compatibility behavior.
|
|
49
49
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
50
50
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
51
|
-
version: "0.3.
|
|
51
|
+
version: "0.3.110",
|
|
52
52
|
updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
|
|
53
53
|
packageCapabilities: {
|
|
54
54
|
updatePreferences: 1
|