@speedkit/cli 4.26.0 → 4.27.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 +17 -0
- package/README.md +203 -15
- package/dist/commands/auto-prewarm.d.ts +12 -1
- package/dist/commands/auto-prewarm.js +26 -7
- package/dist/commands/generate-pop-config.d.ts +1 -0
- package/dist/commands/generate-pop-config.js +9 -3
- package/dist/commands/prewarm.js +3 -1
- package/dist/commands/query/parameter.d.ts +27 -0
- package/dist/commands/query/parameter.js +28 -0
- package/dist/commands/query/prewarm.d.ts +32 -0
- package/dist/commands/query/prewarm.js +32 -0
- package/dist/helpers/clipboard.d.ts +19 -0
- package/dist/helpers/clipboard.js +70 -0
- package/dist/helpers/evaluate-speed-kit-config.d.ts +8 -18
- package/dist/helpers/evaluate-speed-kit-config.js +8 -6
- package/dist/helpers/evaluate-speed-kit-config.spec.d.ts +1 -0
- package/dist/helpers/evaluate-speed-kit-config.spec.js +78 -0
- package/dist/models/cli-parameters.d.ts +106 -0
- package/dist/models/cli-parameters.js +183 -0
- package/dist/models/cli-parameters.spec.d.ts +1 -0
- package/dist/models/cli-parameters.spec.js +47 -0
- package/dist/services/athena/athena-service-factory.d.ts +4 -1
- package/dist/services/athena/athena-service-factory.js +9 -3
- package/dist/services/athena/athena-service.d.ts +13 -1
- package/dist/services/athena/athena-service.js +27 -5
- package/dist/services/athena/athena-service.spec.d.ts +1 -0
- package/dist/services/athena/athena-service.spec.js +93 -0
- package/dist/services/deploy/handler/install-resource-handler.js +3 -4
- package/dist/services/onboarding/onboarding-service-factory.js +2 -2
- package/dist/services/pop-config/pop-config-service.d.ts +8 -1
- package/dist/services/pop-config/pop-config-service.js +16 -28
- package/dist/services/prewarm/assets/asset-api-client.d.ts +1 -1
- package/dist/services/prewarm/assets/asset-api-client.js +5 -4
- package/dist/services/prewarm/auto-pre-warm-factory.d.ts +20 -2
- package/dist/services/prewarm/auto-pre-warm-factory.js +24 -6
- package/dist/services/prewarm/csv-reader.d.ts +21 -2
- package/dist/services/prewarm/csv-reader.js +71 -9
- package/dist/services/prewarm/csv-reader.spec.d.ts +1 -0
- package/dist/services/prewarm/csv-reader.spec.js +75 -0
- package/dist/services/prewarm/index.d.ts +1 -0
- package/dist/services/prewarm/index.js +1 -0
- package/dist/services/prewarm/pre-warm-factory.d.ts +0 -2
- package/dist/services/prewarm/pre-warm-factory.js +8 -11
- package/dist/services/prewarm/pre-warm-model.d.ts +14 -1
- package/dist/services/prewarm/pre-warm-model.js +0 -1
- package/dist/services/prewarm/pre-warm-service.d.ts +9 -4
- package/dist/services/prewarm/pre-warm-service.js +18 -14
- package/dist/services/prewarm/prewarm-targets.d.ts +12 -0
- package/dist/services/prewarm/prewarm-targets.js +18 -0
- package/dist/services/prewarm/prewarm-targets.spec.d.ts +1 -0
- package/dist/services/prewarm/prewarm-targets.spec.js +29 -0
- package/dist/services/query-builder/error/match-matches-nothing-error.d.ts +11 -0
- package/dist/services/query-builder/error/match-matches-nothing-error.js +17 -0
- package/dist/services/query-builder/queries/device.d.ts +21 -0
- package/dist/services/query-builder/queries/device.js +26 -0
- package/dist/services/query-builder/queries/index.d.ts +6 -0
- package/dist/services/query-builder/queries/index.js +6 -0
- package/dist/services/query-builder/queries/page-filter.d.ts +39 -0
- package/dist/services/query-builder/queries/page-filter.js +135 -0
- package/dist/services/query-builder/queries/page-source.d.ts +30 -0
- package/dist/services/query-builder/queries/page-source.js +79 -0
- package/dist/services/query-builder/queries/parameter-query.d.ts +14 -0
- package/dist/services/query-builder/queries/parameter-query.js +142 -0
- package/dist/services/query-builder/queries/pops-query.d.ts +27 -0
- package/dist/services/query-builder/queries/pops-query.js +65 -0
- package/dist/services/query-builder/queries/prewarm-query.d.ts +18 -0
- package/dist/services/query-builder/queries/prewarm-query.js +227 -0
- package/dist/services/query-builder/queries/query-model.d.ts +97 -0
- package/dist/services/query-builder/queries/query-model.js +19 -0
- package/dist/services/query-builder/queries/spec/golden-configs.d.ts +18 -0
- package/dist/services/query-builder/queries/spec/golden-configs.js +116 -0
- package/dist/services/query-builder/queries/spec/golden.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/golden.spec.js +161 -0
- package/dist/services/query-builder/queries/spec/pops-query.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/pops-query.spec.js +58 -0
- package/dist/services/query-builder/queries/spec/queries.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/queries.spec.js +312 -0
- package/dist/services/query-builder/queries/spec/reported-bugs.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/reported-bugs.spec.js +86 -0
- package/dist/services/query-builder/query-builder-factory.d.ts +2 -2
- package/dist/services/query-builder/query-builder-factory.js +36 -8
- package/dist/services/query-builder/query-builder-model.d.ts +33 -19
- package/dist/services/query-builder/query-builder-model.js +5 -5
- package/dist/services/query-builder/query-builder-service.d.ts +26 -5
- package/dist/services/query-builder/query-builder-service.js +157 -44
- package/dist/services/query-builder/query-command.d.ts +27 -0
- package/dist/services/query-builder/query-command.js +36 -0
- package/dist/services/query-builder/rules/index.d.ts +8 -0
- package/dist/services/query-builder/rules/index.js +8 -0
- package/dist/services/query-builder/rules/match-pattern.d.ts +51 -0
- package/dist/services/query-builder/rules/match-pattern.js +141 -0
- package/dist/services/query-builder/rules/pattern-language.d.ts +59 -0
- package/dist/services/query-builder/rules/pattern-language.js +159 -0
- package/dist/services/query-builder/rules/rule-model.d.ts +107 -0
- package/dist/services/query-builder/rules/rule-model.js +54 -0
- package/dist/services/query-builder/rules/rule-parser.d.ts +33 -0
- package/dist/services/query-builder/rules/rule-parser.js +189 -0
- package/dist/services/query-builder/rules/rule-reducer.d.ts +71 -0
- package/dist/services/query-builder/rules/rule-reducer.js +383 -0
- package/dist/services/query-builder/rules/rule-relevance.d.ts +43 -0
- package/dist/services/query-builder/rules/rule-relevance.js +69 -0
- package/dist/services/query-builder/rules/rule-to-sql.d.ts +51 -0
- package/dist/services/query-builder/rules/rule-to-sql.js +75 -0
- package/dist/services/query-builder/rules/spec/pattern-language.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/pattern-language.spec.js +90 -0
- package/dist/services/query-builder/rules/spec/rule-parser.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-parser.spec.js +150 -0
- package/dist/services/query-builder/rules/spec/rule-reducer.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-reducer.spec.js +169 -0
- package/dist/services/query-builder/rules/spec/rule-relevance.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-relevance.spec.js +88 -0
- package/dist/services/query-builder/rules/spec/rule-to-sql.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-to-sql.spec.js +110 -0
- package/dist/services/query-builder/rules/spec/runtime-semantics.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/runtime-semantics.spec.js +55 -0
- package/dist/services/query-builder/rules/spec/strip-parameters.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/strip-parameters.spec.js +85 -0
- package/dist/services/query-builder/rules/strip-parameters.d.ts +39 -0
- package/dist/services/query-builder/rules/strip-parameters.js +197 -0
- package/dist/services/query-builder/sql/expression.d.ts +73 -0
- package/dist/services/query-builder/sql/expression.js +133 -0
- package/dist/services/query-builder/sql/format.d.ts +14 -0
- package/dist/services/query-builder/sql/format.js +48 -0
- package/dist/services/query-builder/sql/index.d.ts +4 -0
- package/dist/services/query-builder/sql/index.js +4 -0
- package/dist/services/query-builder/sql/predicate.d.ts +66 -0
- package/dist/services/query-builder/sql/predicate.js +124 -0
- package/dist/services/query-builder/sql/regex.d.ts +28 -0
- package/dist/services/query-builder/sql/regex.js +81 -0
- package/dist/services/query-builder/sql/spec/expression.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/expression.spec.js +75 -0
- package/dist/services/query-builder/sql/spec/format.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/format.spec.js +37 -0
- package/dist/services/query-builder/sql/spec/predicate.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/predicate.spec.js +78 -0
- package/dist/services/query-builder/sql/spec/regex.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/regex.spec.js +62 -0
- package/oclif.manifest.json +401 -100
- package/package.json +11 -2
- package/dist/commands/build-parameter-query.d.ts +0 -14
- package/dist/commands/build-parameter-query.js +0 -25
- package/dist/commands/build-prewarm-query.d.ts +0 -16
- package/dist/commands/build-prewarm-query.js +0 -37
- package/dist/helpers/build-query-helper.d.ts +0 -32
- package/dist/helpers/build-query-helper.js +0 -223
- package/dist/helpers/get-parsed-config.d.ts +0 -15
- package/dist/helpers/get-parsed-config.js +0 -62
- package/dist/services/onboarding/dashboard/parameter-query-builder.d.ts +0 -8
- package/dist/services/onboarding/dashboard/parameter-query-builder.js +0 -63
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** How far back a query looks, per query type. */
|
|
2
|
+
export const DEFAULT_QUERY_DAYS = {
|
|
3
|
+
parameter: 30,
|
|
4
|
+
pops: 7,
|
|
5
|
+
prewarm: 10,
|
|
6
|
+
};
|
|
7
|
+
/** Today, as `YYYY-MM-DD` — the format of the `date` partition column. */
|
|
8
|
+
export function today(now = new Date()) {
|
|
9
|
+
return now.toISOString().slice(0, 10);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The day the query starts at, `days` before now, as `YYYY-MM-DD` — the
|
|
13
|
+
* format of the `date` partition column.
|
|
14
|
+
*/
|
|
15
|
+
export function sinceDate(days, now = new Date()) {
|
|
16
|
+
const window = Math.max(1, Math.floor(days));
|
|
17
|
+
const since = new Date(now.getTime() - window * 24 * 60 * 60 * 1000);
|
|
18
|
+
return since.toISOString().slice(0, 10);
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config shapes the query builder has to get right, each one standing for a
|
|
3
|
+
* class of real customer configs.
|
|
4
|
+
*
|
|
5
|
+
* These feed the golden queries in `golden.spec.ts`: the whole emitted SQL and
|
|
6
|
+
* the whole accounting are checked in next to them, so any change to the
|
|
7
|
+
* builder shows up as a reviewable diff instead of as a surprise in a
|
|
8
|
+
* pre-warm run.
|
|
9
|
+
*/
|
|
10
|
+
export interface GoldenConfig {
|
|
11
|
+
/** The evaluated `window.speedKit` of a customer config. */
|
|
12
|
+
readonly config: Record<string, unknown>;
|
|
13
|
+
/** Why this shape is here. */
|
|
14
|
+
readonly describes: string;
|
|
15
|
+
readonly hosts: readonly string[];
|
|
16
|
+
readonly name: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const GOLDEN_CONFIGS: readonly GoldenConfig[];
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export const GOLDEN_CONFIGS = [
|
|
2
|
+
{
|
|
3
|
+
config: {
|
|
4
|
+
blacklist: [
|
|
5
|
+
{
|
|
6
|
+
pathname: [
|
|
7
|
+
// Guards for pages `enabledSites` never allowed — the bulk of a
|
|
8
|
+
// real blacklist, and all reducible.
|
|
9
|
+
/^\/checkout/i,
|
|
10
|
+
/^\/cart/i,
|
|
11
|
+
/^\/account/i,
|
|
12
|
+
// These can hit an enabled page and have to survive.
|
|
13
|
+
/\.pdf/i,
|
|
14
|
+
/paypal/i,
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
{ contentType: ["image", "script"], pathname: [/\/media\//i] },
|
|
18
|
+
{ host: ["tracking.example.com"] },
|
|
19
|
+
],
|
|
20
|
+
disabledScopes: ["/checkout", "/cart"],
|
|
21
|
+
disabledSites: [{ pathname: [/^\/search/i] }],
|
|
22
|
+
enabledSites: [
|
|
23
|
+
{
|
|
24
|
+
host: ["www.shop.de"],
|
|
25
|
+
pathname: [/^\/$/, /^\/p\//i, /^\/c\//i],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
ruleSetConfig: { version: 2 },
|
|
29
|
+
stripQueryParams: [{ params: ["gclid", /^utm[_-]/i] }],
|
|
30
|
+
whitelist: [{ contentType: ["navigate"], host: ["www.shop.de"] }],
|
|
31
|
+
},
|
|
32
|
+
describes: "the recommended shape: version 2, enabledSites, and a blacklist most of which cannot fire",
|
|
33
|
+
hosts: ["www.shop.de"],
|
|
34
|
+
name: "enabled-sites-v2",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
config: {
|
|
38
|
+
blacklist: [{ pathname: ["/login", "/warenkorb"] }],
|
|
39
|
+
disabledSites: [{ pathname: [/\/kasse/i] }],
|
|
40
|
+
// No ruleSetConfig: version 1, where a string is a prefix and a
|
|
41
|
+
// pathname pattern is matched against the query string as well.
|
|
42
|
+
stripQueryParams: ["utm", /session$/i],
|
|
43
|
+
whitelist: [{ host: ["www.shop.de", "m.shop.de"] }],
|
|
44
|
+
},
|
|
45
|
+
describes: "a version 1 config without enabledSites, where every page is enabled unless disabled",
|
|
46
|
+
hosts: ["www.shop.de", "m.shop.de"],
|
|
47
|
+
name: "disabled-sites-v1",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
config: {
|
|
51
|
+
enabledSites: [
|
|
52
|
+
{ host: ["www.shop.de"], pathname: [/^\/de\//i] },
|
|
53
|
+
{ host: ["www.shop.fr"], pathname: [/^\/fr\//i] },
|
|
54
|
+
],
|
|
55
|
+
ruleSetConfig: { version: 2 },
|
|
56
|
+
whitelist: [{ host: ["www.shop.de", "www.shop.fr"] }],
|
|
57
|
+
},
|
|
58
|
+
describes: "one market per rule, which only works if host and pathname stay together",
|
|
59
|
+
hosts: ["www.shop.de", "www.shop.fr"],
|
|
60
|
+
name: "multi-market",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
config: {
|
|
64
|
+
enabledSites: [{ pathname: [/^\/p\//i, /^\/c\//i] }],
|
|
65
|
+
ruleSetConfig: { version: 2 },
|
|
66
|
+
stripQueryParams: [
|
|
67
|
+
{ params: ["gclid"] },
|
|
68
|
+
// Only stripped on product pages.
|
|
69
|
+
{ enabledSites: [{ pathname: [/^\/p\//i] }], params: ["size"] },
|
|
70
|
+
// Only stripped on images, which a page impression never is.
|
|
71
|
+
{ params: ["width"], rules: [{ contentType: ["image"] }] },
|
|
72
|
+
],
|
|
73
|
+
whitelist: [{ host: ["www.shop.de"] }],
|
|
74
|
+
},
|
|
75
|
+
describes: "parameters stripped only on certain pages or resources",
|
|
76
|
+
hosts: ["www.shop.de"],
|
|
77
|
+
name: "scoped-strip-params",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
config: {
|
|
81
|
+
blacklist: [
|
|
82
|
+
// A real shape: the hosts are only blacklisted for browsers without
|
|
83
|
+
// WebP, and the evaluated config carries the empty branch. Read as
|
|
84
|
+
// "any host" this rule would exclude every page.
|
|
85
|
+
{ host: [] },
|
|
86
|
+
{ pathname: [/^\/checkout/i] },
|
|
87
|
+
],
|
|
88
|
+
enabledSites: [{ pathname: [/^\/$/, /^\/p\//i] }],
|
|
89
|
+
ruleSetConfig: { version: 2 },
|
|
90
|
+
whitelist: [{ host: ["www.shop.de"] }],
|
|
91
|
+
},
|
|
92
|
+
describes: "a condition that is an empty list, which matches nothing",
|
|
93
|
+
hosts: ["www.shop.de"],
|
|
94
|
+
name: "empty-condition",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
config: {
|
|
98
|
+
blacklist: [
|
|
99
|
+
{ contentType: ["fetch"] },
|
|
100
|
+
{ url: ["www.googletagmanager.com/gtm.js"] },
|
|
101
|
+
// A word boundary has no automaton, so no reduction may touch it.
|
|
102
|
+
{ pathname: [/\bprint\b/i] },
|
|
103
|
+
],
|
|
104
|
+
disabledSites: [
|
|
105
|
+
{ cookie: /loggedIn=true/ },
|
|
106
|
+
{ mobile: true, pathname: [/^\/app\//i] },
|
|
107
|
+
],
|
|
108
|
+
enabledSites: [{ pathname: [/^\//i] }],
|
|
109
|
+
ruleSetConfig: { version: 2 },
|
|
110
|
+
whitelist: [],
|
|
111
|
+
},
|
|
112
|
+
describes: "rules the query cannot express literally: a cookie, a device, a pattern with no automaton",
|
|
113
|
+
hosts: ["www.shop.de"],
|
|
114
|
+
name: "unexpressible",
|
|
115
|
+
},
|
|
116
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { expect } from "chai";
|
|
5
|
+
import { describe, it } from "mocha";
|
|
6
|
+
import { parseRuleSets } from "../../rules/index.js";
|
|
7
|
+
import { buildParameterQuery } from "../parameter-query.js";
|
|
8
|
+
import { buildPopsQuery } from "../pops-query.js";
|
|
9
|
+
import { buildPrewarmQuery } from "../prewarm-query.js";
|
|
10
|
+
import { GOLDEN_CONFIGS } from "./golden-configs.js";
|
|
11
|
+
/**
|
|
12
|
+
* The queries these configs produce, in full, checked in beside them.
|
|
13
|
+
*
|
|
14
|
+
* A unit test states one property; a golden query states all of them at once,
|
|
15
|
+
* which is what a reviewer needs for a change that rewrites SQL. Run with
|
|
16
|
+
* `UPDATE_GOLDEN=1 npm test` to write the files, then read the diff — an
|
|
17
|
+
* unexplained line there is a bug, and the diff is the review.
|
|
18
|
+
*/
|
|
19
|
+
const DIRECTORY = join(dirname(fileURLToPath(import.meta.url)), "golden");
|
|
20
|
+
/** A fixed day, so a window never makes a golden query go stale. */
|
|
21
|
+
const NOW = new Date("2026-09-04T09:30:00Z");
|
|
22
|
+
const APP = "shop-de";
|
|
23
|
+
function goldenText(query) {
|
|
24
|
+
const notes = [
|
|
25
|
+
...query.findings.map(({ reason, source }) => `finding: ${source} ${reason}`),
|
|
26
|
+
...query.dropped.map(({ reason, source }) => `dropped: ${source} — ${reason}`),
|
|
27
|
+
...query.approximated.map(({ reason, source }) => `approximated: ${source} — ${reason}`),
|
|
28
|
+
...query.reduced.map(({ reason, source }) => `reduced: ${source} — ${reason}`),
|
|
29
|
+
...query.warnings.map((warning) => `warning: ${warning}`),
|
|
30
|
+
];
|
|
31
|
+
return [
|
|
32
|
+
query.sql.trim(),
|
|
33
|
+
"",
|
|
34
|
+
"-- accounting",
|
|
35
|
+
...(notes.length > 0
|
|
36
|
+
? notes.map((note) => `-- ${note}`)
|
|
37
|
+
: ["-- nothing left out, reduced or approximated"]),
|
|
38
|
+
"",
|
|
39
|
+
].join("\n");
|
|
40
|
+
}
|
|
41
|
+
/** Compares against the checked-in file, or writes it when asked to. */
|
|
42
|
+
function expectGolden(name, text) {
|
|
43
|
+
const file = join(DIRECTORY, `${name}.sql`);
|
|
44
|
+
if (process.env.UPDATE_GOLDEN) {
|
|
45
|
+
mkdirSync(DIRECTORY, { recursive: true });
|
|
46
|
+
writeFileSync(file, text, "utf8");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let expected;
|
|
50
|
+
try {
|
|
51
|
+
expected = readFileSync(file, "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`No golden query at ${file}. Run UPDATE_GOLDEN=1 npm test to write it.`);
|
|
55
|
+
}
|
|
56
|
+
expect(text).to.equal(expected);
|
|
57
|
+
}
|
|
58
|
+
/** The prewarm query for one of the golden configs, with options on top. */
|
|
59
|
+
function prewarmQueryFor(name, options) {
|
|
60
|
+
const { config, hosts } = GOLDEN_CONFIGS.find((candidate) => candidate.name === name);
|
|
61
|
+
return buildPrewarmQuery({
|
|
62
|
+
app: APP,
|
|
63
|
+
days: 10,
|
|
64
|
+
hosts,
|
|
65
|
+
now: NOW,
|
|
66
|
+
ruleSets: parseRuleSets(config),
|
|
67
|
+
...options,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
describe("golden queries", () => {
|
|
71
|
+
for (const { config, describes, hosts, name } of GOLDEN_CONFIGS) {
|
|
72
|
+
describe(`${name} — ${describes}`, () => {
|
|
73
|
+
const input = {
|
|
74
|
+
app: APP,
|
|
75
|
+
days: 10,
|
|
76
|
+
hosts,
|
|
77
|
+
now: NOW,
|
|
78
|
+
ruleSets: parseRuleSets(config),
|
|
79
|
+
};
|
|
80
|
+
it("should build the same prewarm query", () => {
|
|
81
|
+
expectGolden(`${name}.prewarm`, goldenText(buildPrewarmQuery(input)));
|
|
82
|
+
});
|
|
83
|
+
it("should build the same parameter query", () => {
|
|
84
|
+
expectGolden(`${name}.parameter`, goldenText(buildParameterQuery({ ...input, days: 30 })));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
describe("match — a prewarm query narrowed by --match", () => {
|
|
89
|
+
it("should build the same prewarm query", () => {
|
|
90
|
+
expectGolden("enabled-sites-v2.match.prewarm", goldenText(prewarmQueryFor("enabled-sites-v2", {
|
|
91
|
+
match: ["^/p/", "(?i)^/c/[^?]*sale", "[?&]size="],
|
|
92
|
+
})));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe("device — a prewarm list split by device class", () => {
|
|
96
|
+
it("should carry the variation and still judge urls", () => {
|
|
97
|
+
// Every url-level cut at once: the limit, the threshold and the 404 rule
|
|
98
|
+
// all belong to the inner `urls` relation, never to the pairs.
|
|
99
|
+
expectGolden("enabled-sites-v2.device.prewarm", goldenText(prewarmQueryFor("enabled-sites-v2", {
|
|
100
|
+
devices: ["desktop", "mobile"],
|
|
101
|
+
excludeNotFound: true,
|
|
102
|
+
limit: 5000,
|
|
103
|
+
minHits: 2,
|
|
104
|
+
})));
|
|
105
|
+
});
|
|
106
|
+
it("should split a list whose urls are reduced to a cache key", () => {
|
|
107
|
+
expectGolden("scoped-strip-params.device.prewarm", goldenText(prewarmQueryFor("scoped-strip-params", {
|
|
108
|
+
devices: ["desktop", "mobile", "tablet"],
|
|
109
|
+
})));
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe("variation — a prewarm list paired with custom variations", () => {
|
|
113
|
+
it("should cross every url with every variation", () => {
|
|
114
|
+
expectGolden("enabled-sites-v2.variation.prewarm", goldenText(prewarmQueryFor("enabled-sites-v2", {
|
|
115
|
+
variations: ["desktop-pdp-a", "mobile-pdp-a", "break"],
|
|
116
|
+
})));
|
|
117
|
+
});
|
|
118
|
+
it("should pair urls that were reduced to a cache key", () => {
|
|
119
|
+
expectGolden("scoped-strip-params.variation.prewarm", goldenText(prewarmQueryFor("scoped-strip-params", {
|
|
120
|
+
limit: 5000,
|
|
121
|
+
variations: ["men", "women"],
|
|
122
|
+
})));
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe("count — how large the list is, instead of what is in it", () => {
|
|
126
|
+
it("should count the list every other flag leaves", () => {
|
|
127
|
+
expectGolden("enabled-sites-v2.count.prewarm", goldenText(prewarmQueryFor("enabled-sites-v2", {
|
|
128
|
+
count: true,
|
|
129
|
+
excludeNotFound: true,
|
|
130
|
+
limit: 5000,
|
|
131
|
+
minHits: 2,
|
|
132
|
+
})));
|
|
133
|
+
});
|
|
134
|
+
it("should count a split list per device class, plus a total", () => {
|
|
135
|
+
expectGolden("enabled-sites-v2.device.count.prewarm", goldenText(prewarmQueryFor("enabled-sites-v2", {
|
|
136
|
+
count: true,
|
|
137
|
+
devices: ["desktop", "mobile"],
|
|
138
|
+
})));
|
|
139
|
+
});
|
|
140
|
+
it("should count custom variations as a product, not as rows", () => {
|
|
141
|
+
expectGolden("enabled-sites-v2.variation.count.prewarm", goldenText(prewarmQueryFor("enabled-sites-v2", {
|
|
142
|
+
count: true,
|
|
143
|
+
variations: ["desktop-pdp-a", "mobile-pdp-a", "break"],
|
|
144
|
+
})));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe("pops — the same query for every app", () => {
|
|
148
|
+
it("should build the same pops query", () => {
|
|
149
|
+
const { parameters, sql } = buildPopsQuery({ app: APP, now: NOW });
|
|
150
|
+
expectGolden("pops", [sql.trim(), "", `-- parameters: ${parameters.join(", ")}`, ""].join("\n"));
|
|
151
|
+
});
|
|
152
|
+
it("should build the same pops query from rum", () => {
|
|
153
|
+
const { parameters, sql } = buildPopsQuery({
|
|
154
|
+
app: APP,
|
|
155
|
+
now: NOW,
|
|
156
|
+
useRum: true,
|
|
157
|
+
});
|
|
158
|
+
expectGolden("pops.rum", [sql.trim(), "", `-- parameters: ${parameters.join(", ")}`, ""].join("\n"));
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { buildPopsQuery } from "../pops-query.js";
|
|
4
|
+
import { sinceDate, today } from "../query-model.js";
|
|
5
|
+
describe("buildPopsQuery", () => {
|
|
6
|
+
it("should read the request log, not the page impressions", () => {
|
|
7
|
+
const { sql } = buildPopsQuery({ app: "shop-de" });
|
|
8
|
+
expect(sql).to.contain("from fastly.logs");
|
|
9
|
+
expect(sql).to.not.contain("rum.pi");
|
|
10
|
+
});
|
|
11
|
+
it("should pass the window and the app as parameters, in order", () => {
|
|
12
|
+
const { parameters, sql } = buildPopsQuery({ app: "shop-de" });
|
|
13
|
+
expect(sql).to.contain("where date between ? and ?");
|
|
14
|
+
expect(sql).to.contain("and app = ?");
|
|
15
|
+
expect(parameters).to.deep.equal([sinceDate(7), today(), "shop-de"]);
|
|
16
|
+
});
|
|
17
|
+
it("should look back a week by default", () => {
|
|
18
|
+
const [start, end] = buildPopsQuery({ app: "shop-de" }).parameters;
|
|
19
|
+
const days = (Date.parse(String(end)) - Date.parse(String(start))) / 86_400_000;
|
|
20
|
+
expect(days).to.equal(7);
|
|
21
|
+
});
|
|
22
|
+
it("should take the window from the caller", () => {
|
|
23
|
+
const { parameters } = buildPopsQuery({ app: "shop-de", days: 30 });
|
|
24
|
+
expect(parameters[0]).to.equal(sinceDate(30));
|
|
25
|
+
});
|
|
26
|
+
it("should count Speed Kit's own asset requests by default", () => {
|
|
27
|
+
const { sql } = buildPopsQuery({ app: "shop-de" });
|
|
28
|
+
expect(sql).to.contain("and isassetrequest");
|
|
29
|
+
expect(sql).to.contain("and isnavigaterequest");
|
|
30
|
+
expect(sql).to.not.contain("ispibeacon");
|
|
31
|
+
});
|
|
32
|
+
it("should count beacons instead when there is no accelerated traffic", () => {
|
|
33
|
+
const { sql } = buildPopsQuery({ app: "shop-de", useRum: true });
|
|
34
|
+
expect(sql).to.contain("and ispibeacon");
|
|
35
|
+
expect(sql).to.not.contain("isassetrequest");
|
|
36
|
+
});
|
|
37
|
+
it("should exclude bots and unknown origins", () => {
|
|
38
|
+
const { sql } = buildPopsQuery({ app: "shop-de" });
|
|
39
|
+
expect(sql).to.contain("and client.bot is null");
|
|
40
|
+
expect(sql).to.contain("and origin not in ('Unknown', '')");
|
|
41
|
+
});
|
|
42
|
+
it("should keep the locale prefix of an origin", () => {
|
|
43
|
+
const { sql } = buildPopsQuery({ app: "shop-de" });
|
|
44
|
+
expect(sql).to.contain("regexp_extract(url_extract_path(asseturl)");
|
|
45
|
+
expect(sql).to.contain(String.raw `(?i)((/\w+)?/[a-z]{2}([-/_][a-z]{2})*/)`);
|
|
46
|
+
});
|
|
47
|
+
it("should aggregate per region, pop and origin", () => {
|
|
48
|
+
const { sql } = buildPopsQuery({ app: "shop-de" });
|
|
49
|
+
expect(sql).to.contain("coalesce(originWithPrefix, origin) as origin");
|
|
50
|
+
expect(sql).to.contain("count() as hits");
|
|
51
|
+
expect(sql).to.contain("group by grouping sets (");
|
|
52
|
+
expect(sql).to.contain("having originWithPrefix is null or originWithPrefix <> 'Unknown'");
|
|
53
|
+
});
|
|
54
|
+
it("should project only what the aggregation reads", () => {
|
|
55
|
+
// `select *` would carry every column of the log through the CTE.
|
|
56
|
+
expect(buildPopsQuery({ app: "shop-de" }).sql).to.not.contain("select *");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|