@nitpicker/report-google-sheets 0.4.2 → 0.4.4
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/package.json +7 -4
- package/CHANGELOG.md +0 -16
- package/src/__tests__/api/create-sheets.api.ts +0 -234
- package/src/__tests__/api/helpers.ts +0 -148
- package/src/__tests__/api/sheets.api.ts +0 -217
- package/src/archive.ts +0 -29
- package/src/data/add-to-summary.ts +0 -10
- package/src/data/create-discrepancies.ts +0 -81
- package/src/data/create-image-list.ts +0 -74
- package/src/data/create-links.ts +0 -134
- package/src/data/create-page-list.ts +0 -472
- package/src/data/create-referrers-relational-table.ts +0 -115
- package/src/data/create-resources-relational-table.ts +0 -104
- package/src/data/create-resources.ts +0 -51
- package/src/data/create-violations.spec.ts +0 -95
- package/src/data/create-violations.ts +0 -47
- package/src/debug.ts +0 -7
- package/src/index.ts +0 -1
- package/src/load-config.spec.ts +0 -37
- package/src/load-config.ts +0 -17
- package/src/report.ts +0 -231
- package/src/reports/get-plugin-reports.spec.ts +0 -42
- package/src/reports/get-plugin-reports.ts +0 -24
- package/src/sheets/create-cell-data.ts +0 -1
- package/src/sheets/create-sheets.ts +0 -523
- package/src/sheets/default-cell-format.spec.ts +0 -13
- package/src/sheets/default-cell-format.ts +0 -8
- package/src/sheets/format.spec.ts +0 -17
- package/src/sheets/format.ts +0 -14
- package/src/sheets/types.ts +0 -106
- package/src/types.ts +0 -11
- package/src/utils/has-prop-filter.spec.ts +0 -25
- package/src/utils/has-prop-filter.ts +0 -21
- package/src/utils/non-null-filter.spec.ts +0 -27
- package/src/utils/non-null-filter.ts +0 -15
- package/tsconfig.json +0 -11
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
import type { CreateSheet, createCellTextFormat } from '../sheets/types.js';
|
|
2
|
-
import type { Referrer } from '@nitpicker/crawler';
|
|
3
|
-
|
|
4
|
-
import { decodeURISafely } from '@d-zero/shared/decode-uri-safely';
|
|
5
|
-
|
|
6
|
-
import { pLog, reportLog } from '../debug.js';
|
|
7
|
-
import { createCellData } from '../sheets/create-cell-data.js';
|
|
8
|
-
import { defaultCellFormat } from '../sheets/default-cell-format.js';
|
|
9
|
-
import { booleanFormatError } from '../sheets/format.js';
|
|
10
|
-
import { nonNullFilter } from '../utils/non-null-filter.js';
|
|
11
|
-
|
|
12
|
-
const log = pLog.extend('PageList');
|
|
13
|
-
|
|
14
|
-
const indexTitles = new Map<string, string>();
|
|
15
|
-
|
|
16
|
-
const indexRefs = new Map<
|
|
17
|
-
string,
|
|
18
|
-
{
|
|
19
|
-
basename: string | null;
|
|
20
|
-
referrers: Referrer[];
|
|
21
|
-
}[]
|
|
22
|
-
>();
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Creates the "Page List" sheet configuration -- the primary sitemap-style report.
|
|
26
|
-
*
|
|
27
|
-
* This is the most complex sheet, combining crawler metadata with analyze
|
|
28
|
-
* plugin data into a comprehensive per-page inventory:
|
|
29
|
-
*
|
|
30
|
-
* - **URL decomposition**: Protocol, domain, and up to 10 path segments
|
|
31
|
-
* for hierarchical filtering in the spreadsheet.
|
|
32
|
-
* - **Title shortening**: Directory index titles are subtracted from child
|
|
33
|
-
* page titles to produce concise display titles (e.g. removing the site
|
|
34
|
-
* name suffix). The `indexTitles` map accumulates these across pages.
|
|
35
|
-
* - **Link quality**: Internal/external link counts with bad-link breakdowns
|
|
36
|
-
* (status >= 400, excluding 401 which is often auth-protected).
|
|
37
|
-
* - **SEO metadata**: description, keywords, canonical, alternate, OGP, etc.
|
|
38
|
-
* - **Plugin columns**: Dynamic columns from analyze plugin `pageData`.
|
|
39
|
-
*
|
|
40
|
-
* Conditional formatting highlights:
|
|
41
|
-
* - Bad links (non-zero count)
|
|
42
|
-
* - Missing language attribute
|
|
43
|
-
* - Low internal referrer count (orphan pages)
|
|
44
|
-
* - Suspicious path names (copy, dummy, underscore prefixed)
|
|
45
|
-
* - HTTP protocol (non-HTTPS)
|
|
46
|
-
* - Error-like titles and non-success status codes
|
|
47
|
-
*
|
|
48
|
-
* Unused path columns (beyond the deepest URL) are hidden automatically.
|
|
49
|
-
* @param reports
|
|
50
|
-
*/
|
|
51
|
-
export const createPageList: CreateSheet = (reports) => {
|
|
52
|
-
const reportPageData = reports
|
|
53
|
-
.map((r) => (r.pageData ? { name: r.name, pageData: r.pageData } : null))
|
|
54
|
-
.filter(nonNullFilter);
|
|
55
|
-
|
|
56
|
-
let maxDepth = 0;
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
name: 'Page List',
|
|
60
|
-
createHeaders() {
|
|
61
|
-
const headers = [
|
|
62
|
-
'Title',
|
|
63
|
-
'Full Title',
|
|
64
|
-
'URL',
|
|
65
|
-
'Protocol',
|
|
66
|
-
'Domain',
|
|
67
|
-
'path1',
|
|
68
|
-
'path2',
|
|
69
|
-
'path3',
|
|
70
|
-
'path4',
|
|
71
|
-
'path5',
|
|
72
|
-
'path6',
|
|
73
|
-
'path7',
|
|
74
|
-
'path8',
|
|
75
|
-
'path9',
|
|
76
|
-
'path10',
|
|
77
|
-
'Status Code',
|
|
78
|
-
'Redirect From',
|
|
79
|
-
'Language',
|
|
80
|
-
'Internal Links',
|
|
81
|
-
'Internal Bad Links',
|
|
82
|
-
'External Links',
|
|
83
|
-
'External Bad Links',
|
|
84
|
-
'Internal Referrers',
|
|
85
|
-
'description',
|
|
86
|
-
'keywords',
|
|
87
|
-
'noindex',
|
|
88
|
-
'nofollow',
|
|
89
|
-
'noarchive',
|
|
90
|
-
'canonical',
|
|
91
|
-
'alternate',
|
|
92
|
-
'twitter:card',
|
|
93
|
-
'og:site_name',
|
|
94
|
-
'og:url',
|
|
95
|
-
'og:title',
|
|
96
|
-
'og:description',
|
|
97
|
-
'og:type',
|
|
98
|
-
'og:image',
|
|
99
|
-
];
|
|
100
|
-
|
|
101
|
-
for (const report of reports) {
|
|
102
|
-
if (report.pageData) {
|
|
103
|
-
headers.push(...Object.values(report.pageData.headers));
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return headers;
|
|
108
|
-
},
|
|
109
|
-
async eachPage(page) {
|
|
110
|
-
if (!page.isInternalPage() || !page.isTarget) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const url = page.url;
|
|
115
|
-
|
|
116
|
-
maxDepth = Math.max(url.depth, maxDepth);
|
|
117
|
-
|
|
118
|
-
const paths = [...url.paths];
|
|
119
|
-
paths[paths.length - 1] = `${paths.at(-1)}${url.query ? `?${url.query}` : ''}`;
|
|
120
|
-
const [path1, path2, path3, path4, path5, path6, path7, path8, path9, path10] =
|
|
121
|
-
paths.map((p) => `/${decodeURISafely(p)}`);
|
|
122
|
-
|
|
123
|
-
const anchors = await page.getAnchors();
|
|
124
|
-
let iLinks = 0;
|
|
125
|
-
const iBadLinks: string[] = [];
|
|
126
|
-
let xLinks = 0;
|
|
127
|
-
const xBadLinks: string[] = [];
|
|
128
|
-
for (const anchor of anchors) {
|
|
129
|
-
if (anchor.isExternal) {
|
|
130
|
-
xLinks += 1;
|
|
131
|
-
if (!anchor.status || (anchor.status >= 400 && anchor.status !== 401)) {
|
|
132
|
-
const url =
|
|
133
|
-
anchor.href === anchor.url ? anchor.url : `${anchor.href} => ${anchor.url}`;
|
|
134
|
-
xBadLinks.push(
|
|
135
|
-
`${anchor.textContent} (${anchor.status} ${anchor.statusText} ${url})`,
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
iLinks += 1;
|
|
140
|
-
if (!anchor.status || (anchor.status >= 400 && anchor.status !== 401)) {
|
|
141
|
-
const url =
|
|
142
|
-
anchor.href === anchor.url ? anchor.url : `${anchor.href} => ${anchor.url}`;
|
|
143
|
-
iBadLinks.push(
|
|
144
|
-
`${anchor.textContent} (${anchor.status} ${anchor.statusText} ${url})`,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const refers = await page.getReferrers();
|
|
151
|
-
|
|
152
|
-
let title = page.title;
|
|
153
|
-
const dirname = url.dirname || '/';
|
|
154
|
-
const parentDir = `/${url.paths.slice(0, -2).join('/')}`;
|
|
155
|
-
|
|
156
|
-
const dirTitle = url.isIndex
|
|
157
|
-
? indexTitles.get(parentDir) || indexTitles.get(dirname)
|
|
158
|
-
: indexTitles.get(dirname) || indexTitles.get(parentDir);
|
|
159
|
-
|
|
160
|
-
if (dirTitle && title.includes(dirTitle)) {
|
|
161
|
-
title = title.replace(dirTitle, '').replaceAll(/\|||/g, '').trim();
|
|
162
|
-
if (!title) {
|
|
163
|
-
title = page.title;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const parentRefs = indexRefs.get(dirname);
|
|
168
|
-
|
|
169
|
-
if (url.isIndex) {
|
|
170
|
-
indexTitles.set(dirname, page.title);
|
|
171
|
-
|
|
172
|
-
if (parentRefs) {
|
|
173
|
-
parentRefs.push({
|
|
174
|
-
basename: url.basename,
|
|
175
|
-
referrers: refers,
|
|
176
|
-
});
|
|
177
|
-
// return;
|
|
178
|
-
} else {
|
|
179
|
-
indexRefs.set(dirname, [
|
|
180
|
-
{
|
|
181
|
-
basename: url.basename,
|
|
182
|
-
referrers: refers,
|
|
183
|
-
},
|
|
184
|
-
]);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const isRoot = url.dirname == null;
|
|
189
|
-
const depth = isRoot ? 0 : url.depth - (url.isIndex ? 1 : 0);
|
|
190
|
-
|
|
191
|
-
const data = [
|
|
192
|
-
createCellData(
|
|
193
|
-
{
|
|
194
|
-
value: title,
|
|
195
|
-
cellFormat: { padding: { left: Math.max(depth, 0) * 20 + 3 } },
|
|
196
|
-
note: `Full-title:\n${page.title}`,
|
|
197
|
-
},
|
|
198
|
-
defaultCellFormat,
|
|
199
|
-
),
|
|
200
|
-
createCellData({ value: page.title }, defaultCellFormat),
|
|
201
|
-
createCellData(
|
|
202
|
-
{
|
|
203
|
-
value: page.url.href,
|
|
204
|
-
textFormat: { link: { uri: page.url.href } },
|
|
205
|
-
},
|
|
206
|
-
defaultCellFormat,
|
|
207
|
-
),
|
|
208
|
-
createCellData({ value: url.protocol }, defaultCellFormat),
|
|
209
|
-
createCellData({ value: url.hostname }, defaultCellFormat),
|
|
210
|
-
createCellData({ value: path1 || null }, defaultCellFormat),
|
|
211
|
-
createCellData({ value: path2 || null }, defaultCellFormat),
|
|
212
|
-
createCellData({ value: path3 || null }, defaultCellFormat),
|
|
213
|
-
createCellData({ value: path4 || null }, defaultCellFormat),
|
|
214
|
-
createCellData({ value: path5 || null }, defaultCellFormat),
|
|
215
|
-
createCellData({ value: path6 || null }, defaultCellFormat),
|
|
216
|
-
createCellData({ value: path7 || null }, defaultCellFormat),
|
|
217
|
-
createCellData({ value: path8 || null }, defaultCellFormat),
|
|
218
|
-
createCellData({ value: path9 || null }, defaultCellFormat),
|
|
219
|
-
createCellData({ value: path10 || null }, defaultCellFormat),
|
|
220
|
-
createCellData({ value: page.status || -1 }, defaultCellFormat),
|
|
221
|
-
createCellData(
|
|
222
|
-
{
|
|
223
|
-
value: page.redirectFrom.length,
|
|
224
|
-
note: page.redirectFrom.map((r) => r.url).join('\n'),
|
|
225
|
-
},
|
|
226
|
-
defaultCellFormat,
|
|
227
|
-
),
|
|
228
|
-
createCellData({ value: page.lang || 'N/A' }, defaultCellFormat),
|
|
229
|
-
createCellData({ value: iLinks }, defaultCellFormat),
|
|
230
|
-
createCellData(
|
|
231
|
-
{ value: iBadLinks.length, note: iBadLinks.join('\n') },
|
|
232
|
-
defaultCellFormat,
|
|
233
|
-
),
|
|
234
|
-
createCellData({ value: xLinks }, defaultCellFormat),
|
|
235
|
-
createCellData(
|
|
236
|
-
{ value: xBadLinks.length, note: xBadLinks.join('\n') },
|
|
237
|
-
defaultCellFormat,
|
|
238
|
-
),
|
|
239
|
-
createCellData(
|
|
240
|
-
() => ({
|
|
241
|
-
value:
|
|
242
|
-
url.isIndex && parentRefs
|
|
243
|
-
? parentRefs.reduce((prev, ref) => prev + ref.referrers.length, 0)
|
|
244
|
-
: refers.length,
|
|
245
|
-
note:
|
|
246
|
-
url.isIndex && parentRefs
|
|
247
|
-
? parentRefs
|
|
248
|
-
.map(
|
|
249
|
-
(ref) =>
|
|
250
|
-
`[[/${ref.basename || ''}]]\n${ref.referrers
|
|
251
|
-
.map((ref) => ref.url)
|
|
252
|
-
.join('\n')}`,
|
|
253
|
-
)
|
|
254
|
-
.join('\n\n')
|
|
255
|
-
: refers.map((ref) => ref.url).join('\n'),
|
|
256
|
-
}),
|
|
257
|
-
defaultCellFormat,
|
|
258
|
-
),
|
|
259
|
-
createCellData({ value: page.description }, defaultCellFormat),
|
|
260
|
-
createCellData({ value: page.keywords }, defaultCellFormat),
|
|
261
|
-
createCellData({ value: !!page.noindex }, defaultCellFormat),
|
|
262
|
-
createCellData({ value: !!page.nofollow }, defaultCellFormat),
|
|
263
|
-
createCellData({ value: !!page.noarchive }, defaultCellFormat),
|
|
264
|
-
createCellData({ value: page.canonical }, defaultCellFormat),
|
|
265
|
-
createCellData({ value: page.alternate }, defaultCellFormat),
|
|
266
|
-
createCellData({ value: page.twitter_card }, defaultCellFormat),
|
|
267
|
-
createCellData({ value: page.og_site_name }, defaultCellFormat),
|
|
268
|
-
createCellData(
|
|
269
|
-
{
|
|
270
|
-
value: page.og_url,
|
|
271
|
-
textFormat: { link: { uri: page.og_url } },
|
|
272
|
-
},
|
|
273
|
-
defaultCellFormat,
|
|
274
|
-
),
|
|
275
|
-
createCellData({ value: page.og_title }, defaultCellFormat),
|
|
276
|
-
createCellData({ value: page.og_description }, defaultCellFormat),
|
|
277
|
-
createCellData({ value: page.og_type }, defaultCellFormat),
|
|
278
|
-
createCellData({ value: page.og_image }, defaultCellFormat),
|
|
279
|
-
];
|
|
280
|
-
|
|
281
|
-
for (const report of reportPageData) {
|
|
282
|
-
const tableData = report.pageData.data[page.url.href];
|
|
283
|
-
const options = report.pageData.options
|
|
284
|
-
? report.pageData.options[page.url.href]
|
|
285
|
-
: null;
|
|
286
|
-
|
|
287
|
-
if (!tableData) {
|
|
288
|
-
reportLog("%s did'nt have table of %s", report.name, page.url);
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
reportLog('Add %s to table from %s', page.url.href, report.name);
|
|
293
|
-
data.push(
|
|
294
|
-
...Object.keys(report.pageData.headers).map((key) => {
|
|
295
|
-
const option = options ? options[key] || null : null;
|
|
296
|
-
const data = tableData[key];
|
|
297
|
-
|
|
298
|
-
const format: createCellTextFormat = {};
|
|
299
|
-
let note: string | undefined;
|
|
300
|
-
|
|
301
|
-
if (option) {
|
|
302
|
-
if (option.bold) {
|
|
303
|
-
format.bold = !!option.bold;
|
|
304
|
-
}
|
|
305
|
-
if (option.fontFamily != null) {
|
|
306
|
-
format.fontFamily = `${option.fontFamily}`;
|
|
307
|
-
}
|
|
308
|
-
if (option.fontSize != null) {
|
|
309
|
-
format.fontSize = +option.fontSize;
|
|
310
|
-
}
|
|
311
|
-
if (option.color != null) {
|
|
312
|
-
// format.foregroundColor = option.color;
|
|
313
|
-
}
|
|
314
|
-
if (option.italic != null) {
|
|
315
|
-
format.italic = !!option.italic;
|
|
316
|
-
}
|
|
317
|
-
if (option.strike != null) {
|
|
318
|
-
format.strikethrough = !!option.strike;
|
|
319
|
-
}
|
|
320
|
-
if (option.underline != null) {
|
|
321
|
-
format.underline = !!option.underline;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
note = data?.note || `${option.note || ''}`;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const value = data?.value;
|
|
328
|
-
|
|
329
|
-
return createCellData(
|
|
330
|
-
{ value, textFormat: format, note, ifNull: false },
|
|
331
|
-
defaultCellFormat,
|
|
332
|
-
);
|
|
333
|
-
}),
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return [data];
|
|
338
|
-
},
|
|
339
|
-
async updateSheet(sheet) {
|
|
340
|
-
await sheet.frozen(1, 1);
|
|
341
|
-
|
|
342
|
-
await sheet.conditionalFormat(
|
|
343
|
-
[
|
|
344
|
-
sheet.getColNumByHeaderName('Internal Bad Links'),
|
|
345
|
-
sheet.getColNumByHeaderName('External Bad Links'),
|
|
346
|
-
],
|
|
347
|
-
{
|
|
348
|
-
booleanRule: {
|
|
349
|
-
condition: {
|
|
350
|
-
type: 'NUMBER_NOT_EQ',
|
|
351
|
-
values: [
|
|
352
|
-
{
|
|
353
|
-
userEnteredValue: '0',
|
|
354
|
-
},
|
|
355
|
-
],
|
|
356
|
-
},
|
|
357
|
-
format: booleanFormatError,
|
|
358
|
-
},
|
|
359
|
-
},
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
await sheet.conditionalFormat([sheet.getColNumByHeaderName('Language')], {
|
|
363
|
-
booleanRule: {
|
|
364
|
-
condition: {
|
|
365
|
-
type: 'TEXT_EQ',
|
|
366
|
-
values: [
|
|
367
|
-
{
|
|
368
|
-
userEnteredValue: 'N/A',
|
|
369
|
-
},
|
|
370
|
-
],
|
|
371
|
-
},
|
|
372
|
-
format: booleanFormatError,
|
|
373
|
-
},
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
await sheet.conditionalFormat([sheet.getColNumByHeaderName('Internal Referrers')], {
|
|
377
|
-
booleanRule: {
|
|
378
|
-
condition: {
|
|
379
|
-
type: 'NUMBER_LESS',
|
|
380
|
-
values: [
|
|
381
|
-
{
|
|
382
|
-
userEnteredValue: '2',
|
|
383
|
-
},
|
|
384
|
-
],
|
|
385
|
-
},
|
|
386
|
-
format: booleanFormatError,
|
|
387
|
-
},
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
await sheet.conditionalFormat(
|
|
391
|
-
[
|
|
392
|
-
sheet.getColNumByHeaderName('path1'),
|
|
393
|
-
sheet.getColNumByHeaderName('path2'),
|
|
394
|
-
sheet.getColNumByHeaderName('path3'),
|
|
395
|
-
sheet.getColNumByHeaderName('path4'),
|
|
396
|
-
sheet.getColNumByHeaderName('path5'),
|
|
397
|
-
sheet.getColNumByHeaderName('path6'),
|
|
398
|
-
sheet.getColNumByHeaderName('path7'),
|
|
399
|
-
sheet.getColNumByHeaderName('path8'),
|
|
400
|
-
sheet.getColNumByHeaderName('path9'),
|
|
401
|
-
sheet.getColNumByHeaderName('path10'),
|
|
402
|
-
],
|
|
403
|
-
{
|
|
404
|
-
booleanRule: {
|
|
405
|
-
condition: {
|
|
406
|
-
type: 'CUSTOM_FORMULA',
|
|
407
|
-
values: [
|
|
408
|
-
{
|
|
409
|
-
userEnteredValue:
|
|
410
|
-
'=REGEXMATCH(INDIRECT(ADDRESS(ROW(),COLUMN())), "(?i)(^/_|_$|_copy|-copy|copy_|copy-|dummy)")',
|
|
411
|
-
},
|
|
412
|
-
],
|
|
413
|
-
},
|
|
414
|
-
format: booleanFormatError,
|
|
415
|
-
},
|
|
416
|
-
},
|
|
417
|
-
);
|
|
418
|
-
|
|
419
|
-
await sheet.conditionalFormat([sheet.getColNumByHeaderName('Title')], {
|
|
420
|
-
booleanRule: {
|
|
421
|
-
condition: {
|
|
422
|
-
type: 'CUSTOM_FORMULA',
|
|
423
|
-
values: [
|
|
424
|
-
{
|
|
425
|
-
userEnteredValue:
|
|
426
|
-
'=REGEXMATCH(INDIRECT(ADDRESS(ROW(),COLUMN())), "(?i)(^| )(401|403|404|500|501|502|503)")',
|
|
427
|
-
},
|
|
428
|
-
],
|
|
429
|
-
},
|
|
430
|
-
format: booleanFormatError,
|
|
431
|
-
},
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
await sheet.conditionalFormat([sheet.getColNumByHeaderName('Protocol')], {
|
|
435
|
-
booleanRule: {
|
|
436
|
-
condition: {
|
|
437
|
-
type: 'TEXT_EQ',
|
|
438
|
-
values: [
|
|
439
|
-
{
|
|
440
|
-
userEnteredValue: 'http:',
|
|
441
|
-
},
|
|
442
|
-
],
|
|
443
|
-
},
|
|
444
|
-
format: booleanFormatError,
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
await sheet.conditionalFormat([sheet.getColNumByHeaderName('Status Code')], {
|
|
449
|
-
booleanRule: {
|
|
450
|
-
condition: {
|
|
451
|
-
type: 'NUMBER_NOT_BETWEEN',
|
|
452
|
-
values: [
|
|
453
|
-
{
|
|
454
|
-
userEnteredValue: '200',
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
userEnteredValue: '399',
|
|
458
|
-
},
|
|
459
|
-
],
|
|
460
|
-
},
|
|
461
|
-
format: booleanFormatError,
|
|
462
|
-
},
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
for (let i = maxDepth + 1; i <= 10; i++) {
|
|
466
|
-
const name = `path${i}`;
|
|
467
|
-
log('Hide col %s', name);
|
|
468
|
-
await sheet.hideCol(sheet.getColNumByHeaderName(name));
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
};
|
|
472
|
-
};
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import type { CreateSheet } from '../sheets/types.js';
|
|
2
|
-
import type { Cell } from '@d-zero/google-sheets';
|
|
3
|
-
|
|
4
|
-
import { pLog } from '../debug.js';
|
|
5
|
-
import { createCellData } from '../sheets/create-cell-data.js';
|
|
6
|
-
import { defaultCellFormat } from '../sheets/default-cell-format.js';
|
|
7
|
-
import { booleanFormatError } from '../sheets/format.js';
|
|
8
|
-
|
|
9
|
-
const log = pLog.extend('ReferrersRelationalTable');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Creates the "Referrers Relational Table" sheet configuration.
|
|
13
|
-
*
|
|
14
|
-
* Produces a normalized many-to-many table linking each page to all
|
|
15
|
-
* pages that reference it (referrers). Each row represents one
|
|
16
|
-
* referrer-to-page relationship with the referrer's text content
|
|
17
|
-
* and the page's HTTP status info.
|
|
18
|
-
*
|
|
19
|
-
* This relational format (as opposed to the denormalized referrer
|
|
20
|
-
* column in "Links") enables pivot-table analysis and filtering
|
|
21
|
-
* in Google Sheets -- e.g. "which pages link to this 404 page?"
|
|
22
|
-
*
|
|
23
|
-
* Redirect chains are noted: when the referrer originally linked to
|
|
24
|
-
* a different URL that redirected to the current page, a
|
|
25
|
-
* `[REDIRECTED FROM]` note is added.
|
|
26
|
-
*/
|
|
27
|
-
export const createReferrersRelationalTable: CreateSheet = () => {
|
|
28
|
-
return {
|
|
29
|
-
name: 'Referrers Relational Table',
|
|
30
|
-
createHeaders() {
|
|
31
|
-
return [
|
|
32
|
-
//
|
|
33
|
-
'Link (To)',
|
|
34
|
-
'Referrer (From)',
|
|
35
|
-
'Referrer Content',
|
|
36
|
-
'Link Status Code',
|
|
37
|
-
'Link Status Text',
|
|
38
|
-
'Link Content Type',
|
|
39
|
-
];
|
|
40
|
-
},
|
|
41
|
-
async eachPage(page, num, total) {
|
|
42
|
-
const p = Math.round((num / total) * 100);
|
|
43
|
-
log('Create relational table (%d%% %d/%d)', p, num, total);
|
|
44
|
-
|
|
45
|
-
const data: Cell[][] = [];
|
|
46
|
-
|
|
47
|
-
const referrers = await page.getReferrers();
|
|
48
|
-
|
|
49
|
-
for (const ref of referrers) {
|
|
50
|
-
const text = ref.textContent || '__NO_TEXT_CONTENT__';
|
|
51
|
-
const url = ref.url + (ref.hash ? `#${ref.hash}` : '');
|
|
52
|
-
const pass =
|
|
53
|
-
page.url.href === ref.through ? '' : `[REDIRECTED FROM] ${ref.through}`;
|
|
54
|
-
|
|
55
|
-
data.push([
|
|
56
|
-
createCellData(
|
|
57
|
-
{
|
|
58
|
-
value: page.url.href,
|
|
59
|
-
textFormat: { link: { uri: page.url.href } },
|
|
60
|
-
note: pass === '' ? undefined : pass,
|
|
61
|
-
},
|
|
62
|
-
defaultCellFormat,
|
|
63
|
-
),
|
|
64
|
-
createCellData(
|
|
65
|
-
{
|
|
66
|
-
value: url,
|
|
67
|
-
textFormat: { link: { uri: url } },
|
|
68
|
-
},
|
|
69
|
-
defaultCellFormat,
|
|
70
|
-
),
|
|
71
|
-
createCellData({ value: text }, defaultCellFormat),
|
|
72
|
-
createCellData({ value: page.status || -1 }, defaultCellFormat),
|
|
73
|
-
createCellData({ value: page.statusText || '' }, defaultCellFormat),
|
|
74
|
-
createCellData({ value: page.contentType || '' }, defaultCellFormat),
|
|
75
|
-
]);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return data;
|
|
79
|
-
},
|
|
80
|
-
async updateSheet(sheet) {
|
|
81
|
-
await sheet.frozen(2, 1);
|
|
82
|
-
|
|
83
|
-
await sheet.conditionalFormat([sheet.getColNumByHeaderName('Link Status Code')], {
|
|
84
|
-
booleanRule: {
|
|
85
|
-
condition: {
|
|
86
|
-
type: 'NUMBER_GREATER_THAN_EQ',
|
|
87
|
-
values: [
|
|
88
|
-
{
|
|
89
|
-
userEnteredValue: '400',
|
|
90
|
-
},
|
|
91
|
-
],
|
|
92
|
-
},
|
|
93
|
-
format: booleanFormatError,
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
await sheet.conditionalFormat([sheet.getColNumByHeaderName('Link Status Code')], {
|
|
98
|
-
booleanRule: {
|
|
99
|
-
condition: {
|
|
100
|
-
type: 'NUMBER_NOT_BETWEEN',
|
|
101
|
-
values: [
|
|
102
|
-
{
|
|
103
|
-
userEnteredValue: '200',
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
userEnteredValue: '399',
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
},
|
|
110
|
-
format: booleanFormatError,
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
};
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type { CreateSheet } from '../sheets/types.js';
|
|
2
|
-
import type { Cell } from '@d-zero/google-sheets';
|
|
3
|
-
|
|
4
|
-
import { pLog } from '../debug.js';
|
|
5
|
-
import { createCellData } from '../sheets/create-cell-data.js';
|
|
6
|
-
import { defaultCellFormat } from '../sheets/default-cell-format.js';
|
|
7
|
-
import { booleanFormatError } from '../sheets/format.js';
|
|
8
|
-
|
|
9
|
-
const log = pLog.extend('ReferrersRelationalTable');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Creates the "Resources Relational Table" sheet configuration.
|
|
13
|
-
*
|
|
14
|
-
* Produces a normalized many-to-many table linking each network
|
|
15
|
-
* resource (CSS, JS, images, fonts, etc.) to the pages that
|
|
16
|
-
* reference it. Each row represents one page-to-resource
|
|
17
|
-
* relationship with the resource's HTTP status and size metadata.
|
|
18
|
-
*
|
|
19
|
-
* Unlike the "Resources" sheet which shows one row per resource
|
|
20
|
-
* with a referrer count, this relational table enables filtering
|
|
21
|
-
* and pivot analysis -- e.g. "which pages load this broken CSS file?"
|
|
22
|
-
*/
|
|
23
|
-
export const createResourcesRelationalTable: CreateSheet = () => {
|
|
24
|
-
return {
|
|
25
|
-
name: 'Resources Relational Table',
|
|
26
|
-
createHeaders() {
|
|
27
|
-
return [
|
|
28
|
-
//
|
|
29
|
-
'Referred Page (From)',
|
|
30
|
-
'Resource (To)',
|
|
31
|
-
'Resource Status Code',
|
|
32
|
-
'Resource Status Text',
|
|
33
|
-
'Resource Content Type',
|
|
34
|
-
'Resource Size',
|
|
35
|
-
];
|
|
36
|
-
},
|
|
37
|
-
async eachResource(resource) {
|
|
38
|
-
log(`Read: Resource referrers (Search: ${resource.url})`);
|
|
39
|
-
|
|
40
|
-
const data: Cell[][] = [];
|
|
41
|
-
|
|
42
|
-
const referrers = await resource.getReferrers();
|
|
43
|
-
|
|
44
|
-
for (const url of referrers) {
|
|
45
|
-
data.push([
|
|
46
|
-
createCellData(
|
|
47
|
-
{
|
|
48
|
-
value: url,
|
|
49
|
-
textFormat: { link: { uri: url } },
|
|
50
|
-
},
|
|
51
|
-
defaultCellFormat,
|
|
52
|
-
),
|
|
53
|
-
createCellData({ value: resource.url }, defaultCellFormat),
|
|
54
|
-
createCellData({ value: resource.status }, defaultCellFormat),
|
|
55
|
-
createCellData({ value: resource.statusText }, defaultCellFormat),
|
|
56
|
-
createCellData({ value: resource.contentType }, defaultCellFormat),
|
|
57
|
-
createCellData({ value: resource.contentLength }, defaultCellFormat),
|
|
58
|
-
]);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return data;
|
|
62
|
-
},
|
|
63
|
-
async updateSheet(sheet) {
|
|
64
|
-
await sheet.frozen(2, 1);
|
|
65
|
-
|
|
66
|
-
await sheet.conditionalFormat(
|
|
67
|
-
[sheet.getColNumByHeaderName('Resource Status Code')],
|
|
68
|
-
{
|
|
69
|
-
booleanRule: {
|
|
70
|
-
condition: {
|
|
71
|
-
type: 'NUMBER_GREATER_THAN_EQ',
|
|
72
|
-
values: [
|
|
73
|
-
{
|
|
74
|
-
userEnteredValue: '400',
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
format: booleanFormatError,
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
await sheet.conditionalFormat(
|
|
84
|
-
[sheet.getColNumByHeaderName('Resource Status Code')],
|
|
85
|
-
{
|
|
86
|
-
booleanRule: {
|
|
87
|
-
condition: {
|
|
88
|
-
type: 'NUMBER_NOT_BETWEEN',
|
|
89
|
-
values: [
|
|
90
|
-
{
|
|
91
|
-
userEnteredValue: '200',
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
userEnteredValue: '399',
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
},
|
|
98
|
-
format: booleanFormatError,
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
);
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
};
|