indiecrm-cli 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +447 -0
- package/CODE_OF_CONDUCT.md +35 -0
- package/CONTRIBUTING.md +7 -0
- package/README.md +14 -2
- package/RESEARCH.md +346 -0
- package/SECURITY.md +35 -0
- package/dist/affiliate-copy.js +49 -0
- package/dist/auth.js +453 -0
- package/dist/bigquery.js +199 -0
- package/dist/chrome-browser.js +231 -0
- package/dist/cli.js +19652 -0
- package/dist/company-identity-review.js +78 -0
- package/dist/company-leads.js +422 -0
- package/dist/company-recovery.js +84 -0
- package/dist/deel-outreach.js +469 -0
- package/dist/deel-salesnav.js +368 -0
- package/dist/direct-path.js +326 -0
- package/dist/domain.js +53 -0
- package/dist/domainfinder.js +764 -0
- package/dist/engine.js +216 -0
- package/dist/historical-queries.js +189 -0
- package/dist/hunter-emailfinder.js +252 -0
- package/dist/icp-templates.js +171 -0
- package/dist/indiecrm/commands.js +108 -0
- package/dist/indiecrm-cli.js +2 -104
- package/dist/instantly.js +136 -0
- package/dist/io.js +21 -0
- package/dist/leadlists-funnel.js +148 -0
- package/dist/linkedin-companies.js +562 -0
- package/dist/linkedin-product-details.js +1203 -0
- package/dist/linkedin-product-search.js +1081 -0
- package/dist/linkedin-products.js +786 -0
- package/dist/linkedin-session-contracts.js +3 -0
- package/dist/linkedin-session.js +846 -0
- package/dist/providers.js +1 -0
- package/dist/research-browser-preference.js +37 -0
- package/dist/sales-navigator.js +1231 -0
- package/dist/salesnav-backfill.js +710 -0
- package/dist/sample-data.js +34 -0
- package/dist/session-recovery.js +62 -0
- package/dist/vendor/salesprompter-shared/extension-session-contracts.js +29 -0
- package/dist/vendor/salesprompter-shared/linkedin-session.js +22 -0
- package/dist/vendor/salesprompter-shared/phantombuster-contracts.js +16 -0
- package/dist/vendor/salesprompter-shared/session-vault-contracts.js +17 -0
- package/package.json +73 -14
|
@@ -0,0 +1,1231 @@
|
|
|
1
|
+
export class SalesNavigatorSliceTooBroadError extends Error {
|
|
2
|
+
totalResults;
|
|
3
|
+
details;
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "SalesNavigatorSliceTooBroadError";
|
|
7
|
+
this.totalResults = options?.totalResults ?? null;
|
|
8
|
+
this.details = options?.details;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class SalesNavigatorCrawlStopError extends Error {
|
|
12
|
+
totalResults;
|
|
13
|
+
details;
|
|
14
|
+
constructor(message, options) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "SalesNavigatorCrawlStopError";
|
|
17
|
+
this.totalResults = options?.totalResults ?? null;
|
|
18
|
+
this.details = options?.details;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export const DEFAULT_SALES_NAVIGATOR_PEOPLE_SLICE_FILTERS = [
|
|
22
|
+
{
|
|
23
|
+
type: "FUNCTION",
|
|
24
|
+
values: [
|
|
25
|
+
{
|
|
26
|
+
id: "12",
|
|
27
|
+
text: "Human Resources",
|
|
28
|
+
selectionType: "INCLUDED",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: "SENIORITY_LEVEL",
|
|
34
|
+
values: [
|
|
35
|
+
{
|
|
36
|
+
id: "220",
|
|
37
|
+
text: "Director",
|
|
38
|
+
selectionType: "INCLUDED",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: "YEARS_AT_CURRENT_COMPANY",
|
|
44
|
+
values: [
|
|
45
|
+
{
|
|
46
|
+
id: "3",
|
|
47
|
+
text: "3 to 5 years",
|
|
48
|
+
selectionType: "INCLUDED",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: "YEARS_IN_CURRENT_POSITION",
|
|
54
|
+
values: [
|
|
55
|
+
{
|
|
56
|
+
id: "3",
|
|
57
|
+
text: "3 to 5 years",
|
|
58
|
+
selectionType: "INCLUDED",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: "COMPANY_HEADCOUNT",
|
|
64
|
+
values: [
|
|
65
|
+
{
|
|
66
|
+
id: "D",
|
|
67
|
+
text: "51-200",
|
|
68
|
+
selectionType: "INCLUDED",
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "COMPANY_TYPE",
|
|
74
|
+
values: [
|
|
75
|
+
{
|
|
76
|
+
id: "P",
|
|
77
|
+
text: "Privately Held",
|
|
78
|
+
selectionType: "INCLUDED",
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: "YEARS_OF_EXPERIENCE",
|
|
84
|
+
values: [
|
|
85
|
+
{
|
|
86
|
+
id: "5",
|
|
87
|
+
text: "More than 10 years",
|
|
88
|
+
selectionType: "INCLUDED",
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
const LINKEDIN_SALES_NAVIGATOR_PEOPLE_SEARCH_URL = "https://www.linkedin.com/sales/search/people";
|
|
94
|
+
const LINKEDIN_SALES_NAVIGATOR_ACCOUNT_SEARCH_URL = "https://www.linkedin.com/sales/search/company";
|
|
95
|
+
export const SALES_NAVIGATOR_PEOPLE_MAX_RESULTS = 2_500;
|
|
96
|
+
export const SALES_NAVIGATOR_CONNECTIONS_OF_MAX_RESULTS = 1_000;
|
|
97
|
+
export const SALES_NAVIGATOR_ACCOUNT_MAX_RESULTS = 1_000;
|
|
98
|
+
function decodeSalesNavigatorQueryValue(value) {
|
|
99
|
+
let decoded = value;
|
|
100
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
101
|
+
try {
|
|
102
|
+
const next = decodeURIComponent(decoded);
|
|
103
|
+
if (next === decoded)
|
|
104
|
+
break;
|
|
105
|
+
decoded = next;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return decoded;
|
|
112
|
+
}
|
|
113
|
+
function getSalesNavigatorQueryValue(sourceQueryUrl) {
|
|
114
|
+
const url = new URL(sourceQueryUrl);
|
|
115
|
+
const queryFromSearch = url.searchParams.get("query");
|
|
116
|
+
const queryFromHash = url.hash.startsWith("#query=")
|
|
117
|
+
? new URLSearchParams(url.hash.slice(1)).get("query")
|
|
118
|
+
: null;
|
|
119
|
+
return decodeSalesNavigatorQueryValue(queryFromSearch ?? queryFromHash ?? "");
|
|
120
|
+
}
|
|
121
|
+
export function isSalesNavigatorConnectionsOfSearch(sourceQueryUrl) {
|
|
122
|
+
const queryValue = getSalesNavigatorQueryValue(sourceQueryUrl);
|
|
123
|
+
return /(?:type\s*:\s*)?(?:CONNECTIONS?_OF|CONNECTION_OF|TEAMLINK_CONNECTION_OF)\b/i.test(queryValue) || /\bconnections?\s+of\b/i.test(queryValue);
|
|
124
|
+
}
|
|
125
|
+
export function getSalesNavigatorPeopleAccessibleResultLimit(sourceQueryUrl) {
|
|
126
|
+
return isSalesNavigatorConnectionsOfSearch(sourceQueryUrl)
|
|
127
|
+
? SALES_NAVIGATOR_CONNECTIONS_OF_MAX_RESULTS
|
|
128
|
+
: SALES_NAVIGATOR_PEOPLE_MAX_RESULTS;
|
|
129
|
+
}
|
|
130
|
+
export function salesNavigatorPeopleSearchHasFilter(sourceQueryUrl, filterType) {
|
|
131
|
+
const escapedFilterType = filterType.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
132
|
+
return new RegExp(`\\btype\\s*:\\s*${escapedFilterType}\\b`, "i").test(getSalesNavigatorQueryValue(sourceQueryUrl));
|
|
133
|
+
}
|
|
134
|
+
export const DEFAULT_SALES_NAVIGATOR_CRAWL_BASELINE_FILTERS = [
|
|
135
|
+
{
|
|
136
|
+
type: "FUNCTION",
|
|
137
|
+
values: [
|
|
138
|
+
{
|
|
139
|
+
id: "12",
|
|
140
|
+
text: "Human Resources",
|
|
141
|
+
selectionType: "INCLUDED",
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
const SALES_NAVIGATOR_FUNCTION_HINTS = [
|
|
147
|
+
{
|
|
148
|
+
name: "Human Resources",
|
|
149
|
+
filterValue: { id: "12", text: "Human Resources", selectionType: "INCLUDED" },
|
|
150
|
+
patterns: [/(^|[^a-z])(human resources|hr\b|people\b|recruit|talent acquisition|talent management)/i],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "Finance",
|
|
154
|
+
filterValue: { text: "Finance", selectionType: "INCLUDED" },
|
|
155
|
+
patterns: [/(^|[^a-z])(finance|accounting|accounts payable|accounts receivable|controller|payroll)/i],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "Information Technology",
|
|
159
|
+
filterValue: { text: "Information Technology", selectionType: "INCLUDED" },
|
|
160
|
+
patterns: [/(^|[^a-z])(information technology|devops|infrastructure|security|help desk|service desk)([^a-z]|$)/i],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "Engineering",
|
|
164
|
+
filterValue: { text: "Engineering", selectionType: "INCLUDED" },
|
|
165
|
+
patterns: [/(^|[^a-z])(engineering|developer|platform engineer|technical lead|cto\b)([^a-z]|$)/i],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "Sales",
|
|
169
|
+
filterValue: { text: "Sales", selectionType: "INCLUDED" },
|
|
170
|
+
patterns: [/(^|[^a-z])(sales|revenue|account executive|business development|sales enablement)/i],
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "Marketing",
|
|
174
|
+
filterValue: { text: "Marketing", selectionType: "INCLUDED" },
|
|
175
|
+
patterns: [/(^|[^a-z])(marketing|demand generation|growth|brand|campaign)/i],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "Operations",
|
|
179
|
+
filterValue: { text: "Operations", selectionType: "INCLUDED" },
|
|
180
|
+
patterns: [/(^|[^a-z])(operations|ops\b|supply chain|procurement|vendor management)/i],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "Legal",
|
|
184
|
+
filterValue: { text: "Legal", selectionType: "INCLUDED" },
|
|
185
|
+
patterns: [/(^|[^a-z])(legal|compliance|general counsel|privacy)/i],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "Support",
|
|
189
|
+
filterValue: { text: "Support", selectionType: "INCLUDED" },
|
|
190
|
+
patterns: [/(^|[^a-z])(support|customer service|customer success|help center)/i],
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
export const DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS = [
|
|
194
|
+
{
|
|
195
|
+
key: "company-headcount",
|
|
196
|
+
filterType: "COMPANY_HEADCOUNT",
|
|
197
|
+
values: [
|
|
198
|
+
{ id: "D", text: "51-200", selectionType: "INCLUDED" },
|
|
199
|
+
{ id: "C", text: "11-50", selectionType: "INCLUDED" },
|
|
200
|
+
{ id: "E", text: "201-500", selectionType: "INCLUDED" },
|
|
201
|
+
{ id: "B", text: "1-10", selectionType: "INCLUDED" },
|
|
202
|
+
{ id: "F", text: "501-1000", selectionType: "INCLUDED" },
|
|
203
|
+
{ id: "G", text: "1001-5000", selectionType: "INCLUDED" },
|
|
204
|
+
{ id: "H", text: "5001-10,000", selectionType: "INCLUDED" },
|
|
205
|
+
{ id: "I", text: "10,000+", selectionType: "INCLUDED" },
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
key: "company-type",
|
|
210
|
+
filterType: "COMPANY_TYPE",
|
|
211
|
+
values: [
|
|
212
|
+
{ id: "P", text: "Privately Held", selectionType: "INCLUDED" },
|
|
213
|
+
{ id: "C", text: "Public Company", selectionType: "INCLUDED" },
|
|
214
|
+
{ text: "Partnership", selectionType: "INCLUDED" },
|
|
215
|
+
{ text: "Self Owned", selectionType: "INCLUDED" },
|
|
216
|
+
{ text: "Non Profit", selectionType: "INCLUDED" },
|
|
217
|
+
{ text: "Educational Institution", selectionType: "INCLUDED" },
|
|
218
|
+
{ text: "Government Agency", selectionType: "INCLUDED" },
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
key: "seniority-level",
|
|
223
|
+
filterType: "SENIORITY_LEVEL",
|
|
224
|
+
values: [
|
|
225
|
+
{ id: "320", text: "Owner / Partner", selectionType: "INCLUDED" },
|
|
226
|
+
{ id: "310", text: "CXO", selectionType: "INCLUDED" },
|
|
227
|
+
{ id: "300", text: "Vice President", selectionType: "INCLUDED" },
|
|
228
|
+
{ id: "220", text: "Director", selectionType: "INCLUDED" },
|
|
229
|
+
{ id: "210", text: "Experienced Manager", selectionType: "INCLUDED" },
|
|
230
|
+
{ id: "120", text: "Senior", selectionType: "INCLUDED" },
|
|
231
|
+
{ id: "110", text: "Entry Level", selectionType: "INCLUDED" },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
key: "years-at-company",
|
|
236
|
+
filterType: "YEARS_AT_CURRENT_COMPANY",
|
|
237
|
+
values: [
|
|
238
|
+
{ id: "1", text: "Less than 1 year", selectionType: "INCLUDED" },
|
|
239
|
+
{ id: "2", text: "1 to 2 years", selectionType: "INCLUDED" },
|
|
240
|
+
{ id: "3", text: "3 to 5 years", selectionType: "INCLUDED" },
|
|
241
|
+
{ id: "4", text: "6 to 10 years", selectionType: "INCLUDED" },
|
|
242
|
+
{ id: "5", text: "More than 10 years", selectionType: "INCLUDED" },
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
key: "years-in-position",
|
|
247
|
+
filterType: "YEARS_IN_CURRENT_POSITION",
|
|
248
|
+
values: [
|
|
249
|
+
{ id: "1", text: "Less than 1 year", selectionType: "INCLUDED" },
|
|
250
|
+
{ id: "2", text: "1 to 2 years", selectionType: "INCLUDED" },
|
|
251
|
+
{ id: "3", text: "3 to 5 years", selectionType: "INCLUDED" },
|
|
252
|
+
{ id: "4", text: "6 to 10 years", selectionType: "INCLUDED" },
|
|
253
|
+
{ id: "5", text: "More than 10 years", selectionType: "INCLUDED" },
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
key: "years-of-experience",
|
|
258
|
+
filterType: "YEARS_OF_EXPERIENCE",
|
|
259
|
+
values: [
|
|
260
|
+
{ id: "1", text: "Less than 1 year", selectionType: "INCLUDED" },
|
|
261
|
+
{ id: "2", text: "1 to 2 years", selectionType: "INCLUDED" },
|
|
262
|
+
{ id: "3", text: "3 to 5 years", selectionType: "INCLUDED" },
|
|
263
|
+
{ id: "4", text: "6 to 10 years", selectionType: "INCLUDED" },
|
|
264
|
+
{ id: "5", text: "More than 10 years", selectionType: "INCLUDED" },
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
export const DEFAULT_SALES_NAVIGATOR_ACCOUNT_CRAWL_DIMENSIONS = [
|
|
269
|
+
{
|
|
270
|
+
key: "company-headcount",
|
|
271
|
+
filterType: "COMPANY_HEADCOUNT",
|
|
272
|
+
supportsResidualExclusion: true,
|
|
273
|
+
values: [
|
|
274
|
+
{ id: "B", text: "1-10", selectionType: "INCLUDED" },
|
|
275
|
+
{ id: "C", text: "11-50", selectionType: "INCLUDED" },
|
|
276
|
+
{ id: "D", text: "51-200", selectionType: "INCLUDED" },
|
|
277
|
+
{ id: "E", text: "201-500", selectionType: "INCLUDED" },
|
|
278
|
+
{ id: "F", text: "501-1,000", selectionType: "INCLUDED" },
|
|
279
|
+
{ id: "G", text: "1,001-5,000", selectionType: "INCLUDED" },
|
|
280
|
+
{ id: "H", text: "5,001-10,000", selectionType: "INCLUDED" },
|
|
281
|
+
{ id: "I", text: "10,001+", selectionType: "INCLUDED" },
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
key: "number-of-followers",
|
|
286
|
+
filterType: "NUM_OF_FOLLOWERS",
|
|
287
|
+
supportsResidualExclusion: true,
|
|
288
|
+
values: [
|
|
289
|
+
{ id: "NFR1", text: "1-50", selectionType: "INCLUDED" },
|
|
290
|
+
{ id: "NFR2", text: "51-100", selectionType: "INCLUDED" },
|
|
291
|
+
{ id: "NFR3", text: "101-1000", selectionType: "INCLUDED" },
|
|
292
|
+
{ id: "NFR4", text: "1001-5000", selectionType: "INCLUDED" },
|
|
293
|
+
{ id: "NFR5", text: "5001+", selectionType: "INCLUDED" },
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
key: "annual-revenue",
|
|
298
|
+
filterType: "ANNUAL_REVENUE",
|
|
299
|
+
supportsResidualExclusion: false,
|
|
300
|
+
combinableValues: false,
|
|
301
|
+
values: [
|
|
302
|
+
{ text: "USD 0-0.5M", rangeValue: { min: 0, max: 0.5 }, selectedSubFilter: "USD" },
|
|
303
|
+
{ text: "USD 0.5-1M", rangeValue: { min: 0.5, max: 1 }, selectedSubFilter: "USD" },
|
|
304
|
+
{ text: "USD 1-2.5M", rangeValue: { min: 1, max: 2.5 }, selectedSubFilter: "USD" },
|
|
305
|
+
{ text: "USD 2.5-5M", rangeValue: { min: 2.5, max: 5 }, selectedSubFilter: "USD" },
|
|
306
|
+
{ text: "USD 5-10M", rangeValue: { min: 5, max: 10 }, selectedSubFilter: "USD" },
|
|
307
|
+
{ text: "USD 10-20M", rangeValue: { min: 10, max: 20 }, selectedSubFilter: "USD" },
|
|
308
|
+
{ text: "USD 20-50M", rangeValue: { min: 20, max: 50 }, selectedSubFilter: "USD" },
|
|
309
|
+
{ text: "USD 50-100M", rangeValue: { min: 50, max: 100 }, selectedSubFilter: "USD" },
|
|
310
|
+
{ text: "USD 100-500M", rangeValue: { min: 100, max: 500 }, selectedSubFilter: "USD" },
|
|
311
|
+
{ text: "USD 500M-1B", rangeValue: { min: 500, max: 1000 }, selectedSubFilter: "USD" },
|
|
312
|
+
{ text: "USD 1B+", rangeValue: { min: 1000, max: 1001 }, selectedSubFilter: "USD" },
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
function ensureLinkedInSalesPeopleSearchUrl(url) {
|
|
317
|
+
if (!/(^|\.)linkedin\.com$/i.test(url.hostname)) {
|
|
318
|
+
throw new Error("Expected a linkedin.com Sales Navigator URL.");
|
|
319
|
+
}
|
|
320
|
+
if (!/^\/sales\/search\/people/i.test(url.pathname)) {
|
|
321
|
+
throw new Error("Expected a LinkedIn Sales Navigator people search URL.");
|
|
322
|
+
}
|
|
323
|
+
if (!url.searchParams.has("query")) {
|
|
324
|
+
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function ensureLinkedInSalesAccountSearchUrl(url) {
|
|
328
|
+
if (!/(^|\.)linkedin\.com$/i.test(url.hostname)) {
|
|
329
|
+
throw new Error("Expected a linkedin.com Sales Navigator URL.");
|
|
330
|
+
}
|
|
331
|
+
if (!/^\/sales\/search\/company/i.test(url.pathname)) {
|
|
332
|
+
throw new Error("Expected a LinkedIn Sales Navigator account search URL.");
|
|
333
|
+
}
|
|
334
|
+
if (!url.searchParams.has("query")) {
|
|
335
|
+
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function ensureLinkedInSalesSearchUrl(url, searchType) {
|
|
339
|
+
if (searchType === "account") {
|
|
340
|
+
ensureLinkedInSalesAccountSearchUrl(url);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
ensureLinkedInSalesPeopleSearchUrl(url);
|
|
344
|
+
}
|
|
345
|
+
function splitTopLevelCommaSeparated(value) {
|
|
346
|
+
const parts = [];
|
|
347
|
+
let depth = 0;
|
|
348
|
+
let start = 0;
|
|
349
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
350
|
+
const char = value[index];
|
|
351
|
+
if (char === "(") {
|
|
352
|
+
depth += 1;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (char === ")") {
|
|
356
|
+
depth = Math.max(0, depth - 1);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (char === "," && depth === 0) {
|
|
360
|
+
const part = value.slice(start, index).trim();
|
|
361
|
+
if (part.length > 0) {
|
|
362
|
+
parts.push(part);
|
|
363
|
+
}
|
|
364
|
+
start = index + 1;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const tail = value.slice(start).trim();
|
|
368
|
+
if (tail.length > 0) {
|
|
369
|
+
parts.push(tail);
|
|
370
|
+
}
|
|
371
|
+
return parts;
|
|
372
|
+
}
|
|
373
|
+
function findFiltersListBounds(queryValue) {
|
|
374
|
+
const marker = "filters:List(";
|
|
375
|
+
const markerIndex = queryValue.indexOf(marker);
|
|
376
|
+
if (markerIndex === -1) {
|
|
377
|
+
throw new Error("Sales Navigator query is missing filters:List(...).");
|
|
378
|
+
}
|
|
379
|
+
let depth = 1;
|
|
380
|
+
let endIndex = -1;
|
|
381
|
+
for (let index = markerIndex + marker.length; index < queryValue.length; index += 1) {
|
|
382
|
+
const char = queryValue[index];
|
|
383
|
+
if (char === "(") {
|
|
384
|
+
depth += 1;
|
|
385
|
+
}
|
|
386
|
+
else if (char === ")") {
|
|
387
|
+
depth -= 1;
|
|
388
|
+
if (depth === 0) {
|
|
389
|
+
endIndex = index;
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (endIndex === -1) {
|
|
395
|
+
throw new Error("Could not parse filters list from Sales Navigator query.");
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
before: queryValue.slice(0, markerIndex),
|
|
399
|
+
filtersContent: queryValue.slice(markerIndex + marker.length, endIndex),
|
|
400
|
+
after: queryValue.slice(endIndex + 1),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function extractFilterType(block) {
|
|
404
|
+
const match = block.match(/^\(type:([A-Z_]+)/);
|
|
405
|
+
return match?.[1] ?? null;
|
|
406
|
+
}
|
|
407
|
+
function buildFilterBlock(filter) {
|
|
408
|
+
const rangeValues = filter.values.filter((value) => value.rangeValue);
|
|
409
|
+
if (rangeValues.length > 0) {
|
|
410
|
+
if (rangeValues.length !== 1 || filter.values.length !== 1) {
|
|
411
|
+
throw new Error(`Sales Navigator range filter ${filter.type} requires exactly one range.`);
|
|
412
|
+
}
|
|
413
|
+
const value = rangeValues[0];
|
|
414
|
+
const range = value.rangeValue;
|
|
415
|
+
const selectedSubFilter = value.selectedSubFilter?.trim();
|
|
416
|
+
return (`(type:${filter.type},rangeValue:(min:${range.min},max:${range.max})` +
|
|
417
|
+
`${selectedSubFilter ? `,selectedSubFilter:${encodeURIComponent(selectedSubFilter)}` : ""})`);
|
|
418
|
+
}
|
|
419
|
+
const encodedValues = filter.values
|
|
420
|
+
.map((value) => {
|
|
421
|
+
const selectionType = value.selectionType ?? "INCLUDED";
|
|
422
|
+
const segments = [];
|
|
423
|
+
if (typeof value.id === "string" && value.id.trim().length > 0) {
|
|
424
|
+
segments.push(`id:${value.id}`);
|
|
425
|
+
}
|
|
426
|
+
// Parentheses are Rest.li delimiters but encodeURIComponent leaves them raw.
|
|
427
|
+
segments.push(`text:${encodeURIComponent(value.text).replace(/[!'()*]/g, char => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)}`);
|
|
428
|
+
segments.push(`selectionType:${selectionType}`);
|
|
429
|
+
return `(${segments.join(",")})`;
|
|
430
|
+
})
|
|
431
|
+
.join(",");
|
|
432
|
+
return `(type:${filter.type},values:List(${encodedValues}))`;
|
|
433
|
+
}
|
|
434
|
+
function dedupeSalesNavigatorFilters(filters) {
|
|
435
|
+
const seen = new Set();
|
|
436
|
+
const deduped = [];
|
|
437
|
+
for (const filter of filters) {
|
|
438
|
+
const values = filter.values.filter((value) => {
|
|
439
|
+
const key = [
|
|
440
|
+
filter.type,
|
|
441
|
+
value.selectionType ?? "INCLUDED",
|
|
442
|
+
value.text.trim().toLowerCase(),
|
|
443
|
+
value.id?.trim().toLowerCase() ?? "",
|
|
444
|
+
value.rangeValue?.min ?? "",
|
|
445
|
+
value.rangeValue?.max ?? "",
|
|
446
|
+
value.selectedSubFilter?.trim().toLowerCase() ?? "",
|
|
447
|
+
].join("|");
|
|
448
|
+
if (seen.has(key)) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
seen.add(key);
|
|
452
|
+
return true;
|
|
453
|
+
});
|
|
454
|
+
if (values.length > 0) {
|
|
455
|
+
deduped.push({
|
|
456
|
+
type: filter.type,
|
|
457
|
+
values,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return deduped;
|
|
462
|
+
}
|
|
463
|
+
export function applySalesNavigatorFiltersToQueryValue(queryValue, filters) {
|
|
464
|
+
const { before, filtersContent, after } = findFiltersListBounds(queryValue);
|
|
465
|
+
const existingBlocks = splitTopLevelCommaSeparated(filtersContent);
|
|
466
|
+
const replacements = new Map(filters.map((filter) => [filter.type, filter]));
|
|
467
|
+
const consumedTypes = new Set();
|
|
468
|
+
const nextBlocks = existingBlocks.map((block) => {
|
|
469
|
+
const type = extractFilterType(block);
|
|
470
|
+
if (!type) {
|
|
471
|
+
return block;
|
|
472
|
+
}
|
|
473
|
+
const replacement = replacements.get(type);
|
|
474
|
+
if (!replacement) {
|
|
475
|
+
return block;
|
|
476
|
+
}
|
|
477
|
+
consumedTypes.add(type);
|
|
478
|
+
return buildFilterBlock(replacement);
|
|
479
|
+
});
|
|
480
|
+
for (const filter of filters) {
|
|
481
|
+
if (!consumedTypes.has(filter.type)) {
|
|
482
|
+
nextBlocks.push(buildFilterBlock(filter));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return `${before}filters:List(${nextBlocks.join(",")})${after}`;
|
|
486
|
+
}
|
|
487
|
+
function canonicalizeSalesNavigatorQueryValue(queryValue) {
|
|
488
|
+
const { filtersContent } = findFiltersListBounds(queryValue);
|
|
489
|
+
return `(filters:List(${filtersContent}))`;
|
|
490
|
+
}
|
|
491
|
+
function canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, searchType = "people") {
|
|
492
|
+
const url = new URL(sourceQueryUrl);
|
|
493
|
+
ensureLinkedInSalesSearchUrl(url, searchType);
|
|
494
|
+
const queryValue = url.searchParams.get("query");
|
|
495
|
+
if (!queryValue) {
|
|
496
|
+
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
497
|
+
}
|
|
498
|
+
const canonicalUrl = new URL(searchType === "account"
|
|
499
|
+
? LINKEDIN_SALES_NAVIGATOR_ACCOUNT_SEARCH_URL
|
|
500
|
+
: LINKEDIN_SALES_NAVIGATOR_PEOPLE_SEARCH_URL);
|
|
501
|
+
canonicalUrl.searchParams.set("query", canonicalizeSalesNavigatorQueryValue(queryValue));
|
|
502
|
+
return canonicalUrl;
|
|
503
|
+
}
|
|
504
|
+
export function buildSalesNavigatorPeopleQuery(sourceQueryUrl, filters) {
|
|
505
|
+
const url = canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, "people");
|
|
506
|
+
const queryValue = url.searchParams.get("query");
|
|
507
|
+
if (!queryValue) {
|
|
508
|
+
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
509
|
+
}
|
|
510
|
+
const appliedFilters = dedupeSalesNavigatorFilters(filters);
|
|
511
|
+
const slicedQueryValue = applySalesNavigatorFiltersToQueryValue(queryValue, appliedFilters);
|
|
512
|
+
url.searchParams.set("query", slicedQueryValue);
|
|
513
|
+
return {
|
|
514
|
+
sourceQueryUrl: canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, "people").toString(),
|
|
515
|
+
slicedQueryUrl: url.toString(),
|
|
516
|
+
appliedFilters,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
export function buildSalesNavigatorAccountQuery(sourceQueryUrl, filters) {
|
|
520
|
+
const url = canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, "account");
|
|
521
|
+
const queryValue = url.searchParams.get("query");
|
|
522
|
+
if (!queryValue) {
|
|
523
|
+
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
524
|
+
}
|
|
525
|
+
const appliedFilters = dedupeSalesNavigatorFilters(filters);
|
|
526
|
+
const slicedQueryValue = applySalesNavigatorFiltersToQueryValue(queryValue, appliedFilters);
|
|
527
|
+
url.searchParams.set("query", slicedQueryValue);
|
|
528
|
+
return {
|
|
529
|
+
sourceQueryUrl: canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, "account").toString(),
|
|
530
|
+
slicedQueryUrl: url.toString(),
|
|
531
|
+
appliedFilters,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
export function buildSalesNavigatorPeopleSearchUrl(filters) {
|
|
535
|
+
const appliedFilters = dedupeSalesNavigatorFilters(filters);
|
|
536
|
+
if (appliedFilters.length === 0) {
|
|
537
|
+
throw new Error("Sales Navigator people search requires at least one filter.");
|
|
538
|
+
}
|
|
539
|
+
const url = new URL(LINKEDIN_SALES_NAVIGATOR_PEOPLE_SEARCH_URL);
|
|
540
|
+
url.searchParams.set("query", `(filters:List(${appliedFilters.map((filter) => buildFilterBlock(filter)).join(",")}))`);
|
|
541
|
+
return url.toString();
|
|
542
|
+
}
|
|
543
|
+
export function buildSalesNavigatorAccountSearchUrl(filters) {
|
|
544
|
+
const appliedFilters = dedupeSalesNavigatorFilters(filters);
|
|
545
|
+
if (appliedFilters.length === 0) {
|
|
546
|
+
throw new Error("Sales Navigator account search requires at least one filter.");
|
|
547
|
+
}
|
|
548
|
+
const url = new URL(LINKEDIN_SALES_NAVIGATOR_ACCOUNT_SEARCH_URL);
|
|
549
|
+
url.searchParams.set("query", `(filters:List(${appliedFilters.map((filter) => buildFilterBlock(filter)).join(",")}))`);
|
|
550
|
+
return url.toString();
|
|
551
|
+
}
|
|
552
|
+
function inferSalesNavigatorFunctionFilters(options) {
|
|
553
|
+
const titleHaystack = options.title.trim();
|
|
554
|
+
const categoryHaystack = [options.categoryName, options.categorySlug]
|
|
555
|
+
.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
556
|
+
.join(" ");
|
|
557
|
+
for (const hint of SALES_NAVIGATOR_FUNCTION_HINTS) {
|
|
558
|
+
if (titleHaystack.length > 0 && hint.patterns.some((pattern) => pattern.test(titleHaystack))) {
|
|
559
|
+
return [
|
|
560
|
+
{
|
|
561
|
+
type: "FUNCTION",
|
|
562
|
+
values: [hint.filterValue]
|
|
563
|
+
}
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
for (const hint of SALES_NAVIGATOR_FUNCTION_HINTS) {
|
|
568
|
+
if (categoryHaystack.length > 0 && hint.patterns.some((pattern) => pattern.test(categoryHaystack))) {
|
|
569
|
+
return [
|
|
570
|
+
{
|
|
571
|
+
type: "FUNCTION",
|
|
572
|
+
values: [hint.filterValue]
|
|
573
|
+
}
|
|
574
|
+
];
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
579
|
+
function normalizeSalesNavigatorTitle(title) {
|
|
580
|
+
return title
|
|
581
|
+
.replace(/[“”"']+/g, "")
|
|
582
|
+
.replace(/\s*[|/]+\s*/g, " / ")
|
|
583
|
+
.replace(/\s*&\s*/g, " & ")
|
|
584
|
+
.replace(/[–—]+/g, "-")
|
|
585
|
+
.replace(/\s+/g, " ")
|
|
586
|
+
.replace(/^[\s\-:;,]+|[\s\-:;,]+$/g, "")
|
|
587
|
+
.trim();
|
|
588
|
+
}
|
|
589
|
+
function canonicalizeSalesNavigatorTitleKey(title) {
|
|
590
|
+
return normalizeSalesNavigatorTitle(title)
|
|
591
|
+
.toLowerCase()
|
|
592
|
+
.replace(/&/g, " and ")
|
|
593
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
594
|
+
.replace(/\s+/g, " ")
|
|
595
|
+
.trim();
|
|
596
|
+
}
|
|
597
|
+
export function deriveSalesNavigatorTitleQuerySeeds(options) {
|
|
598
|
+
const rankedTitles = new Map();
|
|
599
|
+
let firstSeenIndex = 0;
|
|
600
|
+
for (const item of options.items) {
|
|
601
|
+
const sourceProduct = options.sourceProductUrl === item.productUrl;
|
|
602
|
+
for (const intendedRole of item.intendedRoles) {
|
|
603
|
+
const title = normalizeSalesNavigatorTitle(intendedRole);
|
|
604
|
+
if (title.length === 0) {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const normalizedTitle = canonicalizeSalesNavigatorTitleKey(title);
|
|
608
|
+
const existing = rankedTitles.get(normalizedTitle);
|
|
609
|
+
if (existing) {
|
|
610
|
+
existing.matchedProductCount += 1;
|
|
611
|
+
existing.sourceProduct = existing.sourceProduct || sourceProduct;
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
rankedTitles.set(normalizedTitle, {
|
|
615
|
+
title,
|
|
616
|
+
matchedProductCount: 1,
|
|
617
|
+
sourceProduct,
|
|
618
|
+
firstSeenIndex,
|
|
619
|
+
categoryName: item.category?.name,
|
|
620
|
+
categorySlug: item.category?.slug
|
|
621
|
+
});
|
|
622
|
+
firstSeenIndex += 1;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const orderedTitles = [...rankedTitles.values()].sort((left, right) => {
|
|
626
|
+
if (left.sourceProduct !== right.sourceProduct) {
|
|
627
|
+
return left.sourceProduct ? -1 : 1;
|
|
628
|
+
}
|
|
629
|
+
if (left.sourceProduct && right.sourceProduct && left.firstSeenIndex !== right.firstSeenIndex) {
|
|
630
|
+
return left.firstSeenIndex - right.firstSeenIndex;
|
|
631
|
+
}
|
|
632
|
+
if (left.matchedProductCount !== right.matchedProductCount) {
|
|
633
|
+
return right.matchedProductCount - left.matchedProductCount;
|
|
634
|
+
}
|
|
635
|
+
if (left.firstSeenIndex !== right.firstSeenIndex) {
|
|
636
|
+
return left.firstSeenIndex - right.firstSeenIndex;
|
|
637
|
+
}
|
|
638
|
+
return left.title.localeCompare(right.title);
|
|
639
|
+
});
|
|
640
|
+
const limitedTitles = options.titleLimit && options.titleLimit > 0
|
|
641
|
+
? orderedTitles.slice(0, options.titleLimit)
|
|
642
|
+
: orderedTitles;
|
|
643
|
+
return limitedTitles.map((entry) => {
|
|
644
|
+
const appliedFilters = dedupeSalesNavigatorFilters([
|
|
645
|
+
{
|
|
646
|
+
type: "CURRENT_TITLE",
|
|
647
|
+
values: [{ text: entry.title, selectionType: "INCLUDED" }]
|
|
648
|
+
},
|
|
649
|
+
...inferSalesNavigatorFunctionFilters({
|
|
650
|
+
title: entry.title,
|
|
651
|
+
categoryName: entry.categoryName,
|
|
652
|
+
categorySlug: entry.categorySlug
|
|
653
|
+
})
|
|
654
|
+
]);
|
|
655
|
+
return {
|
|
656
|
+
title: entry.title,
|
|
657
|
+
queryUrl: buildSalesNavigatorPeopleSearchUrl(appliedFilters),
|
|
658
|
+
appliedFilters,
|
|
659
|
+
matchedProductCount: entry.matchedProductCount,
|
|
660
|
+
sourceProduct: entry.sourceProduct
|
|
661
|
+
};
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
export function buildSalesNavigatorPeopleSlice(sourceQueryUrl, filters = DEFAULT_SALES_NAVIGATOR_PEOPLE_SLICE_FILTERS) {
|
|
665
|
+
return buildSalesNavigatorPeopleQuery(sourceQueryUrl, filters);
|
|
666
|
+
}
|
|
667
|
+
export function buildSalesNavigatorAccountSlice(sourceQueryUrl, filters = []) {
|
|
668
|
+
return buildSalesNavigatorAccountQuery(sourceQueryUrl, filters);
|
|
669
|
+
}
|
|
670
|
+
export function createSalesNavigatorCrawlSeed(options) {
|
|
671
|
+
const sliced = options.searchType === "account"
|
|
672
|
+
? buildSalesNavigatorAccountQuery(options.sourceQueryUrl, options.baselineFilters ?? [])
|
|
673
|
+
: buildSalesNavigatorPeopleQuery(options.sourceQueryUrl, options.baselineFilters ?? []);
|
|
674
|
+
return {
|
|
675
|
+
...sliced,
|
|
676
|
+
depth: 0,
|
|
677
|
+
retryCount: 0,
|
|
678
|
+
maxResultsPerSearch: options.maxResultsPerSearch,
|
|
679
|
+
numberOfProfiles: options.numberOfProfiles,
|
|
680
|
+
slicePreset: options.slicePreset,
|
|
681
|
+
splitTrail: [],
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
export function expandSalesNavigatorCrawlAttempt(attempt, dimension, searchType = "people") {
|
|
685
|
+
return dimension.values.map((value) => {
|
|
686
|
+
const filters = dedupeSalesNavigatorFilters([
|
|
687
|
+
...attempt.appliedFilters.filter((filter) => filter.type !== dimension.filterType),
|
|
688
|
+
{
|
|
689
|
+
type: dimension.filterType,
|
|
690
|
+
values: [
|
|
691
|
+
{
|
|
692
|
+
...value,
|
|
693
|
+
selectionType: value.selectionType ?? "INCLUDED",
|
|
694
|
+
},
|
|
695
|
+
],
|
|
696
|
+
},
|
|
697
|
+
]);
|
|
698
|
+
const sliced = searchType === "account"
|
|
699
|
+
? buildSalesNavigatorAccountQuery(attempt.sourceQueryUrl, filters)
|
|
700
|
+
: buildSalesNavigatorPeopleQuery(attempt.sourceQueryUrl, filters);
|
|
701
|
+
return {
|
|
702
|
+
...sliced,
|
|
703
|
+
depth: attempt.depth + 1,
|
|
704
|
+
retryCount: 0,
|
|
705
|
+
maxResultsPerSearch: attempt.maxResultsPerSearch,
|
|
706
|
+
numberOfProfiles: attempt.numberOfProfiles,
|
|
707
|
+
slicePreset: attempt.slicePreset,
|
|
708
|
+
splitTrail: [
|
|
709
|
+
...attempt.splitTrail,
|
|
710
|
+
{
|
|
711
|
+
key: dimension.key,
|
|
712
|
+
filterType: dimension.filterType,
|
|
713
|
+
value: {
|
|
714
|
+
...value,
|
|
715
|
+
selectionType: value.selectionType ?? "INCLUDED",
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
};
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
export function buildSalesNavigatorResidualCrawlAttempt(attempt, dimension, searchType = "people") {
|
|
723
|
+
const residualValues = dimension.values.map((value) => ({
|
|
724
|
+
...value,
|
|
725
|
+
selectionType: "EXCLUDED",
|
|
726
|
+
}));
|
|
727
|
+
const filters = dedupeSalesNavigatorFilters([
|
|
728
|
+
...attempt.appliedFilters.filter((filter) => filter.type !== dimension.filterType),
|
|
729
|
+
{
|
|
730
|
+
type: dimension.filterType,
|
|
731
|
+
values: residualValues,
|
|
732
|
+
},
|
|
733
|
+
]);
|
|
734
|
+
const sliced = searchType === "account"
|
|
735
|
+
? buildSalesNavigatorAccountQuery(attempt.sourceQueryUrl, filters)
|
|
736
|
+
: buildSalesNavigatorPeopleQuery(attempt.sourceQueryUrl, filters);
|
|
737
|
+
return {
|
|
738
|
+
...sliced,
|
|
739
|
+
depth: attempt.depth + 1,
|
|
740
|
+
retryCount: 0,
|
|
741
|
+
maxResultsPerSearch: attempt.maxResultsPerSearch,
|
|
742
|
+
numberOfProfiles: attempt.numberOfProfiles,
|
|
743
|
+
slicePreset: attempt.slicePreset,
|
|
744
|
+
splitTrail: [
|
|
745
|
+
...attempt.splitTrail,
|
|
746
|
+
{
|
|
747
|
+
key: `${dimension.key}-residual`,
|
|
748
|
+
filterType: dimension.filterType,
|
|
749
|
+
value: {
|
|
750
|
+
text: `Not in ${dimension.key}`,
|
|
751
|
+
selectionType: "EXCLUDED",
|
|
752
|
+
},
|
|
753
|
+
},
|
|
754
|
+
],
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
export function buildSalesNavigatorCrawlPreview(options) {
|
|
758
|
+
const root = createSalesNavigatorCrawlSeed(options);
|
|
759
|
+
const dimensions = options.dimensions ?? DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS;
|
|
760
|
+
const firstSplit = dimensions[0]
|
|
761
|
+
? expandSalesNavigatorCrawlAttempt(root, dimensions[0], options.searchType)
|
|
762
|
+
: [];
|
|
763
|
+
return { root, firstSplit, dimensions };
|
|
764
|
+
}
|
|
765
|
+
function salesNavigatorDimensionKeyFromTrailKey(key) {
|
|
766
|
+
return key.endsWith("-residual") ? key.slice(0, -"-residual".length) : key;
|
|
767
|
+
}
|
|
768
|
+
function getUnusedSalesNavigatorDimensions(attempt, dimensions) {
|
|
769
|
+
const usedKeys = new Set(attempt.splitTrail.map((entry) => salesNavigatorDimensionKeyFromTrailKey(entry.key)));
|
|
770
|
+
return dimensions.filter((dimension) => !usedKeys.has(dimension.key));
|
|
771
|
+
}
|
|
772
|
+
function buildSalesNavigatorAdaptiveChildren(attempt, dimension, options) {
|
|
773
|
+
const expandedChildren = expandSalesNavigatorCrawlAttempt(attempt, dimension, options.searchType);
|
|
774
|
+
const residualChildren = options.includeResidualExclusionSlices
|
|
775
|
+
&& dimension.supportsResidualExclusion !== false
|
|
776
|
+
? [
|
|
777
|
+
buildSalesNavigatorResidualCrawlAttempt(attempt, dimension, options.searchType),
|
|
778
|
+
]
|
|
779
|
+
: [];
|
|
780
|
+
return [...expandedChildren, ...residualChildren];
|
|
781
|
+
}
|
|
782
|
+
function getSalesNavigatorAttemptDimensionValues(attempt, dimension) {
|
|
783
|
+
const trailEntry = attempt.splitTrail.at(-1);
|
|
784
|
+
if (trailEntry?.key !== dimension.key) {
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
const filter = attempt.appliedFilters.find((entry) => entry.type === dimension.filterType);
|
|
788
|
+
if (!filter || filter.values.length !== 1) {
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
if (filter.values.some((value) => (value.selectionType ?? "INCLUDED") !== "INCLUDED")) {
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
return filter.values;
|
|
795
|
+
}
|
|
796
|
+
function buildSalesNavigatorCombinedCrawlAttempt(attempt, dimension, children, searchType = "people") {
|
|
797
|
+
const values = children.flatMap((child) => getSalesNavigatorAttemptDimensionValues(child.attempt, dimension) ?? []);
|
|
798
|
+
const filters = dedupeSalesNavigatorFilters([
|
|
799
|
+
...attempt.appliedFilters.filter((filter) => filter.type !== dimension.filterType),
|
|
800
|
+
{
|
|
801
|
+
type: dimension.filterType,
|
|
802
|
+
values: values.map((value) => ({
|
|
803
|
+
...value,
|
|
804
|
+
selectionType: value.selectionType ?? "INCLUDED",
|
|
805
|
+
})),
|
|
806
|
+
},
|
|
807
|
+
]);
|
|
808
|
+
const sliced = searchType === "account"
|
|
809
|
+
? buildSalesNavigatorAccountQuery(attempt.sourceQueryUrl, filters)
|
|
810
|
+
: buildSalesNavigatorPeopleQuery(attempt.sourceQueryUrl, filters);
|
|
811
|
+
const valueText = values.map((value) => value.text).join(" + ");
|
|
812
|
+
return {
|
|
813
|
+
...sliced,
|
|
814
|
+
depth: attempt.depth + 1,
|
|
815
|
+
retryCount: 0,
|
|
816
|
+
maxResultsPerSearch: attempt.maxResultsPerSearch,
|
|
817
|
+
numberOfProfiles: attempt.numberOfProfiles,
|
|
818
|
+
slicePreset: attempt.slicePreset,
|
|
819
|
+
splitTrail: [
|
|
820
|
+
...attempt.splitTrail,
|
|
821
|
+
{
|
|
822
|
+
key: dimension.key,
|
|
823
|
+
filterType: dimension.filterType,
|
|
824
|
+
value: {
|
|
825
|
+
text: valueText,
|
|
826
|
+
selectionType: "INCLUDED",
|
|
827
|
+
},
|
|
828
|
+
},
|
|
829
|
+
],
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
async function coalesceSalesNavigatorUnderCapChildren(options) {
|
|
833
|
+
if (options.dimension.combinableValues === false) {
|
|
834
|
+
return options.summaries;
|
|
835
|
+
}
|
|
836
|
+
const packable = [];
|
|
837
|
+
const fixed = [];
|
|
838
|
+
for (const summary of options.summaries) {
|
|
839
|
+
const values = getSalesNavigatorAttemptDimensionValues(summary.attempt, options.dimension);
|
|
840
|
+
if (!values ||
|
|
841
|
+
summary.totalResults == null ||
|
|
842
|
+
summary.totalResults > summary.attempt.maxResultsPerSearch) {
|
|
843
|
+
fixed.push(summary);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
packable.push(summary);
|
|
847
|
+
}
|
|
848
|
+
if (packable.length <= 1) {
|
|
849
|
+
return options.summaries;
|
|
850
|
+
}
|
|
851
|
+
const bins = [];
|
|
852
|
+
const ordered = [...packable].sort((left, right) => (right.totalResults ?? 0) - (left.totalResults ?? 0));
|
|
853
|
+
for (const item of ordered) {
|
|
854
|
+
let placed = false;
|
|
855
|
+
for (const bin of bins) {
|
|
856
|
+
const combinedAttempt = buildSalesNavigatorCombinedCrawlAttempt(options.parentAttempt, options.dimension, [...bin.children, item], options.searchType);
|
|
857
|
+
const combinedSummary = await probeSalesNavigatorAdaptiveAttempt(combinedAttempt, options.probeSlice, options.probeCache);
|
|
858
|
+
if (combinedSummary.totalResults != null &&
|
|
859
|
+
combinedSummary.totalResults <= combinedAttempt.maxResultsPerSearch) {
|
|
860
|
+
bin.children.push(item);
|
|
861
|
+
bin.summary = {
|
|
862
|
+
attempt: combinedAttempt,
|
|
863
|
+
totalResults: combinedSummary.totalResults,
|
|
864
|
+
};
|
|
865
|
+
placed = true;
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (!placed) {
|
|
870
|
+
bins.push({
|
|
871
|
+
children: [item],
|
|
872
|
+
summary: item,
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return [
|
|
877
|
+
...bins.map((bin) => bin.summary),
|
|
878
|
+
...fixed,
|
|
879
|
+
];
|
|
880
|
+
}
|
|
881
|
+
function summarizeSalesNavigatorAdaptiveSplitCandidate(summaries, maxResultsPerSearch, estimate) {
|
|
882
|
+
const knownTotals = summaries
|
|
883
|
+
.map((summary) => summary.totalResults)
|
|
884
|
+
.filter((total) => typeof total === "number");
|
|
885
|
+
const knownChildren = knownTotals.length;
|
|
886
|
+
const oversizedChildren = knownTotals.filter((total) => total > maxResultsPerSearch).length;
|
|
887
|
+
const largestChildTotal = knownTotals.length > 0 ? Math.max(...knownTotals) : null;
|
|
888
|
+
const underCapTotals = knownTotals.filter((total) => total <= maxResultsPerSearch);
|
|
889
|
+
const utilization = underCapTotals.length === 0
|
|
890
|
+
? 0
|
|
891
|
+
: underCapTotals.reduce((sum, total) => sum + total / maxResultsPerSearch, 0) / underCapTotals.length;
|
|
892
|
+
const largestPenalty = largestChildTotal == null
|
|
893
|
+
? maxResultsPerSearch * 2
|
|
894
|
+
: Math.max(0, largestChildTotal - maxResultsPerSearch) /
|
|
895
|
+
maxResultsPerSearch;
|
|
896
|
+
return {
|
|
897
|
+
knownChildren,
|
|
898
|
+
oversizedChildren,
|
|
899
|
+
largestChildTotal,
|
|
900
|
+
estimatedFinalBatches: estimate.estimatedFinalBatches,
|
|
901
|
+
unresolvedChildren: estimate.unresolvedChildren,
|
|
902
|
+
score: estimate.unresolvedChildren * 1_000_000 +
|
|
903
|
+
estimate.estimatedFinalBatches * 1_000 +
|
|
904
|
+
oversizedChildren * 100 +
|
|
905
|
+
largestPenalty * 10 -
|
|
906
|
+
utilization,
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
async function estimateSalesNavigatorMinimumFinalBatches(options) {
|
|
910
|
+
const remainingDimensions = getUnusedSalesNavigatorDimensions(options.attempt, options.dimensions);
|
|
911
|
+
if (remainingDimensions.length === 0 ||
|
|
912
|
+
options.attempt.depth >= options.maxSplitDepth) {
|
|
913
|
+
return {
|
|
914
|
+
estimatedFinalBatches: 1,
|
|
915
|
+
unresolvedChildren: 1,
|
|
916
|
+
largestChildTotal: null,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
const candidateEstimates = [];
|
|
920
|
+
for (const dimension of remainingDimensions) {
|
|
921
|
+
const rawChildren = buildSalesNavigatorAdaptiveChildren(options.attempt, dimension, options);
|
|
922
|
+
const rawSummaries = [];
|
|
923
|
+
for (const child of rawChildren) {
|
|
924
|
+
rawSummaries.push({
|
|
925
|
+
attempt: child,
|
|
926
|
+
...(await probeSalesNavigatorAdaptiveAttempt(child, options.probeSlice, options.probeCache)),
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
const summaries = await coalesceSalesNavigatorUnderCapChildren({
|
|
930
|
+
parentAttempt: options.attempt,
|
|
931
|
+
dimension,
|
|
932
|
+
summaries: rawSummaries,
|
|
933
|
+
searchType: options.searchType,
|
|
934
|
+
probeSlice: options.probeSlice,
|
|
935
|
+
probeCache: options.probeCache,
|
|
936
|
+
});
|
|
937
|
+
let estimatedFinalBatches = 0;
|
|
938
|
+
let unresolvedChildren = 0;
|
|
939
|
+
let largestChildTotal = null;
|
|
940
|
+
for (const summary of summaries) {
|
|
941
|
+
if (summary.totalResults != null) {
|
|
942
|
+
largestChildTotal =
|
|
943
|
+
largestChildTotal == null
|
|
944
|
+
? summary.totalResults
|
|
945
|
+
: Math.max(largestChildTotal, summary.totalResults);
|
|
946
|
+
}
|
|
947
|
+
if (summary.totalResults != null &&
|
|
948
|
+
summary.totalResults <= summary.attempt.maxResultsPerSearch) {
|
|
949
|
+
estimatedFinalBatches += 1;
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
const childEstimate = await estimateSalesNavigatorMinimumFinalBatches({
|
|
953
|
+
...options,
|
|
954
|
+
attempt: summary.attempt,
|
|
955
|
+
});
|
|
956
|
+
estimatedFinalBatches += childEstimate.estimatedFinalBatches;
|
|
957
|
+
unresolvedChildren += childEstimate.unresolvedChildren;
|
|
958
|
+
if (childEstimate.largestChildTotal != null) {
|
|
959
|
+
largestChildTotal =
|
|
960
|
+
largestChildTotal == null
|
|
961
|
+
? childEstimate.largestChildTotal
|
|
962
|
+
: Math.max(largestChildTotal, childEstimate.largestChildTotal);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
candidateEstimates.push({
|
|
966
|
+
estimatedFinalBatches,
|
|
967
|
+
unresolvedChildren,
|
|
968
|
+
largestChildTotal,
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
candidateEstimates.sort((left, right) => {
|
|
972
|
+
if (left.unresolvedChildren !== right.unresolvedChildren) {
|
|
973
|
+
return left.unresolvedChildren - right.unresolvedChildren;
|
|
974
|
+
}
|
|
975
|
+
if (left.estimatedFinalBatches !== right.estimatedFinalBatches) {
|
|
976
|
+
return left.estimatedFinalBatches - right.estimatedFinalBatches;
|
|
977
|
+
}
|
|
978
|
+
return dimensionsSortValue(right.largestChildTotal) -
|
|
979
|
+
dimensionsSortValue(left.largestChildTotal);
|
|
980
|
+
});
|
|
981
|
+
return candidateEstimates[0] ?? {
|
|
982
|
+
estimatedFinalBatches: 1,
|
|
983
|
+
unresolvedChildren: 1,
|
|
984
|
+
largestChildTotal: null,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
async function probeSalesNavigatorAdaptiveAttempt(attempt, probeSlice, probeCache) {
|
|
988
|
+
let cached = probeCache.get(attempt.slicedQueryUrl);
|
|
989
|
+
if (!cached) {
|
|
990
|
+
cached = probeSlice(attempt);
|
|
991
|
+
probeCache.set(attempt.slicedQueryUrl, cached);
|
|
992
|
+
}
|
|
993
|
+
return await cached;
|
|
994
|
+
}
|
|
995
|
+
async function chooseSalesNavigatorAdaptiveSplitCandidate(options) {
|
|
996
|
+
const candidates = [];
|
|
997
|
+
const probeCache = new Map();
|
|
998
|
+
for (const dimension of options.dimensions) {
|
|
999
|
+
const children = buildSalesNavigatorAdaptiveChildren(options.attempt, dimension, options).filter((child) => !options.seenQueryUrls.has(child.slicedQueryUrl));
|
|
1000
|
+
if (children.length === 0) {
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
const summaries = [];
|
|
1004
|
+
for (const child of children) {
|
|
1005
|
+
summaries.push({
|
|
1006
|
+
attempt: child,
|
|
1007
|
+
...(await probeSalesNavigatorAdaptiveAttempt(child, options.probeSlice, probeCache)),
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
const coalescedSummaries = await coalesceSalesNavigatorUnderCapChildren({
|
|
1011
|
+
parentAttempt: options.attempt,
|
|
1012
|
+
dimension,
|
|
1013
|
+
summaries,
|
|
1014
|
+
searchType: options.searchType,
|
|
1015
|
+
probeSlice: options.probeSlice,
|
|
1016
|
+
probeCache,
|
|
1017
|
+
});
|
|
1018
|
+
const coalescedChildren = coalescedSummaries.map((summary) => summary.attempt);
|
|
1019
|
+
let estimatedFinalBatches = 0;
|
|
1020
|
+
let unresolvedChildren = 0;
|
|
1021
|
+
let largestChildTotal = null;
|
|
1022
|
+
for (const summary of coalescedSummaries) {
|
|
1023
|
+
if (summary.totalResults != null) {
|
|
1024
|
+
largestChildTotal =
|
|
1025
|
+
largestChildTotal == null
|
|
1026
|
+
? summary.totalResults
|
|
1027
|
+
: Math.max(largestChildTotal, summary.totalResults);
|
|
1028
|
+
}
|
|
1029
|
+
if (summary.totalResults != null &&
|
|
1030
|
+
summary.totalResults <= summary.attempt.maxResultsPerSearch) {
|
|
1031
|
+
estimatedFinalBatches += 1;
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
const childEstimate = await estimateSalesNavigatorMinimumFinalBatches({
|
|
1035
|
+
attempt: summary.attempt,
|
|
1036
|
+
dimensions: options.dimensions,
|
|
1037
|
+
maxSplitDepth: options.maxSplitDepth,
|
|
1038
|
+
searchType: options.searchType,
|
|
1039
|
+
includeResidualExclusionSlices: options.includeResidualExclusionSlices,
|
|
1040
|
+
probeSlice: options.probeSlice,
|
|
1041
|
+
probeCache,
|
|
1042
|
+
});
|
|
1043
|
+
estimatedFinalBatches += childEstimate.estimatedFinalBatches;
|
|
1044
|
+
unresolvedChildren += childEstimate.unresolvedChildren;
|
|
1045
|
+
if (childEstimate.largestChildTotal != null) {
|
|
1046
|
+
largestChildTotal =
|
|
1047
|
+
largestChildTotal == null
|
|
1048
|
+
? childEstimate.largestChildTotal
|
|
1049
|
+
: Math.max(largestChildTotal, childEstimate.largestChildTotal);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const score = summarizeSalesNavigatorAdaptiveSplitCandidate(coalescedSummaries, options.attempt.maxResultsPerSearch, {
|
|
1053
|
+
estimatedFinalBatches,
|
|
1054
|
+
unresolvedChildren,
|
|
1055
|
+
});
|
|
1056
|
+
const largestTotal = largestChildTotal == null
|
|
1057
|
+
? score.largestChildTotal
|
|
1058
|
+
: score.largestChildTotal == null
|
|
1059
|
+
? largestChildTotal
|
|
1060
|
+
: Math.max(largestChildTotal, score.largestChildTotal);
|
|
1061
|
+
candidates.push({
|
|
1062
|
+
dimension,
|
|
1063
|
+
children: coalescedChildren,
|
|
1064
|
+
summaries: coalescedSummaries,
|
|
1065
|
+
...score,
|
|
1066
|
+
largestChildTotal: largestTotal,
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
candidates.sort((left, right) => {
|
|
1070
|
+
if (left.score !== right.score) {
|
|
1071
|
+
return left.score - right.score;
|
|
1072
|
+
}
|
|
1073
|
+
if (left.oversizedChildren !== right.oversizedChildren) {
|
|
1074
|
+
return left.oversizedChildren - right.oversizedChildren;
|
|
1075
|
+
}
|
|
1076
|
+
return dimensionsSortValue(right.largestChildTotal) -
|
|
1077
|
+
dimensionsSortValue(left.largestChildTotal);
|
|
1078
|
+
});
|
|
1079
|
+
return candidates[0] ?? null;
|
|
1080
|
+
}
|
|
1081
|
+
function dimensionsSortValue(value) {
|
|
1082
|
+
return value ?? -1;
|
|
1083
|
+
}
|
|
1084
|
+
export async function executeSalesNavigatorAdaptiveCrawl(options) {
|
|
1085
|
+
const dimensions = options.dimensions ?? DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS;
|
|
1086
|
+
const maxSplitDepth = Math.min(options.maxSplitDepth ?? dimensions.length, dimensions.length);
|
|
1087
|
+
const maxSlices = options.maxSlices ?? 1000;
|
|
1088
|
+
const maxRetries = options.maxRetries ?? 1;
|
|
1089
|
+
const exported = [];
|
|
1090
|
+
const splitEvents = [];
|
|
1091
|
+
const failed = [];
|
|
1092
|
+
const unresolved = [];
|
|
1093
|
+
const queue = [
|
|
1094
|
+
createSalesNavigatorCrawlSeed({
|
|
1095
|
+
sourceQueryUrl: options.sourceQueryUrl,
|
|
1096
|
+
baselineFilters: options.baselineFilters,
|
|
1097
|
+
maxResultsPerSearch: options.maxResultsPerSearch,
|
|
1098
|
+
numberOfProfiles: options.numberOfProfiles,
|
|
1099
|
+
slicePreset: options.slicePreset,
|
|
1100
|
+
searchType: options.searchType,
|
|
1101
|
+
}),
|
|
1102
|
+
];
|
|
1103
|
+
const seenQueryUrls = new Set(queue.map((attempt) => attempt.slicedQueryUrl));
|
|
1104
|
+
let attempted = 0;
|
|
1105
|
+
let truncated = false;
|
|
1106
|
+
while (queue.length > 0) {
|
|
1107
|
+
if (attempted >= maxSlices) {
|
|
1108
|
+
truncated = true;
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
1111
|
+
const attempt = queue.shift();
|
|
1112
|
+
if (!attempt) {
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
attempted += 1;
|
|
1116
|
+
try {
|
|
1117
|
+
const result = await options.exportSlice(attempt);
|
|
1118
|
+
exported.push({ attempt, result });
|
|
1119
|
+
}
|
|
1120
|
+
catch (error) {
|
|
1121
|
+
if (error instanceof SalesNavigatorCrawlStopError) {
|
|
1122
|
+
failed.push({
|
|
1123
|
+
attempt,
|
|
1124
|
+
error: error.message,
|
|
1125
|
+
totalResults: error.totalResults,
|
|
1126
|
+
});
|
|
1127
|
+
truncated = true;
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
if (error instanceof SalesNavigatorSliceTooBroadError) {
|
|
1131
|
+
const remainingDimensions = getUnusedSalesNavigatorDimensions(attempt, dimensions);
|
|
1132
|
+
if (remainingDimensions.length === 0 || attempt.depth >= maxSplitDepth) {
|
|
1133
|
+
unresolved.push({
|
|
1134
|
+
attempt,
|
|
1135
|
+
error: error.message,
|
|
1136
|
+
totalResults: error.totalResults,
|
|
1137
|
+
});
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
let nextDimension = remainingDimensions[0];
|
|
1141
|
+
let children = [];
|
|
1142
|
+
let probeSummary;
|
|
1143
|
+
let strategy = "fixed-order";
|
|
1144
|
+
if (options.adaptiveSplitProbing && options.probeSlice) {
|
|
1145
|
+
const candidate = await chooseSalesNavigatorAdaptiveSplitCandidate({
|
|
1146
|
+
attempt,
|
|
1147
|
+
dimensions: remainingDimensions,
|
|
1148
|
+
searchType: options.searchType,
|
|
1149
|
+
includeResidualExclusionSlices: options.includeResidualExclusionSlices,
|
|
1150
|
+
seenQueryUrls,
|
|
1151
|
+
maxSplitDepth,
|
|
1152
|
+
probeSlice: options.probeSlice,
|
|
1153
|
+
});
|
|
1154
|
+
if (candidate) {
|
|
1155
|
+
nextDimension = candidate.dimension;
|
|
1156
|
+
children = candidate.children;
|
|
1157
|
+
strategy = "adaptive-probe";
|
|
1158
|
+
probeSummary = [
|
|
1159
|
+
{
|
|
1160
|
+
dimension: candidate.dimension.key,
|
|
1161
|
+
childCount: candidate.children.length,
|
|
1162
|
+
knownChildren: candidate.knownChildren,
|
|
1163
|
+
oversizedChildren: candidate.oversizedChildren,
|
|
1164
|
+
largestChildTotal: candidate.largestChildTotal,
|
|
1165
|
+
estimatedFinalBatches: candidate.estimatedFinalBatches,
|
|
1166
|
+
unresolvedChildren: candidate.unresolvedChildren,
|
|
1167
|
+
score: candidate.score,
|
|
1168
|
+
},
|
|
1169
|
+
];
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (children.length === 0) {
|
|
1173
|
+
const expandedChildren = expandSalesNavigatorCrawlAttempt(attempt, nextDimension, options.searchType);
|
|
1174
|
+
const residualChildren = options.includeResidualExclusionSlices
|
|
1175
|
+
&& nextDimension.supportsResidualExclusion !== false
|
|
1176
|
+
? [
|
|
1177
|
+
buildSalesNavigatorResidualCrawlAttempt(attempt, nextDimension, options.searchType),
|
|
1178
|
+
]
|
|
1179
|
+
: [];
|
|
1180
|
+
children = [...expandedChildren, ...residualChildren];
|
|
1181
|
+
}
|
|
1182
|
+
children = children.filter((child) => {
|
|
1183
|
+
if (seenQueryUrls.has(child.slicedQueryUrl)) {
|
|
1184
|
+
return false;
|
|
1185
|
+
}
|
|
1186
|
+
seenQueryUrls.add(child.slicedQueryUrl);
|
|
1187
|
+
return true;
|
|
1188
|
+
});
|
|
1189
|
+
if (children.length === 0) {
|
|
1190
|
+
unresolved.push({
|
|
1191
|
+
attempt,
|
|
1192
|
+
error: error.message,
|
|
1193
|
+
totalResults: error.totalResults,
|
|
1194
|
+
});
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
splitEvents.push({
|
|
1198
|
+
attempt,
|
|
1199
|
+
totalResults: error.totalResults,
|
|
1200
|
+
nextDimension: nextDimension.key,
|
|
1201
|
+
childCount: children.length,
|
|
1202
|
+
strategy,
|
|
1203
|
+
probeSummary,
|
|
1204
|
+
});
|
|
1205
|
+
queue.push(...children);
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1209
|
+
if (attempt.retryCount < maxRetries) {
|
|
1210
|
+
queue.push({
|
|
1211
|
+
...attempt,
|
|
1212
|
+
retryCount: attempt.retryCount + 1,
|
|
1213
|
+
});
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
failed.push({
|
|
1217
|
+
attempt,
|
|
1218
|
+
error: message,
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return {
|
|
1223
|
+
exported,
|
|
1224
|
+
splitEvents,
|
|
1225
|
+
failed,
|
|
1226
|
+
unresolved,
|
|
1227
|
+
attempted,
|
|
1228
|
+
truncated,
|
|
1229
|
+
remainingQueue: queue.length,
|
|
1230
|
+
};
|
|
1231
|
+
}
|