@dench.com/cli 3.8.0-staging.389 → 3.8.1-staging.390

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.
@@ -8,6 +8,8 @@ export type TimelineRun = {
8
8
  startedAt?: number;
9
9
  waitUntil?: number;
10
10
  retryAt?: number;
11
+ /** A wait for acceptance: when it stops waiting either way. */
12
+ timeoutAt?: number;
11
13
  completedAt?: number;
12
14
  emailMessageId?: string;
13
15
  lastError?: string;
@@ -49,6 +51,8 @@ export type TimelineResult = {
49
51
  type: string;
50
52
  order: number;
51
53
  waitHours?: number;
54
+ /** If / else and Wait until they accept steps: what they check. */
55
+ label?: string;
52
56
  }>;
53
57
  runs: TimelineRun[];
54
58
  emailMessages: TimelineEmail[];
@@ -63,6 +67,10 @@ export type TimelineStep = {
63
67
  outcome: string;
64
68
  at: number | null;
65
69
  detail: string[];
70
+ /** An If / else or a wait for acceptance: the path it took. */
71
+ branch?: "yes" | "no";
72
+ /** Still waiting for them to accept: when it stops waiting. */
73
+ acceptanceDeadline?: number;
66
74
  subject?: string;
67
75
  text?: string;
68
76
  error?: string;
@@ -101,16 +109,66 @@ const EMAIL_STATUS: Record<string, string> = {
101
109
  failed: "could not send",
102
110
  };
103
111
 
112
+ function resultField(result: unknown, key: string): unknown {
113
+ return result && typeof result === "object"
114
+ ? (result as Record<string, unknown>)[key]
115
+ : undefined;
116
+ }
117
+
104
118
  function skipReason(result: unknown): string {
105
- const reason =
106
- result && typeof result === "object"
107
- ? (result as { reason?: unknown }).reason
108
- : undefined;
119
+ const reason = resultField(result, "reason");
109
120
  return (typeof reason === "string" && SKIP_REASONS[reason]) || "Skipped";
110
121
  }
111
122
 
123
+ /** Why an If / else or a wait for acceptance took its No path. */
124
+ const NO_REASONS: Record<string, string> = {
125
+ missing_linkedin_identity: "no LinkedIn profile",
126
+ missing_email_identity: "no email address",
127
+ linkedin_connection_timeout: "they did not accept in time",
128
+ linkedin_invite_withdrawn: "the connection request was withdrawn",
129
+ linkedin_invite_not_sent: "no connection request went out",
130
+ };
131
+
132
+ const LOGIC_TYPES = new Set(["condition", "wait_for_connection"]);
133
+
134
+ export function branchOf(run: TimelineRun): "yes" | "no" | undefined {
135
+ if (!LOGIC_TYPES.has(run.type) || run.status !== "sent") return undefined;
136
+ const branch = resultField(run.result, "branch");
137
+ return branch === "yes" || branch === "no" ? branch : undefined;
138
+ }
139
+
140
+ /** What the editor calls each exit: Yes / No, or Accepted / Not accepted. */
141
+ export function branchExitLabel(type: string, branch: "yes" | "no"): string {
142
+ if (type === "wait_for_connection")
143
+ return branch === "yes" ? "Accepted" : "Not accepted";
144
+ return branch === "yes" ? "Yes" : "No";
145
+ }
146
+
147
+ function logicOutcome(run: TimelineRun): string | undefined {
148
+ if (!LOGIC_TYPES.has(run.type)) return undefined;
149
+ const branch = branchOf(run);
150
+ if (branch) {
151
+ const reason = resultField(run.result, "reason");
152
+ const why =
153
+ branch === "no" && typeof reason === "string" ? NO_REASONS[reason] : "";
154
+ return `${branchExitLabel(run.type, branch)}${why ? ` (${why})` : ""}`;
155
+ }
156
+ if (run.status === "sent") return "Done";
157
+ if (run.type === "wait_for_connection")
158
+ return run.status === "waiting"
159
+ ? "Waiting for them to accept"
160
+ : run.status === "queued" || run.status === "running"
161
+ ? "Checking whether they accepted"
162
+ : undefined;
163
+ return ["queued", "running", "waiting"].includes(run.status)
164
+ ? "Checking"
165
+ : undefined;
166
+ }
167
+
112
168
  function outcomeOf(run: TimelineRun): string {
113
169
  if (run.status === "skipped") return skipReason(run.result);
170
+ const logic = logicOutcome(run);
171
+ if (logic) return logic;
114
172
  if (run.status === "sent") {
115
173
  if (run.type === "wait") return "Waited";
116
174
  if (run.type === "linkedin_visit") return "Visited";
@@ -153,6 +211,11 @@ export function timelineSteps(result: TimelineResult): TimelineStep[] {
153
211
  run.status === "sent" || run.status === "skipped"
154
212
  ? undefined
155
213
  : (run.lastError ?? email?.lastError)?.trim();
214
+ const branch = branchOf(run);
215
+ const waitingForAccept =
216
+ run.type === "wait_for_connection" &&
217
+ run.status === "waiting" &&
218
+ typeof run.timeoutAt === "number";
156
219
  return {
157
220
  step: (step?.order ?? run.stepIndex) + 1,
158
221
  stepId: String(run.stepId),
@@ -167,6 +230,8 @@ export function timelineSteps(result: TimelineResult): TimelineStep[] {
167
230
  run.scheduledAt ??
168
231
  null,
169
232
  detail,
233
+ ...(branch ? { branch } : {}),
234
+ ...(waitingForAccept ? { acceptanceDeadline: run.timeoutAt } : {}),
170
235
  ...(email?.subject ? { subject: email.subject } : {}),
171
236
  ...(text ? { text } : {}),
172
237
  ...(problem ? { error: problem } : {}),
@@ -4183,7 +4183,7 @@ const rawApiOperations = [
4183
4183
  id: "campaigns.analytics",
4184
4184
  group: "campaigns",
4185
4185
  summary:
4186
- "Campaign funnel: enrolled, contacted, invited, accepted, messaged, replied, opens and clicks.",
4186
+ "Campaign funnel: enrolled, contacted, invited, accepted, messaged, replied, opens and clicks, and for a workflow how many people took each If / else and Wait for them to accept path (branchStats).",
4187
4187
  cli: "dench campaign get",
4188
4188
  method: "GET",
4189
4189
  path: "/campaigns/{sequenceId}/analytics",
@@ -4237,7 +4237,7 @@ const rawApiOperations = [
4237
4237
  id: "campaigns.steps.replace",
4238
4238
  group: "campaigns",
4239
4239
  summary:
4240
- "Replace every step of a draft campaign (email, wait, LinkedIn visit, connection request, message, post reaction, InMail). Steps are checked first: a field the step type would drop, missing text or {TOKEN} single braces are rejected, not saved. Configure sender pools on the campaign; step sender ids are legacy-only.",
4240
+ "Replace every step of a draft campaign (email, wait, LinkedIn visit, connection request, message, post reaction, InMail, If / else, Wait for them to accept). A workflow (stepModel workflow, or steps with key, next, yesNext or noNext) branches: condition and wait_for_connection steps continue on yesNext or noNext, other steps on next; each points at another step's key or end. Steps are checked first: a field the step type would drop, missing text, {TOKEN} single braces or paths that don't hold together are rejected, not saved. Configure sender pools on the campaign; step sender ids are legacy-only.",
4241
4241
  cli: "dench campaign steps set",
4242
4242
  method: "PUT",
4243
4243
  path: "/campaigns/{sequenceId}/steps",
@@ -4249,7 +4249,7 @@ const rawApiOperations = [
4249
4249
  id: "campaigns.steps.update",
4250
4250
  group: "campaigns",
4251
4251
  summary:
4252
- "Edit the text, waits and conditions of a launched (or paused) campaign's steps in place. Each step keeps its id, type and place; pass the stepId and the updatedAt you read, and only the fields you change. Messages already sent or being prepared keep their text; drafts use campaigns.steps.replace.",
4252
+ "Edit the text, waits and conditions of a launched (or paused) campaign's steps in place. Each step keeps its id, type and place; pass the stepId and the updatedAt you read, and only the fields you change. A workflow keeps its paths and what each If / else checks (key, next, yesNext, noNext, check, conditionField, conditionOperator); conditionValue and acceptance waits stay editable. Messages already sent or being prepared keep their text; drafts use campaigns.steps.replace.",
4253
4253
  cli: "dench campaign steps edit",
4254
4254
  method: "PATCH",
4255
4255
  path: "/campaigns/{sequenceId}/steps",