@red-hat-developer-hub/backstage-plugin-adoption-insights-backend 0.0.2 → 0.0.4
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/CHANGELOG.md +12 -0
- package/dist/database/adapters/BaseAdapter.cjs.js +2 -2
- package/dist/database/adapters/BaseAdapter.cjs.js.map +1 -1
- package/dist/database/partition.cjs.js +29 -7
- package/dist/database/partition.cjs.js.map +1 -1
- package/dist/utils/partition.cjs.js +19 -0
- package/dist/utils/partition.cjs.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-adoption-insights-backend
|
2
2
|
|
3
|
+
## 0.0.4
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- 1e6eff6: Fix partition overlap error
|
8
|
+
|
9
|
+
## 0.0.3
|
10
|
+
|
11
|
+
### Patch Changes
|
12
|
+
|
13
|
+
- 808a0a4: fix subquery alias in total_users endpoint
|
14
|
+
|
3
15
|
## 0.0.2
|
4
16
|
|
5
17
|
### Patch Changes
|
@@ -85,8 +85,8 @@ class BaseDatabaseAdapter {
|
|
85
85
|
const { start_date, end_date } = this.filters;
|
86
86
|
const db = this.db;
|
87
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
|
-
)
|
88
|
+
db("events").select("user_ref").whereBetween("created_at", [start_date, end_date]).groupBy("user_ref").as("sub")
|
89
|
+
);
|
90
90
|
return query.then((result) => {
|
91
91
|
const { licensedUsers } = this.config;
|
92
92
|
result[0] = { ...result[0], licensed_users: licensedUsers };
|
@@ -1 +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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"}
|
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 .as('sub'),\n );\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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"}
|
@@ -1,16 +1,38 @@
|
|
1
1
|
'use strict';
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
const
|
8
|
-
const
|
9
|
-
|
3
|
+
var partition = require('../utils/partition.cjs.js');
|
4
|
+
|
5
|
+
const createPartition = async (knex, year, month, attempts = /* @__PURE__ */ new Map(), maxRetries = 1) => {
|
6
|
+
const start = new Date(year, month - 1, 1);
|
7
|
+
const end = new Date(year, month, 1);
|
8
|
+
const startDate = start.toISOString().slice(0, 10);
|
9
|
+
const endDate = end.toISOString().slice(0, 10);
|
10
|
+
const partitionName = `events_${year}_${month.toString().padStart(2, "0")}`;
|
11
|
+
const key = `${year}_${month}`;
|
12
|
+
const currentAttempt = attempts.get(key) ?? 0;
|
13
|
+
if (currentAttempt > maxRetries) {
|
14
|
+
throw new Error(`Exceeded max retries for partition ${key}`);
|
15
|
+
}
|
16
|
+
attempts.set(key, currentAttempt + 1);
|
17
|
+
try {
|
18
|
+
await knex.schema.raw(`
|
10
19
|
CREATE TABLE IF NOT EXISTS ${partitionName}
|
11
20
|
PARTITION OF events
|
12
21
|
FOR VALUES FROM ('${startDate}') TO ('${endDate}');
|
13
22
|
`);
|
23
|
+
} catch (error) {
|
24
|
+
if (partition.isPartitionOverlapError(error)) {
|
25
|
+
const overlappingPartition = partition.extractOverlappingPartition(error.message);
|
26
|
+
const { year: y, month: m } = partition.parsePartitionDate(overlappingPartition);
|
27
|
+
await knex.schema.raw(
|
28
|
+
`DROP TABLE IF EXISTS ${overlappingPartition} CASCADE`
|
29
|
+
);
|
30
|
+
await createPartition(knex, y, m, attempts, maxRetries);
|
31
|
+
await createPartition(knex, year, month, attempts, maxRetries);
|
32
|
+
} else {
|
33
|
+
throw error;
|
34
|
+
}
|
35
|
+
}
|
14
36
|
};
|
15
37
|
const schedulePartition = async (client, services) => {
|
16
38
|
const { logger, scheduler } = services;
|
@@ -1 +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
|
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';\nimport {\n extractOverlappingPartition,\n isPartitionOverlapError,\n parsePartitionDate,\n} from '../utils/partition';\n\ntype AttemptTracker = Map<string, number>;\n\nexport const createPartition = async (\n knex: Knex,\n year: number,\n month: number,\n attempts: AttemptTracker = new Map(),\n maxRetries = 1,\n) => {\n const start = new Date(year, month - 1, 1);\n const end = new Date(year, month, 1);\n\n const startDate = start.toISOString().slice(0, 10);\n const endDate = end.toISOString().slice(0, 10);\n\n const partitionName = `events_${year}_${month.toString().padStart(2, '0')}`;\n const key = `${year}_${month}`;\n\n // track max attempts\n const currentAttempt = attempts.get(key) ?? 0;\n if (currentAttempt > maxRetries) {\n throw new Error(`Exceeded max retries for partition ${key}`);\n }\n attempts.set(key, currentAttempt + 1);\n\n try {\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 } catch (error) {\n if (isPartitionOverlapError(error)) {\n const overlappingPartition = extractOverlappingPartition(error.message);\n const { year: y, month: m } = parsePartitionDate(overlappingPartition);\n\n await knex.schema.raw(\n `DROP TABLE IF EXISTS ${overlappingPartition} CASCADE`,\n );\n\n // Recreate the dropped overlapping partition\n await createPartition(knex, y, m, attempts, maxRetries);\n\n // Retry the current one\n await createPartition(knex, year, month, attempts, maxRetries);\n } else {\n throw error;\n }\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":["isPartitionOverlapError","extractOverlappingPartition","parsePartitionDate"],"mappings":"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"}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const extractOverlappingPartition = (message) => {
|
4
|
+
const match = message.match(/would overlap partition "(.*?)"$/);
|
5
|
+
return match?.[1] || "";
|
6
|
+
};
|
7
|
+
const parsePartitionDate = (name) => {
|
8
|
+
const match = name.match(/events_(\d{4})_(\d{1,2})/);
|
9
|
+
if (!match) throw new Error(`Cannot parse partition name: ${name}`);
|
10
|
+
return { year: parseInt(match[1], 10), month: parseInt(match[2], 10) };
|
11
|
+
};
|
12
|
+
const isPartitionOverlapError = (error) => {
|
13
|
+
return error !== null && typeof error.message === "string" && error.message.includes("would overlap partition");
|
14
|
+
};
|
15
|
+
|
16
|
+
exports.extractOverlappingPartition = extractOverlappingPartition;
|
17
|
+
exports.isPartitionOverlapError = isPartitionOverlapError;
|
18
|
+
exports.parsePartitionDate = parsePartitionDate;
|
19
|
+
//# sourceMappingURL=partition.cjs.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"partition.cjs.js","sources":["../../src/utils/partition.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 */\nexport const extractOverlappingPartition = (message: string): string => {\n const match = message.match(/would overlap partition \"(.*?)\"$/);\n return match?.[1] || '';\n};\n\nexport const parsePartitionDate = (\n name: string,\n): { year: number; month: number } => {\n const match = name.match(/events_(\\d{4})_(\\d{1,2})/);\n if (!match) throw new Error(`Cannot parse partition name: ${name}`);\n return { year: parseInt(match[1], 10), month: parseInt(match[2], 10) };\n};\n\nexport const isPartitionOverlapError = (error: unknown): boolean => {\n return (\n error !== null &&\n typeof (error as any).message === 'string' &&\n (error as any).message.includes('would overlap partition')\n );\n};\n"],"names":[],"mappings":";;AAea,MAAA,2BAAA,GAA8B,CAAC,OAA4B,KAAA;AACtE,EAAM,MAAA,KAAA,GAAQ,OAAQ,CAAA,KAAA,CAAM,kCAAkC,CAAA;AAC9D,EAAO,OAAA,KAAA,GAAQ,CAAC,CAAK,IAAA,EAAA;AACvB;AAEa,MAAA,kBAAA,GAAqB,CAChC,IACoC,KAAA;AACpC,EAAM,MAAA,KAAA,GAAQ,IAAK,CAAA,KAAA,CAAM,0BAA0B,CAAA;AACnD,EAAA,IAAI,CAAC,KAAO,EAAA,MAAM,IAAI,KAAM,CAAA,CAAA,6BAAA,EAAgC,IAAI,CAAE,CAAA,CAAA;AAClE,EAAA,OAAO,EAAE,IAAA,EAAM,QAAS,CAAA,KAAA,CAAM,CAAC,CAAG,EAAA,EAAE,CAAG,EAAA,KAAA,EAAO,QAAS,CAAA,KAAA,CAAM,CAAC,CAAA,EAAG,EAAE,CAAE,EAAA;AACvE;AAEa,MAAA,uBAAA,GAA0B,CAAC,KAA4B,KAAA;AAClE,EACE,OAAA,KAAA,KAAU,QACV,OAAQ,KAAA,CAAc,YAAY,QACjC,IAAA,KAAA,CAAc,OAAQ,CAAA,QAAA,CAAS,yBAAyB,CAAA;AAE7D;;;;;;"}
|