@red-hat-developer-hub/backstage-plugin-adoption-insights-backend 0.0.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +180 -0
  3. package/config.d.ts +47 -0
  4. package/dist/controllers/EventApiController.cjs.js +122 -0
  5. package/dist/controllers/EventApiController.cjs.js.map +1 -0
  6. package/dist/database/DatabaseFactory.cjs.js +21 -0
  7. package/dist/database/DatabaseFactory.cjs.js.map +1 -0
  8. package/dist/database/adapters/BaseAdapter.cjs.js +276 -0
  9. package/dist/database/adapters/BaseAdapter.cjs.js.map +1 -0
  10. package/dist/database/adapters/PostgresAdapter.cjs.js +92 -0
  11. package/dist/database/adapters/PostgresAdapter.cjs.js.map +1 -0
  12. package/dist/database/adapters/SqliteAdapter.cjs.js +61 -0
  13. package/dist/database/adapters/SqliteAdapter.cjs.js.map +1 -0
  14. package/dist/database/migration.cjs.js +19 -0
  15. package/dist/database/migration.cjs.js.map +1 -0
  16. package/dist/database/partition.cjs.js +40 -0
  17. package/dist/database/partition.cjs.js.map +1 -0
  18. package/dist/domain/EventBatchProcessor.cjs.js +87 -0
  19. package/dist/domain/EventBatchProcessor.cjs.js.map +1 -0
  20. package/dist/index.cjs.js +10 -0
  21. package/dist/index.cjs.js.map +1 -0
  22. package/dist/index.d.ts +10 -0
  23. package/dist/models/Event.cjs.js +41 -0
  24. package/dist/models/Event.cjs.js.map +1 -0
  25. package/dist/plugin.cjs.js +64 -0
  26. package/dist/plugin.cjs.js.map +1 -0
  27. package/dist/router.cjs.js +63 -0
  28. package/dist/router.cjs.js.map +1 -0
  29. package/dist/types/event-request.cjs.js +14 -0
  30. package/dist/types/event-request.cjs.js.map +1 -0
  31. package/dist/utils/config.cjs.js +19 -0
  32. package/dist/utils/config.cjs.js.map +1 -0
  33. package/dist/utils/date.cjs.js +46 -0
  34. package/dist/utils/date.cjs.js.map +1 -0
  35. package/dist/validation/ValidationError.cjs.js +13 -0
  36. package/dist/validation/ValidationError.cjs.js.map +1 -0
  37. package/dist/validation/event-request.cjs.js +53 -0
  38. package/dist/validation/event-request.cjs.js.map +1 -0
  39. package/dist/validation/event.cjs.js +19 -0
  40. package/dist/validation/event.cjs.js.map +1 -0
  41. package/migrations/20250227120154_init.js +71 -0
  42. package/migrations/20250301104959_failed_events.js +39 -0
  43. package/package.json +71 -0
@@ -0,0 +1,276 @@
1
+ 'use strict';
2
+
3
+ var date = require('../../utils/date.cjs.js');
4
+
5
+ class BaseDatabaseAdapter {
6
+ db;
7
+ logger;
8
+ filters;
9
+ config;
10
+ constructor(db, logger) {
11
+ this.db = db;
12
+ this.logger = logger;
13
+ }
14
+ setFilters(filters) {
15
+ this.filters = filters;
16
+ return this;
17
+ }
18
+ setConfig(config) {
19
+ this.config = config;
20
+ return this;
21
+ }
22
+ async insertFailedEvent(event, errorMessage, retryAttempts = 0) {
23
+ const db = this.db;
24
+ try {
25
+ await db("failed_events").insert({
26
+ event_data: event,
27
+ error_message: errorMessage,
28
+ retry_attempts: retryAttempts,
29
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
30
+ });
31
+ this.logger.info(`[DB] Failed event logged: ${event}`);
32
+ } catch (error) {
33
+ this.logger.error(`[DB] Error inserting failed event:`, error);
34
+ }
35
+ }
36
+ async insertEvents(events) {
37
+ try {
38
+ await this.db.transaction(async (trx) => {
39
+ const evts = events.map((event) => event.toJSON());
40
+ await trx("events").insert(evts);
41
+ });
42
+ this.logger.info(
43
+ `[DB] Successfully inserted ${events.length} events in bulk`
44
+ );
45
+ } catch (error) {
46
+ this.logger.error(`[DB] Error inserting batch:`, error);
47
+ throw error;
48
+ }
49
+ }
50
+ selectFromEvents(queryBuilder, columns, dateRange) {
51
+ const query = queryBuilder.select(columns).from("events").andWhere("action", "navigate");
52
+ if (dateRange) {
53
+ query.whereBetween("created_at", dateRange);
54
+ }
55
+ return query;
56
+ }
57
+ async getDailyUsers() {
58
+ this.ensureFiltersSet();
59
+ const { start_date, end_date } = this.filters;
60
+ const db = this.db;
61
+ const groupedEventsQuery = db("events").select(db.raw(this.getDynamicDateGrouping()), "user_ref").whereBetween("created_at", [start_date, end_date]).groupByRaw("date, user_ref");
62
+ const firstSeenQuery = db("events").select("user_ref").min("created_at as first_seen").groupBy("user_ref");
63
+ const query = db.from(db.raw("(?) as ge", [groupedEventsQuery])).leftJoin(
64
+ db.raw("(?) as fs", [firstSeenQuery]),
65
+ "ge.user_ref",
66
+ "fs.user_ref"
67
+ ).select(
68
+ "ge.date",
69
+ db.raw("CAST(COUNT(*) as INTEGER) as total_users"),
70
+ db.raw(
71
+ `CAST(SUM(CASE WHEN fs.first_seen >= ${this.getFormatedDate(
72
+ "ge.date"
73
+ )} THEN 1 ELSE 0 END) as INTEGER) as new_users`
74
+ ),
75
+ db.raw(
76
+ `CAST(SUM(CASE WHEN fs.first_seen < ${this.getFormatedDate(
77
+ "ge.date"
78
+ )} THEN 1 ELSE 0 END) as INTEGER) as returning_users`
79
+ )
80
+ ).groupBy("ge.date").orderBy("ge.date");
81
+ return query.then((data) => this.getResponseWithGrouping(data));
82
+ }
83
+ async getUsers() {
84
+ this.ensureFiltersSet();
85
+ const { start_date, end_date } = this.filters;
86
+ const db = this.db;
87
+ const query = db("events").select(db.raw("CAST(COUNT(*) as INTEGER) as logged_in_users")).from(
88
+ db("events").select("user_ref").whereBetween("created_at", [start_date, end_date]).groupBy("user_ref")
89
+ ).as("sub");
90
+ return query.then((result) => {
91
+ const { licensedUsers } = this.config;
92
+ result[0] = { ...result[0], licensed_users: licensedUsers };
93
+ return this.getResponseData(result);
94
+ });
95
+ }
96
+ async getTopTemplateViews() {
97
+ this.ensureFiltersSet();
98
+ const { start_date, end_date, limit } = this.filters;
99
+ const db = this.db;
100
+ const query = db("events").select(
101
+ db.raw(`context->>'entityRef' AS entityref`),
102
+ db.raw("CAST(COUNT(*) as INTEGER) AS count"),
103
+ db.raw(this.getLastUsedDate())
104
+ ).where({
105
+ action: "click",
106
+ subject: "Create",
107
+ plugin_id: "scaffolder"
108
+ }).whereBetween("created_at", [start_date, end_date]).groupByRaw("entityref").orderBy("count", "desc").limit(Number(limit) || 3);
109
+ return query.then((data) => this.getResponseData(data, "last_used"));
110
+ }
111
+ async getTopSearches() {
112
+ const db = this.db;
113
+ this.ensureFiltersSet();
114
+ const { start_date, end_date, limit } = this.filters;
115
+ const query = db("events").select(
116
+ db.raw(this.getDynamicDateGrouping()),
117
+ db.raw("CAST(COUNT(*) as INTEGER) AS count")
118
+ ).whereBetween("created_at", [start_date, end_date]).andWhere("action", "search").groupByRaw("date").orderBy("date", "asc").limit(Number(limit) || 3);
119
+ return query.then((data) => this.getResponseWithGrouping(data));
120
+ }
121
+ async getTopTechDocsViews() {
122
+ this.ensureFiltersSet();
123
+ const { start_date, end_date, limit } = this.filters;
124
+ const db = this.db;
125
+ const query = db("events").select(
126
+ db.raw("CAST(COUNT(*) as INTEGER) AS count"),
127
+ db.raw(this.getLastUsedDate()),
128
+ db.raw(`COALESCE(LOWER(attributes->>'kind'), '') AS kind`),
129
+ db.raw(`COALESCE(attributes->>'name', '') AS name`),
130
+ db.raw(`COALESCE(attributes->>'namespace', '') AS namespace`)
131
+ ).where({
132
+ action: "navigate",
133
+ plugin_id: "techdocs"
134
+ }).whereBetween("created_at", [start_date, end_date]).groupByRaw(`name, kind, namespace`).orderBy("count", "desc").limit(Number(limit) || 3);
135
+ return query.then((data) => this.getResponseData(data, "last_used"));
136
+ }
137
+ async getTopCatalogEntitiesViews() {
138
+ this.ensureFiltersSet();
139
+ const { start_date, end_date, limit, kind } = this.filters;
140
+ const db = this.db;
141
+ const query = db("events").select(
142
+ "plugin_id",
143
+ db.raw(`LOWER(attributes->>'kind') AS kind`),
144
+ db.raw(`attributes->>'name' AS name`),
145
+ db.raw(`attributes->>'namespace' AS namespace`),
146
+ db.raw(this.getLastUsedDate()),
147
+ db.raw("CAST(COUNT(*) as INTEGER) AS count")
148
+ ).whereBetween("created_at", [start_date, end_date]).andWhere(db.raw(`attributes->>'kind' IS NOT NULL`)).andWhere("action", "navigate").andWhere("plugin_id", "catalog").groupByRaw("plugin_id, kind, name, namespace").orderBy("count", "desc").limit(Number(limit) || 3);
149
+ if (kind) {
150
+ query.andWhere(db.raw(`LOWER(attributes->>'kind') = ?`, [kind]));
151
+ }
152
+ return query.then((data) => this.getResponseData(data, "last_used"));
153
+ }
154
+ async getTopPluginViews() {
155
+ this.ensureFiltersSet();
156
+ const { start_date, end_date, limit = 3 } = this.filters;
157
+ const dateRange = [start_date, end_date];
158
+ const db = this.db;
159
+ const getTrendDataQuery = (qb) => {
160
+ const trend_data_columns = [
161
+ "plugin_id",
162
+ db.raw(this.getDynamicDateGrouping()),
163
+ db.raw("CAST(COUNT(*) as INTEGER) AS count")
164
+ ];
165
+ return this.selectFromEvents(qb, trend_data_columns, dateRange).groupBy(
166
+ "plugin_id",
167
+ "date"
168
+ );
169
+ };
170
+ const getPluginCountsQuery = (qb) => {
171
+ const plugin_counts_columns = [
172
+ "plugin_id",
173
+ db.raw("CAST(COUNT(*) as INTEGER) AS visit_count")
174
+ ];
175
+ return this.selectFromEvents(
176
+ qb,
177
+ plugin_counts_columns,
178
+ dateRange
179
+ ).groupBy("plugin_id");
180
+ };
181
+ const getAggregatedTrendsQuery = (qb) => {
182
+ return qb.select([
183
+ "plugin_id",
184
+ db.raw(`
185
+ json(${this.getJsonAggregationQuery("date", "count")}) AS trend,
186
+ COALESCE((SELECT count FROM trend_data td WHERE td.plugin_id = t.plugin_id ORDER BY date LIMIT 1),0) AS first_count,
187
+ COALESCE((SELECT count FROM trend_data td WHERE td.plugin_id = t.plugin_id ORDER BY date DESC LIMIT 1),0) AS last_count
188
+ `)
189
+ ]).from("trend_data AS t").groupBy("plugin_id");
190
+ };
191
+ const query = db.with("trend_data", (qb) => getTrendDataQuery(qb)).with("plugin_counts", (qb) => getPluginCountsQuery(qb)).with("aggregated_trends", (qb) => getAggregatedTrendsQuery(qb)).select([
192
+ "p.plugin_id",
193
+ "p.visit_count",
194
+ "t.trend",
195
+ db.raw(`
196
+ CASE
197
+ WHEN t.first_count = 0 THEN NULL
198
+ ELSE ROUND(((t.last_count - t.first_count) * 100.0) / NULLIF(t.first_count, 0), 2)
199
+ END AS trend_percentage
200
+ `)
201
+ ]).from("plugin_counts AS p").leftJoin("aggregated_trends AS t", "p.plugin_id", "t.plugin_id").orderBy("p.visit_count", "desc").limit(limit);
202
+ return query.then((data) => {
203
+ return this.getResponseWithGrouping(
204
+ this.transformJson(data, "trend"),
205
+ "trend"
206
+ );
207
+ });
208
+ }
209
+ // Helper methods
210
+ transformDateRange(dateRange) {
211
+ return dateRange;
212
+ }
213
+ transformJson(data, jsonField) {
214
+ return data.map((row) => ({
215
+ ...row,
216
+ [jsonField]: row[jsonField] ? JSON.parse(JSON.stringify(row[jsonField])) : null
217
+ }));
218
+ }
219
+ modifyDateInObject(obj, datePath = "date") {
220
+ if (obj[datePath]) {
221
+ return {
222
+ ...obj,
223
+ [datePath]: date.convertToLocalTimezone(obj[datePath])
224
+ };
225
+ }
226
+ return obj;
227
+ }
228
+ getResponseData(data, datePath = "date") {
229
+ return {
230
+ data: data.map((d) => {
231
+ if (Array.isArray(d[datePath])) {
232
+ return {
233
+ ...d,
234
+ [datePath]: d[datePath].map(
235
+ (dp) => this.modifyDateInObject(dp)
236
+ )
237
+ };
238
+ }
239
+ return this.modifyDateInObject(d, datePath);
240
+ })
241
+ };
242
+ }
243
+ getResponseWithGrouping = (data, datePath = "date") => {
244
+ const grouping = this.getDynamicDateGrouping(true);
245
+ if (grouping === "hourly") {
246
+ return {
247
+ grouping,
248
+ data: data.map((d) => {
249
+ if (Array.isArray(d[datePath])) {
250
+ return {
251
+ ...d,
252
+ [datePath]: d[datePath].map(
253
+ (dp) => this.modifyDateInObject(dp)
254
+ )
255
+ };
256
+ }
257
+ return this.modifyDateInObject(d, datePath);
258
+ })
259
+ };
260
+ }
261
+ return {
262
+ grouping,
263
+ data
264
+ };
265
+ };
266
+ ensureFiltersSet() {
267
+ if (!this.filters) {
268
+ throw new Error(
269
+ "Filters must be set using setFilters() before calling methods."
270
+ );
271
+ }
272
+ }
273
+ }
274
+
275
+ exports.BaseDatabaseAdapter = BaseDatabaseAdapter;
276
+ //# sourceMappingURL=BaseAdapter.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseAdapter.cjs.js","sources":["../../../src/database/adapters/BaseAdapter.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Knex } from 'knex';\nimport { Filters, EventDatabase, UserConfig } from '../event-database';\nimport { Event } from '../../models/Event';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n DailyUser,\n Grouping,\n ResponseData,\n ResponseWithGrouping,\n} from '../../types/event';\nimport { convertToLocalTimezone } from '../../utils/date';\n\nexport abstract class BaseDatabaseAdapter implements EventDatabase {\n protected db: Knex;\n private readonly logger: LoggerService;\n protected filters?: Filters;\n protected config?: UserConfig;\n\n constructor(db: Knex, logger: LoggerService) {\n this.db = db;\n this.logger = logger;\n }\n\n setFilters(filters: Filters) {\n this.filters = filters;\n return this;\n }\n setConfig(config: UserConfig) {\n this.config = config;\n return this;\n }\n\n async insertFailedEvent(\n event: string,\n errorMessage: string,\n retryAttempts = 0,\n ) {\n const db = this.db;\n try {\n await db('failed_events').insert({\n event_data: event,\n error_message: errorMessage,\n retry_attempts: retryAttempts,\n created_at: new Date().toISOString(),\n });\n\n this.logger.info(`[DB] Failed event logged: ${event}`);\n } catch (error) {\n this.logger.error(`[DB] Error inserting failed event:`, error);\n }\n }\n\n async insertEvents(events: Event[]): Promise<void> {\n try {\n await this.db.transaction(async trx => {\n const evts = events.map(event => event.toJSON());\n await trx('events').insert(evts); // Bulk insert into events table ('events')\n });\n this.logger.info(\n `[DB] Successfully inserted ${events.length} events in bulk`,\n );\n } catch (error) {\n this.logger.error(`[DB] Error inserting batch:`, error);\n throw error; // Re-throw the error for retry handling\n }\n }\n\n protected selectFromEvents(\n queryBuilder: Knex.QueryBuilder,\n columns: (string | Knex.Raw<any>)[],\n dateRange?: [string, string],\n ): Knex.QueryBuilder {\n const query = queryBuilder\n .select(columns)\n .from('events')\n .andWhere('action', 'navigate');\n\n if (dateRange) {\n query.whereBetween('created_at', dateRange);\n }\n\n return query;\n }\n\n async getDailyUsers(): Promise<Knex.QueryBuilder> {\n this.ensureFiltersSet();\n const { start_date, end_date } = this.filters!;\n const db = this.db;\n\n const groupedEventsQuery = db('events')\n .select(db.raw(this.getDynamicDateGrouping()), 'user_ref')\n .whereBetween('created_at', [start_date, end_date])\n .groupByRaw('date, user_ref');\n\n const firstSeenQuery = db('events')\n .select('user_ref')\n .min('created_at as first_seen')\n .groupBy('user_ref');\n\n const query = db\n .from(db.raw('(?) as ge', [groupedEventsQuery]))\n .leftJoin(\n db.raw('(?) as fs', [firstSeenQuery]),\n 'ge.user_ref',\n 'fs.user_ref',\n )\n .select(\n 'ge.date',\n db.raw('CAST(COUNT(*) as INTEGER) as total_users'),\n db.raw(\n `CAST(SUM(CASE WHEN fs.first_seen >= ${this.getFormatedDate(\n 'ge.date',\n )} THEN 1 ELSE 0 END) as INTEGER) as new_users`,\n ),\n db.raw(\n `CAST(SUM(CASE WHEN fs.first_seen < ${this.getFormatedDate(\n 'ge.date',\n )} THEN 1 ELSE 0 END) as INTEGER) as returning_users`,\n ),\n )\n .groupBy('ge.date')\n .orderBy('ge.date');\n\n return query.then(data => this.getResponseWithGrouping<DailyUser[]>(data));\n }\n\n async getUsers(): Promise<Knex.QueryBuilder> {\n this.ensureFiltersSet();\n const { start_date, end_date } = this.filters!;\n const db = this.db;\n const query = db('events')\n .select(db.raw('CAST(COUNT(*) as INTEGER) as logged_in_users'))\n .from(\n db('events')\n .select('user_ref')\n .whereBetween('created_at', [start_date, end_date])\n .groupBy('user_ref'),\n )\n .as('sub');\n\n return query.then(result => {\n const { licensedUsers } = this.config!;\n result[0] = { ...result[0], licensed_users: licensedUsers } as any;\n return this.getResponseData(result);\n });\n }\n\n async getTopTemplateViews(): Promise<Knex.QueryBuilder> {\n this.ensureFiltersSet();\n const { start_date, end_date, limit } = this.filters!;\n const db = this.db;\n const query = db('events')\n .select(\n db.raw(`context->>'entityRef' AS entityref`),\n db.raw('CAST(COUNT(*) as INTEGER) AS count'),\n db.raw(this.getLastUsedDate()),\n )\n .where({\n action: 'click',\n subject: 'Create',\n plugin_id: 'scaffolder',\n })\n .whereBetween('created_at', [start_date, end_date])\n .groupByRaw('entityref')\n .orderBy('count', 'desc')\n .limit(Number(limit) || 3);\n\n return query.then(data => this.getResponseData(data, 'last_used'));\n }\n\n async getTopSearches(): Promise<Knex.QueryBuilder> {\n const db = this.db;\n this.ensureFiltersSet();\n const { start_date, end_date, limit } = this.filters!;\n const query = db('events')\n .select(\n db.raw(this.getDynamicDateGrouping()),\n db.raw('CAST(COUNT(*) as INTEGER) AS count'),\n )\n .whereBetween('created_at', [start_date, end_date])\n .andWhere('action', 'search')\n .groupByRaw('date')\n .orderBy('date', 'asc')\n .limit(Number(limit) || 3);\n\n return query.then(data => this.getResponseWithGrouping(data));\n }\n\n async getTopTechDocsViews(): Promise<Knex.QueryBuilder> {\n this.ensureFiltersSet();\n const { start_date, end_date, limit } = this.filters!;\n const db = this.db;\n const query = db('events')\n .select(\n db.raw('CAST(COUNT(*) as INTEGER) AS count'),\n db.raw(this.getLastUsedDate()),\n db.raw(`COALESCE(LOWER(attributes->>'kind'), '') AS kind`),\n db.raw(`COALESCE(attributes->>'name', '') AS name`),\n db.raw(`COALESCE(attributes->>'namespace', '') AS namespace`),\n )\n .where({\n action: 'navigate',\n plugin_id: 'techdocs',\n })\n .whereBetween('created_at', [start_date, end_date])\n .groupByRaw(`name, kind, namespace`)\n .orderBy('count', 'desc')\n .limit(Number(limit) || 3);\n\n return query.then(data => this.getResponseData(data, 'last_used'));\n }\n\n async getTopCatalogEntitiesViews(): Promise<Knex.QueryBuilder> {\n this.ensureFiltersSet();\n const { start_date, end_date, limit, kind } = this.filters!;\n const db = this.db;\n const query = db('events')\n .select(\n 'plugin_id',\n db.raw(`LOWER(attributes->>'kind') AS kind`),\n db.raw(`attributes->>'name' AS name`),\n db.raw(`attributes->>'namespace' AS namespace`),\n db.raw(this.getLastUsedDate()),\n db.raw('CAST(COUNT(*) as INTEGER) AS count'),\n )\n .whereBetween('created_at', [start_date, end_date])\n .andWhere(db.raw(`attributes->>'kind' IS NOT NULL`))\n .andWhere('action', 'navigate')\n .andWhere('plugin_id', 'catalog')\n .groupByRaw('plugin_id, kind, name, namespace')\n .orderBy('count', 'desc')\n .limit(Number(limit) || 3);\n\n if (kind) {\n query.andWhere(db.raw(`LOWER(attributes->>'kind') = ?`, [kind]));\n }\n return query.then(data => this.getResponseData(data, 'last_used'));\n }\n\n async getTopPluginViews(): Promise<Knex.QueryBuilder> {\n this.ensureFiltersSet();\n const { start_date, end_date, limit = 3 } = this.filters!;\n const dateRange: [string, string] = [start_date, end_date];\n const db = this.db;\n\n // return grouped plugin counts by date.\n const getTrendDataQuery = (qb: Knex.QueryBuilder) => {\n const trend_data_columns = [\n 'plugin_id',\n db.raw(this.getDynamicDateGrouping()),\n db.raw('CAST(COUNT(*) as INTEGER) AS count'),\n ];\n\n return this.selectFromEvents(qb, trend_data_columns, dateRange).groupBy(\n 'plugin_id',\n 'date',\n );\n };\n\n // return grouped plugin counts\n const getPluginCountsQuery = (qb: Knex.QueryBuilder) => {\n const plugin_counts_columns = [\n 'plugin_id',\n db.raw('CAST(COUNT(*) as INTEGER) AS visit_count'),\n ];\n return this.selectFromEvents(\n qb,\n plugin_counts_columns,\n dateRange,\n ).groupBy('plugin_id');\n };\n\n // return aggregated trends JSON object and first_count, last_count to calculate the trend percentage\n const getAggregatedTrendsQuery = (qb: Knex.QueryBuilder) => {\n return qb\n .select([\n 'plugin_id',\n db.raw(`\n json(${this.getJsonAggregationQuery('date', 'count')}) AS trend,\n COALESCE((SELECT count FROM trend_data td WHERE td.plugin_id = t.plugin_id ORDER BY date LIMIT 1),0) AS first_count,\n COALESCE((SELECT count FROM trend_data td WHERE td.plugin_id = t.plugin_id ORDER BY date DESC LIMIT 1),0) AS last_count\n `),\n ])\n .from('trend_data AS t')\n .groupBy('plugin_id');\n };\n\n // Main query to get top plugins counts, trends for the given date range.\n const query = db\n .with('trend_data', qb => getTrendDataQuery(qb))\n .with('plugin_counts', qb => getPluginCountsQuery(qb))\n .with('aggregated_trends', qb => getAggregatedTrendsQuery(qb))\n .select([\n 'p.plugin_id',\n 'p.visit_count',\n 't.trend',\n db.raw(`\n CASE\n WHEN t.first_count = 0 THEN NULL\n ELSE ROUND(((t.last_count - t.first_count) * 100.0) / NULLIF(t.first_count, 0), 2)\n END AS trend_percentage\n `),\n ])\n .from('plugin_counts AS p')\n .leftJoin('aggregated_trends AS t', 'p.plugin_id', 't.plugin_id')\n .orderBy('p.visit_count', 'desc')\n .limit(limit);\n\n return query.then(data => {\n return this.getResponseWithGrouping(\n this.transformJson(data, 'trend'),\n 'trend',\n );\n });\n }\n\n abstract getDate(): string;\n abstract getLastUsedDate(): string;\n abstract isJsonSupported(): boolean;\n abstract isPartitionSupported(): boolean;\n abstract getDateBetweenQuery(): string;\n abstract getDynamicDateGrouping(onlyText?: boolean): Grouping | string;\n abstract getFormatedDate(column: string): string;\n abstract getJsonAggregationQuery(...args: any[]): string;\n\n // Helper methods\n transformDateRange(dateRange: [string, string]): [string, string] {\n return dateRange;\n }\n\n transformJson(data: any[], jsonField: string): any {\n return data.map((row: any) => ({\n ...row,\n [jsonField]: row[jsonField]\n ? JSON.parse(JSON.stringify(row[jsonField]))\n : null,\n }));\n }\n\n modifyDateInObject<T extends any>(\n obj: T & { [key: string]: string | number },\n datePath: string = 'date',\n ) {\n if (obj[datePath]) {\n return {\n ...obj,\n [datePath]: convertToLocalTimezone(obj[datePath] as string),\n };\n }\n return obj;\n }\n\n getResponseData<T extends any[]>(\n data: T,\n datePath: string = 'date',\n ): ResponseData<T> {\n return {\n data: data.map(d => {\n if (Array.isArray(d[datePath])) {\n return {\n ...d,\n [datePath]: d[datePath].map((dp: any) =>\n this.modifyDateInObject(dp),\n ),\n };\n }\n return this.modifyDateInObject(d, datePath);\n }) as T,\n };\n }\n\n getResponseWithGrouping = <T extends any[]>(\n data: T,\n datePath: string = 'date',\n ): ResponseWithGrouping<T> => {\n const grouping = this.getDynamicDateGrouping(true) as Grouping;\n\n if (grouping === 'hourly') {\n return {\n grouping,\n data: data.map(d => {\n if (Array.isArray(d[datePath])) {\n return {\n ...d,\n [datePath]: d[datePath].map((dp: any) =>\n this.modifyDateInObject(dp),\n ),\n };\n }\n return this.modifyDateInObject(d, datePath);\n }) as T,\n };\n }\n\n return {\n grouping,\n data,\n };\n };\n\n ensureFiltersSet() {\n if (!this.filters) {\n throw new Error(\n 'Filters must be set using setFilters() before calling methods.',\n );\n }\n }\n}\n"],"names":["convertToLocalTimezone"],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ var BaseAdapter = require('./BaseAdapter.cjs.js');
4
+ var date = require('../../utils/date.cjs.js');
5
+
6
+ class PostgresAdapter extends BaseAdapter.BaseDatabaseAdapter {
7
+ isJsonSupported() {
8
+ return true;
9
+ }
10
+ isPartitionSupported() {
11
+ return true;
12
+ }
13
+ getFormatedDate(column) {
14
+ return `${column}::timestamp`;
15
+ }
16
+ getDate() {
17
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
18
+ return this.db.raw(
19
+ `to_char(created_at AT TIME ZONE 'UTC' AT TIME ZONE ?,'YYYY-MM-DD"T"HH24:MI:SS.MSZ')`,
20
+ [timeZone]
21
+ ).toQuery();
22
+ }
23
+ getLastUsedDate() {
24
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
25
+ return this.db.raw(
26
+ `to_char(MAX(created_at) AT TIME ZONE 'UTC' AT TIME ZONE ?,'YYYY-MM-DD"T"HH24:MI:SS.FF3') || 'Z' AS last_used`,
27
+ [timeZone]
28
+ ).toQuery();
29
+ }
30
+ getDateBetweenQuery() {
31
+ return "created_at";
32
+ }
33
+ getJsonAggregationQuery(...args) {
34
+ const { grouping } = this.filters;
35
+ const fieldMappings = args.map((field) => {
36
+ if (field === "date" && grouping === "hourly") {
37
+ return `'${field}', to_char(date, 'YYYY-MM-DD HH24:MI:SS')`;
38
+ }
39
+ return `'${field}', ${field}`;
40
+ }).join(", ");
41
+ return `jsonb_agg(jsonb_build_object(${fieldMappings}) ORDER BY date)`;
42
+ }
43
+ transformJson(data) {
44
+ return data;
45
+ }
46
+ getDynamicDateGrouping(onlyText = false) {
47
+ this.ensureFiltersSet();
48
+ const { start_date, end_date, grouping: groupingStrategy } = this.filters;
49
+ const dateDiff = date.calculateDateRange(start_date, end_date);
50
+ const grouping = groupingStrategy || date.getDateGroupingType(dateDiff, start_date, end_date);
51
+ if (onlyText) {
52
+ return grouping;
53
+ }
54
+ return `${this.getDateGroupingQuery(grouping)} as date`;
55
+ }
56
+ getDateGroupingQuery(grouping) {
57
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
58
+ const rawQuery = (query, bindings) => this.db.raw(query, bindings).toQuery();
59
+ switch (grouping) {
60
+ case "hourly":
61
+ return rawQuery(`date_trunc('hour', created_at AT TIME ZONE ?)`, [
62
+ timeZone
63
+ ]);
64
+ case "daily":
65
+ return rawQuery(`to_char(created_at AT TIME ZONE ?, 'YYYY-MM-DD')`, [
66
+ timeZone
67
+ ]);
68
+ case "weekly":
69
+ return rawQuery(
70
+ `to_char(date_trunc('week', created_at AT TIME ZONE ?), 'YYYY-MM-DD')`,
71
+ [timeZone]
72
+ );
73
+ case "monthly":
74
+ return rawQuery(
75
+ `to_char(
76
+ LEAST (
77
+ (date_trunc('month', created_at AT TIME ZONE ?)
78
+ + interval '1 month' - interval '1 day'),
79
+ ?::date
80
+ ),
81
+ 'YYYY-MM-DD'
82
+ )`,
83
+ [timeZone, this.filters?.end_date]
84
+ );
85
+ default:
86
+ throw new Error("Invalid date grouping");
87
+ }
88
+ }
89
+ }
90
+
91
+ exports.PostgresAdapter = PostgresAdapter;
92
+ //# sourceMappingURL=PostgresAdapter.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostgresAdapter.cjs.js","sources":["../../../src/database/adapters/PostgresAdapter.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { BaseDatabaseAdapter } from './BaseAdapter';\nimport { calculateDateRange, getDateGroupingType } from '../../utils/date';\n\nexport class PostgresAdapter extends BaseDatabaseAdapter {\n isJsonSupported(): boolean {\n return true;\n }\n\n isPartitionSupported(): boolean {\n return true;\n }\n\n getFormatedDate(column: string): string {\n return `${column}::timestamp`;\n }\n\n getDate(): string {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n return this.db\n .raw(\n `to_char(created_at AT TIME ZONE 'UTC' AT TIME ZONE ?,'YYYY-MM-DD\"T\"HH24:MI:SS.MSZ')`,\n [timeZone],\n )\n .toQuery();\n }\n\n getLastUsedDate(): string {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n return this.db\n .raw(\n `to_char(MAX(created_at) AT TIME ZONE 'UTC' AT TIME ZONE ?,'YYYY-MM-DD\"T\"HH24:MI:SS.FF3') || 'Z' AS last_used`,\n [timeZone],\n )\n .toQuery();\n }\n\n getDateBetweenQuery() {\n return 'created_at';\n }\n getJsonAggregationQuery(...args: any[]): string {\n const { grouping } = this.filters!;\n const fieldMappings = args\n .map(field => {\n if (field === 'date' && grouping === 'hourly') {\n return `'${field}', to_char(date, 'YYYY-MM-DD HH24:MI:SS')`;\n }\n return `'${field}', ${field}`;\n })\n .join(', ');\n\n return `jsonb_agg(jsonb_build_object(${fieldMappings}) ORDER BY date)`;\n }\n\n transformJson(data: any[]): any {\n return data;\n }\n\n getDynamicDateGrouping(onlyText: boolean = false): string {\n this.ensureFiltersSet();\n const { start_date, end_date, grouping: groupingStrategy } = this.filters!;\n const dateDiff = calculateDateRange(start_date, end_date);\n const grouping =\n groupingStrategy || getDateGroupingType(dateDiff, start_date, end_date);\n\n if (onlyText) {\n return grouping;\n }\n\n return `${this.getDateGroupingQuery(grouping)} as date`;\n }\n\n private getDateGroupingQuery(grouping: string): string {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n\n const rawQuery = (query: any, bindings: any) =>\n this.db.raw(query, bindings).toQuery();\n switch (grouping) {\n case 'hourly':\n return rawQuery(`date_trunc('hour', created_at AT TIME ZONE ?)`, [\n timeZone,\n ]);\n case 'daily':\n return rawQuery(`to_char(created_at AT TIME ZONE ?, 'YYYY-MM-DD')`, [\n timeZone,\n ]);\n case 'weekly':\n return rawQuery(\n `to_char(date_trunc('week', created_at AT TIME ZONE ?), 'YYYY-MM-DD')`,\n [timeZone],\n );\n case 'monthly':\n return rawQuery(\n `to_char(\n LEAST (\n (date_trunc('month', created_at AT TIME ZONE ?) \n + interval '1 month' - interval '1 day'), \n ?::date\n ),\n 'YYYY-MM-DD'\n )`,\n [timeZone, this.filters?.end_date],\n );\n default:\n throw new Error('Invalid date grouping');\n }\n }\n}\n"],"names":["BaseDatabaseAdapter","calculateDateRange","getDateGroupingType"],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ var BaseAdapter = require('./BaseAdapter.cjs.js');
4
+ var date = require('../../utils/date.cjs.js');
5
+
6
+ class SqliteAdapter extends BaseAdapter.BaseDatabaseAdapter {
7
+ isJsonSupported() {
8
+ return false;
9
+ }
10
+ isPartitionSupported() {
11
+ return false;
12
+ }
13
+ getDate() {
14
+ return `strftime('%Y-%m-%d', created_at, 'localtime') AS date`;
15
+ }
16
+ getLastUsedDate() {
17
+ return `strftime('%Y-%m-%dT%H:%M:%SZ', MAX(created_at)) AS last_used`;
18
+ }
19
+ getFormatedDate(column) {
20
+ return `datetime(${column})`;
21
+ }
22
+ getDateBetweenQuery() {
23
+ return "created_at BETWEEN datetime(?) AND datetime(?)";
24
+ }
25
+ getJsonAggregationQuery(...args) {
26
+ const fieldMappings = args.map((field) => `'${field}', ${field}`).join(", ");
27
+ return `json_group_array(json_object(${fieldMappings}))`;
28
+ }
29
+ transformJson(data, jsonField) {
30
+ return data.map((row) => ({
31
+ ...row,
32
+ [jsonField]: row[jsonField] ? JSON.parse(row[jsonField]) : null
33
+ }));
34
+ }
35
+ getDynamicDateGrouping(onlyText = false) {
36
+ const { start_date, end_date, grouping: groupingStrategy } = this.filters;
37
+ const dateDiff = date.calculateDateRange(start_date, end_date);
38
+ const grouping = groupingStrategy || date.getDateGroupingType(dateDiff, start_date, end_date);
39
+ if (onlyText) {
40
+ return grouping;
41
+ }
42
+ return this.db.raw(`${this.getDateGroupingQuery(grouping)} as date`).toQuery();
43
+ }
44
+ getDateGroupingQuery(grouping) {
45
+ switch (grouping) {
46
+ case "hourly":
47
+ return `strftime('%Y-%m-%d %H:00:00', created_at, 'localtime')`;
48
+ case "daily":
49
+ return `strftime('%Y-%m-%d', created_at, 'localtime')`;
50
+ case "weekly":
51
+ return `strftime('%Y-%m-%d', date(created_at, 'weekday 0', '-7 days'))`;
52
+ case "monthly":
53
+ return `strftime('%Y-%m-01', date(created_at, 'localtime'))`;
54
+ default:
55
+ throw new Error("Invalid date grouping");
56
+ }
57
+ }
58
+ }
59
+
60
+ exports.SqliteAdapter = SqliteAdapter;
61
+ //# sourceMappingURL=SqliteAdapter.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteAdapter.cjs.js","sources":["../../../src/database/adapters/SqliteAdapter.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { BaseDatabaseAdapter } from './BaseAdapter';\nimport { calculateDateRange, getDateGroupingType } from '../../utils/date';\n\nexport class SqliteAdapter extends BaseDatabaseAdapter {\n isJsonSupported(): boolean {\n return false;\n }\n\n isPartitionSupported(): boolean {\n return false;\n }\n\n getDate(): string {\n return `strftime('%Y-%m-%d', created_at, 'localtime') AS date`;\n }\n\n getLastUsedDate(): string {\n return `strftime('%Y-%m-%dT%H:%M:%SZ', MAX(created_at)) AS last_used`;\n }\n\n getFormatedDate(column: string): string {\n return `datetime(${column})`;\n }\n\n getDateBetweenQuery(): string {\n return 'created_at BETWEEN datetime(?) AND datetime(?)';\n }\n\n getJsonAggregationQuery(...args: any[]): string {\n const fieldMappings = args.map(field => `'${field}', ${field}`).join(', ');\n return `json_group_array(json_object(${fieldMappings}))`;\n }\n\n transformJson(data: any[], jsonField: string): any {\n return data.map((row: any) => ({\n ...row,\n [jsonField]: row[jsonField] ? JSON.parse(row[jsonField]) : null,\n }));\n }\n\n getDynamicDateGrouping(onlyText: boolean = false): string {\n const { start_date, end_date, grouping: groupingStrategy } = this.filters!;\n const dateDiff = calculateDateRange(start_date, end_date);\n\n const grouping =\n groupingStrategy || getDateGroupingType(dateDiff, start_date, end_date);\n\n if (onlyText) {\n return grouping;\n }\n\n return this.db\n .raw(`${this.getDateGroupingQuery(grouping)} as date`)\n .toQuery();\n }\n\n private getDateGroupingQuery(grouping: string): string {\n switch (grouping) {\n case 'hourly':\n return `strftime('%Y-%m-%d %H:00:00', created_at, 'localtime')`;\n case 'daily':\n return `strftime('%Y-%m-%d', created_at, 'localtime')`;\n case 'weekly':\n return `strftime('%Y-%m-%d', date(created_at, 'weekday 0', '-7 days'))`;\n case 'monthly':\n return `strftime('%Y-%m-01', date(created_at, 'localtime'))`;\n default:\n throw new Error('Invalid date grouping');\n }\n }\n}\n"],"names":["BaseDatabaseAdapter","calculateDateRange","getDateGroupingType"],"mappings":";;;;;AAkBO,MAAM,sBAAsBA,+BAAoB,CAAA;AAAA,EACrD,eAA2B,GAAA;AACzB,IAAO,OAAA,KAAA;AAAA;AACT,EAEA,oBAAgC,GAAA;AAC9B,IAAO,OAAA,KAAA;AAAA;AACT,EAEA,OAAkB,GAAA;AAChB,IAAO,OAAA,CAAA,qDAAA,CAAA;AAAA;AACT,EAEA,eAA0B,GAAA;AACxB,IAAO,OAAA,CAAA,4DAAA,CAAA;AAAA;AACT,EAEA,gBAAgB,MAAwB,EAAA;AACtC,IAAA,OAAO,YAAY,MAAM,CAAA,CAAA,CAAA;AAAA;AAC3B,EAEA,mBAA8B,GAAA;AAC5B,IAAO,OAAA,gDAAA;AAAA;AACT,EAEA,2BAA2B,IAAqB,EAAA;AAC9C,IAAM,MAAA,aAAA,GAAgB,IAAK,CAAA,GAAA,CAAI,CAAS,KAAA,KAAA,CAAA,CAAA,EAAI,KAAK,CAAA,GAAA,EAAM,KAAK,CAAA,CAAE,CAAE,CAAA,IAAA,CAAK,IAAI,CAAA;AACzE,IAAA,OAAO,gCAAgC,aAAa,CAAA,EAAA,CAAA;AAAA;AACtD,EAEA,aAAA,CAAc,MAAa,SAAwB,EAAA;AACjD,IAAO,OAAA,IAAA,CAAK,GAAI,CAAA,CAAC,GAAc,MAAA;AAAA,MAC7B,GAAG,GAAA;AAAA,MACH,CAAC,SAAS,GAAG,GAAI,CAAA,SAAS,CAAI,GAAA,IAAA,CAAK,KAAM,CAAA,GAAA,CAAI,SAAS,CAAC,CAAI,GAAA;AAAA,KAC3D,CAAA,CAAA;AAAA;AACJ,EAEA,sBAAA,CAAuB,WAAoB,KAAe,EAAA;AACxD,IAAA,MAAM,EAAE,UAAY,EAAA,QAAA,EAAU,QAAU,EAAA,gBAAA,KAAqB,IAAK,CAAA,OAAA;AAClE,IAAM,MAAA,QAAA,GAAWC,uBAAmB,CAAA,UAAA,EAAY,QAAQ,CAAA;AAExD,IAAA,MAAM,QACJ,GAAA,gBAAA,IAAoBC,wBAAoB,CAAA,QAAA,EAAU,YAAY,QAAQ,CAAA;AAExE,IAAA,IAAI,QAAU,EAAA;AACZ,MAAO,OAAA,QAAA;AAAA;AAGT,IAAO,OAAA,IAAA,CAAK,EACT,CAAA,GAAA,CAAI,CAAG,EAAA,IAAA,CAAK,qBAAqB,QAAQ,CAAC,CAAU,QAAA,CAAA,CAAA,CACpD,OAAQ,EAAA;AAAA;AACb,EAEQ,qBAAqB,QAA0B,EAAA;AACrD,IAAA,QAAQ,QAAU;AAAA,MAChB,KAAK,QAAA;AACH,QAAO,OAAA,CAAA,sDAAA,CAAA;AAAA,MACT,KAAK,OAAA;AACH,QAAO,OAAA,CAAA,6CAAA,CAAA;AAAA,MACT,KAAK,QAAA;AACH,QAAO,OAAA,CAAA,8DAAA,CAAA;AAAA,MACT,KAAK,SAAA;AACH,QAAO,OAAA,CAAA,mDAAA,CAAA;AAAA,MACT;AACE,QAAM,MAAA,IAAI,MAAM,uBAAuB,CAAA;AAAA;AAC3C;AAEJ;;;;"}
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+
5
+ const migrationsDir = backendPluginApi.resolvePackagePath(
6
+ "@red-hat-developer-hub/backstage-plugin-adoption-insights-backend",
7
+ "migrations"
8
+ );
9
+ async function migrate(databaseManager) {
10
+ const knex = await databaseManager.getClient();
11
+ if (!databaseManager.migrations?.skip) {
12
+ await knex.migrate.latest({
13
+ directory: migrationsDir
14
+ });
15
+ }
16
+ }
17
+
18
+ exports.migrate = migrate;
19
+ //# sourceMappingURL=migration.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.cjs.js","sources":["../../src/database/migration.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n DatabaseService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\n\nconst migrationsDir = resolvePackagePath(\n '@red-hat-developer-hub/backstage-plugin-adoption-insights-backend',\n 'migrations',\n);\n\nexport async function migrate(databaseManager: DatabaseService) {\n const knex = await databaseManager.getClient();\n\n if (!databaseManager.migrations?.skip) {\n await knex.migrate.latest({\n directory: migrationsDir,\n });\n }\n}\n"],"names":["resolvePackagePath"],"mappings":";;;;AAoBA,MAAM,aAAgB,GAAAA,mCAAA;AAAA,EACpB,mEAAA;AAAA,EACA;AACF,CAAA;AAEA,eAAsB,QAAQ,eAAkC,EAAA;AAC9D,EAAM,MAAA,IAAA,GAAO,MAAM,eAAA,CAAgB,SAAU,EAAA;AAE7C,EAAI,IAAA,CAAC,eAAgB,CAAA,UAAA,EAAY,IAAM,EAAA;AACrC,IAAM,MAAA,IAAA,CAAK,QAAQ,MAAO,CAAA;AAAA,MACxB,SAAW,EAAA;AAAA,KACZ,CAAA;AAAA;AAEL;;;;"}
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const createPartition = async (knex, year, month) => {
4
+ const startDate = `${year}-${month.toString().padStart(2, "0")}-01`;
5
+ const nextMonth = new Date(year, month, 1);
6
+ nextMonth.setMonth(nextMonth.getMonth() + 1);
7
+ const endDate = `${nextMonth.getFullYear()}-${(nextMonth.getMonth() + 1).toString().padStart(2, "0")}-01`;
8
+ const partitionName = `events_${year}_${month}`;
9
+ await knex.schema.raw(`
10
+ CREATE TABLE IF NOT EXISTS ${partitionName}
11
+ PARTITION OF events
12
+ FOR VALUES FROM ('${startDate}') TO ('${endDate}');
13
+ `);
14
+ };
15
+ const schedulePartition = async (client, services) => {
16
+ const { logger, scheduler } = services;
17
+ const partitionEventsTable = async () => {
18
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
19
+ const month = (/* @__PURE__ */ new Date()).getMonth() + 1;
20
+ logger.info(
21
+ `Creating partition for ${year}_${month.toString().padStart(2, "0")}`
22
+ );
23
+ await createPartition(client, year, month);
24
+ };
25
+ const runner = scheduler.createScheduledTaskRunner({
26
+ frequency: { cron: "0 0 1 * *" },
27
+ // Runs at midnight on the 1st of every month
28
+ timeout: { seconds: 30 }
29
+ });
30
+ runner.run({
31
+ id: "create-partition",
32
+ fn: partitionEventsTable
33
+ });
34
+ logger.info("[TASK] Running initial execution on startup...");
35
+ await partitionEventsTable();
36
+ };
37
+
38
+ exports.createPartition = createPartition;
39
+ exports.schedulePartition = schedulePartition;
40
+ //# sourceMappingURL=partition.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"partition.cjs.js","sources":["../../src/database/partition.ts"],"sourcesContent":["import { Knex } from 'knex';\n/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n LoggerService,\n SchedulerService,\n} from '@backstage/backend-plugin-api/index';\n\nexport const createPartition = async (\n knex: Knex,\n year: number,\n month: number,\n) => {\n const startDate = `${year}-${month.toString().padStart(2, '0')}-01`;\n const nextMonth = new Date(year, month, 1);\n nextMonth.setMonth(nextMonth.getMonth() + 1);\n const endDate = `${nextMonth.getFullYear()}-${(nextMonth.getMonth() + 1)\n .toString()\n .padStart(2, '0')}-01`;\n\n const partitionName = `events_${year}_${month}`;\n\n await knex.schema.raw(`\n CREATE TABLE IF NOT EXISTS ${partitionName} \n PARTITION OF events\n FOR VALUES FROM ('${startDate}') TO ('${endDate}');\n `);\n};\n\nexport const schedulePartition = async (\n client: Knex,\n services: {\n logger: LoggerService;\n scheduler: SchedulerService;\n },\n) => {\n const { logger, scheduler } = services;\n const partitionEventsTable = async () => {\n const year = new Date().getFullYear();\n const month = new Date().getMonth() + 1;\n logger.info(\n `Creating partition for ${year}_${month.toString().padStart(2, '0')}`,\n );\n await createPartition(client, year, month);\n };\n\n const runner = scheduler.createScheduledTaskRunner({\n frequency: { cron: '0 0 1 * *' }, // Runs at midnight on the 1st of every month\n timeout: { seconds: 30 },\n });\n\n runner.run({\n id: 'create-partition',\n fn: partitionEventsTable,\n });\n\n logger.info('[TASK] Running initial execution on startup...');\n await partitionEventsTable();\n};\n"],"names":[],"mappings":";;AAqBO,MAAM,eAAkB,GAAA,OAC7B,IACA,EAAA,IAAA,EACA,KACG,KAAA;AACH,EAAM,MAAA,SAAA,GAAY,CAAG,EAAA,IAAI,CAAI,CAAA,EAAA,KAAA,CAAM,UAAW,CAAA,QAAA,CAAS,CAAG,EAAA,GAAG,CAAC,CAAA,GAAA,CAAA;AAC9D,EAAA,MAAM,SAAY,GAAA,IAAI,IAAK,CAAA,IAAA,EAAM,OAAO,CAAC,CAAA;AACzC,EAAA,SAAA,CAAU,QAAS,CAAA,SAAA,CAAU,QAAS,EAAA,GAAI,CAAC,CAAA;AAC3C,EAAA,MAAM,OAAU,GAAA,CAAA,EAAG,SAAU,CAAA,WAAA,EAAa,CAAK,CAAA,EAAA,CAAA,SAAA,CAAU,QAAS,EAAA,GAAI,GACnE,QAAS,EAAA,CACT,QAAS,CAAA,CAAA,EAAG,GAAG,CAAC,CAAA,GAAA,CAAA;AAEnB,EAAA,MAAM,aAAgB,GAAA,CAAA,OAAA,EAAU,IAAI,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA;AAE7C,EAAM,MAAA,IAAA,CAAK,OAAO,GAAI,CAAA;AAAA,iCAAA,EACW,aAAa,CAAA;AAAA;AAAA,wBAEtB,EAAA,SAAS,WAAW,OAAO,CAAA;AAAA,IAChD,CAAA,CAAA;AACL;AAEa,MAAA,iBAAA,GAAoB,OAC/B,MAAA,EACA,QAIG,KAAA;AACH,EAAM,MAAA,EAAE,MAAQ,EAAA,SAAA,EAAc,GAAA,QAAA;AAC9B,EAAA,MAAM,uBAAuB,YAAY;AACvC,IAAA,MAAM,IAAO,GAAA,iBAAA,IAAI,IAAK,EAAA,EAAE,WAAY,EAAA;AACpC,IAAA,MAAM,KAAQ,GAAA,iBAAA,IAAI,IAAK,EAAA,EAAE,UAAa,GAAA,CAAA;AACtC,IAAO,MAAA,CAAA,IAAA;AAAA,MACL,CAAA,uBAAA,EAA0B,IAAI,CAAI,CAAA,EAAA,KAAA,CAAM,UAAW,CAAA,QAAA,CAAS,CAAG,EAAA,GAAG,CAAC,CAAA;AAAA,KACrE;AACA,IAAM,MAAA,eAAA,CAAgB,MAAQ,EAAA,IAAA,EAAM,KAAK,CAAA;AAAA,GAC3C;AAEA,EAAM,MAAA,MAAA,GAAS,UAAU,yBAA0B,CAAA;AAAA,IACjD,SAAA,EAAW,EAAE,IAAA,EAAM,WAAY,EAAA;AAAA;AAAA,IAC/B,OAAA,EAAS,EAAE,OAAA,EAAS,EAAG;AAAA,GACxB,CAAA;AAED,EAAA,MAAA,CAAO,GAAI,CAAA;AAAA,IACT,EAAI,EAAA,kBAAA;AAAA,IACJ,EAAI,EAAA;AAAA,GACL,CAAA;AAED,EAAA,MAAA,CAAO,KAAK,gDAAgD,CAAA;AAC5D,EAAA,MAAM,oBAAqB,EAAA;AAC7B;;;;;"}