@socialgouv/matomo-postgres 1.5.2-beta.1 → 1.5.2-beta.3
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/dist/__tests__/importEvent.test.js +14 -0
- package/dist/__tests__/index.test.js +190 -0
- package/dist/__tests__/visit.json +103 -0
- package/dist/config.js +10 -0
- package/dist/db.js +24 -0
- package/dist/importDate.js +88 -0
- package/dist/importEvent.js +86 -0
- package/dist/index.js +82 -0
- package/dist/migrate-down.js +66 -0
- package/dist/migrate-latest.js +86 -0
- package/dist/migrations/20230301-01-initial.js +70 -0
- package/dist/migrations/20230301-02-indexes.js +105 -0
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
process.env.MATOMO_SITE = "42";
|
|
7
|
+
process.env.PROJECT_NAME = "some-project";
|
|
8
|
+
const visit_json_1 = __importDefault(require("./visit.json"));
|
|
9
|
+
const importEvent_1 = require("../importEvent");
|
|
10
|
+
test("getEventsFromMatomoVisit: should merge action events", () => {
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
const visits = (0, importEvent_1.getEventsFromMatomoVisit)(visit_json_1.default);
|
|
13
|
+
expect(visits).toMatchSnapshot();
|
|
14
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const mock_pgQuery = jest.fn();
|
|
3
|
+
const mock_matomoApi = jest.fn();
|
|
4
|
+
// import formatISO from "date-fns/formatISO";
|
|
5
|
+
// import addDays from "date-fns/addDays";
|
|
6
|
+
// import { INITIAL_OFFSET } from "../config";
|
|
7
|
+
process.env.MATOMO_SITE = "42";
|
|
8
|
+
process.env.PROJECT_NAME = "some-project";
|
|
9
|
+
// import matomoVisit from "./visit.json";
|
|
10
|
+
// import run from "../index";
|
|
11
|
+
// const NB_REQUEST_TO_INIT_DB = 3; // Number of query to init DB (createTable.js)
|
|
12
|
+
// const TEST_DATE = new Date();
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
//const isoDate = (date) => formatISO(date, { representation: "date" });
|
|
15
|
+
// jest.mock("pg", () => {
|
|
16
|
+
// class Client {
|
|
17
|
+
// escapeIdentifier(name) {
|
|
18
|
+
// return name;
|
|
19
|
+
// }
|
|
20
|
+
// end() {}
|
|
21
|
+
// connect() {
|
|
22
|
+
// return Promise.resolve();
|
|
23
|
+
// }
|
|
24
|
+
// query(...args) {
|
|
25
|
+
// return Promise.resolve(mock_pgQuery(...args));
|
|
26
|
+
// }
|
|
27
|
+
// }
|
|
28
|
+
// return {
|
|
29
|
+
// Client,
|
|
30
|
+
// };
|
|
31
|
+
// });
|
|
32
|
+
// jest.mock("piwik-client", () => {
|
|
33
|
+
// class MatomoClient {
|
|
34
|
+
// api(...args: any[]) {
|
|
35
|
+
// return mock_matomoApi(...args);
|
|
36
|
+
// }
|
|
37
|
+
// }
|
|
38
|
+
// return MatomoClient;
|
|
39
|
+
// });
|
|
40
|
+
test("test", () => { });
|
|
41
|
+
/*
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
jest.resetAllMocks();
|
|
45
|
+
jest.resetModules();
|
|
46
|
+
process.env.STARTDATE = "";
|
|
47
|
+
//process.env.OFFSET = "";
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("run: should create table", async () => {
|
|
51
|
+
mock_pgQuery.mockReturnValue({ rows: [] });
|
|
52
|
+
mock_matomoApi.mockImplementation((options, cb) => {
|
|
53
|
+
return cb(null, []);
|
|
54
|
+
});
|
|
55
|
+
await run();
|
|
56
|
+
expect(mock_pgQuery.mock.calls[0]).toMatchSnapshot();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("run: should fetch the latest event date if no date provided", async () => {
|
|
60
|
+
jest.useFakeTimers().setSystemTime(TEST_DATE.getTime());
|
|
61
|
+
mock_pgQuery.mockReturnValue({ rows: [] });
|
|
62
|
+
|
|
63
|
+
mock_matomoApi.mockImplementation((options, cb) => {
|
|
64
|
+
return cb(null, [
|
|
65
|
+
{
|
|
66
|
+
...matomoVisit,
|
|
67
|
+
idVisit: 123,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
...matomoVisit,
|
|
71
|
+
idVisit: 124,
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await run();
|
|
77
|
+
|
|
78
|
+
// check matomo requests
|
|
79
|
+
expect(mock_matomoApi.mock.calls[0][0].date).toEqual(isoDate(TEST_DATE));
|
|
80
|
+
expect(mock_matomoApi.mock.calls[0][0].filter_offset).toEqual(0);
|
|
81
|
+
|
|
82
|
+
// check db queries
|
|
83
|
+
expect(mock_pgQuery.mock.calls[NB_REQUEST_TO_INIT_DB][0]).toEqual(
|
|
84
|
+
"select action_timestamp from matomo order by action_timestamp desc limit 1"
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("run: should resume using latest event date - offset if no date provided", async () => {
|
|
89
|
+
jest.useFakeTimers().setSystemTime(TEST_DATE.getTime());
|
|
90
|
+
|
|
91
|
+
const LAST_EVENT_DATE_OFFSET = 2;
|
|
92
|
+
// @ts-ignore
|
|
93
|
+
const LAST_EVENT_DATE = addDays(TEST_DATE, -LAST_EVENT_DATE_OFFSET);
|
|
94
|
+
|
|
95
|
+
mock_pgQuery.mockReturnValue({ rows: [{ action_timestamp: LAST_EVENT_DATE.getTime() }] });
|
|
96
|
+
|
|
97
|
+
mock_matomoApi.mockImplementation((options, cb) => {
|
|
98
|
+
return cb(null, [
|
|
99
|
+
{
|
|
100
|
+
...matomoVisit,
|
|
101
|
+
idVisit: 123,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
...matomoVisit,
|
|
105
|
+
idVisit: 124,
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await run();
|
|
111
|
+
|
|
112
|
+
// check matomo requests
|
|
113
|
+
expect(mock_matomoApi.mock.calls[0][0].date).toEqual(
|
|
114
|
+
// @ts-ignore
|
|
115
|
+
isoDate(addDays(LAST_EVENT_DATE, -parseInt(INITIAL_OFFSET)))
|
|
116
|
+
);
|
|
117
|
+
expect(mock_matomoApi.mock.calls[0][0].filter_offset).toEqual(0);
|
|
118
|
+
|
|
119
|
+
const daysCount = LAST_EVENT_DATE_OFFSET + parseInt(INITIAL_OFFSET) + 1;
|
|
120
|
+
expect(mock_matomoApi.mock.calls.length).toEqual(daysCount);
|
|
121
|
+
|
|
122
|
+
// check db queries
|
|
123
|
+
expect(mock_pgQuery.mock.calls.length).toEqual(NB_REQUEST_TO_INIT_DB + 1 + daysCount * 7); // NB_REQUEST_TO_INIT_DB + select queries + days offset
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("run: should use today date if nothing in DB", async () => {
|
|
127
|
+
jest.useFakeTimers().setSystemTime(TEST_DATE.getTime());
|
|
128
|
+
mock_pgQuery.mockReturnValue({ rows: [] });
|
|
129
|
+
|
|
130
|
+
mock_matomoApi.mockImplementation((options, cb) => {
|
|
131
|
+
return cb(null, [
|
|
132
|
+
{
|
|
133
|
+
...matomoVisit,
|
|
134
|
+
idVisit: 123,
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await run();
|
|
140
|
+
|
|
141
|
+
// check matomo requests
|
|
142
|
+
expect(mock_matomoApi.mock.calls.length).toEqual(1);
|
|
143
|
+
expect(mock_matomoApi.mock.calls[0][0].date).toEqual(isoDate(TEST_DATE));
|
|
144
|
+
|
|
145
|
+
// check the 4 events inserted
|
|
146
|
+
expect(mock_pgQuery.mock.calls.length).toEqual(NB_REQUEST_TO_INIT_DB + 5); // NB_REQUEST_TO_INIT_DB + check date + latest + 2 inserts
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("run: should use given date if any", async () => {
|
|
150
|
+
jest.useFakeTimers().setSystemTime(TEST_DATE.getTime());
|
|
151
|
+
mock_pgQuery.mockReturnValue({ rows: [] });
|
|
152
|
+
|
|
153
|
+
mock_matomoApi.mockImplementation((options, cb) => {
|
|
154
|
+
return cb(null, [
|
|
155
|
+
{
|
|
156
|
+
...matomoVisit,
|
|
157
|
+
idVisit: 123,
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// @ts-ignore
|
|
163
|
+
await run(isoDate(addDays(TEST_DATE, -10)) + "T00:00:00.000Z");
|
|
164
|
+
|
|
165
|
+
expect(mock_matomoApi.mock.calls.length).toEqual(11);
|
|
166
|
+
expect(mock_pgQuery.mock.calls.length).toEqual(NB_REQUEST_TO_INIT_DB + 11 * 4); // NB_REQUEST_TO_INIT_DB + inserts. no initial select as date is provided
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("run: should use STARTDATE if any", async () => {
|
|
170
|
+
// @ts-ignore
|
|
171
|
+
process.env.STARTDATE = isoDate(addDays(TEST_DATE, -5)) + "T00:00:00.000Z";
|
|
172
|
+
jest.useFakeTimers().setSystemTime(TEST_DATE.getTime());
|
|
173
|
+
mock_pgQuery.mockReturnValue({ rows: [] });
|
|
174
|
+
|
|
175
|
+
mock_matomoApi.mockImplementation((options, cb) => {
|
|
176
|
+
return cb(null, [
|
|
177
|
+
{
|
|
178
|
+
...matomoVisit,
|
|
179
|
+
idVisit: 123,
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
await run();
|
|
185
|
+
|
|
186
|
+
expect(mock_matomoApi.mock.calls.length).toEqual(6);
|
|
187
|
+
|
|
188
|
+
expect(mock_pgQuery.mock.calls.length).toEqual(NB_REQUEST_TO_INIT_DB + 1 + 6 * 4); // NB_REQUEST_TO_INIT_DB + initial select + inserts.
|
|
189
|
+
});
|
|
190
|
+
*/
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"idVisit": "124",
|
|
3
|
+
"idSite": "42",
|
|
4
|
+
"country": "Argentine",
|
|
5
|
+
"operatingSystemName": "Mac",
|
|
6
|
+
"deviceBrand": "Inconnu",
|
|
7
|
+
"deviceModel": "Générique Bureau",
|
|
8
|
+
"visitDuration": "300",
|
|
9
|
+
"daysSinceFirstVisit": "23",
|
|
10
|
+
"actions": "2",
|
|
11
|
+
"visitorType": "returningCustomer",
|
|
12
|
+
"visitorId": "visitorId",
|
|
13
|
+
"referrerType": "referrerType",
|
|
14
|
+
"referrerName": "referrerName",
|
|
15
|
+
"siteName": "tests",
|
|
16
|
+
"userId": "24",
|
|
17
|
+
"region": "Buenos Aires",
|
|
18
|
+
"city": "Buenos Aires",
|
|
19
|
+
"dimension1": "guest",
|
|
20
|
+
"dimension3": "page",
|
|
21
|
+
"dimension6": "shop",
|
|
22
|
+
"dimension7": "v1.2.3",
|
|
23
|
+
"dimension8": "fr",
|
|
24
|
+
"dimension9": "light",
|
|
25
|
+
"dimension10": "36",
|
|
26
|
+
"firstActionTimestamp": 1629496512,
|
|
27
|
+
"actionDetails": [
|
|
28
|
+
{
|
|
29
|
+
"type": "event",
|
|
30
|
+
"url": "https://dive-shop.net/products/basic-wetsuit/",
|
|
31
|
+
"pageIdAction": "304",
|
|
32
|
+
"idpageview": "",
|
|
33
|
+
"serverTimePretty": "20 août 2021 21:35:18",
|
|
34
|
+
"pageId": "19696671",
|
|
35
|
+
"eventCategory": "Ecommerce",
|
|
36
|
+
"eventAction": "Cart change",
|
|
37
|
+
"timeSpent": 48,
|
|
38
|
+
"timeSpentPretty": "48s",
|
|
39
|
+
"pageviewPosition": "8",
|
|
40
|
+
"timestamp": 1629495318,
|
|
41
|
+
"icon": "plugins/Morpheus/images/event.png",
|
|
42
|
+
"iconSVG": "plugins/Morpheus/images/event.svg",
|
|
43
|
+
"title": "Evènement",
|
|
44
|
+
"subtitle": "Catégorie: \"Ecommerce', Action: \"Cart change\"",
|
|
45
|
+
"eventName": "added - Basic Wetsuit",
|
|
46
|
+
"eventValue": 1,
|
|
47
|
+
"dimension2": "julien",
|
|
48
|
+
"dimension4": "indonesia",
|
|
49
|
+
"dimension5": "diving",
|
|
50
|
+
"customVariables": {
|
|
51
|
+
"1": {
|
|
52
|
+
"customVariableName1": "page-author",
|
|
53
|
+
"customVariableValue1": "Julien"
|
|
54
|
+
},
|
|
55
|
+
"2": {
|
|
56
|
+
"customVariableName2": "post-age",
|
|
57
|
+
"customVariableValue2": "-430 days"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"type": "action",
|
|
63
|
+
"url": "https://dive-shop.net/products/diving-boots/",
|
|
64
|
+
"pageTitle": "Divezone Brand Diving Boots - Divezone Store",
|
|
65
|
+
"pageIdAction": "60",
|
|
66
|
+
"idpageview": "8CDIez",
|
|
67
|
+
"serverTimePretty": "20 août 2021 21:30:25",
|
|
68
|
+
"pageId": "19696664",
|
|
69
|
+
"timeSpent": "2",
|
|
70
|
+
"timeSpentPretty": "2s",
|
|
71
|
+
"pageviewPosition": "5",
|
|
72
|
+
"title": "Divezone Brand Diving Boots - Divezone Store",
|
|
73
|
+
"subtitle": "https://dive-shop.net/products/diving-boots/",
|
|
74
|
+
"icon": "",
|
|
75
|
+
"iconSVG": "plugins/Morpheus/images/action.svg",
|
|
76
|
+
"timestamp": 1629495025,
|
|
77
|
+
"dimension2": "julien",
|
|
78
|
+
"dimension4": "indonesia",
|
|
79
|
+
"dimension5": "diving"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"type": "search",
|
|
83
|
+
"url": "https://dive-shop.net/products/diving-boots/",
|
|
84
|
+
"pageTitle": "Divezone Brand Diving Boots - Divezone Store",
|
|
85
|
+
"pageIdAction": "60",
|
|
86
|
+
"idpageview": "8CDIez",
|
|
87
|
+
"serverTimePretty": "20 août 2021 21:30:25",
|
|
88
|
+
"pageId": "19696664",
|
|
89
|
+
"timeSpent": "2",
|
|
90
|
+
"timeSpentPretty": "2s",
|
|
91
|
+
"pageviewPosition": "5",
|
|
92
|
+
"title": "Divezone Brand Diving Boots - Divezone Store",
|
|
93
|
+
"subtitle": "https://dive-shop.net/products/diving-boots/",
|
|
94
|
+
"icon": "",
|
|
95
|
+
"iconSVG": "plugins/Morpheus/images/action.svg",
|
|
96
|
+
"timestamp": 1629495022,
|
|
97
|
+
"dimension2": "julien",
|
|
98
|
+
"dimension4": "indonesia",
|
|
99
|
+
"dimension5": "diving",
|
|
100
|
+
"siteSearchKeyword": "scuba"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
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 || "https://matomo.fabrique.social.gouv.fr/";
|
|
6
|
+
exports.MATOMO_SITE = process.env.MATOMO_SITE || 0;
|
|
7
|
+
exports.PGDATABASE = process.env.PGDATABASE || "";
|
|
8
|
+
exports.DESTINATION_TABLE = process.env.DESTINATION_TABLE || "matomo";
|
|
9
|
+
exports.INITIAL_OFFSET = process.env.INITIAL_OFFSET || "3";
|
|
10
|
+
exports.RESULTPERPAGE = process.env.RESULTPERPAGE || "500";
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.db = void 0;
|
|
7
|
+
const pg_1 = require("pg");
|
|
8
|
+
const kysely_1 = require("kysely");
|
|
9
|
+
const debug_1 = __importDefault(require("debug"));
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const debug = (0, debug_1.default)("db");
|
|
12
|
+
exports.db = new kysely_1.Kysely({
|
|
13
|
+
dialect: new kysely_1.PostgresDialect({
|
|
14
|
+
pool: new pg_1.Pool({
|
|
15
|
+
connectionString: config_1.PGDATABASE,
|
|
16
|
+
}),
|
|
17
|
+
}),
|
|
18
|
+
log(event) {
|
|
19
|
+
if (event.level === "query") {
|
|
20
|
+
// debug(event.query.sql);
|
|
21
|
+
// debug(event.query.parameters);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.importDate = void 0;
|
|
16
|
+
const p_all_1 = __importDefault(require("p-all"));
|
|
17
|
+
const debug_1 = __importDefault(require("debug"));
|
|
18
|
+
const formatISO_1 = __importDefault(require("date-fns/formatISO"));
|
|
19
|
+
const kysely_1 = require("kysely");
|
|
20
|
+
const db_1 = require("./db");
|
|
21
|
+
const importEvent_1 = require("./importEvent");
|
|
22
|
+
const config_1 = require("./config");
|
|
23
|
+
const debug = (0, debug_1.default)("importDate");
|
|
24
|
+
/** return date as ISO yyyy-mm-dd */
|
|
25
|
+
const isoDate = (date) => (0, formatISO_1.default)(date, { representation: "date" });
|
|
26
|
+
/** check how many visits complete for a given date */
|
|
27
|
+
const getRecordsCount = (date) => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
|
+
const result = yield db_1.db
|
|
29
|
+
.selectFrom(config_1.DESTINATION_TABLE)
|
|
30
|
+
.select(db_1.db.fn.count("idvisit").distinct().as("count"))
|
|
31
|
+
// UTC to be iso with matomo matomo data
|
|
32
|
+
.where((0, kysely_1.sql) `date(timezone('UTC', action_timestamp))`, "=", date)
|
|
33
|
+
.executeTakeFirst();
|
|
34
|
+
// start at previous visit in case action didnt finished to record
|
|
35
|
+
const count = Math.max(0, (result && parseInt(result.count) - 1) || 0);
|
|
36
|
+
return count;
|
|
37
|
+
});
|
|
38
|
+
/** import all event from givent date */
|
|
39
|
+
const importDate = (piwikApi, date, filterOffset = 0) => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
|
+
const limit = parseInt(config_1.RESULTPERPAGE);
|
|
41
|
+
const offset = filterOffset || (yield getRecordsCount(isoDate(date)));
|
|
42
|
+
if (!offset) {
|
|
43
|
+
debug(`${isoDate(date)}: load ${limit} visits`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
debug(`${isoDate(date)}: load ${limit} more visits after ${offset}`);
|
|
47
|
+
}
|
|
48
|
+
// fetch visits details
|
|
49
|
+
const visits = yield new Promise((resolve) => piwikApi({
|
|
50
|
+
method: "Live.getLastVisitsDetails",
|
|
51
|
+
period: "day",
|
|
52
|
+
date: isoDate(date),
|
|
53
|
+
// minTimestamp: isoDate(new Date()) === isoDate(date) ? date.getTime() / 1000 : undefined, // if today, dont go further (??)
|
|
54
|
+
filter_limit: limit,
|
|
55
|
+
filter_offset: offset,
|
|
56
|
+
filter_sort_order: "asc",
|
|
57
|
+
idSite: config_1.MATOMO_SITE,
|
|
58
|
+
}, (err, visits = []) => {
|
|
59
|
+
if (err) {
|
|
60
|
+
console.error("err", err);
|
|
61
|
+
resolve([]);
|
|
62
|
+
}
|
|
63
|
+
return resolve(visits);
|
|
64
|
+
}));
|
|
65
|
+
debug(`fetched ${visits.length} visits`);
|
|
66
|
+
// flatten all events
|
|
67
|
+
const eventsFromVisits = visits.flatMap(importEvent_1.getEventsFromMatomoVisit);
|
|
68
|
+
const allEvents = eventsFromVisits.filter((event) => {
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
if (!allEvents.length) {
|
|
72
|
+
debug(`no more valid events after ${isoDate(date)}`);
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
debug(`import ${allEvents.length} events`);
|
|
76
|
+
// serial-import events into PG
|
|
77
|
+
const importedEvents = yield (0, p_all_1.default)(allEvents.map((event) => () => (0, importEvent_1.importEvent)(event)), { concurrency: 10, stopOnError: true });
|
|
78
|
+
// continue to next page if necessary
|
|
79
|
+
if (visits.length === limit) {
|
|
80
|
+
const nextOffset = offset + limit;
|
|
81
|
+
const nextEvents = yield (0, exports.importDate)(piwikApi, date, nextOffset);
|
|
82
|
+
return [...importedEvents, ...(nextEvents || [])];
|
|
83
|
+
}
|
|
84
|
+
debug(`finished importing ${isoDate(date)}, offset ${offset}`);
|
|
85
|
+
return importedEvents || [];
|
|
86
|
+
});
|
|
87
|
+
exports.importDate = importDate;
|
|
88
|
+
module.exports = { importDate: exports.importDate };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getEventsFromMatomoVisit = exports.importEvent = void 0;
|
|
4
|
+
const config_1 = require("./config");
|
|
5
|
+
const db_1 = require("./db");
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {Client} client
|
|
9
|
+
* @param {import("types").Event} event
|
|
10
|
+
*
|
|
11
|
+
* @return {Promise<Record<"rows", any[]>>}
|
|
12
|
+
*/
|
|
13
|
+
const importEvent = (event) =>
|
|
14
|
+
// @ts-ignore // TODO
|
|
15
|
+
db_1.db
|
|
16
|
+
.insertInto(config_1.DESTINATION_TABLE)
|
|
17
|
+
.values([Object.assign({}, event)])
|
|
18
|
+
.onConflict((oc) => oc.doNothing())
|
|
19
|
+
.execute();
|
|
20
|
+
exports.importEvent = importEvent;
|
|
21
|
+
const matomoProps = [
|
|
22
|
+
"idSite",
|
|
23
|
+
"idVisit",
|
|
24
|
+
"actions",
|
|
25
|
+
"country",
|
|
26
|
+
"region",
|
|
27
|
+
"city",
|
|
28
|
+
"operatingSystemName",
|
|
29
|
+
"deviceModel",
|
|
30
|
+
"deviceBrand",
|
|
31
|
+
"visitDuration",
|
|
32
|
+
"daysSinceFirstVisit",
|
|
33
|
+
"visitorType",
|
|
34
|
+
"visitorId",
|
|
35
|
+
"referrerType",
|
|
36
|
+
"referrerName",
|
|
37
|
+
"siteName",
|
|
38
|
+
"userId",
|
|
39
|
+
];
|
|
40
|
+
/** @type Record<string, (a: import("types/matomo-api").ActionDetail) => string | number> */
|
|
41
|
+
const actionProps = {
|
|
42
|
+
action_type: (action) => action.type,
|
|
43
|
+
action_title: (action) => action.title,
|
|
44
|
+
action_eventcategory: (action) => action.eventCategory,
|
|
45
|
+
action_eventaction: (action) => action.eventAction,
|
|
46
|
+
action_eventname: (action) => action.eventName,
|
|
47
|
+
action_eventvalue: (action) => action.eventValue,
|
|
48
|
+
action_timespent: (action) => action.timeSpent,
|
|
49
|
+
action_timestamp: (action) => new Date(action.timestamp * 1000).toISOString(),
|
|
50
|
+
action_url: (action) => action.url,
|
|
51
|
+
sitesearchkeyword: (action) => action.siteSearchKeyword,
|
|
52
|
+
};
|
|
53
|
+
const getEventsFromMatomoVisit = (matomoVisit) => {
|
|
54
|
+
return matomoVisit.actionDetails.map((actionDetail, actionIndex) => {
|
|
55
|
+
const usercustomproperties = {};
|
|
56
|
+
for (let k = 1; k < 10; k++) {
|
|
57
|
+
const property = actionDetail.customVariables && actionDetail.customVariables[k];
|
|
58
|
+
if (!property)
|
|
59
|
+
continue; // max 10 custom variables
|
|
60
|
+
//@ts-ignore
|
|
61
|
+
usercustomproperties[property[`customVariableName${k}`]] = property[`customVariableValue${k}`];
|
|
62
|
+
}
|
|
63
|
+
/** @type {Record<string, string>} */
|
|
64
|
+
const usercustomdimensions = {};
|
|
65
|
+
for (let k = 1; k < 11; k++) {
|
|
66
|
+
const dimension = `dimension${k}`;
|
|
67
|
+
//@ts-ignore
|
|
68
|
+
const value = actionDetail[dimension] || matomoVisit[dimension];
|
|
69
|
+
if (!value)
|
|
70
|
+
continue; // max 10 custom variables
|
|
71
|
+
//@ts-ignore
|
|
72
|
+
usercustomdimensions[dimension] = value;
|
|
73
|
+
}
|
|
74
|
+
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) })), {
|
|
75
|
+
action_id: `${matomoVisit.idVisit}_${actionIndex}`,
|
|
76
|
+
})), {
|
|
77
|
+
// custom variables
|
|
78
|
+
usercustomproperties,
|
|
79
|
+
// custom dimensions
|
|
80
|
+
// We keep both for backwards compatibility.
|
|
81
|
+
// Current implementation is flat with one column for each dimension.
|
|
82
|
+
usercustomdimensions }), usercustomdimensions);
|
|
83
|
+
return event;
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
exports.getEventsFromMatomoVisit = getEventsFromMatomoVisit;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const kysely_1 = require("kysely");
|
|
16
|
+
const p_all_1 = __importDefault(require("p-all"));
|
|
17
|
+
const debug_1 = __importDefault(require("debug"));
|
|
18
|
+
const eachDayOfInterval_1 = __importDefault(require("date-fns/eachDayOfInterval"));
|
|
19
|
+
const piwik_client_1 = __importDefault(require("piwik-client"));
|
|
20
|
+
const db_1 = require("./db");
|
|
21
|
+
const config_1 = require("./config");
|
|
22
|
+
const importDate_1 = require("./importDate");
|
|
23
|
+
const debug = (0, debug_1.default)("index");
|
|
24
|
+
function run(date) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
debug("run, date=" + date);
|
|
27
|
+
const piwik = new piwik_client_1.default(config_1.MATOMO_URL, config_1.MATOMO_KEY);
|
|
28
|
+
// priority:
|
|
29
|
+
// - optional parameter date
|
|
30
|
+
// - last event in the table
|
|
31
|
+
// - optional env.STARTDATE
|
|
32
|
+
// - today
|
|
33
|
+
let referenceDate;
|
|
34
|
+
if (!referenceDate && date)
|
|
35
|
+
referenceDate = new Date(date);
|
|
36
|
+
if (!referenceDate)
|
|
37
|
+
referenceDate = yield findLastEventInMatomo(db_1.db);
|
|
38
|
+
if (!referenceDate && process.env.STARTDATE)
|
|
39
|
+
referenceDate = new Date(process.env.STARTDATE);
|
|
40
|
+
if (!referenceDate)
|
|
41
|
+
referenceDate = new Date(new Date().getTime() - +config_1.INITIAL_OFFSET * 24 * 60 * 60 * 1000);
|
|
42
|
+
const dates = (0, eachDayOfInterval_1.default)({
|
|
43
|
+
start: referenceDate,
|
|
44
|
+
end: new Date(new Date().getTime()),
|
|
45
|
+
});
|
|
46
|
+
debug(`import starting at : ${dates[0].toISOString()}`);
|
|
47
|
+
// for each date, serial-import data
|
|
48
|
+
const res = yield (0, p_all_1.default)(dates.map((date) => () => (0, importDate_1.importDate)(piwik.api.bind(piwik), date)), { concurrency: 1, stopOnError: true });
|
|
49
|
+
debug("close");
|
|
50
|
+
return res;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
exports.default = run;
|
|
54
|
+
if (require.main === module) {
|
|
55
|
+
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
56
|
+
if (!config_1.MATOMO_SITE)
|
|
57
|
+
return console.error("Missing env MATOMO_SITE");
|
|
58
|
+
if (!config_1.MATOMO_KEY)
|
|
59
|
+
return console.error("Missing env MATOMO_KEY");
|
|
60
|
+
if (!config_1.PGDATABASE)
|
|
61
|
+
return console.error("Missing env PGDATABASE");
|
|
62
|
+
yield run();
|
|
63
|
+
debug("run finished");
|
|
64
|
+
db_1.db.destroy();
|
|
65
|
+
}))();
|
|
66
|
+
}
|
|
67
|
+
function findLastEventInMatomo(db) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
const latest = yield db
|
|
70
|
+
.selectFrom(config_1.DESTINATION_TABLE)
|
|
71
|
+
.select((0, kysely_1.sql) `action_timestamp at time zone 'UTC'`.as("action_timestamp"))
|
|
72
|
+
.orderBy("action_timestamp", "desc")
|
|
73
|
+
.limit(1)
|
|
74
|
+
.executeTakeFirst();
|
|
75
|
+
if (latest) {
|
|
76
|
+
// check from the day before just to be sure we haev all events
|
|
77
|
+
const date = new Date(new Date(latest.action_timestamp).getTime() - 2 * 24 * 60 * 60 * 1000);
|
|
78
|
+
return date;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
const path = __importStar(require("path"));
|
|
36
|
+
const fs_1 = require("fs");
|
|
37
|
+
const kysely_1 = require("kysely");
|
|
38
|
+
const db_1 = require("./db");
|
|
39
|
+
function migrateDown() {
|
|
40
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
const migrator = new kysely_1.Migrator({
|
|
42
|
+
db: db_1.db,
|
|
43
|
+
provider: new kysely_1.FileMigrationProvider({
|
|
44
|
+
fs: fs_1.promises,
|
|
45
|
+
path,
|
|
46
|
+
migrationFolder: __dirname + "/migrations",
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
const { error, results } = yield migrator.migrateDown();
|
|
50
|
+
results === null || results === void 0 ? void 0 : results.forEach((it) => {
|
|
51
|
+
if (it.status === "Success") {
|
|
52
|
+
console.log(`down migration "${it.migrationName}" was executed successfully`);
|
|
53
|
+
}
|
|
54
|
+
else if (it.status === "Error") {
|
|
55
|
+
console.error(`failed to execute down migration "${it.migrationName}"`);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (error) {
|
|
59
|
+
console.error("failed to down migrate");
|
|
60
|
+
console.error(error);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
yield db_1.db.destroy();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
migrateDown();
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
const path = __importStar(require("path"));
|
|
36
|
+
const fs_1 = require("fs");
|
|
37
|
+
const kysely_1 = require("kysely");
|
|
38
|
+
const db_1 = require("./db");
|
|
39
|
+
const config_1 = require("./config");
|
|
40
|
+
function migrateToLatest() {
|
|
41
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
const extension = yield db_1.db
|
|
43
|
+
.selectFrom("pg_extension")
|
|
44
|
+
//@ts-ignore
|
|
45
|
+
.select("extname")
|
|
46
|
+
//@ts-ignore
|
|
47
|
+
.where("extname", "=", "pg_partman")
|
|
48
|
+
.executeTakeFirst();
|
|
49
|
+
if (extension) {
|
|
50
|
+
console.error("pg_partman extension detected; Skip migrations");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const migrator = new kysely_1.Migrator({
|
|
54
|
+
db: db_1.db,
|
|
55
|
+
provider: new kysely_1.FileMigrationProvider({
|
|
56
|
+
fs: fs_1.promises,
|
|
57
|
+
path,
|
|
58
|
+
migrationFolder: __dirname + "/migrations",
|
|
59
|
+
}),
|
|
60
|
+
// allow to have mutliple migratable instances in a single schema
|
|
61
|
+
migrationTableName: `${config_1.DESTINATION_TABLE}_migration`,
|
|
62
|
+
migrationLockTableName: `${config_1.DESTINATION_TABLE}_migration_lock`,
|
|
63
|
+
});
|
|
64
|
+
const { error, results } = yield migrator.migrateToLatest();
|
|
65
|
+
results === null || results === void 0 ? void 0 : results.forEach((it) => {
|
|
66
|
+
if (it.status === "Success") {
|
|
67
|
+
console.log(`migration "${it.migrationName}" was executed successfully`);
|
|
68
|
+
}
|
|
69
|
+
else if (it.status === "Error") {
|
|
70
|
+
console.error(`failed to execute migration "${it.migrationName}"`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
if (error) {
|
|
74
|
+
console.error("failed to migrate");
|
|
75
|
+
console.error(error);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (!(results === null || results === void 0 ? void 0 : results.length)) {
|
|
80
|
+
console.log("No migration to run");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
yield db_1.db.destroy();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
migrateToLatest();
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.down = exports.up = void 0;
|
|
13
|
+
const kysely_1 = require("kysely");
|
|
14
|
+
const DESTINATION_TABLE = process.env.DESTINATION_TABLE || "matomo";
|
|
15
|
+
function up(db) {
|
|
16
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
yield db.schema
|
|
18
|
+
.createTable(DESTINATION_TABLE)
|
|
19
|
+
.ifNotExists()
|
|
20
|
+
.addColumn("action_id", "text", (col) => col.unique().notNull())
|
|
21
|
+
.addColumn("idsite", "text")
|
|
22
|
+
.addColumn("idvisit", "text")
|
|
23
|
+
.addColumn("actions", "text")
|
|
24
|
+
.addColumn("country", "text")
|
|
25
|
+
.addColumn("region", "text")
|
|
26
|
+
.addColumn("city", "text")
|
|
27
|
+
.addColumn("operatingsystemname", "text")
|
|
28
|
+
.addColumn("devicemodel", "text")
|
|
29
|
+
.addColumn("devicebrand", "text")
|
|
30
|
+
.addColumn("visitduration", "text")
|
|
31
|
+
.addColumn("dayssincefirstvisit", "text")
|
|
32
|
+
.addColumn("visitortype", "text")
|
|
33
|
+
.addColumn("sitename", "text")
|
|
34
|
+
.addColumn("userid", "text")
|
|
35
|
+
.addColumn("serverdateprettyfirstaction", "date")
|
|
36
|
+
.addColumn("action_type", "text")
|
|
37
|
+
.addColumn("action_eventcategory", "text")
|
|
38
|
+
.addColumn("action_eventaction", "text")
|
|
39
|
+
.addColumn("action_eventname", "text")
|
|
40
|
+
.addColumn("action_eventvalue", "numeric")
|
|
41
|
+
.addColumn("action_timespent", "text")
|
|
42
|
+
.addColumn("action_timestamp", "timestamptz", (col) => col.defaultTo((0, kysely_1.sql) `now()`))
|
|
43
|
+
.addColumn("usercustomproperties", "json")
|
|
44
|
+
.addColumn("usercustomdimensions", "json")
|
|
45
|
+
.addColumn("dimension1", "text")
|
|
46
|
+
.addColumn("dimension2", "text")
|
|
47
|
+
.addColumn("dimension3", "text")
|
|
48
|
+
.addColumn("dimension4", "text")
|
|
49
|
+
.addColumn("dimension5", "text")
|
|
50
|
+
.addColumn("dimension6", "text")
|
|
51
|
+
.addColumn("dimension7", "text")
|
|
52
|
+
.addColumn("dimension8", "text")
|
|
53
|
+
.addColumn("dimension9", "text")
|
|
54
|
+
.addColumn("dimension10", "text")
|
|
55
|
+
.addColumn("action_url", "text")
|
|
56
|
+
.addColumn("sitesearchkeyword", "text")
|
|
57
|
+
.addColumn("action_title", "text")
|
|
58
|
+
.addColumn("visitorid", "text")
|
|
59
|
+
.addColumn("referrertype", "text")
|
|
60
|
+
.addColumn("referrername", "text")
|
|
61
|
+
.execute();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
exports.up = up;
|
|
65
|
+
function down(db) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
yield db.schema.dropTable(DESTINATION_TABLE).execute();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
exports.down = down;
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.down = exports.up = void 0;
|
|
13
|
+
const kysely_1 = require("kysely");
|
|
14
|
+
const DESTINATION_TABLE = process.env.DESTINATION_TABLE || "matomo";
|
|
15
|
+
const indexes = [
|
|
16
|
+
{
|
|
17
|
+
name: "idx_action_eventaction_matomo",
|
|
18
|
+
columns: ["action_eventaction"],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "idx_action_eventcategory_matomo",
|
|
22
|
+
columns: ["action_eventcategory"],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "idx_action_id",
|
|
26
|
+
columns: ["action_id"],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "idx_action_timestamp_matomo",
|
|
30
|
+
columns: ["action_timestamp"],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "idx_action_type_matomo",
|
|
34
|
+
columns: ["action_type"],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "idx_actionurl",
|
|
38
|
+
columns: ["action_url"],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "idx_dimension1",
|
|
42
|
+
columns: ["dimension1"],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "idx_dimension2",
|
|
46
|
+
columns: ["dimension2"],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "idx_dimension3",
|
|
50
|
+
columns: ["dimension3"],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "idx_dimension4",
|
|
54
|
+
columns: ["dimension4"],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "idx_dimension5",
|
|
58
|
+
columns: ["dimension5"],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "idx_idvisit_matomo",
|
|
62
|
+
columns: ["idvisit"],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "idx_region",
|
|
66
|
+
columns: ["region"],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "idx_userid",
|
|
70
|
+
columns: ["userid"],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "idx_visitorid",
|
|
74
|
+
columns: ["visitorid"],
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
function up(db) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
indexes.forEach((index) => __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
yield db.schema
|
|
81
|
+
.createIndex(index.name)
|
|
82
|
+
.ifNotExists()
|
|
83
|
+
.on(DESTINATION_TABLE)
|
|
84
|
+
.using("btree")
|
|
85
|
+
.columns(index.columns)
|
|
86
|
+
.execute();
|
|
87
|
+
}));
|
|
88
|
+
yield db.schema
|
|
89
|
+
.createIndex("actions_day")
|
|
90
|
+
.ifNotExists()
|
|
91
|
+
.on(DESTINATION_TABLE)
|
|
92
|
+
.expression((0, kysely_1.sql) `date(timezone('UTC', action_timestamp))`)
|
|
93
|
+
.execute();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
exports.up = up;
|
|
97
|
+
function down(db) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
indexes.forEach((index) => __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
yield db.schema.dropIndex(index.name).execute();
|
|
101
|
+
}));
|
|
102
|
+
db.schema.dropIndex("actions_day").execute();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
exports.down = down;
|
package/package.json
CHANGED