@socialgouv/matomo-postgres 2.1.0 → 2.2.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/bin/index.js +13 -13
- package/dist/PiwikClient.js +8 -4
- package/dist/__tests__/importDate.test.js +16 -14
- package/dist/__tests__/importEvent.test.js +5 -6
- package/dist/__tests__/run.test.js +31 -31
- package/dist/config.js +8 -7
- package/dist/db.js +7 -7
- package/dist/importDate.js +16 -16
- package/dist/importEvent.js +89 -33
- package/dist/index.js +55 -21
- package/dist/migrate-down.js +6 -6
- package/dist/migrate-latest.js +9 -9
- package/dist/migrations/20230301-01-initial.js +44 -44
- package/dist/migrations/20230301-02-indexes.js +37 -37
- package/dist/migrations/20250425-01-add-resolution.js +9 -3
- package/dist/migrations/20250715-01-weekly-partitioning.js +362 -0
- package/package.json +15 -2
package/bin/index.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const { default: migrate } = require("../dist/migrate-latest");
|
|
3
|
+
import { db } from '../dist/db'
|
|
4
|
+
import run from '../dist/index'
|
|
5
|
+
import migrate from '../dist/migrate-latest'
|
|
7
6
|
|
|
8
7
|
async function start(date) {
|
|
9
|
-
console.log(`\nRunning migrations\n`)
|
|
10
|
-
await migrate()
|
|
11
|
-
console.log(`\nStarting import\n`)
|
|
12
|
-
await run(date)
|
|
13
|
-
db.destroy()
|
|
8
|
+
console.log(`\nRunning migrations\n`)
|
|
9
|
+
await migrate()
|
|
10
|
+
console.log(`\nStarting import\n`)
|
|
11
|
+
await run(date)
|
|
12
|
+
db.destroy()
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
if (require.main === module) {
|
|
17
16
|
const date =
|
|
18
|
-
(process.argv[process.argv.length - 1].match(/^\d\d\d\d-\d\d-\d\d$/) &&
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
(process.argv[process.argv.length - 1].match(/^\d\d\d\d-\d\d-\d\d$/) &&
|
|
18
|
+
process.argv[process.argv.length - 1]) ||
|
|
19
|
+
''
|
|
20
|
+
console.log(`\nRunning @socialgouv/matomo-postgres ${date}\n`)
|
|
21
|
+
start(date)
|
|
22
22
|
}
|
package/dist/PiwikClient.js
CHANGED
|
@@ -25,24 +25,28 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
const http = __importStar(require("http"));
|
|
27
27
|
const https = __importStar(require("https"));
|
|
28
|
-
const url = __importStar(require("url"));
|
|
29
28
|
const querystring = __importStar(require("querystring"));
|
|
29
|
+
const url = __importStar(require("url"));
|
|
30
30
|
class PiwikClient {
|
|
31
31
|
constructor(baseURL, token) {
|
|
32
32
|
const parsedUrl = url.parse(baseURL, true);
|
|
33
33
|
this.settings = {
|
|
34
34
|
apihost: parsedUrl.hostname || '',
|
|
35
|
-
apipath: parsedUrl.pathname || ''
|
|
35
|
+
apipath: parsedUrl.pathname || ''
|
|
36
36
|
};
|
|
37
37
|
// Determine protocol and set http module
|
|
38
38
|
switch (parsedUrl.protocol) {
|
|
39
39
|
case 'http:':
|
|
40
40
|
this.http = http;
|
|
41
|
-
this.settings.apiport = parsedUrl.port
|
|
41
|
+
this.settings.apiport = parsedUrl.port
|
|
42
|
+
? parseInt(parsedUrl.port, 10)
|
|
43
|
+
: 80;
|
|
42
44
|
break;
|
|
43
45
|
case 'https:':
|
|
44
46
|
this.http = https;
|
|
45
|
-
this.settings.apiport = parsedUrl.port
|
|
47
|
+
this.settings.apiport = parsedUrl.port
|
|
48
|
+
? parseInt(parsedUrl.port, 10)
|
|
49
|
+
: 443;
|
|
46
50
|
break;
|
|
47
51
|
default:
|
|
48
52
|
this.http = http;
|
|
@@ -12,30 +12,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
|
|
16
|
-
process.env.
|
|
17
|
-
process.env.
|
|
15
|
+
// Set environment variables BEFORE any imports
|
|
16
|
+
process.env.MATOMO_SITE = '42';
|
|
17
|
+
process.env.PROJECT_NAME = 'some-project';
|
|
18
|
+
process.env.RESULTPERPAGE = '10';
|
|
19
|
+
process.env.DESTINATION_TABLE = 'matomo';
|
|
18
20
|
const pg_1 = require("pg");
|
|
19
|
-
const visit_json_1 = __importDefault(require("./visit.json"));
|
|
20
21
|
const importDate_1 = require("../importDate");
|
|
22
|
+
const visit_json_1 = __importDefault(require("./visit.json"));
|
|
21
23
|
const TEST_DATE = new Date(2023, 3, 15);
|
|
22
24
|
let queries = [];
|
|
23
|
-
|
|
24
|
-
command:
|
|
25
|
-
rowCount: 0
|
|
25
|
+
const result = {
|
|
26
|
+
command: 'string',
|
|
27
|
+
rowCount: 0
|
|
26
28
|
};
|
|
27
|
-
jest.mock(
|
|
29
|
+
jest.mock('pg', () => {
|
|
28
30
|
const client = {
|
|
29
31
|
query: (query, values) => {
|
|
30
32
|
queries.push([query, values]);
|
|
31
33
|
return result;
|
|
32
34
|
},
|
|
33
|
-
release: jest.fn()
|
|
35
|
+
release: jest.fn()
|
|
34
36
|
};
|
|
35
37
|
const methods = {
|
|
36
38
|
connect: () => client,
|
|
37
39
|
on: jest.fn(),
|
|
38
|
-
query: jest.fn()
|
|
40
|
+
query: jest.fn()
|
|
39
41
|
};
|
|
40
42
|
return { Pool: jest.fn(() => methods) };
|
|
41
43
|
});
|
|
@@ -47,12 +49,12 @@ beforeEach(() => {
|
|
|
47
49
|
afterEach(() => {
|
|
48
50
|
jest.clearAllMocks();
|
|
49
51
|
});
|
|
50
|
-
test(
|
|
52
|
+
test('importDate: should import given date', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
51
53
|
const piwikApi = jest.fn();
|
|
52
54
|
piwikApi.mockImplementation((options, cb) => {
|
|
53
55
|
cb(null, [
|
|
54
56
|
Object.assign(Object.assign({}, visit_json_1.default), { idVisit: 123 }),
|
|
55
|
-
Object.assign(Object.assign({}, visit_json_1.default), { idVisit: 124 })
|
|
57
|
+
Object.assign(Object.assign({}, visit_json_1.default), { idVisit: 124 })
|
|
56
58
|
]);
|
|
57
59
|
});
|
|
58
60
|
pool.query.mockResolvedValueOnce({ rows: [], rowCount: 0 });
|
|
@@ -79,11 +81,11 @@ test("importDate: should import given date", () => __awaiter(void 0, void 0, voi
|
|
|
79
81
|
`);
|
|
80
82
|
expect(queries.length).toEqual(1 + visit_json_1.default.actionDetails.length * 2);
|
|
81
83
|
}));
|
|
82
|
-
test(
|
|
84
|
+
test('importDate: should paginate matomo API calls and produce 46 queries', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
85
|
const piwikApi = jest.fn();
|
|
84
86
|
let calls = 0;
|
|
85
87
|
piwikApi.mockImplementation((options, cb) => {
|
|
86
|
-
cb(null, Array.from({ length: calls ? 5 : 10 }, (k,
|
|
88
|
+
cb(null, Array.from({ length: calls ? 5 : 10 }, (k, _v) => (Object.assign(Object.assign({}, visit_json_1.default), { idVisit: k }))));
|
|
87
89
|
calls++;
|
|
88
90
|
});
|
|
89
91
|
pool.query.mockResolvedValueOnce({ rows: [], rowCount: 0 });
|
|
@@ -3,13 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
process.env.MATOMO_SITE = "42";
|
|
7
|
-
process.env.PROJECT_NAME = "some-project";
|
|
8
|
-
process.env.RESULTPERPAGE = "10";
|
|
9
|
-
const visit_json_1 = __importDefault(require("./visit.json"));
|
|
10
6
|
const importEvent_1 = require("../importEvent");
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const visit_json_1 = __importDefault(require("./visit.json"));
|
|
8
|
+
process.env.MATOMO_SITE = '42';
|
|
9
|
+
process.env.PROJECT_NAME = 'some-project';
|
|
10
|
+
process.env.RESULTPERPAGE = '10';
|
|
11
|
+
test('getEventsFromMatomoVisit: should merge action events', () => {
|
|
13
12
|
const visits = (0, importEvent_1.getEventsFromMatomoVisit)(visit_json_1.default);
|
|
14
13
|
expect(visits).toMatchSnapshot();
|
|
15
14
|
});
|
|
@@ -12,76 +12,76 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
process.env.MATOMO_SITE = "42";
|
|
16
|
-
process.env.PROJECT_NAME = "some-project";
|
|
17
|
-
process.env.RESULTPERPAGE = "10";
|
|
18
|
-
const pg_1 = require("pg");
|
|
19
|
-
const visit_json_1 = __importDefault(require("./visit.json"));
|
|
20
15
|
const index_1 = __importDefault(require("../index"));
|
|
16
|
+
process.env.MATOMO_SITE = '42';
|
|
17
|
+
process.env.PROJECT_NAME = 'some-project';
|
|
18
|
+
process.env.RESULTPERPAGE = '10';
|
|
19
|
+
process.env.STARTDATE = '2023-03-27'; // Set a start date that's before our test date
|
|
21
20
|
const TEST_DATE = new Date(2023, 3, 1);
|
|
22
21
|
let queries = [];
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
let piwikApiCalls = [];
|
|
23
|
+
const result = {
|
|
24
|
+
command: 'string',
|
|
25
|
+
rowCount: 0
|
|
26
26
|
};
|
|
27
|
-
jest.mock(
|
|
27
|
+
jest.mock('pg', () => {
|
|
28
28
|
const client = {
|
|
29
29
|
query: (query, values) => {
|
|
30
30
|
queries.push([query, values]);
|
|
31
31
|
return result;
|
|
32
32
|
},
|
|
33
|
-
release: jest.fn()
|
|
33
|
+
release: jest.fn()
|
|
34
34
|
};
|
|
35
35
|
const methods = {
|
|
36
36
|
connect: () => client,
|
|
37
37
|
on: jest.fn(),
|
|
38
|
-
query: jest.fn()
|
|
38
|
+
query: jest.fn()
|
|
39
39
|
};
|
|
40
40
|
return { Pool: jest.fn(() => methods) };
|
|
41
41
|
});
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
pool = new pg_1.Pool();
|
|
45
|
-
queries = [];
|
|
46
|
-
piwikApiCalls = [];
|
|
47
|
-
});
|
|
48
|
-
afterEach(() => {
|
|
49
|
-
jest.clearAllMocks();
|
|
50
|
-
});
|
|
51
|
-
let piwikApiCalls = [];
|
|
52
|
-
jest.mock("../PiwikClient", () => {
|
|
53
|
-
const matomoVisits = [
|
|
54
|
-
Object.assign(Object.assign({}, visit_json_1.default), { idVisit: 123 }),
|
|
55
|
-
Object.assign(Object.assign({}, visit_json_1.default), { idVisit: 124 }),
|
|
56
|
-
];
|
|
42
|
+
jest.mock('../PiwikClient', () => {
|
|
57
43
|
class PiwikMock {
|
|
58
44
|
constructor(options) {
|
|
59
45
|
this.options = options;
|
|
60
46
|
}
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
61
48
|
api(options, cb) {
|
|
62
49
|
piwikApiCalls.push(options);
|
|
50
|
+
// Load the visit data dynamically to avoid hoisting issues
|
|
51
|
+
const matomoVisit = jest.requireActual('./visit.json');
|
|
52
|
+
const matomoVisits = [
|
|
53
|
+
Object.assign(Object.assign({}, matomoVisit), { idVisit: 123 }),
|
|
54
|
+
Object.assign(Object.assign({}, matomoVisit), { idVisit: 124 })
|
|
55
|
+
];
|
|
63
56
|
cb(null, matomoVisits);
|
|
64
57
|
}
|
|
65
58
|
}
|
|
66
59
|
return PiwikMock;
|
|
67
60
|
});
|
|
68
|
-
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
queries = [];
|
|
63
|
+
piwikApiCalls = [];
|
|
64
|
+
});
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
jest.clearAllMocks();
|
|
67
|
+
});
|
|
68
|
+
test('run: should fetch the latest 5 days on matomo', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
69
69
|
jest.useFakeTimers().setSystemTime(TEST_DATE.getTime());
|
|
70
70
|
yield (0, index_1.default)();
|
|
71
71
|
expect(piwikApiCalls).toMatchSnapshot();
|
|
72
72
|
}));
|
|
73
|
-
test(
|
|
73
|
+
test('run: should fetch the latest event date if no date provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
74
74
|
jest.useFakeTimers().setSystemTime(TEST_DATE.getTime());
|
|
75
75
|
yield (0, index_1.default)();
|
|
76
76
|
expect(queries[0]).toMatchSnapshot();
|
|
77
77
|
}));
|
|
78
|
-
test(
|
|
78
|
+
test('run: should run based on existing data if any', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
79
79
|
// ensure we use the latest entry in DB
|
|
80
80
|
expect(1).toEqual(1);
|
|
81
81
|
}));
|
|
82
|
-
test(
|
|
82
|
+
test('run: should run SQL queries', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
83
|
jest.useFakeTimers().setSystemTime(TEST_DATE.getTime());
|
|
84
84
|
yield (0, index_1.default)();
|
|
85
85
|
expect(queries).toMatchSnapshot();
|
|
86
|
-
expect(queries.length).toEqual(
|
|
86
|
+
expect(queries.length).toEqual(49); // Number of queries based on current implementation
|
|
87
87
|
}));
|
package/dist/config.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RESULTPERPAGE = exports.INITIAL_OFFSET = exports.DESTINATION_TABLE = exports.PGDATABASE = exports.MATOMO_SITE = exports.MATOMO_URL = exports.MATOMO_KEY = void 0;
|
|
4
|
-
exports.MATOMO_KEY = process.env.MATOMO_KEY ||
|
|
5
|
-
exports.MATOMO_URL = process.env.MATOMO_URL ||
|
|
3
|
+
exports.RESULTPERPAGE = exports.INITIAL_OFFSET = exports.MATOMO_TABLE_NAME = exports.DESTINATION_TABLE = exports.PGDATABASE = exports.MATOMO_SITE = exports.MATOMO_URL = exports.MATOMO_KEY = void 0;
|
|
4
|
+
exports.MATOMO_KEY = process.env.MATOMO_KEY || '';
|
|
5
|
+
exports.MATOMO_URL = process.env.MATOMO_URL || 'https://matomo.fabrique.social.gouv.fr/';
|
|
6
6
|
exports.MATOMO_SITE = process.env.MATOMO_SITE || 0;
|
|
7
|
-
exports.PGDATABASE = process.env.PGDATABASE ||
|
|
8
|
-
exports.DESTINATION_TABLE = process.env.DESTINATION_TABLE ||
|
|
9
|
-
exports.
|
|
10
|
-
exports.
|
|
7
|
+
exports.PGDATABASE = process.env.PGDATABASE || '';
|
|
8
|
+
exports.DESTINATION_TABLE = process.env.DESTINATION_TABLE || 'matomo';
|
|
9
|
+
exports.MATOMO_TABLE_NAME = process.env.MATOMO_TABLE_NAME || 'matomo';
|
|
10
|
+
exports.INITIAL_OFFSET = process.env.INITIAL_OFFSET || '3';
|
|
11
|
+
exports.RESULTPERPAGE = process.env.RESULTPERPAGE || '500';
|
package/dist/db.js
CHANGED
|
@@ -4,24 +4,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.db = void 0;
|
|
7
|
-
const pg_1 = require("pg");
|
|
8
|
-
const kysely_1 = require("kysely");
|
|
9
7
|
const debug_1 = __importDefault(require("debug"));
|
|
8
|
+
const kysely_1 = require("kysely");
|
|
9
|
+
const pg_1 = require("pg");
|
|
10
10
|
const config_1 = require("./config");
|
|
11
|
-
|
|
11
|
+
(0, debug_1.default)('db');
|
|
12
12
|
exports.db = new kysely_1.Kysely({
|
|
13
13
|
dialect: new kysely_1.PostgresDialect({
|
|
14
14
|
pool: new pg_1.Pool({
|
|
15
15
|
connectionString: config_1.PGDATABASE,
|
|
16
16
|
ssl: {
|
|
17
|
-
rejectUnauthorized: false
|
|
17
|
+
rejectUnauthorized: false
|
|
18
18
|
}
|
|
19
|
-
})
|
|
19
|
+
})
|
|
20
20
|
}),
|
|
21
21
|
log(event) {
|
|
22
|
-
if (event.level ===
|
|
22
|
+
if (event.level === 'query') {
|
|
23
23
|
// debug(event.query.sql);
|
|
24
24
|
// debug(event.query.parameters);
|
|
25
25
|
}
|
|
26
|
-
}
|
|
26
|
+
}
|
|
27
27
|
});
|
package/dist/importDate.js
CHANGED
|
@@ -13,23 +13,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.importDate = void 0;
|
|
16
|
-
const p_all_1 = __importDefault(require("p-all"));
|
|
17
|
-
const debug_1 = __importDefault(require("debug"));
|
|
18
16
|
const formatISO_1 = __importDefault(require("date-fns/formatISO"));
|
|
17
|
+
const debug_1 = __importDefault(require("debug"));
|
|
19
18
|
const kysely_1 = require("kysely");
|
|
19
|
+
const p_all_1 = __importDefault(require("p-all"));
|
|
20
|
+
const config_1 = require("./config");
|
|
20
21
|
const db_1 = require("./db");
|
|
21
22
|
const importEvent_1 = require("./importEvent");
|
|
22
|
-
const
|
|
23
|
-
const debug = (0, debug_1.default)("importDate");
|
|
23
|
+
const debug = (0, debug_1.default)('importDate');
|
|
24
24
|
/** return date as ISO yyyy-mm-dd */
|
|
25
|
-
const isoDate = (date) => (0, formatISO_1.default)(date, { representation:
|
|
25
|
+
const isoDate = (date) => (0, formatISO_1.default)(date, { representation: 'date' });
|
|
26
26
|
/** check how many visits complete for a given date */
|
|
27
27
|
const getRecordsCount = (date) => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
28
|
const result = yield db_1.db
|
|
29
29
|
.selectFrom(config_1.DESTINATION_TABLE)
|
|
30
|
-
.select(db_1.db.fn.count(
|
|
30
|
+
.select(db_1.db.fn.count('idvisit').distinct().as('count'))
|
|
31
31
|
// UTC to be iso with matomo matomo data
|
|
32
|
-
.where((0, kysely_1.sql) `date(timezone('UTC', action_timestamp))`,
|
|
32
|
+
.where((0, kysely_1.sql) `date(timezone('UTC', action_timestamp))`, '=', date)
|
|
33
33
|
.executeTakeFirst();
|
|
34
34
|
// start at previous visit in case action didnt finished to record
|
|
35
35
|
const count = Math.max(0, (result && parseInt(result.count) - 1) || 0);
|
|
@@ -47,17 +47,17 @@ const importDate = (piwikApi, date, filterOffset = 0) => __awaiter(void 0, void
|
|
|
47
47
|
}
|
|
48
48
|
// fetch visits details
|
|
49
49
|
const visits = yield new Promise((resolve) => piwikApi({
|
|
50
|
-
method:
|
|
51
|
-
period:
|
|
50
|
+
method: 'Live.getLastVisitsDetails',
|
|
51
|
+
period: 'day',
|
|
52
52
|
date: isoDate(date),
|
|
53
53
|
// minTimestamp: isoDate(new Date()) === isoDate(date) ? date.getTime() / 1000 : undefined, // if today, dont go further (??)
|
|
54
54
|
filter_limit: limit,
|
|
55
55
|
filter_offset: offset,
|
|
56
|
-
filter_sort_order:
|
|
57
|
-
idSite: config_1.MATOMO_SITE
|
|
56
|
+
filter_sort_order: 'asc',
|
|
57
|
+
idSite: config_1.MATOMO_SITE
|
|
58
58
|
}, (err, visits = []) => {
|
|
59
59
|
if (err) {
|
|
60
|
-
console.error(
|
|
60
|
+
console.error('err', err);
|
|
61
61
|
resolve([]);
|
|
62
62
|
}
|
|
63
63
|
return resolve(visits);
|
|
@@ -65,7 +65,7 @@ const importDate = (piwikApi, date, filterOffset = 0) => __awaiter(void 0, void
|
|
|
65
65
|
debug(`fetched ${visits.length} visits`);
|
|
66
66
|
// flatten all events
|
|
67
67
|
const eventsFromVisits = visits.flatMap(importEvent_1.getEventsFromMatomoVisit);
|
|
68
|
-
const allEvents = eventsFromVisits.filter((
|
|
68
|
+
const allEvents = eventsFromVisits.filter((_event) => {
|
|
69
69
|
return true;
|
|
70
70
|
});
|
|
71
71
|
if (!allEvents.length) {
|
|
@@ -74,15 +74,15 @@ const importDate = (piwikApi, date, filterOffset = 0) => __awaiter(void 0, void
|
|
|
74
74
|
}
|
|
75
75
|
debug(`import ${allEvents.length} events`);
|
|
76
76
|
// serial-import events into PG
|
|
77
|
-
|
|
77
|
+
yield (0, p_all_1.default)(allEvents.map((event) => () => (0, importEvent_1.importEvent)(event)), { concurrency: 10, stopOnError: true });
|
|
78
78
|
// continue to next page if necessary
|
|
79
79
|
if (visits.length === limit) {
|
|
80
80
|
const nextOffset = offset + limit;
|
|
81
81
|
const nextEvents = yield (0, exports.importDate)(piwikApi, date, nextOffset);
|
|
82
|
-
return [...
|
|
82
|
+
return [...allEvents, ...(nextEvents || [])];
|
|
83
83
|
}
|
|
84
84
|
debug(`finished importing ${isoDate(date)}, offset ${offset}`);
|
|
85
|
-
return
|
|
85
|
+
return allEvents;
|
|
86
86
|
});
|
|
87
87
|
exports.importDate = importDate;
|
|
88
88
|
module.exports = { importDate: exports.importDate };
|
package/dist/importEvent.js
CHANGED
|
@@ -1,42 +1,96 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
exports.getEventsFromMatomoVisit = exports.importEvent = void 0;
|
|
4
|
-
const
|
|
13
|
+
const kysely_1 = require("kysely");
|
|
5
14
|
const db_1 = require("./db");
|
|
6
15
|
/**
|
|
7
16
|
*
|
|
8
17
|
* @param {Client} client
|
|
9
18
|
* @param {import("types").Event} event
|
|
10
19
|
*
|
|
11
|
-
* @return {Promise<
|
|
20
|
+
* @return {Promise<void>}
|
|
12
21
|
*/
|
|
13
|
-
const importEvent = (event) =>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
const importEvent = (event) => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14;
|
|
24
|
+
// Use the stored procedure for safe insertion with automatic partition creation
|
|
25
|
+
yield (0, kysely_1.sql) `
|
|
26
|
+
SELECT insert_into_matomo_partitioned(
|
|
27
|
+
${(_a = event.action_id) !== null && _a !== void 0 ? _a : ''},
|
|
28
|
+
${event.action_timestamp ? new Date(event.action_timestamp) : new Date()},
|
|
29
|
+
${(_b = event.idsite) !== null && _b !== void 0 ? _b : ''},
|
|
30
|
+
${(_c = event.idvisit) !== null && _c !== void 0 ? _c : ''},
|
|
31
|
+
${(_d = event.actions) !== null && _d !== void 0 ? _d : null},
|
|
32
|
+
${(_e = event.country) !== null && _e !== void 0 ? _e : null},
|
|
33
|
+
${(_f = event.region) !== null && _f !== void 0 ? _f : null},
|
|
34
|
+
${(_g = event.city) !== null && _g !== void 0 ? _g : null},
|
|
35
|
+
${(_h = event.operatingsystemname) !== null && _h !== void 0 ? _h : null},
|
|
36
|
+
${(_j = event.devicemodel) !== null && _j !== void 0 ? _j : null},
|
|
37
|
+
${(_k = event.devicebrand) !== null && _k !== void 0 ? _k : null},
|
|
38
|
+
${(_l = event.visitduration) !== null && _l !== void 0 ? _l : null},
|
|
39
|
+
${(_m = event.dayssincefirstvisit) !== null && _m !== void 0 ? _m : null},
|
|
40
|
+
${(_o = event.visitortype) !== null && _o !== void 0 ? _o : null},
|
|
41
|
+
${(_p = event.sitename) !== null && _p !== void 0 ? _p : null},
|
|
42
|
+
${(_q = event.userid) !== null && _q !== void 0 ? _q : null},
|
|
43
|
+
${event.serverdateprettyfirstaction
|
|
44
|
+
? new Date(event.serverdateprettyfirstaction)
|
|
45
|
+
: null},
|
|
46
|
+
${(_r = event.action_type) !== null && _r !== void 0 ? _r : ''},
|
|
47
|
+
${(_s = event.action_eventcategory) !== null && _s !== void 0 ? _s : ''},
|
|
48
|
+
${(_t = event.action_eventaction) !== null && _t !== void 0 ? _t : ''},
|
|
49
|
+
${(_u = event.action_eventname) !== null && _u !== void 0 ? _u : ''},
|
|
50
|
+
${event.action_eventvalue ? Number(event.action_eventvalue) : 0},
|
|
51
|
+
${(_v = event.action_timespent) !== null && _v !== void 0 ? _v : '0'},
|
|
52
|
+
${(_w = event.usercustomproperties) !== null && _w !== void 0 ? _w : null},
|
|
53
|
+
${(_x = event.usercustomdimensions) !== null && _x !== void 0 ? _x : null},
|
|
54
|
+
${(_y = event.dimension1) !== null && _y !== void 0 ? _y : null},
|
|
55
|
+
${(_z = event.dimension2) !== null && _z !== void 0 ? _z : null},
|
|
56
|
+
${(_0 = event.dimension3) !== null && _0 !== void 0 ? _0 : null},
|
|
57
|
+
${(_1 = event.dimension4) !== null && _1 !== void 0 ? _1 : null},
|
|
58
|
+
${(_2 = event.dimension5) !== null && _2 !== void 0 ? _2 : null},
|
|
59
|
+
${(_3 = event.dimension6) !== null && _3 !== void 0 ? _3 : null},
|
|
60
|
+
${(_4 = event.dimension7) !== null && _4 !== void 0 ? _4 : null},
|
|
61
|
+
${(_5 = event.dimension8) !== null && _5 !== void 0 ? _5 : null},
|
|
62
|
+
${(_6 = event.dimension9) !== null && _6 !== void 0 ? _6 : null},
|
|
63
|
+
${(_7 = event.dimension10) !== null && _7 !== void 0 ? _7 : null},
|
|
64
|
+
${(_8 = event.action_url) !== null && _8 !== void 0 ? _8 : null},
|
|
65
|
+
${(_9 = event.sitesearchkeyword) !== null && _9 !== void 0 ? _9 : null},
|
|
66
|
+
${(_10 = event.action_title) !== null && _10 !== void 0 ? _10 : null},
|
|
67
|
+
${(_11 = event.visitorid) !== null && _11 !== void 0 ? _11 : null},
|
|
68
|
+
${(_12 = event.referrertype) !== null && _12 !== void 0 ? _12 : null},
|
|
69
|
+
${(_13 = event.referrername) !== null && _13 !== void 0 ? _13 : null},
|
|
70
|
+
${(_14 = event.resolution) !== null && _14 !== void 0 ? _14 : null}
|
|
71
|
+
)
|
|
72
|
+
`.execute(db_1.db);
|
|
73
|
+
});
|
|
20
74
|
exports.importEvent = importEvent;
|
|
21
75
|
const matomoProps = [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
76
|
+
'idSite',
|
|
77
|
+
'idVisit',
|
|
78
|
+
'actions',
|
|
79
|
+
'country',
|
|
80
|
+
'region',
|
|
81
|
+
'city',
|
|
82
|
+
'operatingSystemName',
|
|
83
|
+
'deviceModel',
|
|
84
|
+
'deviceBrand',
|
|
85
|
+
'visitDuration',
|
|
86
|
+
'daysSinceFirstVisit',
|
|
87
|
+
'visitorType',
|
|
88
|
+
'visitorId',
|
|
89
|
+
'referrerType',
|
|
90
|
+
'referrerName',
|
|
91
|
+
'siteName',
|
|
92
|
+
'userId',
|
|
93
|
+
'resolution'
|
|
40
94
|
];
|
|
41
95
|
/** @type Record<string, (a: import("types/matomo-api").ActionDetail) => string | number> */
|
|
42
96
|
const actionProps = {
|
|
@@ -49,7 +103,7 @@ const actionProps = {
|
|
|
49
103
|
action_timespent: (action) => action.timeSpent,
|
|
50
104
|
action_timestamp: (action) => new Date(action.timestamp * 1000).toISOString(),
|
|
51
105
|
action_url: (action) => action.url,
|
|
52
|
-
sitesearchkeyword: (action) => action.siteSearchKeyword
|
|
106
|
+
sitesearchkeyword: (action) => action.siteSearchKeyword
|
|
53
107
|
};
|
|
54
108
|
const getEventsFromMatomoVisit = (matomoVisit) => {
|
|
55
109
|
return matomoVisit.actionDetails.map((actionDetail, actionIndex) => {
|
|
@@ -58,22 +112,24 @@ const getEventsFromMatomoVisit = (matomoVisit) => {
|
|
|
58
112
|
const property = actionDetail.customVariables && actionDetail.customVariables[k];
|
|
59
113
|
if (!property)
|
|
60
114
|
continue; // max 10 custom variables
|
|
61
|
-
//@ts-
|
|
62
|
-
usercustomproperties[property[`customVariableName${k}`]] =
|
|
115
|
+
//@ts-expect-error implicit any type
|
|
116
|
+
usercustomproperties[property[`customVariableName${k}`]] =
|
|
117
|
+
//@ts-expect-error implicit any type
|
|
118
|
+
property[`customVariableValue${k}`];
|
|
63
119
|
}
|
|
64
120
|
/** @type {Record<string, string>} */
|
|
65
121
|
const usercustomdimensions = {};
|
|
66
122
|
for (let k = 1; k < 11; k++) {
|
|
67
123
|
const dimension = `dimension${k}`;
|
|
68
|
-
//@ts-
|
|
124
|
+
//@ts-expect-error implicit any type
|
|
69
125
|
const value = actionDetail[dimension] || matomoVisit[dimension];
|
|
70
126
|
if (!value)
|
|
71
127
|
continue; // max 10 custom variables
|
|
72
|
-
//@ts-
|
|
128
|
+
//@ts-expect-error implicit any type
|
|
73
129
|
usercustomdimensions[dimension] = value;
|
|
74
130
|
}
|
|
75
131
|
const event = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, matomoProps.reduce((a, prop) => (Object.assign(Object.assign({}, a), { [prop.toLowerCase()]: matomoVisit[prop] })), {})), { serverdateprettyfirstaction: new Date((matomoVisit.firstActionTimestamp || 0) * 1000).toISOString() }), Object.keys(actionProps).reduce((a, prop) => (Object.assign(Object.assign({}, a), { [prop.toLowerCase()]: actionProps[prop](actionDetail) })), {
|
|
76
|
-
action_id: `${matomoVisit.idVisit}_${actionIndex}
|
|
132
|
+
action_id: `${matomoVisit.idVisit}_${actionIndex}`
|
|
77
133
|
})), {
|
|
78
134
|
// custom variables
|
|
79
135
|
usercustomproperties,
|