@red-hat-developer-hub/backstage-plugin-adoption-insights-backend 0.2.0 → 0.2.1

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-adoption-insights-backend
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 9c0b135: Audit logging support
8
+ - 8ea6f37: Ensure Dates created for partitioning always are created with the UTC timezone
9
+ - 586901c: Timezone Fixes for Consistent Data Grouping and Display
10
+
11
+ - Backend now accepts an explicit timezone parameter from the frontend instead of relying on Intl.DateTimeFormat().resolvedOptions().timeZone.
12
+ - This eliminates discrepancies between frontend simulation and backend processing.
13
+ - Accurate Date Range Construction
14
+ - Updated PostgreSQL query to respect user timezone.
15
+ - Tooltips now show formatted times based on user's selected timeszone.
16
+
17
+ - 742d79e: fixed an issue for the last month of the year introduced in the previous MR
18
+
3
19
  ## 0.2.0
4
20
 
5
21
  ### Minor Changes
@@ -14,37 +14,62 @@ class EventApiController {
14
14
  database;
15
15
  config;
16
16
  processor;
17
- constructor(eventDatabase, processor, config) {
17
+ auditor;
18
+ constructor(eventDatabase, processor, config, auditor) {
18
19
  this.database = eventDatabase;
19
20
  this.processor = processor;
20
21
  this.config = config;
22
+ this.auditor = auditor;
21
23
  }
22
24
  async getBaseUrl(pluginId) {
23
25
  return `${this.config.getString("backend.baseUrl")}/api/${pluginId}`;
24
26
  }
25
- processIncomingEvents(events) {
27
+ processIncomingEvents(events, auditEvent) {
26
28
  const proccessedEvents = events.filter((e) => !!e.context?.userId).map((event) => new Event.Event(event, this.database.isJsonSupported()));
27
29
  proccessedEvents.forEach((event$1) => {
28
30
  const result = event.EventSchema.safeParse(event$1);
29
31
  if (!result.success) {
32
+ auditEvent.fail({
33
+ error: new Error(JSON.stringify(result.error.flatten()))
34
+ });
30
35
  throw new ValidationError.ValidationError("Invalid event data", result.error.flatten());
31
36
  }
37
+ auditEvent.success({ meta: { eventId: event$1.id } });
32
38
  return this.processor.addEvent(event$1);
33
39
  });
34
40
  }
35
- trackEvents(req, res) {
41
+ async trackEvents(req, res) {
36
42
  const events = req.body;
43
+ const auditEvent = await this.auditor.createEvent({
44
+ eventId: "capture-event",
45
+ request: req
46
+ });
37
47
  try {
38
- this.processIncomingEvents(events);
48
+ this.processIncomingEvents(events, auditEvent);
39
49
  res.status(200).json({ success: true, message: "Event received" });
40
50
  } catch (error) {
51
+ auditEvent.fail({
52
+ error: error instanceof Error ? error : new Error(String(error))
53
+ });
41
54
  res.status(400).json({ message: error.message, errors: error.details.fieldErrors });
42
55
  }
43
56
  }
44
57
  // Get insights based on the type of data requested
45
58
  async getInsights(req, res) {
59
+ const auditEvent = await this.auditor.createEvent({
60
+ eventId: "get-insights",
61
+ request: req
62
+ });
46
63
  const parsed = eventRequest.EventRequestSchema.safeParse(req.query);
47
64
  if (!parsed.success) {
65
+ auditEvent.fail({
66
+ error: new Error(
67
+ JSON.stringify({
68
+ message: "Invalid query",
69
+ errors: parsed.error.flatten().fieldErrors
70
+ })
71
+ )
72
+ });
48
73
  res.status(400).json({
49
74
  message: "Invalid query",
50
75
  errors: parsed.error.flatten().fieldErrors
@@ -55,8 +80,8 @@ class EventApiController {
55
80
  const licensedUsers = config.getLicensedUsersCount(this.config);
56
81
  const filters = {
57
82
  ...params,
58
- end_date: date.toEndOfDayUTC(params.end_date),
59
- start_date: date.toStartOfDayUTC(params.start_date)
83
+ end_date: date.toEndOfDayUTC(params.end_date, params.timezone),
84
+ start_date: date.toStartOfDayUTC(params.start_date, params.timezone)
60
85
  };
61
86
  const db = this.database;
62
87
  db.setFilters(filters);
@@ -75,6 +100,13 @@ class EventApiController {
75
100
  if (type === "top_techdocs") {
76
101
  await this.getTechdocsMetadata(req, result);
77
102
  }
103
+ auditEvent.success({
104
+ meta: {
105
+ queryType: type,
106
+ format,
107
+ resultsCount: result.data?.length || 0
108
+ }
109
+ });
78
110
  if (format === "csv" && result.data) {
79
111
  const csv = json2Csv.json2csv(result.data);
80
112
  res.header("Content-Type", "text/csv");
@@ -84,6 +116,9 @@ class EventApiController {
84
116
  }
85
117
  res.json(result);
86
118
  } catch (error) {
119
+ auditEvent.fail({
120
+ error: error instanceof Error ? error : new Error(String(error))
121
+ });
87
122
  res.status(500).json({ error: "Internal Server Error" });
88
123
  return;
89
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EventApiController.cjs.js","sources":["../../src/controllers/EventApiController.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 { Request, Response } from 'express';\nimport { json2csv as Parser } from 'json-2-csv';\nimport { Knex } from 'knex';\nimport { AnalyticsEvent } from '@backstage/core-plugin-api';\nimport { EventDatabase, Filters } from '../database/event-database';\nimport { EventBatchProcessor } from '../domain/EventBatchProcessor';\nimport { Event } from '../models/Event';\nimport { QueryParams, QueryType } from '../types/event-request';\nimport { toEndOfDayUTC, toStartOfDayUTC } from '../utils/date';\nimport { EventSchema } from '../validation/event';\nimport { EventRequestSchema } from '../validation/event-request';\nimport { ValidationError } from '../validation/ValidationError';\nimport { RootConfigService } from '@backstage/backend-plugin-api';\nimport { getLicensedUsersCount } from '../utils/config';\nimport { TechDocsCount, TopTechDocsCount } from '../types/event';\n\nclass EventApiController {\n private readonly database: EventDatabase;\n private readonly config: RootConfigService;\n private readonly processor: EventBatchProcessor;\n\n constructor(\n eventDatabase: EventDatabase,\n processor: EventBatchProcessor,\n config: RootConfigService,\n ) {\n this.database = eventDatabase;\n this.processor = processor;\n this.config = config;\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n return `${this.config.getString('backend.baseUrl')}/api/${pluginId}`;\n }\n\n private processIncomingEvents(events: AnalyticsEvent[]): void {\n const proccessedEvents = events\n .filter(e => !!e.context?.userId)\n .map(event => new Event(event, this.database.isJsonSupported()));\n\n proccessedEvents.forEach(event => {\n const result = EventSchema.safeParse(event);\n\n if (!result.success) {\n throw new ValidationError('Invalid event data', result.error.flatten());\n }\n\n return this.processor.addEvent(event);\n });\n }\n\n trackEvents(req: Request<{}, {}, AnalyticsEvent[]>, res: Response): void {\n const events = req.body;\n\n try {\n this.processIncomingEvents(events);\n res.status(200).json({ success: true, message: 'Event received' });\n } catch (error) {\n res\n .status(400)\n .json({ message: error.message, errors: error.details.fieldErrors });\n }\n }\n\n // Get insights based on the type of data requested\n async getInsights(\n req: Request<{}, {}, {}, QueryParams>,\n res: Response,\n ): Promise<void> {\n const parsed = EventRequestSchema.safeParse(req.query);\n if (!parsed.success) {\n res.status(400).json({\n message: 'Invalid query',\n errors: parsed.error.flatten().fieldErrors,\n });\n return;\n }\n const { type, format, ...params } = parsed.data;\n const licensedUsers = getLicensedUsersCount(this.config);\n const filters: Filters = {\n ...params,\n end_date: toEndOfDayUTC(params.end_date) as string,\n start_date: toStartOfDayUTC(params.start_date) as string,\n };\n const db = this.database;\n\n db.setFilters(filters);\n db.setConfig({ licensedUsers });\n const queryHandlers: Record<QueryType, () => Promise<Knex.QueryBuilder>> = {\n total_users: () => db.getUsers(),\n active_users: () => db.getDailyUsers(),\n top_searches: () => db.getTopSearches(),\n top_plugins: () => db.getTopPluginViews(),\n top_techdocs: () => db.getTopTechDocsViews(),\n top_templates: () => db.getTopTemplateViews(),\n top_catalog_entities: () => db.getTopCatalogEntitiesViews(),\n };\n\n try {\n const result = await queryHandlers[type as QueryType]();\n\n if (type === 'top_techdocs') {\n await this.getTechdocsMetadata(req, result);\n }\n\n if (format === 'csv' && result.data) {\n const csv = Parser(result.data);\n res.header('Content-Type', 'text/csv');\n res.attachment(`adoption_insights_${type}.csv`);\n res.send(csv);\n return;\n }\n res.json(result);\n } catch (error) {\n res.status(500).json({ error: 'Internal Server Error' });\n return;\n }\n }\n\n async getTechdocsMetadata(\n req: Request<{}, {}, {}, QueryParams>,\n result: TopTechDocsCount,\n ) {\n const promises: Promise<void>[] = [];\n const baseUrl = await this.getBaseUrl('techdocs');\n\n result.data.forEach((row: TechDocsCount) => {\n if (!row.namespace || !row.kind || !row.name) {\n row.site_name = '';\n } else {\n promises.push(\n fetch(\n `${baseUrl}/metadata/techdocs/${row.namespace}/${row.kind}/${row.name}`,\n {\n headers: {\n Accept: 'application/json',\n Authorization: req.headers.authorization as string,\n },\n },\n )\n .then(async response => {\n const data = await response.json();\n row.site_name = data.site_name ?? row.name;\n })\n .catch(e => {\n console.warn(e);\n row.site_name = row.name;\n }),\n );\n }\n });\n\n await Promise.all(promises);\n }\n}\n\nexport default EventApiController;\n"],"names":["Event","event","EventSchema","ValidationError","EventRequestSchema","getLicensedUsersCount","toEndOfDayUTC","toStartOfDayUTC","Parser"],"mappings":"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"}
1
+ {"version":3,"file":"EventApiController.cjs.js","sources":["../../src/controllers/EventApiController.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 { Request, Response } from 'express';\nimport { json2csv as Parser } from 'json-2-csv';\nimport { Knex } from 'knex';\nimport { AnalyticsEvent } from '@backstage/core-plugin-api';\nimport { EventDatabase, Filters } from '../database/event-database';\nimport { EventBatchProcessor } from '../domain/EventBatchProcessor';\nimport { Event } from '../models/Event';\nimport { QueryParams, QueryType } from '../types/event-request';\nimport { toEndOfDayUTC, toStartOfDayUTC } from '../utils/date';\nimport { EventSchema } from '../validation/event';\nimport { EventRequestSchema } from '../validation/event-request';\nimport { ValidationError } from '../validation/ValidationError';\nimport {\n AuditorService,\n AuditorServiceEvent,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { getLicensedUsersCount } from '../utils/config';\nimport { TechDocsCount, TopTechDocsCount } from '../types/event';\n\nclass EventApiController {\n private readonly database: EventDatabase;\n private readonly config: RootConfigService;\n private readonly processor: EventBatchProcessor;\n private readonly auditor: AuditorService;\n\n constructor(\n eventDatabase: EventDatabase,\n processor: EventBatchProcessor,\n config: RootConfigService,\n auditor: AuditorService,\n ) {\n this.database = eventDatabase;\n this.processor = processor;\n this.config = config;\n this.auditor = auditor;\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n return `${this.config.getString('backend.baseUrl')}/api/${pluginId}`;\n }\n\n private processIncomingEvents(\n events: AnalyticsEvent[],\n auditEvent: AuditorServiceEvent,\n ): void {\n const proccessedEvents = events\n .filter(e => !!e.context?.userId)\n .map(event => new Event(event, this.database.isJsonSupported()));\n\n proccessedEvents.forEach(event => {\n const result = EventSchema.safeParse(event);\n\n if (!result.success) {\n auditEvent.fail({\n error: new Error(JSON.stringify(result.error.flatten())),\n });\n throw new ValidationError('Invalid event data', result.error.flatten());\n }\n auditEvent.success({ meta: { eventId: event.id } });\n return this.processor.addEvent(event);\n });\n }\n\n async trackEvents(\n req: Request<{}, {}, AnalyticsEvent[]>,\n res: Response,\n ): Promise<void> {\n const events = req.body;\n\n const auditEvent = await this.auditor.createEvent({\n eventId: 'capture-event',\n request: req,\n });\n\n try {\n this.processIncomingEvents(events, auditEvent);\n res.status(200).json({ success: true, message: 'Event received' });\n } catch (error) {\n auditEvent.fail({\n error: error instanceof Error ? error : new Error(String(error)),\n });\n res\n .status(400)\n .json({ message: error.message, errors: error.details.fieldErrors });\n }\n }\n\n // Get insights based on the type of data requested\n async getInsights(\n req: Request<{}, {}, {}, QueryParams>,\n res: Response,\n ): Promise<void> {\n const auditEvent = await this.auditor.createEvent({\n eventId: 'get-insights',\n request: req,\n });\n\n const parsed = EventRequestSchema.safeParse(req.query);\n if (!parsed.success) {\n auditEvent.fail({\n error: new Error(\n JSON.stringify({\n message: 'Invalid query',\n errors: parsed.error.flatten().fieldErrors,\n }),\n ),\n });\n res.status(400).json({\n message: 'Invalid query',\n errors: parsed.error.flatten().fieldErrors,\n });\n return;\n }\n const { type, format, ...params } = parsed.data;\n const licensedUsers = getLicensedUsersCount(this.config);\n const filters: Filters = {\n ...params,\n end_date: toEndOfDayUTC(params.end_date, params.timezone) as string,\n start_date: toStartOfDayUTC(params.start_date, params.timezone) as string,\n };\n const db = this.database;\n\n db.setFilters(filters);\n db.setConfig({ licensedUsers });\n const queryHandlers: Record<QueryType, () => Promise<Knex.QueryBuilder>> = {\n total_users: () => db.getUsers(),\n active_users: () => db.getDailyUsers(),\n top_searches: () => db.getTopSearches(),\n top_plugins: () => db.getTopPluginViews(),\n top_techdocs: () => db.getTopTechDocsViews(),\n top_templates: () => db.getTopTemplateViews(),\n top_catalog_entities: () => db.getTopCatalogEntitiesViews(),\n };\n\n try {\n const result = await queryHandlers[type as QueryType]();\n\n if (type === 'top_techdocs') {\n await this.getTechdocsMetadata(req, result);\n }\n\n auditEvent.success({\n meta: {\n queryType: type,\n format: format,\n resultsCount: result.data?.length || 0,\n },\n });\n\n if (format === 'csv' && result.data) {\n const csv = Parser(result.data);\n res.header('Content-Type', 'text/csv');\n res.attachment(`adoption_insights_${type}.csv`);\n res.send(csv);\n return;\n }\n res.json(result);\n } catch (error) {\n auditEvent.fail({\n error: error instanceof Error ? error : new Error(String(error)),\n });\n res.status(500).json({ error: 'Internal Server Error' });\n return;\n }\n }\n\n async getTechdocsMetadata(\n req: Request<{}, {}, {}, QueryParams>,\n result: TopTechDocsCount,\n ) {\n const promises: Promise<void>[] = [];\n const baseUrl = await this.getBaseUrl('techdocs');\n\n result.data.forEach((row: TechDocsCount) => {\n if (!row.namespace || !row.kind || !row.name) {\n row.site_name = '';\n } else {\n promises.push(\n fetch(\n `${baseUrl}/metadata/techdocs/${row.namespace}/${row.kind}/${row.name}`,\n {\n headers: {\n Accept: 'application/json',\n Authorization: req.headers.authorization as string,\n },\n },\n )\n .then(async response => {\n const data = await response.json();\n row.site_name = data.site_name ?? row.name;\n })\n .catch(e => {\n console.warn(e);\n row.site_name = row.name;\n }),\n );\n }\n });\n\n await Promise.all(promises);\n }\n}\n\nexport default EventApiController;\n"],"names":["Event","event","EventSchema","ValidationError","EventRequestSchema","getLicensedUsersCount","toEndOfDayUTC","toStartOfDayUTC","Parser"],"mappings":"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"}
@@ -58,7 +58,10 @@ class BaseDatabaseAdapter {
58
58
  this.ensureFiltersSet();
59
59
  const { start_date, end_date } = this.filters;
60
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");
61
+ const groupedEventsQuery = db("events").select(
62
+ db.raw(this.getDynamicDateGrouping({ useTimestamp: true })),
63
+ "user_ref"
64
+ ).whereBetween("created_at", [start_date, end_date]).groupByRaw("date, user_ref");
62
65
  const firstSeenQuery = db("events").select("user_ref").min("created_at as first_seen").groupBy("user_ref");
63
66
  const query = db.from(db.raw("(?) as ge", [groupedEventsQuery])).leftJoin(
64
67
  db.raw("(?) as fs", [firstSeenQuery]),
@@ -113,7 +116,7 @@ class BaseDatabaseAdapter {
113
116
  this.ensureFiltersSet();
114
117
  const { start_date, end_date, limit } = this.filters;
115
118
  const query = db("events").select(
116
- db.raw(this.getDynamicDateGrouping()),
119
+ db.raw(this.getDynamicDateGrouping({ useTimestamp: true })),
117
120
  db.raw("CAST(COUNT(*) as INTEGER) AS count")
118
121
  ).whereBetween("created_at", [start_date, end_date]).andWhere("action", "search").groupByRaw("date").orderBy("date", "asc").limit(Number(limit) || 3);
119
122
  return query.then((data) => this.getResponseWithGrouping(data));
@@ -159,7 +162,7 @@ class BaseDatabaseAdapter {
159
162
  const getTrendDataQuery = (qb) => {
160
163
  const trend_data_columns = [
161
164
  "plugin_id",
162
- db.raw(this.getDynamicDateGrouping()),
165
+ db.raw(this.getDynamicDateGrouping({ useTimestamp: false })),
163
166
  db.raw("CAST(COUNT(*) as INTEGER) AS count")
164
167
  ];
165
168
  return this.selectFromEvents(qb, trend_data_columns, dateRange).groupBy(
@@ -220,7 +223,12 @@ class BaseDatabaseAdapter {
220
223
  if (obj[datePath]) {
221
224
  return {
222
225
  ...obj,
223
- [datePath]: date.convertToLocalTimezone(obj[datePath])
226
+ ...this.isTimezoneSupported() ? {
227
+ [datePath]: date.convertToTargetTimezone(
228
+ obj[datePath],
229
+ this.filters?.timezone
230
+ )
231
+ } : {}
224
232
  };
225
233
  }
226
234
  return obj;
@@ -241,7 +249,9 @@ class BaseDatabaseAdapter {
241
249
  };
242
250
  }
243
251
  getResponseWithGrouping = (data, datePath = "date") => {
244
- const grouping = this.getDynamicDateGrouping(true);
252
+ const grouping = this.getDynamicDateGrouping({
253
+ onlyText: true
254
+ });
245
255
  if (grouping === "hourly") {
246
256
  return {
247
257
  grouping,
@@ -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 .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
+ {"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 { convertToTargetTimezone } 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(\n db.raw(this.getDynamicDateGrouping({ useTimestamp: true })),\n 'user_ref',\n )\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({ useTimestamp: true })),\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({ useTimestamp: false })),\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 isTimezoneSupported(): boolean;\n abstract isPartitionSupported(): boolean;\n abstract getDateBetweenQuery(): string;\n abstract getDynamicDateGrouping(options?: {\n onlyText?: boolean;\n useTimestamp?: boolean;\n }): 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 ...(this.isTimezoneSupported()\n ? {\n [datePath]: convertToTargetTimezone(\n obj[datePath] as string,\n this.filters?.timezone,\n ),\n }\n : {}),\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({\n onlyText: true,\n }) 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":["convertToTargetTimezone"],"mappings":"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"}
@@ -10,20 +10,23 @@ class PostgresAdapter extends BaseAdapter.BaseDatabaseAdapter {
10
10
  isPartitionSupported() {
11
11
  return true;
12
12
  }
13
+ isTimezoneSupported() {
14
+ return true;
15
+ }
13
16
  getFormatedDate(column) {
14
17
  return `${column}::timestamp`;
15
18
  }
16
19
  getDate() {
17
- const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
20
+ const timeZone = this.filters?.timezone || new Intl.DateTimeFormat().resolvedOptions().timeZone;
18
21
  return this.db.raw(
19
- `to_char(created_at AT TIME ZONE 'UTC' AT TIME ZONE ?,'YYYY-MM-DD"T"HH24:MI:SS.MSZ')`,
22
+ `to_char(created_at AT AT TIME ZONE ?,'YYYY-MM-DD"T"HH24:MI:SS.MSZ')`,
20
23
  [timeZone]
21
24
  ).toQuery();
22
25
  }
23
26
  getLastUsedDate() {
24
- const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
27
+ const timeZone = this.filters?.timezone || new Intl.DateTimeFormat().resolvedOptions().timeZone;
25
28
  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`,
29
+ `to_char(MAX(created_at) AT TIME ZONE ?,'YYYY-MM-DD"T"HH24:MI:SS.FF3') || 'Z' AS last_used`,
27
30
  [timeZone]
28
31
  ).toQuery();
29
32
  }
@@ -43,44 +46,48 @@ class PostgresAdapter extends BaseAdapter.BaseDatabaseAdapter {
43
46
  transformJson(data) {
44
47
  return data;
45
48
  }
46
- getDynamicDateGrouping(onlyText = false) {
49
+ getDynamicDateGrouping({ onlyText = false, useTimestamp = false }) {
47
50
  this.ensureFiltersSet();
48
51
  const { start_date, end_date, grouping: groupingStrategy } = this.filters;
49
52
  const dateDiff = date.calculateDateRange(start_date, end_date);
50
- const grouping = groupingStrategy || date.getDateGroupingType(dateDiff, start_date, end_date);
53
+ const grouping = groupingStrategy || date.getDateGroupingType(dateDiff);
51
54
  if (onlyText) {
52
55
  return grouping;
53
56
  }
54
- return `${this.getDateGroupingQuery(grouping)} as date`;
57
+ return `${this.getDateGroupingQuery(grouping, useTimestamp)} as date`;
55
58
  }
56
- getDateGroupingQuery(grouping) {
57
- const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
59
+ getDateGroupingQuery(grouping, useTimeStamp = false) {
60
+ const timeZone = this.filters?.timezone || new Intl.DateTimeFormat().resolvedOptions().timeZone;
58
61
  const rawQuery = (query, bindings) => this.db.raw(query, bindings).toQuery();
59
62
  switch (grouping) {
60
63
  case "hourly":
61
- return rawQuery(`date_trunc('hour', created_at AT TIME ZONE ?)`, [
62
- timeZone
63
- ]);
64
+ return useTimeStamp ? rawQuery(
65
+ `to_char(date_trunc('hour', created_at AT TIME ZONE ?) AT TIME ZONE ?, 'YYYY-MM-DD"T"HH24:MI:SSOF')`,
66
+ [timeZone, timeZone]
67
+ ) : rawQuery(
68
+ `date_trunc('hour', created_at AT TIME ZONE ?) AT TIME ZONE ?`,
69
+ [timeZone, timeZone]
70
+ );
64
71
  case "daily":
65
72
  return rawQuery(`to_char(created_at AT TIME ZONE ?, 'YYYY-MM-DD')`, [
66
73
  timeZone
67
74
  ]);
68
75
  case "weekly":
69
76
  return rawQuery(
70
- `to_char(date_trunc('week', created_at AT TIME ZONE ?), 'YYYY-MM-DD')`,
71
- [timeZone]
77
+ `to_char(date_trunc('week', created_at AT TIME ZONE ?) AT TIME ZONE ?, 'YYYY-MM-DD"T"HH24:MI:SSOF')`,
78
+ [timeZone, timeZone]
72
79
  );
73
80
  case "monthly":
74
81
  return rawQuery(
75
82
  `to_char(
76
83
  LEAST (
77
- (date_trunc('month', created_at AT TIME ZONE ?)
84
+ (date_trunc('month', created_at AT TIME ZONE ?) AT TIME ZONE ?
78
85
  + interval '1 month' - interval '1 day'),
79
86
  ?::date
80
87
  ),
81
- 'YYYY-MM-DD'
88
+ 'YYYY-MM-DD"T"HH24:MI:SSOF'
82
89
  )`,
83
- [timeZone, this.filters?.end_date]
90
+ [timeZone, timeZone, this.filters?.end_date]
84
91
  );
85
92
  default:
86
93
  throw new Error("Invalid date grouping");
@@ -1 +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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"}
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 isTimezoneSupported(): boolean {\n return true;\n }\n\n getFormatedDate(column: string): string {\n return `${column}::timestamp`;\n }\n\n getDate(): string {\n const timeZone =\n this.filters?.timezone ||\n new Intl.DateTimeFormat().resolvedOptions().timeZone;\n return this.db\n .raw(\n `to_char(created_at AT AT TIME ZONE ?,'YYYY-MM-DD\"T\"HH24:MI:SS.MSZ')`,\n [timeZone],\n )\n .toQuery();\n }\n\n getLastUsedDate(): string {\n const timeZone =\n this.filters?.timezone ||\n new Intl.DateTimeFormat().resolvedOptions().timeZone;\n return this.db\n .raw(\n `to_char(MAX(created_at) 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 = false, useTimestamp = 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 = groupingStrategy || getDateGroupingType(dateDiff);\n\n if (onlyText) {\n return grouping;\n }\n\n return `${this.getDateGroupingQuery(grouping, useTimestamp)} as date`;\n }\n\n private getDateGroupingQuery(\n grouping: string,\n useTimeStamp: boolean = false,\n ): string {\n const timeZone =\n this.filters?.timezone ||\n new 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 useTimeStamp\n ? rawQuery(\n `to_char(date_trunc('hour', created_at AT TIME ZONE ?) AT TIME ZONE ?, 'YYYY-MM-DD\"T\"HH24:MI:SSOF')`,\n [timeZone, timeZone],\n )\n : rawQuery(\n `date_trunc('hour', created_at AT TIME ZONE ?) AT TIME ZONE ?`,\n [timeZone, 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 ?) AT TIME ZONE ?, 'YYYY-MM-DD\"T\"HH24:MI:SSOF')`,\n [timeZone, timeZone],\n );\n case 'monthly':\n return rawQuery(\n `to_char(\n LEAST (\n (date_trunc('month', created_at AT TIME ZONE ?) AT TIME ZONE ?\n + interval '1 month' - interval '1 day'), \n ?::date\n ),\n 'YYYY-MM-DD\"T\"HH24:MI:SSOF'\n )`,\n [timeZone, 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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"}
@@ -10,11 +10,14 @@ class SqliteAdapter extends BaseAdapter.BaseDatabaseAdapter {
10
10
  isPartitionSupported() {
11
11
  return false;
12
12
  }
13
+ isTimezoneSupported() {
14
+ return false;
15
+ }
13
16
  getDate() {
14
- return `strftime('%Y-%m-%d', created_at, 'localtime') AS date`;
17
+ return `strftime('%Y-%m-%d', datetime(created_at, '${date.getTimeZoneOffsetString()}')) AS date`;
15
18
  }
16
19
  getLastUsedDate() {
17
- return `strftime('%Y-%m-%dT%H:%M:%SZ', MAX(created_at)) AS last_used`;
20
+ return `strftime('%Y-%m-%dT%H:%M:%SZ', MAX(datetime(created_at, '${date.getTimeZoneOffsetString()}'))) AS last_used`;
18
21
  }
19
22
  getFormatedDate(column) {
20
23
  return `datetime(${column})`;
@@ -32,25 +35,26 @@ class SqliteAdapter extends BaseAdapter.BaseDatabaseAdapter {
32
35
  [jsonField]: row[jsonField] ? JSON.parse(row[jsonField]) : null
33
36
  }));
34
37
  }
35
- getDynamicDateGrouping(onlyText = false) {
38
+ getDynamicDateGrouping({ onlyText = false }) {
36
39
  const { start_date, end_date, grouping: groupingStrategy } = this.filters;
37
40
  const dateDiff = date.calculateDateRange(start_date, end_date);
38
- const grouping = groupingStrategy || date.getDateGroupingType(dateDiff, start_date, end_date);
41
+ const grouping = groupingStrategy || date.getDateGroupingType(dateDiff);
39
42
  if (onlyText) {
40
43
  return grouping;
41
44
  }
42
45
  return this.db.raw(`${this.getDateGroupingQuery(grouping)} as date`).toQuery();
43
46
  }
44
47
  getDateGroupingQuery(grouping) {
48
+ const offsetStr = date.getTimeZoneOffsetString();
45
49
  switch (grouping) {
46
50
  case "hourly":
47
- return `strftime('%Y-%m-%d %H:00:00', created_at, 'localtime')`;
51
+ return `strftime('%Y-%m-%d %H:00:00', datetime(created_at, '${offsetStr}'))`;
48
52
  case "daily":
49
- return `strftime('%Y-%m-%d', created_at, 'localtime')`;
53
+ return `strftime('%Y-%m-%d', datetime(created_at, '${offsetStr}'))`;
50
54
  case "weekly":
51
- return `strftime('%Y-%m-%d', date(created_at, 'weekday 0', '-7 days'))`;
55
+ return `strftime('%Y-%m-%d', datetime(created_at, '${offsetStr}', 'weekday 0', '-6 days'))`;
52
56
  case "monthly":
53
- return `strftime('%Y-%m-01', date(created_at, 'localtime'))`;
57
+ return `strftime('%Y-%m-%d', datetime(created_at, '${offsetStr}'))`;
54
58
  default:
55
59
  throw new Error("Invalid date grouping");
56
60
  }
@@ -1 +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;;;;"}
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 {\n calculateDateRange,\n getDateGroupingType,\n getTimeZoneOffsetString,\n} 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 isTimezoneSupported(): boolean {\n return false;\n }\n\n getDate(): string {\n return `strftime('%Y-%m-%d', datetime(created_at, '${getTimeZoneOffsetString()}')) AS date`;\n }\n\n getLastUsedDate(): string {\n return `strftime('%Y-%m-%dT%H:%M:%SZ', MAX(datetime(created_at, '${getTimeZoneOffsetString()}'))) 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 = false }): string {\n const { start_date, end_date, grouping: groupingStrategy } = this.filters!;\n const dateDiff = calculateDateRange(start_date, end_date);\n\n const grouping = groupingStrategy || getDateGroupingType(dateDiff);\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 const offsetStr = getTimeZoneOffsetString();\n switch (grouping) {\n case 'hourly':\n return `strftime('%Y-%m-%d %H:00:00', datetime(created_at, '${offsetStr}'))`;\n case 'daily':\n return `strftime('%Y-%m-%d', datetime(created_at, '${offsetStr}'))`;\n case 'weekly':\n return `strftime('%Y-%m-%d', datetime(created_at, '${offsetStr}', 'weekday 0', '-6 days'))`;\n case 'monthly':\n return `strftime('%Y-%m-%d', datetime(created_at, '${offsetStr}'))`;\n default:\n throw new Error('Invalid date grouping');\n }\n }\n}\n"],"names":["BaseDatabaseAdapter","getTimeZoneOffsetString","calculateDateRange","getDateGroupingType"],"mappings":";;;;;AAsBO,MAAM,sBAAsBA,+BAAoB,CAAA;AAAA,EACrD,eAA2B,GAAA;AACzB,IAAO,OAAA,KAAA;AAAA;AACT,EAEA,oBAAgC,GAAA;AAC9B,IAAO,OAAA,KAAA;AAAA;AACT,EAEA,mBAA+B,GAAA;AAC7B,IAAO,OAAA,KAAA;AAAA;AACT,EAEA,OAAkB,GAAA;AAChB,IAAO,OAAA,CAAA,2CAAA,EAA8CC,8BAAyB,CAAA,WAAA,CAAA;AAAA;AAChF,EAEA,eAA0B,GAAA;AACxB,IAAO,OAAA,CAAA,yDAAA,EAA4DA,8BAAyB,CAAA,iBAAA,CAAA;AAAA;AAC9F,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,sBAAuB,CAAA,EAAE,QAAW,GAAA,KAAA,EAAiB,EAAA;AACnD,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,IAAM,MAAA,QAAA,GAAW,gBAAoB,IAAAC,wBAAA,CAAoB,QAAQ,CAAA;AAEjE,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,MAAM,YAAYF,4BAAwB,EAAA;AAC1C,IAAA,QAAQ,QAAU;AAAA,MAChB,KAAK,QAAA;AACH,QAAA,OAAO,uDAAuD,SAAS,CAAA,GAAA,CAAA;AAAA,MACzE,KAAK,OAAA;AACH,QAAA,OAAO,8CAA8C,SAAS,CAAA,GAAA,CAAA;AAAA,MAChE,KAAK,QAAA;AACH,QAAA,OAAO,8CAA8C,SAAS,CAAA,2BAAA,CAAA;AAAA,MAChE,KAAK,SAAA;AACH,QAAA,OAAO,8CAA8C,SAAS,CAAA,GAAA,CAAA;AAAA,MAChE;AACE,QAAM,MAAA,IAAI,MAAM,uBAAuB,CAAA;AAAA;AAC3C;AAEJ;;;;"}
@@ -1,12 +1,24 @@
1
1
  'use strict';
2
2
 
3
3
  var partition = require('../utils/partition.cjs.js');
4
+ var luxon = require('luxon');
4
5
 
5
6
  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);
7
+ const startObject = luxon.DateTime.fromObject(
8
+ { year, month, day: 1 },
9
+ { zone: "UTC" }
10
+ );
11
+ const endObject = luxon.DateTime.fromObject(
12
+ { year, month, day: 1 },
13
+ { zone: "UTC" }
14
+ ).plus({ month: 1 });
15
+ if (!endObject.isValid || !startObject.isValid) {
16
+ throw new Error(
17
+ `The combination of year ${year} and month ${month} is invalid.`
18
+ );
19
+ }
20
+ const startDate = startObject.toISODate();
21
+ const endDate = endObject.toISODate();
10
22
  const partitionName = `events_${year}_${month.toString().padStart(2, "0")}`;
11
23
  const key = `${year}_${month}`;
12
24
  const currentAttempt = attempts.get(key) ?? 0;
@@ -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 { LoggerService, SchedulerService } from '@backstage/backend-plugin-api';\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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"}
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 { LoggerService, SchedulerService } from '@backstage/backend-plugin-api';\nimport {\n extractOverlappingPartition,\n isPartitionOverlapError,\n parsePartitionDate,\n} from '../utils/partition';\nimport { DateTime } from 'luxon';\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 startObject = DateTime.fromObject(\n { year, month, day: 1 },\n { zone: 'UTC' },\n );\n const endObject = DateTime.fromObject(\n { year, month: month, day: 1 },\n { zone: 'UTC' },\n ).plus({ month: 1 });\n if (!endObject.isValid || !startObject.isValid) {\n throw new Error(\n `The combination of year ${year} and month ${month} is invalid.`,\n );\n }\n\n const startDate = startObject.toISODate();\n const endDate = endObject.toISODate();\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":["DateTime","isPartitionOverlapError","extractOverlappingPartition","parsePartitionDate"],"mappings":"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"}
@@ -15,6 +15,7 @@ const adoptionInsightsPlugin = backendPluginApi.createBackendPlugin({
15
15
  register(env) {
16
16
  env.registerInit({
17
17
  deps: {
18
+ auditor: backendPluginApi.coreServices.auditor,
18
19
  config: backendPluginApi.coreServices.rootConfig,
19
20
  logger: backendPluginApi.coreServices.logger,
20
21
  httpAuth: backendPluginApi.coreServices.httpAuth,
@@ -25,6 +26,7 @@ const adoptionInsightsPlugin = backendPluginApi.createBackendPlugin({
25
26
  permissionsRegistry: backendPluginApi.coreServices.permissionsRegistry
26
27
  },
27
28
  async init({
29
+ auditor,
28
30
  config: config$1,
29
31
  logger,
30
32
  httpAuth,
@@ -44,7 +46,8 @@ const adoptionInsightsPlugin = backendPluginApi.createBackendPlugin({
44
46
  const eventApiController = new EventApiController.default(
45
47
  db,
46
48
  processor,
47
- config$1
49
+ config$1,
50
+ auditor
48
51
  );
49
52
  await migration.migrate(database);
50
53
  if (db.isPartitionSupported()) {
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.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 coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { adoptionInsightsEventsReadPermission } from '@red-hat-developer-hub/backstage-plugin-adoption-insights-common';\nimport { createRouter } from './router';\nimport { migrate } from './database/migration';\nimport { DatabaseFactory } from './database/DatabaseFactory';\nimport { EventBatchProcessor } from './domain/EventBatchProcessor';\nimport EventApiController from './controllers/EventApiController';\nimport { schedulePartition } from './database/partition';\nimport { getConfigurationOptions } from './utils/config';\n\n/**\n * adoptionInsightsPlugin backend plugin\n *\n * @public\n */\nexport const adoptionInsightsPlugin = createBackendPlugin({\n pluginId: 'adoption-insights',\n register(env) {\n env.registerInit({\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n httpAuth: coreServices.httpAuth,\n httpRouter: coreServices.httpRouter,\n database: coreServices.database,\n scheduler: coreServices.scheduler,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n },\n async init({\n config,\n logger,\n httpAuth,\n httpRouter,\n database,\n scheduler,\n permissions,\n permissionsRegistry,\n }) {\n // Register plugin permission\n permissionsRegistry.addPermissions([\n adoptionInsightsEventsReadPermission,\n ]);\n\n // Queue configuration\n const options = getConfigurationOptions(config);\n const client = await database.getClient();\n const db = DatabaseFactory.getDatabase(client, logger);\n const processor = new EventBatchProcessor(db, logger, options);\n const eventApiController = new EventApiController(\n db,\n processor,\n config,\n );\n\n // Migrate database\n await migrate(database);\n\n // Schedule partition creation\n if (db.isPartitionSupported()) {\n schedulePartition(client, { logger, scheduler });\n }\n\n httpRouter.use(\n await createRouter({\n httpAuth,\n permissions,\n eventApiController,\n }),\n );\n\n httpRouter.addAuthPolicy({\n path: '/health',\n allow: 'unauthenticated',\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","config","adoptionInsightsEventsReadPermission","getConfigurationOptions","DatabaseFactory","EventBatchProcessor","EventApiController","migrate","schedulePartition","createRouter"],"mappings":";;;;;;;;;;;;AAiCO,MAAM,yBAAyBA,oCAAoB,CAAA;AAAA,EACxD,QAAU,EAAA,mBAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,aAAaA,6BAAa,CAAA,WAAA;AAAA,QAC1B,qBAAqBA,6BAAa,CAAA;AAAA,OACpC;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,gBACTC,QAAA;AAAA,QACA,MAAA;AAAA,QACA,QAAA;AAAA,QACA,UAAA;AAAA,QACA,QAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA,OACC,EAAA;AAED,QAAA,mBAAA,CAAoB,cAAe,CAAA;AAAA,UACjCC;AAAA,SACD,CAAA;AAGD,QAAM,MAAA,OAAA,GAAUC,+BAAwBF,QAAM,CAAA;AAC9C,QAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,SAAU,EAAA;AACxC,QAAA,MAAM,EAAK,GAAAG,+BAAA,CAAgB,WAAY,CAAA,MAAA,EAAQ,MAAM,CAAA;AACrD,QAAA,MAAM,SAAY,GAAA,IAAIC,uCAAoB,CAAA,EAAA,EAAI,QAAQ,OAAO,CAAA;AAC7D,QAAA,MAAM,qBAAqB,IAAIC,0BAAA;AAAA,UAC7B,EAAA;AAAA,UACA,SAAA;AAAA,UACAL;AAAA,SACF;AAGA,QAAA,MAAMM,kBAAQ,QAAQ,CAAA;AAGtB,QAAI,IAAA,EAAA,CAAG,sBAAwB,EAAA;AAC7B,UAAAC,2BAAA,CAAkB,MAAQ,EAAA,EAAE,MAAQ,EAAA,SAAA,EAAW,CAAA;AAAA;AAGjD,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,QAAA;AAAA,YACA,WAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAEA,QAAA,UAAA,CAAW,aAAc,CAAA;AAAA,UACvB,IAAM,EAAA,SAAA;AAAA,UACN,KAAO,EAAA;AAAA,SACR,CAAA;AAAA;AACH,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.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 coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { adoptionInsightsEventsReadPermission } from '@red-hat-developer-hub/backstage-plugin-adoption-insights-common';\nimport { createRouter } from './router';\nimport { migrate } from './database/migration';\nimport { DatabaseFactory } from './database/DatabaseFactory';\nimport { EventBatchProcessor } from './domain/EventBatchProcessor';\nimport EventApiController from './controllers/EventApiController';\nimport { schedulePartition } from './database/partition';\nimport { getConfigurationOptions } from './utils/config';\n\n/**\n * adoptionInsightsPlugin backend plugin\n *\n * @public\n */\nexport const adoptionInsightsPlugin = createBackendPlugin({\n pluginId: 'adoption-insights',\n register(env) {\n env.registerInit({\n deps: {\n auditor: coreServices.auditor,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n httpAuth: coreServices.httpAuth,\n httpRouter: coreServices.httpRouter,\n database: coreServices.database,\n scheduler: coreServices.scheduler,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n },\n async init({\n auditor,\n config,\n logger,\n httpAuth,\n httpRouter,\n database,\n scheduler,\n permissions,\n permissionsRegistry,\n }) {\n // Register plugin permission\n permissionsRegistry.addPermissions([\n adoptionInsightsEventsReadPermission,\n ]);\n\n // Queue configuration\n const options = getConfigurationOptions(config);\n const client = await database.getClient();\n const db = DatabaseFactory.getDatabase(client, logger);\n const processor = new EventBatchProcessor(db, logger, options);\n const eventApiController = new EventApiController(\n db,\n processor,\n config,\n auditor,\n );\n\n // Migrate database\n await migrate(database);\n\n // Schedule partition creation\n if (db.isPartitionSupported()) {\n schedulePartition(client, { logger, scheduler });\n }\n\n httpRouter.use(\n await createRouter({\n httpAuth,\n permissions,\n eventApiController,\n }),\n );\n\n httpRouter.addAuthPolicy({\n path: '/health',\n allow: 'unauthenticated',\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","config","adoptionInsightsEventsReadPermission","getConfigurationOptions","DatabaseFactory","EventBatchProcessor","EventApiController","migrate","schedulePartition","createRouter"],"mappings":";;;;;;;;;;;;AAiCO,MAAM,yBAAyBA,oCAAoB,CAAA;AAAA,EACxD,QAAU,EAAA,mBAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,SAASC,6BAAa,CAAA,OAAA;AAAA,QACtB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,aAAaA,6BAAa,CAAA,WAAA;AAAA,QAC1B,qBAAqBA,6BAAa,CAAA;AAAA,OACpC;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,OAAA;AAAA,gBACAC,QAAA;AAAA,QACA,MAAA;AAAA,QACA,QAAA;AAAA,QACA,UAAA;AAAA,QACA,QAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA;AAAA,OACC,EAAA;AAED,QAAA,mBAAA,CAAoB,cAAe,CAAA;AAAA,UACjCC;AAAA,SACD,CAAA;AAGD,QAAM,MAAA,OAAA,GAAUC,+BAAwBF,QAAM,CAAA;AAC9C,QAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,SAAU,EAAA;AACxC,QAAA,MAAM,EAAK,GAAAG,+BAAA,CAAgB,WAAY,CAAA,MAAA,EAAQ,MAAM,CAAA;AACrD,QAAA,MAAM,SAAY,GAAA,IAAIC,uCAAoB,CAAA,EAAA,EAAI,QAAQ,OAAO,CAAA;AAC7D,QAAA,MAAM,qBAAqB,IAAIC,0BAAA;AAAA,UAC7B,EAAA;AAAA,UACA,SAAA;AAAA,UACAL,QAAA;AAAA,UACA;AAAA,SACF;AAGA,QAAA,MAAMM,kBAAQ,QAAQ,CAAA;AAGtB,QAAI,IAAA,EAAA,CAAG,sBAAwB,EAAA;AAC7B,UAAAC,2BAAA,CAAkB,MAAQ,EAAA,EAAE,MAAQ,EAAA,SAAA,EAAW,CAAA;AAAA;AAGjD,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,QAAA;AAAA,YACA,WAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAEA,QAAA,UAAA,CAAW,aAAc,CAAA;AAAA,UACvB,IAAM,EAAA,SAAA;AAAA,UACN,KAAO,EAAA;AAAA,SACR,CAAA;AAAA;AACH,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
@@ -9,38 +9,37 @@ const calculateDateRange = (start_date, end_date) => {
9
9
  const end = luxon.DateTime.fromISO(end_date, { zone: "UTC" });
10
10
  return Math.floor(end.diff(start, "days").days);
11
11
  };
12
- const isSameMonth = (start_date, end_date) => {
13
- const start = luxon.DateTime.fromISO(start_date, { zone: "UTC" });
14
- const end = luxon.DateTime.fromISO(end_date, { zone: "UTC" });
15
- return start.hasSame(end, "month");
16
- };
17
- const getDateGroupingType = (dateDiff, start_date, end_date) => {
12
+ const getDateGroupingType = (dateDiff) => {
18
13
  if (dateDiff === 0) return "hourly";
19
14
  if (dateDiff <= 7) return "daily";
20
- if (dateDiff <= 30 && isSameMonth(start_date, end_date)) return "weekly";
15
+ if (dateDiff <= 30) return "weekly";
21
16
  return "monthly";
22
17
  };
23
- const hasZFormat = (dateStr) => {
24
- return dateStr.includes("Z") || dateStr.includes("T");
25
- };
26
- const convertToLocalTimezone = (date) => {
27
- const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
28
- const parsedDate = hasZFormat(date.toString()) ? new Date(date).toISOString() : date;
29
- if (luxon.DateTime.fromISO(parsedDate, { zone: timeZone }).isValid) {
30
- return luxon.DateTime.fromISO(parsedDate, { zone: timeZone }).toFormat(
31
- "yyyy-MM-dd HH:mm:ss ZZZZ"
32
- );
18
+ const convertToTargetTimezone = (date, timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone) => {
19
+ const dateString = date instanceof Date ? date.toISOString() : date;
20
+ const isoParsed = luxon.DateTime.fromISO(dateString, { setZone: true });
21
+ if (isoParsed.isValid) {
22
+ return isoParsed.setZone(timeZone).toISO();
23
+ }
24
+ const fallback = luxon.DateTime.fromFormat(dateString, "yyyy-MM-dd HH:mm:ss", {
25
+ zone: "UTC"
26
+ });
27
+ if (fallback.isValid) {
28
+ return fallback.setZone(timeZone).toISO();
33
29
  }
34
- return luxon.DateTime.fromFormat(parsedDate, "yyyy-MM-dd HH:mm:ss", {
35
- zone: timeZone
36
- }).toFormat("yyyy-MM-dd HH:mm:ss ZZZZ");
30
+ console.warn("Unable to parse date:", date);
31
+ return date;
32
+ };
33
+ const getTimeZoneOffsetString = () => {
34
+ const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
35
+ const now = luxon.DateTime.now().setZone(timeZone);
36
+ return now.toFormat("ZZ");
37
37
  };
38
38
 
39
39
  exports.calculateDateRange = calculateDateRange;
40
- exports.convertToLocalTimezone = convertToLocalTimezone;
40
+ exports.convertToTargetTimezone = convertToTargetTimezone;
41
41
  exports.getDateGroupingType = getDateGroupingType;
42
- exports.hasZFormat = hasZFormat;
43
- exports.isSameMonth = isSameMonth;
42
+ exports.getTimeZoneOffsetString = getTimeZoneOffsetString;
44
43
  exports.toEndOfDayUTC = toEndOfDayUTC;
45
44
  exports.toStartOfDayUTC = toStartOfDayUTC;
46
45
  //# sourceMappingURL=date.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"date.cjs.js","sources":["../../src/utils/date.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 { DateTime } from 'luxon';\nimport { Grouping } from '../types/event';\n\nexport const toStartOfDayUTC = (dateString: string, timezone = 'UTC') =>\n DateTime.fromFormat(dateString, 'yyyy-MM-dd', { zone: timezone })\n .startOf('day')\n .toUTC()\n .toISO();\n\nexport const toEndOfDayUTC = (dateString: string, timezone = 'UTC') =>\n DateTime.fromFormat(dateString, 'yyyy-MM-dd', { zone: timezone })\n .endOf('day')\n .toUTC()\n .toISO();\n\nexport const calculateDateRange = (\n start_date: string,\n end_date: string,\n): number => {\n const start = DateTime.fromISO(start_date, { zone: 'UTC' });\n const end = DateTime.fromISO(end_date, { zone: 'UTC' });\n\n return Math.floor(end.diff(start, 'days').days);\n};\n\nexport const isSameMonth = (start_date: string, end_date: string): boolean => {\n const start = DateTime.fromISO(start_date, { zone: 'UTC' });\n const end = DateTime.fromISO(end_date, { zone: 'UTC' });\n\n return start.hasSame(end, 'month');\n};\n\nexport const getDateGroupingType = (\n dateDiff: number,\n start_date: string,\n end_date: string,\n): Grouping => {\n if (dateDiff === 0) return 'hourly';\n if (dateDiff <= 7) return 'daily';\n if (dateDiff <= 30 && isSameMonth(start_date, end_date)) return 'weekly';\n return 'monthly';\n};\n\nexport const hasZFormat = (dateStr: string): boolean => {\n return dateStr.includes('Z') || dateStr.includes('T');\n};\n\nexport const convertToLocalTimezone = (date: string) => {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n\n const parsedDate = hasZFormat(date.toString())\n ? new Date(date).toISOString()\n : date;\n\n if (DateTime.fromISO(parsedDate, { zone: timeZone }).isValid) {\n return DateTime.fromISO(parsedDate, { zone: timeZone }).toFormat(\n 'yyyy-MM-dd HH:mm:ss ZZZZ',\n );\n }\n return DateTime.fromFormat(parsedDate, 'yyyy-MM-dd HH:mm:ss', {\n zone: timeZone,\n }).toFormat('yyyy-MM-dd HH:mm:ss ZZZZ');\n};\n"],"names":["DateTime"],"mappings":";;;;AAkBO,MAAM,kBAAkB,CAAC,UAAA,EAAoB,WAAW,KAC7D,KAAAA,cAAA,CAAS,WAAW,UAAY,EAAA,YAAA,EAAc,EAAE,IAAM,EAAA,QAAA,EAAU,CAC7D,CAAA,OAAA,CAAQ,KAAK,CACb,CAAA,KAAA,GACA,KAAM;AAEJ,MAAM,gBAAgB,CAAC,UAAA,EAAoB,WAAW,KAC3D,KAAAA,cAAA,CAAS,WAAW,UAAY,EAAA,YAAA,EAAc,EAAE,IAAM,EAAA,QAAA,EAAU,CAC7D,CAAA,KAAA,CAAM,KAAK,CACX,CAAA,KAAA,GACA,KAAM;AAEE,MAAA,kBAAA,GAAqB,CAChC,UAAA,EACA,QACW,KAAA;AACX,EAAA,MAAM,QAAQA,cAAS,CAAA,OAAA,CAAQ,YAAY,EAAE,IAAA,EAAM,OAAO,CAAA;AAC1D,EAAA,MAAM,MAAMA,cAAS,CAAA,OAAA,CAAQ,UAAU,EAAE,IAAA,EAAM,OAAO,CAAA;AAEtD,EAAA,OAAO,KAAK,KAAM,CAAA,GAAA,CAAI,KAAK,KAAO,EAAA,MAAM,EAAE,IAAI,CAAA;AAChD;AAEa,MAAA,WAAA,GAAc,CAAC,UAAA,EAAoB,QAA8B,KAAA;AAC5E,EAAA,MAAM,QAAQA,cAAS,CAAA,OAAA,CAAQ,YAAY,EAAE,IAAA,EAAM,OAAO,CAAA;AAC1D,EAAA,MAAM,MAAMA,cAAS,CAAA,OAAA,CAAQ,UAAU,EAAE,IAAA,EAAM,OAAO,CAAA;AAEtD,EAAO,OAAA,KAAA,CAAM,OAAQ,CAAA,GAAA,EAAK,OAAO,CAAA;AACnC;AAEO,MAAM,mBAAsB,GAAA,CACjC,QACA,EAAA,UAAA,EACA,QACa,KAAA;AACb,EAAI,IAAA,QAAA,KAAa,GAAU,OAAA,QAAA;AAC3B,EAAI,IAAA,QAAA,IAAY,GAAU,OAAA,OAAA;AAC1B,EAAA,IAAI,YAAY,EAAM,IAAA,WAAA,CAAY,UAAY,EAAA,QAAQ,GAAU,OAAA,QAAA;AAChE,EAAO,OAAA,SAAA;AACT;AAEa,MAAA,UAAA,GAAa,CAAC,OAA6B,KAAA;AACtD,EAAA,OAAO,QAAQ,QAAS,CAAA,GAAG,CAAK,IAAA,OAAA,CAAQ,SAAS,GAAG,CAAA;AACtD;AAEa,MAAA,sBAAA,GAAyB,CAAC,IAAiB,KAAA;AACtD,EAAA,MAAM,QAAW,GAAA,IAAA,CAAK,cAAe,EAAA,CAAE,iBAAkB,CAAA,QAAA;AAEzD,EAAM,MAAA,UAAA,GAAa,UAAW,CAAA,IAAA,CAAK,QAAS,EAAC,CACzC,GAAA,IAAI,IAAK,CAAA,IAAI,CAAE,CAAA,WAAA,EACf,GAAA,IAAA;AAEJ,EAAI,IAAAA,cAAA,CAAS,QAAQ,UAAY,EAAA,EAAE,MAAM,QAAS,EAAC,EAAE,OAAS,EAAA;AAC5D,IAAA,OAAOA,eAAS,OAAQ,CAAA,UAAA,EAAY,EAAE,IAAM,EAAA,QAAA,EAAU,CAAE,CAAA,QAAA;AAAA,MACtD;AAAA,KACF;AAAA;AAEF,EAAO,OAAAA,cAAA,CAAS,UAAW,CAAA,UAAA,EAAY,qBAAuB,EAAA;AAAA,IAC5D,IAAM,EAAA;AAAA,GACP,CAAE,CAAA,QAAA,CAAS,0BAA0B,CAAA;AACxC;;;;;;;;;;"}
1
+ {"version":3,"file":"date.cjs.js","sources":["../../src/utils/date.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 { DateTime } from 'luxon';\nimport { Grouping } from '../types/event';\n\nexport const toStartOfDayUTC = (dateString: string, timezone = 'UTC') =>\n DateTime.fromFormat(dateString, 'yyyy-MM-dd', { zone: timezone })\n .startOf('day')\n .toUTC()\n .toISO();\n\nexport const toEndOfDayUTC = (dateString: string, timezone = 'UTC') =>\n DateTime.fromFormat(dateString, 'yyyy-MM-dd', { zone: timezone })\n .endOf('day')\n .toUTC()\n .toISO();\n\nexport const calculateDateRange = (\n start_date: string,\n end_date: string,\n): number => {\n const start = DateTime.fromISO(start_date, { zone: 'UTC' });\n const end = DateTime.fromISO(end_date, { zone: 'UTC' });\n\n return Math.floor(end.diff(start, 'days').days);\n};\n\nexport const isSameMonth = (start_date: string, end_date: string): boolean => {\n const start = DateTime.fromISO(start_date, { zone: 'UTC' });\n const end = DateTime.fromISO(end_date, { zone: 'UTC' });\n\n return start.hasSame(end, 'month');\n};\n\nexport const getDateGroupingType = (dateDiff: number): Grouping => {\n if (dateDiff === 0) return 'hourly';\n if (dateDiff <= 7) return 'daily';\n if (dateDiff <= 30) return 'weekly';\n return 'monthly';\n};\n\nexport const hasZFormat = (dateStr: string): boolean => {\n return dateStr.includes('Z') || dateStr.includes('T');\n};\n\nexport const convertToTargetTimezone = (\n date: string | Date,\n timeZone: string = new Intl.DateTimeFormat().resolvedOptions().timeZone,\n) => {\n const dateString = date instanceof Date ? date.toISOString() : date;\n\n const isoParsed = DateTime.fromISO(dateString, { setZone: true });\n\n if (isoParsed.isValid) {\n return isoParsed.setZone(timeZone).toISO();\n }\n\n // If not valid ISO, try parsing as 'yyyy-MM-dd HH:mm:ss' in UTC\n const fallback = DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm:ss', {\n zone: 'UTC',\n });\n\n if (fallback.isValid) {\n return fallback.setZone(timeZone).toISO();\n }\n\n // Last resort: return the original date\n console.warn('Unable to parse date:', date);\n return date;\n};\n\nexport const getTimeZoneOffsetString = () => {\n const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;\n const now = DateTime.now().setZone(timeZone);\n return now.toFormat('ZZ');\n};\n"],"names":["DateTime"],"mappings":";;;;AAkBO,MAAM,kBAAkB,CAAC,UAAA,EAAoB,WAAW,KAC7D,KAAAA,cAAA,CAAS,WAAW,UAAY,EAAA,YAAA,EAAc,EAAE,IAAM,EAAA,QAAA,EAAU,CAC7D,CAAA,OAAA,CAAQ,KAAK,CACb,CAAA,KAAA,GACA,KAAM;AAEJ,MAAM,gBAAgB,CAAC,UAAA,EAAoB,WAAW,KAC3D,KAAAA,cAAA,CAAS,WAAW,UAAY,EAAA,YAAA,EAAc,EAAE,IAAM,EAAA,QAAA,EAAU,CAC7D,CAAA,KAAA,CAAM,KAAK,CACX,CAAA,KAAA,GACA,KAAM;AAEE,MAAA,kBAAA,GAAqB,CAChC,UAAA,EACA,QACW,KAAA;AACX,EAAA,MAAM,QAAQA,cAAS,CAAA,OAAA,CAAQ,YAAY,EAAE,IAAA,EAAM,OAAO,CAAA;AAC1D,EAAA,MAAM,MAAMA,cAAS,CAAA,OAAA,CAAQ,UAAU,EAAE,IAAA,EAAM,OAAO,CAAA;AAEtD,EAAA,OAAO,KAAK,KAAM,CAAA,GAAA,CAAI,KAAK,KAAO,EAAA,MAAM,EAAE,IAAI,CAAA;AAChD;AASa,MAAA,mBAAA,GAAsB,CAAC,QAA+B,KAAA;AACjE,EAAI,IAAA,QAAA,KAAa,GAAU,OAAA,QAAA;AAC3B,EAAI,IAAA,QAAA,IAAY,GAAU,OAAA,OAAA;AAC1B,EAAI,IAAA,QAAA,IAAY,IAAW,OAAA,QAAA;AAC3B,EAAO,OAAA,SAAA;AACT;AAMa,MAAA,uBAAA,GAA0B,CACrC,IAAA,EACA,QAAmB,GAAA,IAAI,KAAK,cAAe,EAAA,CAAE,eAAgB,EAAA,CAAE,QAC5D,KAAA;AACH,EAAA,MAAM,UAAa,GAAA,IAAA,YAAgB,IAAO,GAAA,IAAA,CAAK,aAAgB,GAAA,IAAA;AAE/D,EAAA,MAAM,YAAYA,cAAS,CAAA,OAAA,CAAQ,YAAY,EAAE,OAAA,EAAS,MAAM,CAAA;AAEhE,EAAA,IAAI,UAAU,OAAS,EAAA;AACrB,IAAA,OAAO,SAAU,CAAA,OAAA,CAAQ,QAAQ,CAAA,CAAE,KAAM,EAAA;AAAA;AAI3C,EAAA,MAAM,QAAW,GAAAA,cAAA,CAAS,UAAW,CAAA,UAAA,EAAY,qBAAuB,EAAA;AAAA,IACtE,IAAM,EAAA;AAAA,GACP,CAAA;AAED,EAAA,IAAI,SAAS,OAAS,EAAA;AACpB,IAAA,OAAO,QAAS,CAAA,OAAA,CAAQ,QAAQ,CAAA,CAAE,KAAM,EAAA;AAAA;AAI1C,EAAQ,OAAA,CAAA,IAAA,CAAK,yBAAyB,IAAI,CAAA;AAC1C,EAAO,OAAA,IAAA;AACT;AAEO,MAAM,0BAA0B,MAAM;AAC3C,EAAA,MAAM,WAAW,IAAI,IAAA,CAAK,cAAe,EAAA,CAAE,iBAAkB,CAAA,QAAA;AAC7D,EAAA,MAAM,GAAM,GAAAA,cAAA,CAAS,GAAI,EAAA,CAAE,QAAQ,QAAQ,CAAA;AAC3C,EAAO,OAAA,GAAA,CAAI,SAAS,IAAI,CAAA;AAC1B;;;;;;;;;"}
@@ -11,6 +11,7 @@ const EventRequestSchema = zod.z.object({
11
11
  grouping: zod.z.enum(["hourly", "weekly", "daily", "monthly"]).optional(),
12
12
  start_date: dateRequiredSchema("start_date"),
13
13
  end_date: dateRequiredSchema("end_date"),
14
+ timezone: zod.z.string(),
14
15
  limit: zod.z.string().regex(/^\d+$/).transform(Number).optional(),
15
16
  kind: zod.z.string().optional(),
16
17
  type: zod.z.enum(eventRequest.QUERY_TYPES, {
@@ -1 +1 @@
1
- {"version":3,"file":"event-request.cjs.js","sources":["../../src/validation/event-request.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 { z } from 'zod';\nimport { DateTime } from 'luxon';\nimport { QUERY_TYPES } from '../types/event-request';\n\nconst dateRequiredSchema = (fieldName: string) =>\n z.string({\n required_error: `${fieldName} is required. Use YYYY-MM-DD (e.g., 2025-03-02)`,\n });\n\nexport const EventRequestSchema = z\n .object({\n grouping: z.enum(['hourly', 'weekly', 'daily', 'monthly']).optional(),\n start_date: dateRequiredSchema('start_date'),\n end_date: dateRequiredSchema('end_date'),\n limit: z.string().regex(/^\\d+$/).transform(Number).optional(),\n kind: z.string().optional(),\n type: z.enum(QUERY_TYPES, {\n errorMap: () => ({\n message: `Invalid type. Allowed values: ${QUERY_TYPES}`,\n }),\n }),\n format: z\n .enum(['csv', 'json'], {\n errorMap: () => ({\n message: 'Invalid format. Allowed values: json, csv',\n }),\n })\n .optional(),\n })\n .superRefine((data, ctx) => {\n const startDate = DateTime.fromISO(data.start_date);\n const endDate = DateTime.fromISO(data.end_date);\n\n if (!startDate.isValid) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'Invalid date format for start_date. Expected YYYY-MM-DD (e.g., 2025-03-02)',\n path: ['start_date'],\n });\n }\n\n if (!endDate.isValid) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'Invalid date format for end_date. Expected YYYY-MM-DD (e.g., 2025-03-02)',\n path: ['end_date'],\n });\n }\n\n if (startDate.isValid && endDate.isValid && startDate > endDate) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'start_date should not be greater than end_date',\n path: ['end_date'],\n });\n }\n });\n"],"names":["z","QUERY_TYPES","DateTime"],"mappings":";;;;;;AAmBA,MAAM,kBAAqB,GAAA,CAAC,SAC1B,KAAAA,KAAA,CAAE,MAAO,CAAA;AAAA,EACP,cAAA,EAAgB,GAAG,SAAS,CAAA,+CAAA;AAC9B,CAAC,CAAA;AAEU,MAAA,kBAAA,GAAqBA,MAC/B,MAAO,CAAA;AAAA,EACN,QAAA,EAAUA,KAAE,CAAA,IAAA,CAAK,CAAC,QAAA,EAAU,UAAU,OAAS,EAAA,SAAS,CAAC,CAAA,CAAE,QAAS,EAAA;AAAA,EACpE,UAAA,EAAY,mBAAmB,YAAY,CAAA;AAAA,EAC3C,QAAA,EAAU,mBAAmB,UAAU,CAAA;AAAA,EACvC,KAAA,EAAOA,KAAE,CAAA,MAAA,EAAS,CAAA,KAAA,CAAM,OAAO,CAAE,CAAA,SAAA,CAAU,MAAM,CAAA,CAAE,QAAS,EAAA;AAAA,EAC5D,IAAM,EAAAA,KAAA,CAAE,MAAO,EAAA,CAAE,QAAS,EAAA;AAAA,EAC1B,IAAA,EAAMA,KAAE,CAAA,IAAA,CAAKC,wBAAa,EAAA;AAAA,IACxB,UAAU,OAAO;AAAA,MACf,OAAA,EAAS,iCAAiCA,wBAAW,CAAA;AAAA,KACvD;AAAA,GACD,CAAA;AAAA,EACD,QAAQD,KACL,CAAA,IAAA,CAAK,CAAC,KAAA,EAAO,MAAM,CAAG,EAAA;AAAA,IACrB,UAAU,OAAO;AAAA,MACf,OAAS,EAAA;AAAA,KACX;AAAA,GACD,EACA,QAAS;AACd,CAAC,CACA,CAAA,WAAA,CAAY,CAAC,IAAA,EAAM,GAAQ,KAAA;AAC1B,EAAA,MAAM,SAAY,GAAAE,cAAA,CAAS,OAAQ,CAAA,IAAA,CAAK,UAAU,CAAA;AAClD,EAAA,MAAM,OAAU,GAAAA,cAAA,CAAS,OAAQ,CAAA,IAAA,CAAK,QAAQ,CAAA;AAE9C,EAAI,IAAA,CAAC,UAAU,OAAS,EAAA;AACtB,IAAA,GAAA,CAAI,QAAS,CAAA;AAAA,MACX,IAAA,EAAMF,MAAE,YAAa,CAAA,MAAA;AAAA,MACrB,OACE,EAAA,4EAAA;AAAA,MACF,IAAA,EAAM,CAAC,YAAY;AAAA,KACpB,CAAA;AAAA;AAGH,EAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACpB,IAAA,GAAA,CAAI,QAAS,CAAA;AAAA,MACX,IAAA,EAAMA,MAAE,YAAa,CAAA,MAAA;AAAA,MACrB,OACE,EAAA,0EAAA;AAAA,MACF,IAAA,EAAM,CAAC,UAAU;AAAA,KAClB,CAAA;AAAA;AAGH,EAAA,IAAI,SAAU,CAAA,OAAA,IAAW,OAAQ,CAAA,OAAA,IAAW,YAAY,OAAS,EAAA;AAC/D,IAAA,GAAA,CAAI,QAAS,CAAA;AAAA,MACX,IAAA,EAAMA,MAAE,YAAa,CAAA,MAAA;AAAA,MACrB,OAAS,EAAA,gDAAA;AAAA,MACT,IAAA,EAAM,CAAC,UAAU;AAAA,KAClB,CAAA;AAAA;AAEL,CAAC;;;;"}
1
+ {"version":3,"file":"event-request.cjs.js","sources":["../../src/validation/event-request.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 { z } from 'zod';\nimport { DateTime } from 'luxon';\nimport { QUERY_TYPES } from '../types/event-request';\n\nconst dateRequiredSchema = (fieldName: string) =>\n z.string({\n required_error: `${fieldName} is required. Use YYYY-MM-DD (e.g., 2025-03-02)`,\n });\n\nexport const EventRequestSchema = z\n .object({\n grouping: z.enum(['hourly', 'weekly', 'daily', 'monthly']).optional(),\n start_date: dateRequiredSchema('start_date'),\n end_date: dateRequiredSchema('end_date'),\n timezone: z.string(),\n limit: z.string().regex(/^\\d+$/).transform(Number).optional(),\n kind: z.string().optional(),\n type: z.enum(QUERY_TYPES, {\n errorMap: () => ({\n message: `Invalid type. Allowed values: ${QUERY_TYPES}`,\n }),\n }),\n format: z\n .enum(['csv', 'json'], {\n errorMap: () => ({\n message: 'Invalid format. Allowed values: json, csv',\n }),\n })\n .optional(),\n })\n .superRefine((data, ctx) => {\n const startDate = DateTime.fromISO(data.start_date);\n const endDate = DateTime.fromISO(data.end_date);\n\n if (!startDate.isValid) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'Invalid date format for start_date. Expected YYYY-MM-DD (e.g., 2025-03-02)',\n path: ['start_date'],\n });\n }\n\n if (!endDate.isValid) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'Invalid date format for end_date. Expected YYYY-MM-DD (e.g., 2025-03-02)',\n path: ['end_date'],\n });\n }\n\n if (startDate.isValid && endDate.isValid && startDate > endDate) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'start_date should not be greater than end_date',\n path: ['end_date'],\n });\n }\n });\n"],"names":["z","QUERY_TYPES","DateTime"],"mappings":";;;;;;AAmBA,MAAM,kBAAqB,GAAA,CAAC,SAC1B,KAAAA,KAAA,CAAE,MAAO,CAAA;AAAA,EACP,cAAA,EAAgB,GAAG,SAAS,CAAA,+CAAA;AAC9B,CAAC,CAAA;AAEU,MAAA,kBAAA,GAAqBA,MAC/B,MAAO,CAAA;AAAA,EACN,QAAA,EAAUA,KAAE,CAAA,IAAA,CAAK,CAAC,QAAA,EAAU,UAAU,OAAS,EAAA,SAAS,CAAC,CAAA,CAAE,QAAS,EAAA;AAAA,EACpE,UAAA,EAAY,mBAAmB,YAAY,CAAA;AAAA,EAC3C,QAAA,EAAU,mBAAmB,UAAU,CAAA;AAAA,EACvC,QAAA,EAAUA,MAAE,MAAO,EAAA;AAAA,EACnB,KAAA,EAAOA,KAAE,CAAA,MAAA,EAAS,CAAA,KAAA,CAAM,OAAO,CAAE,CAAA,SAAA,CAAU,MAAM,CAAA,CAAE,QAAS,EAAA;AAAA,EAC5D,IAAM,EAAAA,KAAA,CAAE,MAAO,EAAA,CAAE,QAAS,EAAA;AAAA,EAC1B,IAAA,EAAMA,KAAE,CAAA,IAAA,CAAKC,wBAAa,EAAA;AAAA,IACxB,UAAU,OAAO;AAAA,MACf,OAAA,EAAS,iCAAiCA,wBAAW,CAAA;AAAA,KACvD;AAAA,GACD,CAAA;AAAA,EACD,QAAQD,KACL,CAAA,IAAA,CAAK,CAAC,KAAA,EAAO,MAAM,CAAG,EAAA;AAAA,IACrB,UAAU,OAAO;AAAA,MACf,OAAS,EAAA;AAAA,KACX;AAAA,GACD,EACA,QAAS;AACd,CAAC,CACA,CAAA,WAAA,CAAY,CAAC,IAAA,EAAM,GAAQ,KAAA;AAC1B,EAAA,MAAM,SAAY,GAAAE,cAAA,CAAS,OAAQ,CAAA,IAAA,CAAK,UAAU,CAAA;AAClD,EAAA,MAAM,OAAU,GAAAA,cAAA,CAAS,OAAQ,CAAA,IAAA,CAAK,QAAQ,CAAA;AAE9C,EAAI,IAAA,CAAC,UAAU,OAAS,EAAA;AACtB,IAAA,GAAA,CAAI,QAAS,CAAA;AAAA,MACX,IAAA,EAAMF,MAAE,YAAa,CAAA,MAAA;AAAA,MACrB,OACE,EAAA,4EAAA;AAAA,MACF,IAAA,EAAM,CAAC,YAAY;AAAA,KACpB,CAAA;AAAA;AAGH,EAAI,IAAA,CAAC,QAAQ,OAAS,EAAA;AACpB,IAAA,GAAA,CAAI,QAAS,CAAA;AAAA,MACX,IAAA,EAAMA,MAAE,YAAa,CAAA,MAAA;AAAA,MACrB,OACE,EAAA,0EAAA;AAAA,MACF,IAAA,EAAM,CAAC,UAAU;AAAA,KAClB,CAAA;AAAA;AAGH,EAAA,IAAI,SAAU,CAAA,OAAA,IAAW,OAAQ,CAAA,OAAA,IAAW,YAAY,OAAS,EAAA;AAC/D,IAAA,GAAA,CAAI,QAAS,CAAA;AAAA,MACX,IAAA,EAAMA,MAAE,YAAa,CAAA,MAAA;AAAA,MACrB,OAAS,EAAA,gDAAA;AAAA,MACT,IAAA,EAAM,CAAC,UAAU;AAAA,KAClB,CAAA;AAAA;AAEL,CAAC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-adoption-insights-backend",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
@@ -55,6 +55,7 @@
55
55
  "@backstage/backend-test-utils": "^1.5.0",
56
56
  "@backstage/cli": "^0.32.1",
57
57
  "@types/express": "^4.17.6",
58
+ "@types/luxon": "^3.5.0",
58
59
  "@types/supertest": "^2.0.12",
59
60
  "supertest": "^6.2.4"
60
61
  },