@canonry/canonry 4.123.0 → 4.125.0
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/assets/assets/{AuditHistoryPanel-BVQMDIhQ.js → AuditHistoryPanel-C-KSU-dr.js} +1 -1
- package/assets/assets/{BacklinksPage-nrHT9VMG.js → BacklinksPage-BH4njwCh.js} +1 -1
- package/assets/assets/{ChartPrimitives-_8qLsQzI.js → ChartPrimitives-BhB9Kxzc.js} +1 -1
- package/assets/assets/{HistoryPage-DqsKKdyy.js → HistoryPage-BQVNx0eu.js} +1 -1
- package/assets/assets/{ProjectPage-GyKoQUv_.js → ProjectPage-DnkNE1AW.js} +1 -1
- package/assets/assets/{RunRow-BRZZcSjQ.js → RunRow-70N3oGz8.js} +1 -1
- package/assets/assets/{RunsPage-24aI8DKF.js → RunsPage-CtuKRP0o.js} +1 -1
- package/assets/assets/{SettingsPage-BaNeTG1i.js → SettingsPage-B1ZGnDz8.js} +1 -1
- package/assets/assets/{TrafficPage-ByvcLSjv.js → TrafficPage-BOGbXHd9.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-B-siTXVg.js → TrafficSourceDetailPage-5jj7n7W1.js} +1 -1
- package/assets/assets/{arrow-left-CBh1cZNH.js → arrow-left-gOf0lyu3.js} +1 -1
- package/assets/assets/{extract-error-message-DJZCPudR.js → extract-error-message-CZKcwJhv.js} +1 -1
- package/assets/assets/{index-mzLganDP.js → index-B_CDPjOg.js} +61 -61
- package/assets/assets/{trash-2-Bh-Yvkgc.js → trash-2-BnFgG3Wq.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-JYBNBK25.js → chunk-DN5H2ZN7.js} +42 -7
- package/dist/{chunk-MMFSI3OG.js → chunk-RFLNTI7Y.js} +28 -2
- package/dist/{chunk-HJIIJI7K.js → chunk-WA57WG6W.js} +1 -1
- package/dist/{chunk-4AZ7VES6.js → chunk-YPRUBNUK.js} +256 -20
- package/dist/cli.js +11 -5
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-K35EN43I.js → intelligence-service-WJ2ZR7GF.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c}from"./index-
|
|
1
|
+
import{c}from"./index-B_CDPjOg.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
|
package/assets/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
13
13
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
14
14
|
<title>Canonry</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-B_CDPjOg.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Be8JdV5M.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/vendor-radix-CQ77EO2y.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/vendor-recharts-DSxTZhaH.js">
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
loadConfigRaw,
|
|
12
12
|
saveConfigPatch
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WA57WG6W.js";
|
|
14
14
|
import {
|
|
15
15
|
CC_CACHE_DIR,
|
|
16
16
|
DUCKDB_SPEC,
|
|
@@ -115,7 +115,7 @@ import {
|
|
|
115
115
|
siteAuditPages,
|
|
116
116
|
siteAuditSnapshots,
|
|
117
117
|
usageCounters
|
|
118
|
-
} from "./chunk-
|
|
118
|
+
} from "./chunk-YPRUBNUK.js";
|
|
119
119
|
import {
|
|
120
120
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
121
121
|
AGENT_PROVIDER_IDS,
|
|
@@ -185,7 +185,7 @@ import {
|
|
|
185
185
|
validationError,
|
|
186
186
|
winnabilityClassLabel,
|
|
187
187
|
withRetry
|
|
188
|
-
} from "./chunk-
|
|
188
|
+
} from "./chunk-RFLNTI7Y.js";
|
|
189
189
|
|
|
190
190
|
// src/telemetry.ts
|
|
191
191
|
import crypto from "crypto";
|
|
@@ -4612,7 +4612,10 @@ async function fetchAllPages(apiKey, path10, queryPairs) {
|
|
|
4612
4612
|
);
|
|
4613
4613
|
}
|
|
4614
4614
|
function insightsPairs(opts) {
|
|
4615
|
-
return
|
|
4615
|
+
return [
|
|
4616
|
+
...(opts?.fields ?? []).map((field) => `fields[]=${encodeURIComponent(field)}`),
|
|
4617
|
+
...(opts?.timeRanges ?? []).map((range) => `time_ranges[]=${encodeURIComponent(JSON.stringify(range))}`)
|
|
4618
|
+
];
|
|
4616
4619
|
}
|
|
4617
4620
|
async function getAdAccount(apiKey) {
|
|
4618
4621
|
validateApiKey(apiKey);
|
|
@@ -4793,6 +4796,31 @@ function removeOpenAiAdsConnection(config, projectName) {
|
|
|
4793
4796
|
var log4 = createLogger("AdsSync");
|
|
4794
4797
|
var CAMPAIGN_INSIGHT_FIELDS = ["campaign.impressions", "campaign.clicks", "campaign.spend", "campaign.conversions", "metadata.readable_time"];
|
|
4795
4798
|
var AD_GROUP_INSIGHT_FIELDS = ["ad_group.impressions", "ad_group.clicks", "ad_group.spend", "ad_group.conversions", "metadata.readable_time"];
|
|
4799
|
+
var INSIGHTS_LOOKBACK_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
4800
|
+
function accountHour(date, timezone) {
|
|
4801
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
4802
|
+
timeZone: timezone,
|
|
4803
|
+
year: "numeric",
|
|
4804
|
+
month: "2-digit",
|
|
4805
|
+
day: "2-digit",
|
|
4806
|
+
hour: "2-digit",
|
|
4807
|
+
hourCycle: "h23"
|
|
4808
|
+
}).formatToParts(date);
|
|
4809
|
+
const value = (type) => {
|
|
4810
|
+
const part = parts.find((candidate) => candidate.type === type)?.value;
|
|
4811
|
+
if (!part) throw new Error(`OpenAI Ads account timezone did not produce a ${type}`);
|
|
4812
|
+
return part;
|
|
4813
|
+
};
|
|
4814
|
+
return `${value("year")}-${value("month")}-${value("day")}T${value("hour")}`;
|
|
4815
|
+
}
|
|
4816
|
+
function trailingAdsInsightHourRange(now, timezone) {
|
|
4817
|
+
return {
|
|
4818
|
+
type: "hour_range",
|
|
4819
|
+
since: accountHour(new Date(now.getTime() - INSIGHTS_LOOKBACK_MS), timezone),
|
|
4820
|
+
until: accountHour(now, timezone),
|
|
4821
|
+
timezone
|
|
4822
|
+
};
|
|
4823
|
+
}
|
|
4796
4824
|
function toInsightUpserts(level, entityId, rows) {
|
|
4797
4825
|
const upserts = [];
|
|
4798
4826
|
for (const row of rows) {
|
|
@@ -4830,6 +4858,7 @@ async function executeAdsSync(db, runId, projectId, opts) {
|
|
|
4830
4858
|
log4.info("sync.start", { runId, projectId, adAccountId: connRow.adAccountId });
|
|
4831
4859
|
const account = await getAdAccount(apiKey);
|
|
4832
4860
|
const campaigns = await listCampaigns(apiKey);
|
|
4861
|
+
const insightTimeRanges = [trailingAdsInsightHourRange(/* @__PURE__ */ new Date(), account.timezone)];
|
|
4833
4862
|
const errors = /* @__PURE__ */ new Map();
|
|
4834
4863
|
const adGroupsByCampaign = /* @__PURE__ */ new Map();
|
|
4835
4864
|
const adsByGroup = /* @__PURE__ */ new Map();
|
|
@@ -4839,12 +4868,18 @@ async function executeAdsSync(db, runId, projectId, opts) {
|
|
|
4839
4868
|
try {
|
|
4840
4869
|
const [adGroups, campaignInsights] = await Promise.all([
|
|
4841
4870
|
listAdGroups(apiKey, campaign.id),
|
|
4842
|
-
getCampaignInsights(apiKey, campaign.id, {
|
|
4871
|
+
getCampaignInsights(apiKey, campaign.id, {
|
|
4872
|
+
fields: CAMPAIGN_INSIGHT_FIELDS,
|
|
4873
|
+
timeRanges: insightTimeRanges
|
|
4874
|
+
})
|
|
4843
4875
|
]);
|
|
4844
4876
|
const groupResults = await Promise.all(adGroups.map(async (group) => ({
|
|
4845
4877
|
group,
|
|
4846
4878
|
ads: await listAds(apiKey, group.id),
|
|
4847
|
-
insights: await getAdGroupInsights(apiKey, group.id, {
|
|
4879
|
+
insights: await getAdGroupInsights(apiKey, group.id, {
|
|
4880
|
+
fields: AD_GROUP_INSIGHT_FIELDS,
|
|
4881
|
+
timeRanges: insightTimeRanges
|
|
4882
|
+
})
|
|
4848
4883
|
})));
|
|
4849
4884
|
syncedCampaigns.push(campaign);
|
|
4850
4885
|
adGroupsByCampaign.set(campaign.id, adGroups);
|
|
@@ -6901,7 +6936,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
6901
6936
|
return result;
|
|
6902
6937
|
}
|
|
6903
6938
|
async function backfillInsightsCommand(project, opts) {
|
|
6904
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
6939
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-WJ2ZR7GF.js");
|
|
6905
6940
|
const config = loadConfig();
|
|
6906
6941
|
const db = createClient(config.database);
|
|
6907
6942
|
migrate(db);
|
|
@@ -867,6 +867,10 @@ function formatWindowCountDelta(d, countLabel, windowLabel) {
|
|
|
867
867
|
const sign = d.deltaAbs > 0 ? "+" : "";
|
|
868
868
|
return `${sign}${formatNumber(Math.round(d.deltaAbs))} ${countLabel} ${windowLabel}`;
|
|
869
869
|
}
|
|
870
|
+
function compactDateToIso(value) {
|
|
871
|
+
if (value.length !== 8 || !/^\d{8}$/.test(value)) return value;
|
|
872
|
+
return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;
|
|
873
|
+
}
|
|
870
874
|
|
|
871
875
|
// ../contracts/src/model-pointers.ts
|
|
872
876
|
var modelPointerEventKindSchema = z7.enum(["introduced", "repointed", "retired"]);
|
|
@@ -3908,7 +3912,18 @@ var ga4SessionHistoryEntrySchema = z24.object({
|
|
|
3908
3912
|
date: z24.string(),
|
|
3909
3913
|
sessions: z24.number(),
|
|
3910
3914
|
organicSessions: z24.number(),
|
|
3911
|
-
|
|
3915
|
+
/**
|
|
3916
|
+
* Unique visitors for the day. Deduplicated by GA when `usersSource` is
|
|
3917
|
+
* `deduplicated`; a landing-page sum (which overcounts multi-page visitors)
|
|
3918
|
+
* for days synced before per-day totals were captured.
|
|
3919
|
+
*/
|
|
3920
|
+
users: z24.number(),
|
|
3921
|
+
/**
|
|
3922
|
+
* How `users` was derived. `deduplicated` matches the GA UI's active users;
|
|
3923
|
+
* `landing-page-sum` is the legacy overcount kept so historical days still
|
|
3924
|
+
* render. Never compare the two across a series without saying which is which.
|
|
3925
|
+
*/
|
|
3926
|
+
usersSource: z24.enum(["deduplicated", "landing-page-sum"])
|
|
3912
3927
|
});
|
|
3913
3928
|
|
|
3914
3929
|
// ../contracts/src/answer-visibility.ts
|
|
@@ -6833,10 +6848,19 @@ var adsOperationStepDtoSchema = z35.discriminatedUnion("state", [
|
|
|
6833
6848
|
adsOperationStepRollbackFailedDtoSchema,
|
|
6834
6849
|
adsOperationStepUnknownDtoSchema
|
|
6835
6850
|
]);
|
|
6851
|
+
var AdsActivationVersionPolicies = {
|
|
6852
|
+
exact: "exact",
|
|
6853
|
+
refreshSemanticallyUnchanged: "refresh_semantically_unchanged"
|
|
6854
|
+
};
|
|
6855
|
+
var adsActivationVersionPolicySchema = z35.enum([
|
|
6856
|
+
AdsActivationVersionPolicies.exact,
|
|
6857
|
+
AdsActivationVersionPolicies.refreshSemanticallyUnchanged
|
|
6858
|
+
]);
|
|
6836
6859
|
var adsActivationGrantCreateRequestSchema = z35.object({
|
|
6837
6860
|
manifest: adsActivationManifestSchema,
|
|
6838
6861
|
executorApiKeyId: adsEntityIdSchema,
|
|
6839
|
-
expiresAt: adsActivationIsoTimestampSchema
|
|
6862
|
+
expiresAt: adsActivationIsoTimestampSchema,
|
|
6863
|
+
versionPolicy: adsActivationVersionPolicySchema.default(AdsActivationVersionPolicies.exact)
|
|
6840
6864
|
}).strict();
|
|
6841
6865
|
var adsActivationGrantResponseSchema = z35.object({
|
|
6842
6866
|
grant: adsActivationGrantDtoSchema
|
|
@@ -7140,6 +7164,7 @@ export {
|
|
|
7140
7164
|
formatDeltaCopy,
|
|
7141
7165
|
formatAverageDelta,
|
|
7142
7166
|
formatWindowCountDelta,
|
|
7167
|
+
compactDateToIso,
|
|
7143
7168
|
evaluateModelPointerExposure,
|
|
7144
7169
|
buildModelChangeNotice,
|
|
7145
7170
|
googleConnectionDtoSchema,
|
|
@@ -7425,6 +7450,7 @@ export {
|
|
|
7425
7450
|
adsOperationResponseSchema,
|
|
7426
7451
|
adsActivationGrantDtoSchema,
|
|
7427
7452
|
adsOperationStepDtoSchema,
|
|
7453
|
+
AdsActivationVersionPolicies,
|
|
7428
7454
|
adsActivationGrantCreateRequestSchema,
|
|
7429
7455
|
adsActivationGrantResponseSchema,
|
|
7430
7456
|
adsActivationGrantRevokeRequestSchema,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
AI_PROVIDER_INFRA_DOMAINS,
|
|
8
8
|
AdsActivationEntityTypes,
|
|
9
9
|
AdsActivationGrantStates,
|
|
10
|
+
AdsActivationVersionPolicies,
|
|
10
11
|
AdsAdGroupBillingEventTypes,
|
|
11
12
|
AdsCampaignBiddingTypes,
|
|
12
13
|
AdsEntityStatuses,
|
|
@@ -141,6 +142,7 @@ import {
|
|
|
141
142
|
classifyTrafficPath,
|
|
142
143
|
clusterByCosine,
|
|
143
144
|
coerceSkillManifest,
|
|
145
|
+
compactDateToIso,
|
|
144
146
|
competitorBatchRequestSchema,
|
|
145
147
|
competitorDtoSchema,
|
|
146
148
|
computeDedupSimilarityStats,
|
|
@@ -334,7 +336,7 @@ import {
|
|
|
334
336
|
wordpressSchemaDeployResultDtoSchema,
|
|
335
337
|
wordpressSchemaStatusResultDtoSchema,
|
|
336
338
|
wordpressStatusDtoSchema
|
|
337
|
-
} from "./chunk-
|
|
339
|
+
} from "./chunk-RFLNTI7Y.js";
|
|
338
340
|
|
|
339
341
|
// src/intelligence-service.ts
|
|
340
342
|
import { eq as eq41, desc as desc18, asc as asc9, and as and32, ne as ne6, or as or8, inArray as inArray15, gte as gte10, lte as lte6 } from "drizzle-orm";
|
|
@@ -378,6 +380,7 @@ __export(schema_exports, {
|
|
|
378
380
|
domainClassifications: () => domainClassifications,
|
|
379
381
|
gaAiReferrals: () => gaAiReferrals,
|
|
380
382
|
gaConnections: () => gaConnections,
|
|
383
|
+
gaDailyTotals: () => gaDailyTotals,
|
|
381
384
|
gaSocialReferrals: () => gaSocialReferrals,
|
|
382
385
|
gaTrafficSnapshots: () => gaTrafficSnapshots,
|
|
383
386
|
gaTrafficSummaries: () => gaTrafficSummaries,
|
|
@@ -823,6 +826,20 @@ var gaTrafficSnapshots = sqliteTable("ga_traffic_snapshots", {
|
|
|
823
826
|
index("idx_ga_traffic_page_normalized").on(table.projectId, table.date, table.landingPageNormalized),
|
|
824
827
|
index("idx_ga_traffic_run").on(table.syncRunId)
|
|
825
828
|
]);
|
|
829
|
+
var gaDailyTotals = sqliteTable("ga_daily_totals", {
|
|
830
|
+
id: text("id").primaryKey(),
|
|
831
|
+
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
832
|
+
date: text("date").notNull(),
|
|
833
|
+
sessions: integer("sessions").notNull().default(0),
|
|
834
|
+
users: integer("users").notNull().default(0),
|
|
835
|
+
syncedAt: text("synced_at").notNull(),
|
|
836
|
+
syncRunId: text("sync_run_id").references(() => runs.id, { onDelete: "cascade" }),
|
|
837
|
+
createdAt: text("created_at").notNull()
|
|
838
|
+
}, (table) => [
|
|
839
|
+
uniqueIndex("idx_ga_daily_totals_project_date").on(table.projectId, table.date),
|
|
840
|
+
index("idx_ga_daily_totals_project").on(table.projectId),
|
|
841
|
+
index("idx_ga_daily_totals_run").on(table.syncRunId)
|
|
842
|
+
]);
|
|
826
843
|
var gaAiReferrals = sqliteTable("ga_ai_referrals", {
|
|
827
844
|
id: text("id").primaryKey(),
|
|
828
845
|
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
@@ -3929,6 +3946,31 @@ var MIGRATION_VERSIONS = [
|
|
|
3929
3946
|
run: (tx) => {
|
|
3930
3947
|
backfillQuerySnapshotServedModel(tx);
|
|
3931
3948
|
}
|
|
3949
|
+
},
|
|
3950
|
+
{
|
|
3951
|
+
// `users` is not additive across landing pages — one visitor who lands on
|
|
3952
|
+
// three pages is one user but contributes to three `ga_traffic_snapshots`
|
|
3953
|
+
// rows. Summing that dimensioned table overcounts the day. This table
|
|
3954
|
+
// stores the same day fetched with NO landing-page dimension, so GA does
|
|
3955
|
+
// the dedup and the stored number matches the GA UI. Same shape and
|
|
3956
|
+
// rationale as `gsc_daily_totals`.
|
|
3957
|
+
version: 106,
|
|
3958
|
+
name: "ga-daily-totals",
|
|
3959
|
+
statements: [
|
|
3960
|
+
`CREATE TABLE IF NOT EXISTS ga_daily_totals (
|
|
3961
|
+
id TEXT PRIMARY KEY,
|
|
3962
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
3963
|
+
date TEXT NOT NULL,
|
|
3964
|
+
sessions INTEGER NOT NULL DEFAULT 0,
|
|
3965
|
+
users INTEGER NOT NULL DEFAULT 0,
|
|
3966
|
+
synced_at TEXT NOT NULL,
|
|
3967
|
+
sync_run_id TEXT REFERENCES runs(id) ON DELETE CASCADE,
|
|
3968
|
+
created_at TEXT NOT NULL
|
|
3969
|
+
)`,
|
|
3970
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_daily_totals_project_date ON ga_daily_totals(project_id, date)`,
|
|
3971
|
+
`CREATE INDEX IF NOT EXISTS idx_ga_daily_totals_project ON ga_daily_totals(project_id)`,
|
|
3972
|
+
`CREATE INDEX IF NOT EXISTS idx_ga_daily_totals_run ON ga_daily_totals(sync_run_id)`
|
|
3973
|
+
]
|
|
3932
3974
|
}
|
|
3933
3975
|
];
|
|
3934
3976
|
function rebuildBacklinkTableWithSource(tx, table) {
|
|
@@ -18582,7 +18624,13 @@ var routeCatalog = [
|
|
|
18582
18624
|
properties: {
|
|
18583
18625
|
manifest: adsActivationManifestSchema2,
|
|
18584
18626
|
executorApiKeyId: adsEntityIdSchema,
|
|
18585
|
-
expiresAt: { type: "string", format: "date-time" }
|
|
18627
|
+
expiresAt: { type: "string", format: "date-time" },
|
|
18628
|
+
versionPolicy: {
|
|
18629
|
+
type: "string",
|
|
18630
|
+
enum: ["exact", "refresh_semantically_unchanged"],
|
|
18631
|
+
default: "exact",
|
|
18632
|
+
description: "Exact rejects any provider version drift. refresh_semantically_unchanged rebinds versions only when the live paused tree still matches its durable create receipts field-for-field."
|
|
18633
|
+
}
|
|
18586
18634
|
}
|
|
18587
18635
|
}
|
|
18588
18636
|
}
|
|
@@ -22475,9 +22523,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
22475
22523
|
row.directSessions = directMap.get(key) ?? 0;
|
|
22476
22524
|
}
|
|
22477
22525
|
for (const row of rows) {
|
|
22478
|
-
|
|
22479
|
-
row.date = `${row.date.slice(0, 4)}-${row.date.slice(4, 6)}-${row.date.slice(6, 8)}`;
|
|
22480
|
-
}
|
|
22526
|
+
row.date = compactDateToIso(row.date);
|
|
22481
22527
|
}
|
|
22482
22528
|
ga4Log("info", "fetch-traffic.done", { propertyId, rowCount: rows.length });
|
|
22483
22529
|
return rows;
|
|
@@ -22544,6 +22590,28 @@ async function fetchAggregateSummary(accessToken, propertyId, days) {
|
|
|
22544
22590
|
ga4Log("info", "fetch-aggregate.done", { propertyId, ...summary });
|
|
22545
22591
|
return summary;
|
|
22546
22592
|
}
|
|
22593
|
+
async function fetchDailyTotals(accessToken, propertyId, days) {
|
|
22594
|
+
validateAccessToken2(accessToken);
|
|
22595
|
+
validatePropertyId(propertyId);
|
|
22596
|
+
const syncDays = Math.min(Math.max(1, days ?? GA4_DEFAULT_SYNC_DAYS), GA4_MAX_SYNC_DAYS);
|
|
22597
|
+
const endDate = /* @__PURE__ */ new Date();
|
|
22598
|
+
const startDate = /* @__PURE__ */ new Date();
|
|
22599
|
+
startDate.setDate(startDate.getDate() - syncDays);
|
|
22600
|
+
ga4Log("info", "fetch-daily-totals.start", { propertyId, days: syncDays });
|
|
22601
|
+
const res = await runReport(accessToken, propertyId, {
|
|
22602
|
+
dateRanges: [{ startDate: formatDate2(startDate), endDate: formatDate2(endDate) }],
|
|
22603
|
+
dimensions: [{ name: GA4_DIMENSIONS.date }],
|
|
22604
|
+
metrics: [{ name: GA4_METRICS.sessions }, { name: GA4_METRICS.totalUsers }],
|
|
22605
|
+
limit: syncDays + 1
|
|
22606
|
+
});
|
|
22607
|
+
const rows = (res.rows ?? []).map((row) => ({
|
|
22608
|
+
date: compactDateToIso(row.dimensionValues[0]?.value ?? ""),
|
|
22609
|
+
sessions: parseInt(row.metricValues[0]?.value ?? "0", 10) || 0,
|
|
22610
|
+
users: parseInt(row.metricValues[1]?.value ?? "0", 10) || 0
|
|
22611
|
+
})).filter((row) => row.date.length > 0);
|
|
22612
|
+
ga4Log("info", "fetch-daily-totals.done", { propertyId, days: syncDays, rows: rows.length });
|
|
22613
|
+
return rows;
|
|
22614
|
+
}
|
|
22547
22615
|
var WINDOW_DAYS = { "7d": 7, "30d": 30, "90d": 90 };
|
|
22548
22616
|
async function fetchWindowSummary(accessToken, propertyId, windowKey) {
|
|
22549
22617
|
validateAccessToken2(accessToken);
|
|
@@ -22691,9 +22759,7 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
22691
22759
|
}
|
|
22692
22760
|
const dedupedRows = [...deduped.values()];
|
|
22693
22761
|
for (const row of dedupedRows) {
|
|
22694
|
-
|
|
22695
|
-
row.date = `${row.date.slice(0, 4)}-${row.date.slice(4, 6)}-${row.date.slice(6, 8)}`;
|
|
22696
|
-
}
|
|
22762
|
+
row.date = compactDateToIso(row.date);
|
|
22697
22763
|
}
|
|
22698
22764
|
ga4Log("info", "fetch-ai-referrals.done", { propertyId, rowCount: dedupedRows.length });
|
|
22699
22765
|
return dedupedRows;
|
|
@@ -22754,9 +22820,7 @@ async function fetchSocialReferrals(accessToken, propertyId, days) {
|
|
|
22754
22820
|
if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
|
|
22755
22821
|
}
|
|
22756
22822
|
for (const row of rows) {
|
|
22757
|
-
|
|
22758
|
-
row.date = `${row.date.slice(0, 4)}-${row.date.slice(4, 6)}-${row.date.slice(6, 8)}`;
|
|
22759
|
-
}
|
|
22823
|
+
row.date = compactDateToIso(row.date);
|
|
22760
22824
|
}
|
|
22761
22825
|
ga4Log("info", "fetch-social-referrals.done", { propertyId, rowCount: rows.length });
|
|
22762
22826
|
return rows;
|
|
@@ -24891,6 +24955,81 @@ async function preflightAdsActivationApproval(provider, request) {
|
|
|
24891
24955
|
}
|
|
24892
24956
|
return { manifest, manifestHash: hashAdsActivationManifest(manifest) };
|
|
24893
24957
|
}
|
|
24958
|
+
async function refreshSemanticallyUnchangedAdsActivationManifest(provider, request) {
|
|
24959
|
+
const manifest = canonicalManifest(request.manifest);
|
|
24960
|
+
await validateAccount(provider, request.adAccountId);
|
|
24961
|
+
await validateExactDescendants(provider, manifest, {
|
|
24962
|
+
entityType: AdsActivationEntityTypes.campaign,
|
|
24963
|
+
entityId: manifest.campaign.id,
|
|
24964
|
+
parentId: null,
|
|
24965
|
+
expectedUpdatedAt: manifest.campaign.expectedUpdatedAt
|
|
24966
|
+
});
|
|
24967
|
+
const versions = /* @__PURE__ */ new Map();
|
|
24968
|
+
for (const target of activationTargets(manifest)) {
|
|
24969
|
+
let entity;
|
|
24970
|
+
try {
|
|
24971
|
+
entity = await readTarget(provider, target);
|
|
24972
|
+
} catch {
|
|
24973
|
+
throw targetError(
|
|
24974
|
+
AdsActivationErrorCodes.providerReadFailed,
|
|
24975
|
+
"An ads entity could not be verified for version refresh",
|
|
24976
|
+
502,
|
|
24977
|
+
target
|
|
24978
|
+
);
|
|
24979
|
+
}
|
|
24980
|
+
validateIdentity(target, entity);
|
|
24981
|
+
validateAdApproval(target, entity);
|
|
24982
|
+
if (entity.status !== PROVIDER_PAUSED) {
|
|
24983
|
+
throw targetError(AdsActivationErrorCodes.entityNotPaused, "Every approved ads entity must still be paused", 409, target);
|
|
24984
|
+
}
|
|
24985
|
+
if (!Number.isInteger(entity.updatedAt) || entity.updatedAt === null || entity.updatedAt < 0) {
|
|
24986
|
+
throw targetError(
|
|
24987
|
+
AdsActivationErrorCodes.providerReadFailed,
|
|
24988
|
+
"An ads entity did not report a usable provider version",
|
|
24989
|
+
502,
|
|
24990
|
+
target
|
|
24991
|
+
);
|
|
24992
|
+
}
|
|
24993
|
+
if (!request.semanticallyMatchesMaterialization(target.entityType, target.entityId, entity)) {
|
|
24994
|
+
throw targetError(
|
|
24995
|
+
AdsActivationErrorCodes.entityMismatch,
|
|
24996
|
+
"An ads entity no longer matches its materialized approved fields",
|
|
24997
|
+
409,
|
|
24998
|
+
target
|
|
24999
|
+
);
|
|
25000
|
+
}
|
|
25001
|
+
versions.set(`${target.entityType}:${target.entityId}`, entity.updatedAt);
|
|
25002
|
+
}
|
|
25003
|
+
const version = (entityType, entityId) => {
|
|
25004
|
+
const current = versions.get(`${entityType}:${entityId}`);
|
|
25005
|
+
if (current === void 0) {
|
|
25006
|
+
throw new AdsActivationError(
|
|
25007
|
+
AdsActivationErrorCodes.persistenceFailed,
|
|
25008
|
+
"A refreshed ads entity version is missing",
|
|
25009
|
+
500
|
|
25010
|
+
);
|
|
25011
|
+
}
|
|
25012
|
+
return current;
|
|
25013
|
+
};
|
|
25014
|
+
const refreshed = {
|
|
25015
|
+
campaign: {
|
|
25016
|
+
id: manifest.campaign.id,
|
|
25017
|
+
expectedUpdatedAt: version(AdsActivationEntityTypes.campaign, manifest.campaign.id),
|
|
25018
|
+
adGroups: manifest.campaign.adGroups.map((group) => ({
|
|
25019
|
+
id: group.id,
|
|
25020
|
+
expectedUpdatedAt: version(AdsActivationEntityTypes.ad_group, group.id),
|
|
25021
|
+
ads: group.ads.map((ad) => ({
|
|
25022
|
+
id: ad.id,
|
|
25023
|
+
expectedUpdatedAt: version(AdsActivationEntityTypes.ad, ad.id)
|
|
25024
|
+
}))
|
|
25025
|
+
}))
|
|
25026
|
+
}
|
|
25027
|
+
};
|
|
25028
|
+
return preflightAdsActivationApproval(provider, {
|
|
25029
|
+
adAccountId: request.adAccountId,
|
|
25030
|
+
manifest: refreshed
|
|
25031
|
+
});
|
|
25032
|
+
}
|
|
24894
25033
|
function stepTarget(manifest, step) {
|
|
24895
25034
|
const target = activationTargets(manifest).find(
|
|
24896
25035
|
(candidate) => candidate.entityType === step.entityType && candidate.entityId === step.entityId
|
|
@@ -26462,7 +26601,11 @@ function registerAdsActivationRoutes(app, opts) {
|
|
|
26462
26601
|
const runtime = await opts.resolveRuntime(project);
|
|
26463
26602
|
let preflight;
|
|
26464
26603
|
try {
|
|
26465
|
-
preflight = await
|
|
26604
|
+
preflight = body.versionPolicy === AdsActivationVersionPolicies.refreshSemanticallyUnchanged ? await refreshSemanticallyUnchangedAdsActivationManifest(runtime.provider, {
|
|
26605
|
+
adAccountId: runtime.adAccountId,
|
|
26606
|
+
manifest: body.manifest,
|
|
26607
|
+
semanticallyMatchesMaterialization: runtime.semanticallyMatchesMaterialization ?? (() => false)
|
|
26608
|
+
}) : await preflightAdsActivationApproval(runtime.provider, {
|
|
26466
26609
|
adAccountId: runtime.adAccountId,
|
|
26467
26610
|
manifest: body.manifest
|
|
26468
26611
|
});
|
|
@@ -26903,6 +27046,44 @@ function entityMatchesReconcileFields(entity, desired, fingerprint) {
|
|
|
26903
27046
|
const candidateFingerprint = requestHash(selected);
|
|
26904
27047
|
return fingerprint === null ? candidateFingerprint === requestHash(desired) : candidateFingerprint === fingerprint;
|
|
26905
27048
|
}
|
|
27049
|
+
function semanticallyMatchesMaterializedEntity(app, input) {
|
|
27050
|
+
const createKind = (() => {
|
|
27051
|
+
switch (input.entityType) {
|
|
27052
|
+
case AdsActivationEntityTypes.campaign:
|
|
27053
|
+
return AdsOperationKinds.campaign_create;
|
|
27054
|
+
case AdsActivationEntityTypes.ad_group:
|
|
27055
|
+
return AdsOperationKinds.ad_group_create;
|
|
27056
|
+
case AdsActivationEntityTypes.ad:
|
|
27057
|
+
return AdsOperationKinds.ad_create;
|
|
27058
|
+
}
|
|
27059
|
+
})();
|
|
27060
|
+
const updateKind = (() => {
|
|
27061
|
+
switch (input.entityType) {
|
|
27062
|
+
case AdsActivationEntityTypes.campaign:
|
|
27063
|
+
return AdsOperationKinds.campaign_update;
|
|
27064
|
+
case AdsActivationEntityTypes.ad_group:
|
|
27065
|
+
return AdsOperationKinds.ad_group_update;
|
|
27066
|
+
case AdsActivationEntityTypes.ad:
|
|
27067
|
+
return AdsOperationKinds.ad_update;
|
|
27068
|
+
}
|
|
27069
|
+
})();
|
|
27070
|
+
const rows = app.db.select().from(adsOperations).where(and20(
|
|
27071
|
+
eq26(adsOperations.projectId, input.projectId),
|
|
27072
|
+
eq26(adsOperations.entityType, input.entityType),
|
|
27073
|
+
eq26(adsOperations.entityId, input.entityId),
|
|
27074
|
+
inArray12(adsOperations.kind, [createKind, updateKind])
|
|
27075
|
+
)).orderBy(asc6(adsOperations.createdAt), asc6(adsOperations.id)).all();
|
|
27076
|
+
if (rows.some((row) => row.state === AdsOperationStates.pending || row.state === AdsOperationStates.reconciling || row.state === AdsOperationStates.unknown || row.kind === updateKind && row.state === AdsOperationStates.succeeded)) {
|
|
27077
|
+
return false;
|
|
27078
|
+
}
|
|
27079
|
+
const creates = rows.filter((row) => row.kind === createKind && row.state === AdsOperationStates.succeeded);
|
|
27080
|
+
if (creates.length !== 1) return false;
|
|
27081
|
+
const create = creates[0];
|
|
27082
|
+
if (create.adAccountId !== input.adAccountId || create.reconcileStrategy !== AdsReconcileStrategies.create_fingerprint || !create.reconcileFields) {
|
|
27083
|
+
return false;
|
|
27084
|
+
}
|
|
27085
|
+
return entityMatchesReconcileFields(input.entity, create.reconcileFields, null);
|
|
27086
|
+
}
|
|
26906
27087
|
function reconcileFieldsWithoutParent(fields) {
|
|
26907
27088
|
const { campaignId: _campaignId, adGroupId: _adGroupId, ...withoutParent } = fields;
|
|
26908
27089
|
return withoutParent;
|
|
@@ -27686,6 +27867,16 @@ async function adsRoutes(app, opts) {
|
|
|
27686
27867
|
}
|
|
27687
27868
|
return {
|
|
27688
27869
|
adAccountId,
|
|
27870
|
+
semanticallyMatchesMaterialization: (entityType, entityId, entity) => {
|
|
27871
|
+
if (entity.status === null || entity.updatedAt === null) return false;
|
|
27872
|
+
return semanticallyMatchesMaterializedEntity(app, {
|
|
27873
|
+
projectId: project.id,
|
|
27874
|
+
adAccountId,
|
|
27875
|
+
entityType,
|
|
27876
|
+
entityId,
|
|
27877
|
+
entity: { ...entity, status: entity.status, updatedAt: entity.updatedAt }
|
|
27878
|
+
});
|
|
27879
|
+
},
|
|
27689
27880
|
provider: {
|
|
27690
27881
|
getAccount: refreshAccount,
|
|
27691
27882
|
getCampaign: (id) => operator.getCampaign(apiKey, id),
|
|
@@ -29234,6 +29425,23 @@ async function cdpRoutes(app, opts) {
|
|
|
29234
29425
|
// ../api-routes/src/ga.ts
|
|
29235
29426
|
import crypto24 from "crypto";
|
|
29236
29427
|
import { eq as eq29, desc as desc13, and as and23, sql as sql12 } from "drizzle-orm";
|
|
29428
|
+
|
|
29429
|
+
// ../api-routes/src/ga-session-history.ts
|
|
29430
|
+
function buildSessionHistory(landingPageDays, dailyTotals) {
|
|
29431
|
+
const totalsByDate = new Map(dailyTotals.map((row) => [row.date, row.users]));
|
|
29432
|
+
return landingPageDays.map((day) => {
|
|
29433
|
+
const deduplicated = totalsByDate.get(day.date);
|
|
29434
|
+
return {
|
|
29435
|
+
date: day.date,
|
|
29436
|
+
sessions: day.sessions,
|
|
29437
|
+
organicSessions: day.organicSessions,
|
|
29438
|
+
users: deduplicated ?? day.users,
|
|
29439
|
+
usersSource: deduplicated === void 0 ? "landing-page-sum" : "deduplicated"
|
|
29440
|
+
};
|
|
29441
|
+
});
|
|
29442
|
+
}
|
|
29443
|
+
|
|
29444
|
+
// ../api-routes/src/ga.ts
|
|
29237
29445
|
function gaLog(level, action, ctx) {
|
|
29238
29446
|
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
|
|
29239
29447
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
@@ -29512,6 +29720,7 @@ async function ga4Routes(app, opts) {
|
|
|
29512
29720
|
}
|
|
29513
29721
|
app.db.delete(gaTrafficSnapshots).where(eq29(gaTrafficSnapshots.projectId, project.id)).run();
|
|
29514
29722
|
app.db.delete(gaTrafficSummaries).where(eq29(gaTrafficSummaries.projectId, project.id)).run();
|
|
29723
|
+
app.db.delete(gaDailyTotals).where(eq29(gaDailyTotals.projectId, project.id)).run();
|
|
29515
29724
|
app.db.delete(gaAiReferrals).where(eq29(gaAiReferrals.projectId, project.id)).run();
|
|
29516
29725
|
app.db.delete(gaSocialReferrals).where(eq29(gaSocialReferrals.projectId, project.id)).run();
|
|
29517
29726
|
const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
|
|
@@ -29576,7 +29785,8 @@ async function ga4Routes(app, opts) {
|
|
|
29576
29785
|
const WINDOW_KEYS = ["7d", "30d", "90d"];
|
|
29577
29786
|
const fetches = [
|
|
29578
29787
|
fetchAggregateSummary(accessToken, propertyId, days),
|
|
29579
|
-
...WINDOW_KEYS.map((w) => fetchWindowSummary(accessToken, propertyId, w))
|
|
29788
|
+
...WINDOW_KEYS.map((w) => fetchWindowSummary(accessToken, propertyId, w)),
|
|
29789
|
+
fetchDailyTotals(accessToken, propertyId, days)
|
|
29580
29790
|
];
|
|
29581
29791
|
if (syncTraffic) fetches.push(fetchTrafficByLandingPage(accessToken, propertyId, days));
|
|
29582
29792
|
if (syncAi) fetches.push(fetchAiReferrals(accessToken, propertyId, days));
|
|
@@ -29584,7 +29794,8 @@ async function ga4Routes(app, opts) {
|
|
|
29584
29794
|
const results = await Promise.all(fetches);
|
|
29585
29795
|
const summary = results[0];
|
|
29586
29796
|
const windowSummaries = results.slice(1, 1 + WINDOW_KEYS.length);
|
|
29587
|
-
|
|
29797
|
+
const dailyTotals = results[1 + WINDOW_KEYS.length];
|
|
29798
|
+
let idx = 2 + WINDOW_KEYS.length;
|
|
29588
29799
|
if (syncTraffic) {
|
|
29589
29800
|
rows = results[idx++];
|
|
29590
29801
|
}
|
|
@@ -29620,6 +29831,25 @@ async function ga4Routes(app, opts) {
|
|
|
29620
29831
|
}).run();
|
|
29621
29832
|
}
|
|
29622
29833
|
}
|
|
29834
|
+
tx.delete(gaDailyTotals).where(
|
|
29835
|
+
and23(
|
|
29836
|
+
eq29(gaDailyTotals.projectId, project.id),
|
|
29837
|
+
sql12`${gaDailyTotals.date} >= ${summary.periodStart}`,
|
|
29838
|
+
sql12`${gaDailyTotals.date} <= ${summary.periodEnd}`
|
|
29839
|
+
)
|
|
29840
|
+
).run();
|
|
29841
|
+
for (const row of dailyTotals) {
|
|
29842
|
+
tx.insert(gaDailyTotals).values({
|
|
29843
|
+
id: crypto24.randomUUID(),
|
|
29844
|
+
projectId: project.id,
|
|
29845
|
+
date: row.date,
|
|
29846
|
+
sessions: row.sessions,
|
|
29847
|
+
users: row.users,
|
|
29848
|
+
syncedAt: now,
|
|
29849
|
+
syncRunId: runId,
|
|
29850
|
+
createdAt: now
|
|
29851
|
+
}).run();
|
|
29852
|
+
}
|
|
29623
29853
|
if (syncAi) {
|
|
29624
29854
|
tx.delete(gaAiReferrals).where(
|
|
29625
29855
|
and23(
|
|
@@ -30116,12 +30346,18 @@ async function ga4Routes(app, opts) {
|
|
|
30116
30346
|
organicSessions: sql12`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
30117
30347
|
users: sql12`SUM(${gaTrafficSnapshots.users})`
|
|
30118
30348
|
}).from(gaTrafficSnapshots).where(and23(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
30119
|
-
|
|
30120
|
-
|
|
30121
|
-
|
|
30122
|
-
|
|
30123
|
-
|
|
30124
|
-
|
|
30349
|
+
const totalConditions = [eq29(gaDailyTotals.projectId, project.id)];
|
|
30350
|
+
if (cutoffDate) totalConditions.push(sql12`${gaDailyTotals.date} >= ${cutoffDate}`);
|
|
30351
|
+
const totals = app.db.select({ date: gaDailyTotals.date, users: gaDailyTotals.users }).from(gaDailyTotals).where(and23(...totalConditions)).all();
|
|
30352
|
+
return buildSessionHistory(
|
|
30353
|
+
rows.map((r) => ({
|
|
30354
|
+
date: r.date,
|
|
30355
|
+
sessions: r.sessions ?? 0,
|
|
30356
|
+
organicSessions: r.organicSessions ?? 0,
|
|
30357
|
+
users: r.users ?? 0
|
|
30358
|
+
})),
|
|
30359
|
+
totals
|
|
30360
|
+
);
|
|
30125
30361
|
});
|
|
30126
30362
|
app.get("/projects/:name/ga/coverage", async (request, _reply) => {
|
|
30127
30363
|
const project = resolveProject(app.db, request.params.name);
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
setTelemetrySource,
|
|
30
30
|
showFirstRunNotice,
|
|
31
31
|
trackEvent
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-DN5H2ZN7.js";
|
|
33
33
|
import {
|
|
34
34
|
CliError,
|
|
35
35
|
EXIT_SYSTEM_ERROR,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
saveConfig,
|
|
47
47
|
saveConfigPatch,
|
|
48
48
|
usageError
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-WA57WG6W.js";
|
|
50
50
|
import {
|
|
51
51
|
apiKeys,
|
|
52
52
|
createClient,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
projects,
|
|
55
55
|
queries,
|
|
56
56
|
renderReportHtml
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-YPRUBNUK.js";
|
|
58
58
|
import {
|
|
59
59
|
AdsOperationKinds,
|
|
60
60
|
AdsOperationStates,
|
|
@@ -95,7 +95,7 @@ import {
|
|
|
95
95
|
providerQuotaPolicySchema,
|
|
96
96
|
resolveProviderInput,
|
|
97
97
|
winnabilityClassSchema
|
|
98
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-RFLNTI7Y.js";
|
|
99
99
|
|
|
100
100
|
// src/cli.ts
|
|
101
101
|
import { pathToFileURL } from "url";
|
|
@@ -3763,10 +3763,16 @@ async function gaSessionHistory(project, opts) {
|
|
|
3763
3763
|
console.log(` ${"DATE".padEnd(dateWidth)} ${"SESSIONS".padEnd(10)}${"ORGANIC".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
3764
3764
|
console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
3765
3765
|
for (const row of result) {
|
|
3766
|
+
const users = row.usersSource === "deduplicated" ? String(row.users) : `${row.users}*`;
|
|
3766
3767
|
console.log(
|
|
3767
|
-
` ${row.date.padEnd(dateWidth)} ${String(row.sessions).padEnd(10)}${String(row.organicSessions).padEnd(10)}${
|
|
3768
|
+
` ${row.date.padEnd(dateWidth)} ${String(row.sessions).padEnd(10)}${String(row.organicSessions).padEnd(10)}${users.padEnd(8)}`
|
|
3768
3769
|
);
|
|
3769
3770
|
}
|
|
3771
|
+
if (result.some((row) => row.usersSource !== "deduplicated")) {
|
|
3772
|
+
console.log("\n * Visitor count is approximate \u2014 it adds up each page a visitor landed on,");
|
|
3773
|
+
console.log(" so someone who read several pages is counted more than once. Run");
|
|
3774
|
+
console.log(` "canonry ga sync ${project}" to replace these with exact counts.`);
|
|
3775
|
+
}
|
|
3770
3776
|
}
|
|
3771
3777
|
async function gaCoverage(project, format) {
|
|
3772
3778
|
const client = getClient6();
|