ga4-export-fixer 0.6.0 → 0.6.1-dev.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/package.json +1 -1
- package/tables/ga4EventsEnhanced/assertions/dailyQuality.js +33 -38
- package/utils.js +63 -29
package/package.json
CHANGED
|
@@ -88,48 +88,43 @@ raw_daily as (
|
|
|
88
88
|
${excludedEventsSQL}
|
|
89
89
|
and cast(event_date as date format 'YYYYMMDD') >= date_sub(current_date(), interval 5 day)
|
|
90
90
|
group by event_date, data_is_final
|
|
91
|
+
),
|
|
92
|
+
daily_comparison as (
|
|
93
|
+
select
|
|
94
|
+
coalesce(e.event_date, r.event_date) as event_date,
|
|
95
|
+
coalesce(e.data_is_final, r.data_is_final) as data_is_final,
|
|
96
|
+
e.session_count as enhanced_sessions,
|
|
97
|
+
r.session_count as raw_sessions,
|
|
98
|
+
e.event_count as enhanced_events,
|
|
99
|
+
r.event_count as raw_events,
|
|
100
|
+
round(e.total_item_revenue, 2) as enhanced_revenue,
|
|
101
|
+
round(r.total_item_revenue, 2) as raw_revenue
|
|
102
|
+
from
|
|
103
|
+
enhanced_daily e
|
|
104
|
+
full outer join
|
|
105
|
+
raw_daily r using(event_date, data_is_final)
|
|
91
106
|
)
|
|
92
107
|
select
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
when e.event_date is null and r.event_count > 0
|
|
103
|
-
then 'MISSING_DAY'
|
|
104
|
-
when coalesce(e.data_is_final, r.data_is_final) = true
|
|
105
|
-
and e.session_count != r.session_count
|
|
106
|
-
then 'SESSION_COUNT_MISMATCH'
|
|
107
|
-
when coalesce(e.data_is_final, r.data_is_final) = true
|
|
108
|
-
and e.event_count != r.event_count
|
|
109
|
-
then 'EVENT_COUNT_MISMATCH'
|
|
110
|
-
when coalesce(e.data_is_final, r.data_is_final) = true
|
|
111
|
-
and round(coalesce(e.total_item_revenue, 0), 2) != round(coalesce(r.total_item_revenue, 0), 2)
|
|
112
|
-
then 'REVENUE_MISMATCH'
|
|
113
|
-
when coalesce(e.data_is_final, r.data_is_final) = false
|
|
114
|
-
and coalesce(e.event_count, 0) > coalesce(r.event_count, 0)
|
|
115
|
-
then 'NON_FINAL_EXCESS_EVENTS'
|
|
116
|
-
end as violation_type
|
|
108
|
+
event_date,
|
|
109
|
+
data_is_final,
|
|
110
|
+
enhanced_sessions,
|
|
111
|
+
raw_sessions,
|
|
112
|
+
enhanced_events,
|
|
113
|
+
raw_events,
|
|
114
|
+
enhanced_revenue,
|
|
115
|
+
raw_revenue,
|
|
116
|
+
violation_type
|
|
117
117
|
from
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
daily_comparison,
|
|
119
|
+
unnest([
|
|
120
|
+
if(enhanced_events is null and raw_events > 0, 'MISSING_DAY', null),
|
|
121
|
+
if(data_is_final = true and enhanced_sessions != raw_sessions, 'SESSION_COUNT_MISMATCH', null),
|
|
122
|
+
if(data_is_final = true and enhanced_events != raw_events, 'EVENT_COUNT_MISMATCH', null),
|
|
123
|
+
if(data_is_final = true and enhanced_revenue != raw_revenue, 'REVENUE_MISMATCH', null),
|
|
124
|
+
if(data_is_final = false and coalesce(enhanced_events, 0) > coalesce(raw_events, 0), 'NON_FINAL_EXCESS_EVENTS', null)
|
|
125
|
+
]) as violation_type
|
|
121
126
|
where
|
|
122
|
-
|
|
123
|
-
e.session_count != r.session_count
|
|
124
|
-
or e.event_count != r.event_count
|
|
125
|
-
or round(coalesce(e.total_item_revenue, 0), 2) != round(coalesce(r.total_item_revenue, 0), 2)
|
|
126
|
-
or e.event_date is null
|
|
127
|
-
))
|
|
128
|
-
or
|
|
129
|
-
(e.event_date is null and r.event_count > 0)
|
|
130
|
-
or
|
|
131
|
-
(coalesce(e.data_is_final, r.data_is_final) = false
|
|
132
|
-
and coalesce(e.event_count, 0) > coalesce(r.event_count, 0))`;
|
|
127
|
+
violation_type is not null`;
|
|
133
128
|
};
|
|
134
129
|
|
|
135
130
|
/**
|
package/utils.js
CHANGED
|
@@ -33,53 +33,87 @@ const mergeUniqueArrays = (...arrays) => {
|
|
|
33
33
|
* @returns {string} Generated SQL query
|
|
34
34
|
*/
|
|
35
35
|
const queryBuilder = (steps) => {
|
|
36
|
+
const INDENT = 2;
|
|
37
|
+
const pad = ' '.repeat(INDENT);
|
|
38
|
+
|
|
39
|
+
// Re-indents a multi-line SQL fragment so that continuation lines
|
|
40
|
+
// (lines after the first) have a consistent base indentation.
|
|
41
|
+
// Preserves relative indentation within the fragment.
|
|
42
|
+
const reindent = (sql, targetIndent) => {
|
|
43
|
+
if (!sql.includes('\n')) return sql;
|
|
44
|
+
const lines = sql.split('\n');
|
|
45
|
+
const continuationLines = lines.slice(1).filter(l => l.trim());
|
|
46
|
+
if (continuationLines.length === 0) return sql;
|
|
47
|
+
const minIndent = Math.min(
|
|
48
|
+
...continuationLines.map(l => l.match(/^ */)[0].length)
|
|
49
|
+
);
|
|
50
|
+
const p = ' '.repeat(targetIndent);
|
|
51
|
+
return lines[0] + '\n' + lines.slice(1)
|
|
52
|
+
.map(l => l.trim() ? p + l.slice(minIndent) : '')
|
|
53
|
+
.join('\n');
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Shifts an entire SQL block right by the given number of spaces.
|
|
57
|
+
// Preserves relative indentation within the block.
|
|
58
|
+
const indentBlock = (sql, spaces) => {
|
|
59
|
+
const p = ' '.repeat(spaces);
|
|
60
|
+
return sql.split('\n')
|
|
61
|
+
.map(l => l.trim() ? p + l : '')
|
|
62
|
+
.join('\n');
|
|
63
|
+
};
|
|
64
|
+
|
|
36
65
|
// Helper function to turn step.columns into SQL string
|
|
37
66
|
const columnsToSQL = (columns) => {
|
|
38
67
|
return Object.entries(columns)
|
|
39
68
|
// exclude all columns that have been explicitly set to undefined
|
|
40
69
|
.filter(([key, value]) => value !== undefined)
|
|
41
70
|
.map(([key, value]) => {
|
|
71
|
+
let entry;
|
|
42
72
|
// if the key and value are the same, return the value as is (i.e. no alias)
|
|
43
73
|
if (key === value) {
|
|
44
|
-
|
|
45
|
-
}
|
|
74
|
+
entry = value;
|
|
46
75
|
// if the key starts with '[sql]', return the value as is (i.e. no alias)
|
|
47
|
-
if (key.startsWith('[sql]')) {
|
|
48
|
-
|
|
76
|
+
} else if (key.startsWith('[sql]')) {
|
|
77
|
+
entry = value;
|
|
78
|
+
} else {
|
|
79
|
+
entry = `${value} as ${key}`;
|
|
49
80
|
}
|
|
50
|
-
return
|
|
81
|
+
return reindent(entry, INDENT);
|
|
51
82
|
})
|
|
52
|
-
.join(',\n
|
|
83
|
+
.join(',\n' + pad);
|
|
53
84
|
};
|
|
54
85
|
|
|
55
86
|
const selectSQL = (step) => {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
${
|
|
87
|
+
const parts = [`select\n${pad}${columnsToSQL(step.columns)}`];
|
|
88
|
+
parts.push(`from\n${pad}${step.from}`);
|
|
89
|
+
|
|
90
|
+
if (step.leftJoin) {
|
|
91
|
+
step.leftJoin.forEach(join => {
|
|
92
|
+
parts.push(`left join\n${pad}${join.table} ${join.condition}`);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (step.where) {
|
|
97
|
+
parts.push(`where\n${pad}${reindent(step.where, INDENT)}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (step.groupBy) {
|
|
101
|
+
parts.push(`group by\n${pad}${step.groupBy.join(', ')}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return parts.join('\n');
|
|
67
105
|
};
|
|
68
106
|
|
|
69
|
-
let sql = "";
|
|
70
107
|
if (steps.length === 1) {
|
|
71
|
-
|
|
72
|
-
const step = steps[0];
|
|
73
|
-
sql = selectSQL(step);
|
|
74
|
-
} else {
|
|
75
|
-
// Multiple steps, all but last are CTEs
|
|
76
|
-
const ctes = steps.slice(0, -1).map(step => {
|
|
77
|
-
return `${step.name} as (${selectSQL(step)})`;
|
|
78
|
-
});
|
|
79
|
-
const lastStep = steps[steps.length - 1];
|
|
80
|
-
sql = `with ${ctes.join(',\n ')}\n${selectSQL(lastStep)}`;
|
|
108
|
+
return selectSQL(steps[0]);
|
|
81
109
|
}
|
|
82
|
-
|
|
110
|
+
|
|
111
|
+
const ctes = steps.slice(0, -1).map(step => {
|
|
112
|
+
const body = indentBlock(selectSQL(step), INDENT);
|
|
113
|
+
return `${step.name} as (\n${body}\n)`;
|
|
114
|
+
});
|
|
115
|
+
const lastStep = steps[steps.length - 1];
|
|
116
|
+
return `with ${ctes.join(',\n')}\n${selectSQL(lastStep)}`;
|
|
83
117
|
};
|
|
84
118
|
|
|
85
119
|
/**
|