@socialgouv/matomo-postgres 2.2.0 → 2.2.2
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/README.md +199 -32
- package/bin/index.js +10 -12
- package/dist/PiwikClient.js +5 -31
- package/dist/__tests__/importDate.test.js +49 -44
- package/dist/__tests__/importEvent.test.js +3 -8
- package/dist/__tests__/run.test.js +22 -19
- package/dist/config.js +11 -11
- package/dist/db.js +10 -15
- package/dist/importDate.js +19 -27
- package/dist/importEvent.js +127 -59
- package/dist/index.js +25 -44
- package/dist/migrate-down.js +10 -35
- package/dist/migrate-latest.js +46 -62
- package/dist/migrations/20230301-01-initial.js +4 -9
- package/dist/migrations/20230301-02-indexes.js +4 -9
- package/dist/migrations/20250425-01-add-resolution.js +18 -11
- package/dist/migrations/20250715-01-weekly-partitioning.js +28 -31
- package/dist/migrations/20250908-01-convention-analysis-index.js +29 -0
- package/package.json +6 -6
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
2
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
3
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -8,11 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
8
|
});
|
|
10
9
|
};
|
|
11
|
-
|
|
12
|
-
exports.down = exports.up = void 0;
|
|
13
|
-
const kysely_1 = require("kysely");
|
|
10
|
+
import { sql } from 'kysely';
|
|
14
11
|
const MATOMO_TABLE_NAME = process.env.MATOMO_TABLE_NAME || 'matomo';
|
|
15
|
-
function up(db) {
|
|
12
|
+
export function up(db) {
|
|
16
13
|
return __awaiter(this, void 0, void 0, function* () {
|
|
17
14
|
yield db.schema
|
|
18
15
|
.createTable(MATOMO_TABLE_NAME)
|
|
@@ -39,7 +36,7 @@ function up(db) {
|
|
|
39
36
|
.addColumn('action_eventname', 'text')
|
|
40
37
|
.addColumn('action_eventvalue', 'numeric')
|
|
41
38
|
.addColumn('action_timespent', 'text')
|
|
42
|
-
.addColumn('action_timestamp', 'timestamptz', (col) => col.defaultTo(
|
|
39
|
+
.addColumn('action_timestamp', 'timestamptz', (col) => col.defaultTo(sql `now()`))
|
|
43
40
|
.addColumn('usercustomproperties', 'json')
|
|
44
41
|
.addColumn('usercustomdimensions', 'json')
|
|
45
42
|
.addColumn('dimension1', 'text')
|
|
@@ -61,10 +58,8 @@ function up(db) {
|
|
|
61
58
|
.execute();
|
|
62
59
|
});
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
function down(db) {
|
|
61
|
+
export function down(db) {
|
|
66
62
|
return __awaiter(this, void 0, void 0, function* () {
|
|
67
63
|
yield db.schema.dropTable(MATOMO_TABLE_NAME).execute();
|
|
68
64
|
});
|
|
69
65
|
}
|
|
70
|
-
exports.down = down;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
2
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
3
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -8,9 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
8
|
});
|
|
10
9
|
};
|
|
11
|
-
|
|
12
|
-
exports.down = exports.up = void 0;
|
|
13
|
-
const kysely_1 = require("kysely");
|
|
10
|
+
import { sql } from 'kysely';
|
|
14
11
|
const MATOMO_TABLE_NAME = process.env.MATOMO_TABLE_NAME || 'matomo';
|
|
15
12
|
const indexes = [
|
|
16
13
|
{
|
|
@@ -74,7 +71,7 @@ const indexes = [
|
|
|
74
71
|
columns: ['visitorid']
|
|
75
72
|
}
|
|
76
73
|
];
|
|
77
|
-
function up(db) {
|
|
74
|
+
export function up(db) {
|
|
78
75
|
return __awaiter(this, void 0, void 0, function* () {
|
|
79
76
|
indexes.forEach((index) => __awaiter(this, void 0, void 0, function* () {
|
|
80
77
|
yield db.schema
|
|
@@ -89,12 +86,11 @@ function up(db) {
|
|
|
89
86
|
.createIndex('actions_day')
|
|
90
87
|
.ifNotExists()
|
|
91
88
|
.on(MATOMO_TABLE_NAME)
|
|
92
|
-
.expression(
|
|
89
|
+
.expression(sql `date(timezone('UTC', action_timestamp))`)
|
|
93
90
|
.execute();
|
|
94
91
|
});
|
|
95
92
|
}
|
|
96
|
-
|
|
97
|
-
function down(db) {
|
|
93
|
+
export function down(db) {
|
|
98
94
|
return __awaiter(this, void 0, void 0, function* () {
|
|
99
95
|
indexes.forEach((index) => __awaiter(this, void 0, void 0, function* () {
|
|
100
96
|
yield db.schema.dropIndex(index.name).execute();
|
|
@@ -102,4 +98,3 @@ function down(db) {
|
|
|
102
98
|
db.schema.dropIndex('actions_day').execute();
|
|
103
99
|
});
|
|
104
100
|
}
|
|
105
|
-
exports.down = down;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
2
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
3
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -8,19 +7,28 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
8
|
});
|
|
10
9
|
};
|
|
11
|
-
|
|
12
|
-
exports.down = exports.up = void 0;
|
|
10
|
+
import { sql } from 'kysely';
|
|
13
11
|
const MATOMO_TABLE_NAME = process.env.MATOMO_TABLE_NAME || 'matomo';
|
|
14
|
-
function up(db) {
|
|
12
|
+
export function up(db) {
|
|
15
13
|
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
// Check if the column already exists before trying to add it
|
|
15
|
+
const columnExists = yield sql `
|
|
16
|
+
SELECT EXISTS (
|
|
17
|
+
SELECT 1
|
|
18
|
+
FROM information_schema.columns
|
|
19
|
+
WHERE table_name = ${MATOMO_TABLE_NAME}
|
|
20
|
+
AND column_name = 'resolution'
|
|
21
|
+
) as exists
|
|
22
|
+
`.execute(db);
|
|
23
|
+
if (!columnExists.rows[0].exists) {
|
|
24
|
+
yield db.schema
|
|
25
|
+
.alterTable(MATOMO_TABLE_NAME)
|
|
26
|
+
.addColumn('resolution', 'text')
|
|
27
|
+
.execute();
|
|
28
|
+
}
|
|
20
29
|
});
|
|
21
30
|
}
|
|
22
|
-
|
|
23
|
-
function down(db) {
|
|
31
|
+
export function down(db) {
|
|
24
32
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
33
|
yield db.schema
|
|
26
34
|
.alterTable(MATOMO_TABLE_NAME)
|
|
@@ -28,4 +36,3 @@ function down(db) {
|
|
|
28
36
|
.execute();
|
|
29
37
|
});
|
|
30
38
|
}
|
|
31
|
-
exports.down = down;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
2
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
3
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -8,15 +7,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
8
|
});
|
|
10
9
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const PARTITIONED_MATOMO_TABLE_NAME = process.env.PARTITIONED_MATOMO_TABLE_NAME || 'matomo_partitioned';
|
|
15
|
-
function up(db) {
|
|
10
|
+
import { sql } from 'kysely';
|
|
11
|
+
import { PARTITIONED_MATOMO_TABLE_NAME } from '../config';
|
|
12
|
+
export function up(db) {
|
|
16
13
|
return __awaiter(this, void 0, void 0, function* () {
|
|
17
14
|
// First, create the partitioned table structure as a partitioned table
|
|
18
|
-
yield
|
|
19
|
-
CREATE TABLE ${
|
|
15
|
+
yield sql `
|
|
16
|
+
CREATE TABLE ${sql.id(`${PARTITIONED_MATOMO_TABLE_NAME}`)} (
|
|
20
17
|
action_id text NOT NULL,
|
|
21
18
|
idsite text,
|
|
22
19
|
idvisit text,
|
|
@@ -62,13 +59,13 @@ function up(db) {
|
|
|
62
59
|
) PARTITION BY RANGE (action_timestamp);
|
|
63
60
|
`.execute(db);
|
|
64
61
|
// Add unique constraint that includes partition key
|
|
65
|
-
yield
|
|
66
|
-
ALTER TABLE ${
|
|
67
|
-
ADD CONSTRAINT ${
|
|
62
|
+
yield sql `
|
|
63
|
+
ALTER TABLE ${sql.id(`${PARTITIONED_MATOMO_TABLE_NAME}`)}
|
|
64
|
+
ADD CONSTRAINT ${sql.id(`${PARTITIONED_MATOMO_TABLE_NAME}_action_id_timestamp_unique`)}
|
|
68
65
|
UNIQUE (action_id, action_timestamp)
|
|
69
66
|
`.execute(db);
|
|
70
67
|
// Create function for automatic weekly partition creation
|
|
71
|
-
yield
|
|
68
|
+
yield sql `
|
|
72
69
|
CREATE OR REPLACE FUNCTION create_weekly_partition_if_not_exists(table_name text, partition_date timestamptz)
|
|
73
70
|
RETURNS void AS $$
|
|
74
71
|
DECLARE
|
|
@@ -102,23 +99,23 @@ function up(db) {
|
|
|
102
99
|
$$ LANGUAGE plpgsql;
|
|
103
100
|
`.execute(db);
|
|
104
101
|
// Create trigger function that automatically creates partitions on insert
|
|
105
|
-
yield
|
|
106
|
-
CREATE OR REPLACE FUNCTION ${
|
|
102
|
+
yield sql `
|
|
103
|
+
CREATE OR REPLACE FUNCTION ${sql.id(`${PARTITIONED_MATOMO_TABLE_NAME}_partition_trigger`)}()
|
|
107
104
|
RETURNS trigger AS $$
|
|
108
105
|
BEGIN
|
|
109
|
-
PERFORM create_weekly_partition_if_not_exists('${
|
|
106
|
+
PERFORM create_weekly_partition_if_not_exists('${sql.raw(PARTITIONED_MATOMO_TABLE_NAME)}', NEW.action_timestamp);
|
|
110
107
|
RETURN NEW;
|
|
111
108
|
END;
|
|
112
109
|
$$ LANGUAGE plpgsql;
|
|
113
110
|
`.execute(db);
|
|
114
111
|
// Create trigger that fires before insert
|
|
115
|
-
yield
|
|
116
|
-
CREATE TRIGGER ${
|
|
117
|
-
BEFORE INSERT ON ${
|
|
118
|
-
FOR EACH ROW EXECUTE FUNCTION ${
|
|
112
|
+
yield sql `
|
|
113
|
+
CREATE TRIGGER ${sql.id(`${PARTITIONED_MATOMO_TABLE_NAME}_auto_partition`)}
|
|
114
|
+
BEFORE INSERT ON ${sql.id(`${PARTITIONED_MATOMO_TABLE_NAME}`)}
|
|
115
|
+
FOR EACH ROW EXECUTE FUNCTION ${sql.id(`${PARTITIONED_MATOMO_TABLE_NAME}_partition_trigger`)}();
|
|
119
116
|
`.execute(db);
|
|
120
117
|
// Create stored procedure for safe insertion with automatic partition creation
|
|
121
|
-
yield
|
|
118
|
+
yield sql `
|
|
122
119
|
CREATE OR REPLACE FUNCTION insert_into_matomo_partitioned(
|
|
123
120
|
p_action_id text,
|
|
124
121
|
p_action_timestamp timestamptz,
|
|
@@ -165,14 +162,14 @@ function up(db) {
|
|
|
165
162
|
)
|
|
166
163
|
RETURNS void
|
|
167
164
|
LANGUAGE plpgsql
|
|
168
|
-
SECURITY
|
|
165
|
+
SECURITY INVOKER
|
|
169
166
|
AS $$
|
|
170
167
|
BEGIN
|
|
171
168
|
-- Ensure partition exists for the given timestamp
|
|
172
|
-
PERFORM create_weekly_partition_if_not_exists('${
|
|
169
|
+
PERFORM create_weekly_partition_if_not_exists('${sql.raw(PARTITIONED_MATOMO_TABLE_NAME)}', p_action_timestamp);
|
|
173
170
|
|
|
174
171
|
-- Insert the data with conflict handling
|
|
175
|
-
INSERT INTO ${
|
|
172
|
+
INSERT INTO ${sql.id(PARTITIONED_MATOMO_TABLE_NAME)} (
|
|
176
173
|
action_id,
|
|
177
174
|
action_timestamp,
|
|
178
175
|
idsite,
|
|
@@ -343,20 +340,20 @@ function up(db) {
|
|
|
343
340
|
yield db.schema
|
|
344
341
|
.createIndex(`actions_day_${PARTITIONED_MATOMO_TABLE_NAME}`)
|
|
345
342
|
.on(PARTITIONED_MATOMO_TABLE_NAME)
|
|
346
|
-
.expression(
|
|
343
|
+
.expression(sql `date(timezone('UTC', action_timestamp))`)
|
|
347
344
|
.execute();
|
|
348
345
|
});
|
|
349
346
|
}
|
|
350
|
-
|
|
351
|
-
function down(db) {
|
|
347
|
+
export function down(db) {
|
|
352
348
|
return __awaiter(this, void 0, void 0, function* () {
|
|
353
349
|
// Drop trigger and function
|
|
354
|
-
|
|
355
|
-
yield
|
|
356
|
-
|
|
357
|
-
yield
|
|
350
|
+
const trigger_name = `${PARTITIONED_MATOMO_TABLE_NAME}_auto_partition`;
|
|
351
|
+
yield sql `DROP TRIGGER IF EXISTS ${sql.id(trigger_name)} ON ${sql.id(PARTITIONED_MATOMO_TABLE_NAME)}`.execute(db);
|
|
352
|
+
const function_name = `${PARTITIONED_MATOMO_TABLE_NAME}_partition_trigger`;
|
|
353
|
+
yield sql `DROP FUNCTION IF EXISTS ${sql.id(function_name)}()`.execute(db);
|
|
354
|
+
yield sql `DROP FUNCTION IF EXISTS create_weekly_partition_if_not_exists(text, timestamptz)`.execute(db);
|
|
355
|
+
yield sql `DROP FUNCTION IF EXISTS insert_into_matomo_partitioned`.execute(db);
|
|
358
356
|
// Drop the partitioned table (this will also drop all partitions)
|
|
359
357
|
yield db.schema.dropTable(PARTITIONED_MATOMO_TABLE_NAME).ifExists().execute();
|
|
360
358
|
});
|
|
361
359
|
}
|
|
362
|
-
exports.down = down;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { sql } from 'kysely';
|
|
11
|
+
const PARTITIONED_MATOMO_TABLE_NAME = process.env.PARTITIONED_MATOMO_TABLE_NAME || 'matomo_partitioned';
|
|
12
|
+
export function up(db) {
|
|
13
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
14
|
+
// Create conditional index for convention collective analysis
|
|
15
|
+
yield sql `
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_convention_analysis_matomo_partitioned
|
|
17
|
+
ON ${sql.id(PARTITIONED_MATOMO_TABLE_NAME)} (action_type, action_url, action_timestamp)
|
|
18
|
+
WHERE action_url LIKE 'https://code.travail.gouv.fr/convention-collective/%'
|
|
19
|
+
`.execute(db);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function down(db) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
// Drop the conditional index
|
|
25
|
+
yield sql `
|
|
26
|
+
DROP INDEX IF EXISTS idx_convention_analysis_matomo_partitioned
|
|
27
|
+
`.execute(db);
|
|
28
|
+
});
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socialgouv/matomo-postgres",
|
|
3
3
|
"description": "Extract visitor events from Matomo API and push to Postgres",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.2",
|
|
5
5
|
"types": "types/index.d.ts",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
|
+
"type": "module",
|
|
12
13
|
"bin": {
|
|
13
14
|
"matomo-postgres": "./bin/index.js"
|
|
14
15
|
},
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"@eslint/js": "^9.31.0",
|
|
42
43
|
"@types/debug": "^4.1.7",
|
|
43
44
|
"@types/jest": "^29.4.0",
|
|
44
|
-
"@types/node": "^
|
|
45
|
+
"@types/node": "^22.0.0",
|
|
45
46
|
"@types/pg": "^8.6.6",
|
|
46
47
|
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
|
47
48
|
"@typescript-eslint/parser": "^8.37.0",
|
|
@@ -50,11 +51,10 @@
|
|
|
50
51
|
"eslint-plugin-prettier": "^5.5.1",
|
|
51
52
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
52
53
|
"globals": "^16.3.0",
|
|
53
|
-
"jest": "^29.
|
|
54
|
-
"knip": "^5.61.3",
|
|
54
|
+
"jest": "^29.7.0",
|
|
55
55
|
"prettier": "^3.6.2",
|
|
56
|
-
"ts-jest": "^29.
|
|
56
|
+
"ts-jest": "^29.4.1",
|
|
57
57
|
"ts-node": "^10.9.1",
|
|
58
|
-
"typescript": "^
|
|
58
|
+
"typescript": "^5.0.0"
|
|
59
59
|
}
|
|
60
60
|
}
|