@solidxai/core 0.1.10 → 0.1.11-beta.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.
Files changed (158) hide show
  1. package/dist/controllers/datasource-introspection.controller.d.ts +234 -0
  2. package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -0
  3. package/dist/controllers/datasource-introspection.controller.js +98 -0
  4. package/dist/controllers/datasource-introspection.controller.js.map +1 -0
  5. package/dist/controllers/datasource-management.controller.d.ts +18 -0
  6. package/dist/controllers/datasource-management.controller.d.ts.map +1 -0
  7. package/dist/controllers/datasource-management.controller.js +55 -0
  8. package/dist/controllers/datasource-management.controller.js.map +1 -0
  9. package/dist/dtos/create-datasource-management.dto.d.ts +25 -0
  10. package/dist/dtos/create-datasource-management.dto.d.ts.map +1 -0
  11. package/dist/dtos/create-datasource-management.dto.js +169 -0
  12. package/dist/dtos/create-datasource-management.dto.js.map +1 -0
  13. package/dist/dtos/datasource-introspection-mapping.dto.d.ts +45 -0
  14. package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -0
  15. package/dist/dtos/datasource-introspection-mapping.dto.js +233 -0
  16. package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -0
  17. package/dist/index.d.ts +5 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/interfaces/datasource-introspection-provider.interface.d.ts +26 -0
  22. package/dist/interfaces/datasource-introspection-provider.interface.d.ts.map +1 -0
  23. package/dist/interfaces/datasource-introspection-provider.interface.js +3 -0
  24. package/dist/interfaces/datasource-introspection-provider.interface.js.map +1 -0
  25. package/dist/repository/scheduled-job.repository.d.ts +3 -0
  26. package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
  27. package/dist/repository/scheduled-job.repository.js +60 -0
  28. package/dist/repository/scheduled-job.repository.js.map +1 -1
  29. package/dist/seeders/module-metadata-seeder.service.d.ts +32 -1
  30. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  31. package/dist/seeders/module-metadata-seeder.service.js +1011 -230
  32. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  33. package/dist/seeders/seed-data/solid-core-metadata.json +21 -0
  34. package/dist/services/action-metadata.service.d.ts +7 -0
  35. package/dist/services/action-metadata.service.d.ts.map +1 -1
  36. package/dist/services/action-metadata.service.js +76 -3
  37. package/dist/services/action-metadata.service.js.map +1 -1
  38. package/dist/services/authentication.service.js +1 -1
  39. package/dist/services/authentication.service.js.map +1 -1
  40. package/dist/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.d.ts.map +1 -1
  41. package/dist/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.js +2 -2
  42. package/dist/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.js.map +1 -1
  43. package/dist/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.d.ts.map +1 -1
  44. package/dist/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.js +2 -2
  45. package/dist/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.js.map +1 -1
  46. package/dist/services/dashboard-providers/mq-dashboard-provider-utils.d.ts +3 -0
  47. package/dist/services/dashboard-providers/mq-dashboard-provider-utils.d.ts.map +1 -1
  48. package/dist/services/dashboard-providers/mq-dashboard-provider-utils.js +71 -0
  49. package/dist/services/dashboard-providers/mq-dashboard-provider-utils.js.map +1 -1
  50. package/dist/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.d.ts.map +1 -1
  51. package/dist/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.js +3 -3
  52. package/dist/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.js.map +1 -1
  53. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts +11 -0
  54. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts.map +1 -0
  55. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js +181 -0
  56. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js.map +1 -0
  57. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts +11 -0
  58. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts.map +1 -0
  59. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js +90 -0
  60. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js.map +1 -0
  61. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts +11 -0
  62. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts.map +1 -0
  63. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js +156 -0
  64. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js.map +1 -0
  65. package/dist/services/datasource-introspection.service.d.ts +288 -0
  66. package/dist/services/datasource-introspection.service.d.ts.map +1 -0
  67. package/dist/services/datasource-introspection.service.js +1278 -0
  68. package/dist/services/datasource-introspection.service.js.map +1 -0
  69. package/dist/services/datasource-management.service.d.ts +60 -0
  70. package/dist/services/datasource-management.service.d.ts.map +1 -0
  71. package/dist/services/datasource-management.service.js +461 -0
  72. package/dist/services/datasource-management.service.js.map +1 -0
  73. package/dist/services/field-metadata.service.d.ts.map +1 -1
  74. package/dist/services/field-metadata.service.js +19 -0
  75. package/dist/services/field-metadata.service.js.map +1 -1
  76. package/dist/services/model-metadata.service.d.ts +1 -3
  77. package/dist/services/model-metadata.service.d.ts.map +1 -1
  78. package/dist/services/model-metadata.service.js +47 -15
  79. package/dist/services/model-metadata.service.js.map +1 -1
  80. package/dist/services/role-metadata.service.d.ts +5 -0
  81. package/dist/services/role-metadata.service.d.ts.map +1 -1
  82. package/dist/services/role-metadata.service.js +85 -31
  83. package/dist/services/role-metadata.service.js.map +1 -1
  84. package/dist/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.d.ts.map +1 -1
  85. package/dist/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.js +2 -3
  86. package/dist/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.js.map +1 -1
  87. package/dist/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.d.ts.map +1 -1
  88. package/dist/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.js +2 -3
  89. package/dist/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.js.map +1 -1
  90. package/dist/services/setting.service.d.ts +1 -0
  91. package/dist/services/setting.service.d.ts.map +1 -1
  92. package/dist/services/setting.service.js +32 -8
  93. package/dist/services/setting.service.js.map +1 -1
  94. package/dist/services/solid-ts-morph.service.d.ts +1 -1
  95. package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
  96. package/dist/services/solid-ts-morph.service.js.map +1 -1
  97. package/dist/services/view-metadata.service.d.ts +6 -0
  98. package/dist/services/view-metadata.service.d.ts.map +1 -1
  99. package/dist/services/view-metadata.service.js +66 -1
  100. package/dist/services/view-metadata.service.js.map +1 -1
  101. package/dist/solid-core.module.d.ts.map +1 -1
  102. package/dist/solid-core.module.js +16 -0
  103. package/dist/solid-core.module.js.map +1 -1
  104. package/dist-tests/api/authenticate.spec.js +119 -0
  105. package/dist-tests/api/authenticate.spec.js.map +1 -0
  106. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
  107. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
  108. package/dist-tests/api/ping.spec.js +21 -0
  109. package/dist-tests/api/ping.spec.js.map +1 -0
  110. package/dist-tests/helpers/auth.js +41 -0
  111. package/dist-tests/helpers/auth.js.map +1 -0
  112. package/dist-tests/helpers/env.js +11 -0
  113. package/dist-tests/helpers/env.js.map +1 -0
  114. package/docs/agent-hub-grooming.md +301 -0
  115. package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +438 -0
  116. package/docs/dashboards/dashboard-curl-smoke-tests.txt +146 -0
  117. package/docs/dashboards/delete-legacy-dashboard-metadata.sql +172 -0
  118. package/docs/datasource-introspection-ddl-analysis.md +326 -0
  119. package/docs/datasource-introspection-implementation-plan.md +306 -0
  120. package/docs/grouping-enhancements.md +89 -0
  121. package/docs/java-spring/README.md +3 -0
  122. package/docs/java-spring/solid-core-module-deep-dive-report.md +1317 -0
  123. package/docs/module-package-import-handoff.md +691 -0
  124. package/docs/seed-changes.md +65 -0
  125. package/docs/test-data-workflow.md +200 -0
  126. package/docs/type-declaration-import-issue.md +24 -0
  127. package/package.json +1 -1
  128. package/src/controllers/datasource-introspection.controller.ts +58 -0
  129. package/src/controllers/datasource-management.controller.ts +24 -0
  130. package/src/dtos/create-datasource-management.dto.ts +139 -0
  131. package/src/dtos/datasource-introspection-mapping.dto.ts +182 -0
  132. package/src/index.ts +5 -0
  133. package/src/interfaces/datasource-introspection-provider.interface.ts +28 -0
  134. package/src/repository/scheduled-job.repository.ts +73 -0
  135. package/src/seeders/module-metadata-seeder.service.ts +1223 -262
  136. package/src/seeders/seed-data/solid-core-metadata.json +22 -1
  137. package/src/services/action-metadata.service.ts +87 -2
  138. package/src/services/authentication.service.ts +1 -1
  139. package/src/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.ts +9 -4
  140. package/src/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.ts +9 -4
  141. package/src/services/dashboard-providers/mq-dashboard-provider-utils.ts +82 -0
  142. package/src/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.ts +10 -4
  143. package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts +185 -0
  144. package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts +95 -0
  145. package/src/services/datasource-introspection/postgres-datasource-introspection-provider.service.ts +160 -0
  146. package/src/services/datasource-introspection.service.ts +1536 -0
  147. package/src/services/datasource-management.service.ts +566 -0
  148. package/src/services/field-metadata.service.ts +21 -1
  149. package/src/services/model-metadata.service.ts +50 -13
  150. package/src/services/role-metadata.service.ts +100 -47
  151. package/src/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.ts +2 -4
  152. package/src/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.ts +2 -4
  153. package/src/services/setting.service.ts +43 -12
  154. package/src/services/solid-ts-morph.service.ts +2 -2
  155. package/src/services/view-metadata.service.ts +79 -1
  156. package/src/solid-core.module.ts +16 -0
  157. package/.claude/settings.local.json +0 -15
  158. package/src/services/1.js +0 -6
@@ -5381,6 +5381,19 @@
5381
5381
  "moduleUserKey": "solid-core",
5382
5382
  "modelUserKey": "fieldMetadata"
5383
5383
  },
5384
+ {
5385
+ "displayName": "Datasource Management",
5386
+ "name": "datasource-management-action",
5387
+ "type": "custom",
5388
+ "domain": "",
5389
+ "context": "",
5390
+ "customComponent": "/admin/core/solid-core/datasources",
5391
+ "customIsModal": true,
5392
+ "serverEndpoint": "",
5393
+ "viewUserKey": "",
5394
+ "moduleUserKey": "solid-core",
5395
+ "modelUserKey": "moduleMetadata"
5396
+ },
5384
5397
  {
5385
5398
  "displayName": "Field Metadata Tree Action",
5386
5399
  "name": "fieldMetadata-tree-action",
@@ -6105,6 +6118,14 @@
6105
6118
  "moduleUserKey": "solid-core",
6106
6119
  "parentMenuItemUserKey": "appBuilder-menu-item"
6107
6120
  },
6121
+ {
6122
+ "displayName": "Datasources",
6123
+ "name": "datasource-management-menu-item",
6124
+ "sequenceNumber": 4,
6125
+ "actionUserKey": "datasource-management-action",
6126
+ "moduleUserKey": "solid-core",
6127
+ "parentMenuItemUserKey": "appBuilder-menu-item"
6128
+ },
6108
6129
  {
6109
6130
  "displayName": "Menu Item",
6110
6131
  "name": "menuItemMetadata-menu-item",
@@ -14737,4 +14758,4 @@
14737
14758
  }
14738
14759
  ],
14739
14760
  "savedFilters": []
14740
- }
14761
+ }
@@ -1,4 +1,4 @@
1
- import { Injectable } from '@nestjs/common';
1
+ import { Injectable, Logger } from '@nestjs/common';
2
2
  import { ModuleRef } from "@nestjs/core";
3
3
  import { InjectEntityManager } from '@nestjs/typeorm';
4
4
  import { CRUDService } from 'src/services/crud.service';
@@ -10,6 +10,79 @@ import { ActionMetadata } from '../entities/action-metadata.entity';
10
10
 
11
11
  @Injectable()
12
12
  export class ActionMetadataService extends CRUDService<ActionMetadata> {
13
+ private readonly logger = new Logger(ActionMetadataService.name);
14
+ private readonly comparableFields = [
15
+ 'name',
16
+ 'displayName',
17
+ 'type',
18
+ 'domain',
19
+ 'context',
20
+ 'customComponent',
21
+ 'customIsModal',
22
+ 'serverEndpoint',
23
+ 'module',
24
+ 'model',
25
+ 'view',
26
+ ] as const;
27
+ private readonly relationCompareFields = new Set(['module', 'model', 'view']);
28
+ private readonly jsonCompareFields = new Set(['domain', 'context']);
29
+
30
+ private normalizeJsonFieldValue(value: any): any {
31
+ if (typeof value === 'string') {
32
+ const trimmedValue = value.trim();
33
+ if (
34
+ (trimmedValue.startsWith('{') && trimmedValue.endsWith('}')) ||
35
+ (trimmedValue.startsWith('[') && trimmedValue.endsWith(']'))
36
+ ) {
37
+ try {
38
+ return this.normalizeJsonFieldValue(JSON.parse(trimmedValue));
39
+ } catch {
40
+ return value;
41
+ }
42
+ }
43
+ return value;
44
+ }
45
+
46
+ if (Array.isArray(value)) {
47
+ return value.map((item) => this.normalizeJsonFieldValue(item));
48
+ }
49
+
50
+ if (value && typeof value === 'object') {
51
+ return Object.keys(value)
52
+ .sort()
53
+ .reduce((result, key) => {
54
+ result[key] = this.normalizeJsonFieldValue(value[key]);
55
+ return result;
56
+ }, {} as Record<string, any>);
57
+ }
58
+
59
+ return value;
60
+ }
61
+
62
+ private getCanonicalJsonFieldString(value: any): string {
63
+ return JSON.stringify(this.normalizeJsonFieldValue(value) ?? null);
64
+ }
65
+
66
+ private hasChanges(existingSolidAction: ActionMetadata, updateSolidActionDto: any): boolean {
67
+ return this.comparableFields.some((key) => {
68
+ const value = updateSolidActionDto[key];
69
+ if (typeof value === 'undefined') {
70
+ return false;
71
+ }
72
+
73
+ if (this.relationCompareFields.has(key)) {
74
+ const relationValue = value as any;
75
+ return (existingSolidAction as any)[key]?.id !== relationValue?.id;
76
+ }
77
+
78
+ if (this.jsonCompareFields.has(key)) {
79
+ return this.getCanonicalJsonFieldString((existingSolidAction as any)[key]) !== this.getCanonicalJsonFieldString(value);
80
+ }
81
+
82
+ return (existingSolidAction as any)[key] !== value;
83
+ });
84
+ }
85
+
13
86
  constructor(
14
87
  @InjectEntityManager()
15
88
  readonly entityManager: EntityManager,
@@ -37,11 +110,23 @@ export class ActionMetadataService extends CRUDService<ActionMetadata> {
37
110
  const existingSolidAction = await this.repo.findOne({
38
111
  where: {
39
112
  name: updateSolidActionDto.name
40
- }
113
+ },
114
+ relations: {
115
+ module: true,
116
+ model: true,
117
+ view: true,
118
+ },
41
119
  })
42
120
 
43
121
  // if found
44
122
  if (existingSolidAction) {
123
+ const hasChanges = this.hasChanges(existingSolidAction, updateSolidActionDto);
124
+
125
+ if (!hasChanges) {
126
+ this.logger.debug(`Skipping action upsert for ${updateSolidActionDto.name}; no changes detected.`);
127
+ return existingSolidAction;
128
+ }
129
+
45
130
  const updatedSolidActionDto = { ...existingSolidAction, ...updateSolidActionDto };
46
131
  return this.repo.save(updatedSolidActionDto);
47
132
  }
@@ -309,7 +309,7 @@ export class AuthenticationService {
309
309
  if (signUpDto.roles && signUpDto.roles.length > 0) {
310
310
  for (let i = 0; i < signUpDto.roles.length; i++) {
311
311
  const roleName = signUpDto.roles[i];
312
- await this.roleMetadataService.findRoleByName(roleName);
312
+ await this.roleMetadataService.assertRoleExistsByName(roleName);
313
313
  }
314
314
  }
315
315
  user.username = signUpDto.username;
@@ -2,7 +2,13 @@ import { Injectable } from "@nestjs/common";
2
2
  import { DashboardWidgetDataProvider } from "src/decorators/dashboard-widget-data-provider.decorator";
3
3
  import { MqMessageRepository } from "src/repository/mq-message.repository";
4
4
  import { IDashboardWidgetDataProvider, IDashboardWidgetDataProviderContext, IDashboardWidgetDataResponseEnvelope } from "src/interfaces";
5
- import { applyMqDashboardFilters, normalizeBucket, toNumber } from "src/services/dashboard-providers/mq-dashboard-provider-utils";
5
+ import {
6
+ applyMqDashboardFilters,
7
+ buildMqDashboardBucketExpression,
8
+ normalizeBucket,
9
+ normalizeMqDashboardBucketValue,
10
+ toNumber,
11
+ } from "src/services/dashboard-providers/mq-dashboard-provider-utils";
6
12
 
7
13
  @DashboardWidgetDataProvider()
8
14
  @Injectable()
@@ -29,7 +35,7 @@ export class MqDashboardLatencyTrendProvider implements IDashboardWidgetDataProv
29
35
  applyMqDashboardFilters(qb, ctxt.variables ?? {});
30
36
  qb.andWhere("mqMessage.elapsedMillis IS NOT NULL");
31
37
 
32
- const bucketExpr = `DATE_TRUNC('${bucket}', mqMessage.createdAt)`;
38
+ const bucketExpr = buildMqDashboardBucketExpression(qb, bucket, "mqMessage.createdAt");
33
39
  const rows = await qb
34
40
  .select(bucketExpr, "bucket")
35
41
  .addSelect("AVG(mqMessage.elapsedMillis)", "value")
@@ -37,7 +43,7 @@ export class MqDashboardLatencyTrendProvider implements IDashboardWidgetDataProv
37
43
  .orderBy("bucket", "ASC")
38
44
  .getRawMany<{ bucket: string; value: string | number }>();
39
45
 
40
- const categories = rows.map((r) => new Date(r.bucket).toISOString());
46
+ const categories = rows.map((r) => normalizeMqDashboardBucketValue(r.bucket));
41
47
  const values = rows.map((r) => Number(toNumber(r.value).toFixed(2)));
42
48
 
43
49
  return {
@@ -55,4 +61,3 @@ export class MqDashboardLatencyTrendProvider implements IDashboardWidgetDataProv
55
61
  };
56
62
  }
57
63
  }
58
-
@@ -2,7 +2,13 @@ import { Injectable } from "@nestjs/common";
2
2
  import { DashboardWidgetDataProvider } from "src/decorators/dashboard-widget-data-provider.decorator";
3
3
  import { MqMessageRepository } from "src/repository/mq-message.repository";
4
4
  import { IDashboardWidgetDataProvider, IDashboardWidgetDataProviderContext, IDashboardWidgetDataResponseEnvelope } from "src/interfaces";
5
- import { applyMqDashboardFilters, normalizeBucket, toNumber } from "src/services/dashboard-providers/mq-dashboard-provider-utils";
5
+ import {
6
+ applyMqDashboardFilters,
7
+ buildMqDashboardBucketExpression,
8
+ normalizeBucket,
9
+ normalizeMqDashboardBucketValue,
10
+ toNumber,
11
+ } from "src/services/dashboard-providers/mq-dashboard-provider-utils";
6
12
 
7
13
  @DashboardWidgetDataProvider()
8
14
  @Injectable()
@@ -31,7 +37,7 @@ export class MqDashboardMessagesOverTimeProvider implements IDashboardWidgetData
31
37
  const qb = await this.mqMessageRepository.createSecurityRuleAwareQueryBuilder("mqMessage");
32
38
  applyMqDashboardFilters(qb, ctxt.variables ?? {});
33
39
 
34
- const bucketExpr = `DATE_TRUNC('${bucket}', mqMessage.createdAt)`;
40
+ const bucketExpr = buildMqDashboardBucketExpression(qb, bucket, "mqMessage.createdAt");
35
41
  const rows = await qb
36
42
  .select(bucketExpr, "bucket")
37
43
  .addSelect("mqMessage.stage", "stage")
@@ -43,7 +49,7 @@ export class MqDashboardMessagesOverTimeProvider implements IDashboardWidgetData
43
49
 
44
50
  const bucketToStageMap = new Map<string, Record<string, number>>();
45
51
  for (const row of rows) {
46
- const key = new Date(row.bucket).toISOString();
52
+ const key = normalizeMqDashboardBucketValue(row.bucket);
47
53
  const entry = bucketToStageMap.get(key) ?? {};
48
54
  entry[row.stage] = (entry[row.stage] ?? 0) + toNumber(row.count, 0);
49
55
  bucketToStageMap.set(key, entry);
@@ -76,4 +82,3 @@ export class MqDashboardMessagesOverTimeProvider implements IDashboardWidgetData
76
82
  };
77
83
  }
78
84
  }
79
-
@@ -54,6 +54,59 @@ export function toNumber(value: any, fallback = 0): number {
54
54
  return Number.isFinite(n) ? n : fallback;
55
55
  }
56
56
 
57
+ export function buildMqDashboardBucketExpression(
58
+ qb: SelectQueryBuilder<MqMessage>,
59
+ bucket: 'hour' | 'day' | 'week' | 'month',
60
+ columnExpr: string,
61
+ ): string {
62
+ const driver = `${qb.connection.options.type ?? ''}`.toLowerCase();
63
+
64
+ switch (driver) {
65
+ case 'postgres':
66
+ case 'cockroachdb':
67
+ return `DATE_TRUNC('${bucket}', ${columnExpr})`;
68
+ case 'mysql':
69
+ case 'mariadb':
70
+ switch (bucket) {
71
+ case 'hour':
72
+ return `DATE_FORMAT(${columnExpr}, '%Y-%m-%d %H:00:00')`;
73
+ case 'day':
74
+ return `DATE_FORMAT(${columnExpr}, '%Y-%m-%d 00:00:00')`;
75
+ case 'week':
76
+ return `STR_TO_DATE(DATE_FORMAT(${columnExpr}, '%x-%v-1'), '%x-%v-%w')`;
77
+ case 'month':
78
+ return `DATE_FORMAT(${columnExpr}, '%Y-%m-01 00:00:00')`;
79
+ default:
80
+ throw new Error(`Unsupported bucket ${bucket} for driver ${driver}`);
81
+ }
82
+ case 'mssql':
83
+ case 'sqlserver':
84
+ switch (bucket) {
85
+ case 'hour':
86
+ return `DATEADD(hour, DATEDIFF(hour, 0, ${columnExpr}), 0)`;
87
+ case 'day':
88
+ return `DATEADD(day, DATEDIFF(day, 0, ${columnExpr}), 0)`;
89
+ case 'week':
90
+ return `DATEADD(week, DATEDIFF(week, 0, ${columnExpr}), 0)`;
91
+ case 'month':
92
+ return `DATEFROMPARTS(YEAR(${columnExpr}), MONTH(${columnExpr}), 1)`;
93
+ default:
94
+ throw new Error(`Unsupported bucket ${bucket} for driver ${driver}`);
95
+ }
96
+ default:
97
+ throw new Error(`Unsupported driver ${driver} for mq dashboard bucket expressions.`);
98
+ }
99
+ }
100
+
101
+ export function normalizeMqDashboardBucketValue(value: any): string {
102
+ if (!value) return '';
103
+ const parsed = value instanceof Date ? value : new Date(value);
104
+ if (Number.isNaN(parsed.getTime())) {
105
+ return `${value}`;
106
+ }
107
+ return parsed.toISOString();
108
+ }
109
+
57
110
 
58
111
  function applyDateFilter(qb: SelectQueryBuilder<MqMessage>, dateVariable: any): void {
59
112
  const range = resolveDateRange(dateVariable);
@@ -88,6 +141,35 @@ function applyStringOrArrayFilter(
88
141
  qb.andWhere(`${qualifiedColumn} IN (:...${paramKey})`, { [paramKey]: values });
89
142
  }
90
143
 
144
+ export function applyCaseInsensitiveContainsFilter<T>(
145
+ qb: SelectQueryBuilder<T>,
146
+ qualifiedColumn: string,
147
+ query: string | undefined,
148
+ paramKey: string,
149
+ ) {
150
+ if (!query) {
151
+ return;
152
+ }
153
+
154
+ const driver = `${qb.connection.options.type ?? ''}`.toLowerCase();
155
+ const value = `%${query}%`;
156
+
157
+ switch (driver) {
158
+ case 'postgres':
159
+ case 'cockroachdb':
160
+ qb.andWhere(`${qualifiedColumn} ILIKE :${paramKey}`, { [paramKey]: value });
161
+ return;
162
+ case 'mysql':
163
+ case 'mariadb':
164
+ case 'mssql':
165
+ case 'sqlserver':
166
+ qb.andWhere(`LOWER(${qualifiedColumn}) LIKE LOWER(:${paramKey})`, { [paramKey]: value });
167
+ return;
168
+ default:
169
+ qb.andWhere(`${qualifiedColumn} LIKE :${paramKey}`, { [paramKey]: value });
170
+ }
171
+ }
172
+
91
173
  function resolveDateRange(dateVariable: any): { from?: Date; to?: Date } {
92
174
  if (!dateVariable) return {};
93
175
 
@@ -2,7 +2,13 @@ import { Injectable } from "@nestjs/common";
2
2
  import { DashboardWidgetDataProvider } from "src/decorators/dashboard-widget-data-provider.decorator";
3
3
  import { IDashboardWidgetDataProvider, IDashboardWidgetDataProviderContext, IDashboardWidgetDataResponseEnvelope } from "src/interfaces";
4
4
  import { MqMessageRepository } from "src/repository/mq-message.repository";
5
- import { applyMqDashboardFilters, normalizeBucket, toNumber } from "src/services/dashboard-providers/mq-dashboard-provider-utils";
5
+ import {
6
+ applyMqDashboardFilters,
7
+ buildMqDashboardBucketExpression,
8
+ normalizeBucket,
9
+ normalizeMqDashboardBucketValue,
10
+ toNumber,
11
+ } from "src/services/dashboard-providers/mq-dashboard-provider-utils";
6
12
 
7
13
  type HeatmapLegendThreshold = {
8
14
  label: string;
@@ -71,7 +77,7 @@ export class MqDashboardQueueSlaHeatmapProvider implements IDashboardWidgetDataP
71
77
  applyMqDashboardFilters(qb, ctxt.variables ?? {}, { ensureQueueJoin: true });
72
78
  qb.andWhere("mqMessage.elapsedMillis IS NOT NULL");
73
79
 
74
- const bucketExpr = `DATE_TRUNC('${bucket}', mqMessage.createdAt)`;
80
+ const bucketExpr = buildMqDashboardBucketExpression(qb, bucket, "mqMessage.createdAt");
75
81
  const rows = await qb
76
82
  .select(bucketExpr, "bucket")
77
83
  .addSelect("mqMessageQueue.name", "queueName")
@@ -93,7 +99,7 @@ export class MqDashboardQueueSlaHeatmapProvider implements IDashboardWidgetDataP
93
99
  const xCategories = Array.from(
94
100
  new Set(
95
101
  rows
96
- .map((row) => row?.bucket ? new Date(row.bucket).toISOString() : "")
102
+ .map((row) => normalizeMqDashboardBucketValue(row?.bucket))
97
103
  .filter((bucketValue) => !!bucketValue)
98
104
  )
99
105
  ).sort();
@@ -108,7 +114,7 @@ export class MqDashboardQueueSlaHeatmapProvider implements IDashboardWidgetDataP
108
114
  const pointDetails: HeatmapPointDetail[] = [];
109
115
 
110
116
  for (const row of rows) {
111
- const bucketValue = row?.bucket ? new Date(row.bucket).toISOString() : "";
117
+ const bucketValue = normalizeMqDashboardBucketValue(row?.bucket);
112
118
  const queueName = `${row?.queueName ?? ""}`.trim();
113
119
  const xIndex = xIndexMap.get(bucketValue);
114
120
  const yIndex = yIndexMap.get(queueName);
@@ -0,0 +1,185 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { DataSource } from "typeorm";
3
+ import {
4
+ DatasourceIntrospectionColumn,
5
+ DatasourceIntrospectionProvider,
6
+ DatasourceIntrospectionTableRef,
7
+ } from "src/interfaces/datasource-introspection-provider.interface";
8
+
9
+ @Injectable()
10
+ export class MssqlDatasourceIntrospectionProviderService implements DatasourceIntrospectionProvider {
11
+ readonly type = "mssql";
12
+
13
+ supports(type: string) {
14
+ return type?.toLowerCase() === this.type;
15
+ }
16
+
17
+ async listTables(dataSource: DataSource): Promise<DatasourceIntrospectionTableRef[]> {
18
+ const rows = await dataSource.query(`
19
+ SELECT
20
+ TABLE_SCHEMA AS tableSchema,
21
+ TABLE_NAME AS tableName
22
+ FROM INFORMATION_SCHEMA.TABLES
23
+ WHERE TABLE_TYPE = 'BASE TABLE'
24
+ AND TABLE_SCHEMA NOT IN ('sys', 'INFORMATION_SCHEMA')
25
+ ORDER BY TABLE_SCHEMA, TABLE_NAME
26
+ `);
27
+
28
+ return rows.map((row: any) => ({
29
+ tableSchema: row.tableSchema ?? null,
30
+ tableName: row.tableName,
31
+ }));
32
+ }
33
+
34
+ async listColumns(dataSource: DataSource): Promise<DatasourceIntrospectionColumn[]> {
35
+ const rows = await dataSource.query(`
36
+ SELECT
37
+ schema_name(t.schema_id) AS tableSchema,
38
+ t.name AS tableName,
39
+ c.name AS columnName,
40
+ ty.name AS dataType,
41
+ c.is_identity AS isAutoGenerated,
42
+ CASE WHEN c.is_nullable = 1 THEN 'YES' ELSE 'NO' END AS isNullable,
43
+ dc.definition AS defaultValue,
44
+ CASE
45
+ WHEN ty.name IN ('nchar', 'nvarchar') AND c.max_length > 0 THEN c.max_length / 2
46
+ ELSE c.max_length
47
+ END AS characterMaximumLength,
48
+ c.precision AS numericPrecision,
49
+ c.scale AS numericScale,
50
+ c.column_id AS ordinalPosition
51
+ FROM sys.tables t
52
+ INNER JOIN sys.columns c ON c.object_id = t.object_id
53
+ INNER JOIN sys.types ty ON ty.user_type_id = c.user_type_id
54
+ LEFT JOIN sys.default_constraints dc ON dc.object_id = c.default_object_id
55
+ WHERE schema_name(t.schema_id) NOT IN ('sys', 'INFORMATION_SCHEMA')
56
+ ORDER BY schema_name(t.schema_id), t.name, c.column_id
57
+ `);
58
+
59
+ return rows.map((row: any) => ({
60
+ tableSchema: row.tableSchema ?? null,
61
+ tableName: row.tableName,
62
+ columnName: row.columnName,
63
+ dataType: row.dataType,
64
+ isAutoGenerated: Boolean(row.isAutoGenerated),
65
+ isNullable: String(row.isNullable).toUpperCase() === "YES",
66
+ defaultValue: row.defaultValue ?? null,
67
+ characterMaximumLength: this.toNumberOrNull(row.characterMaximumLength),
68
+ numericPrecision: this.toNumberOrNull(row.numericPrecision),
69
+ numericScale: this.toNumberOrNull(row.numericScale),
70
+ ordinalPosition: this.toNumberOrNull(row.ordinalPosition),
71
+ }));
72
+ }
73
+
74
+ async getTableDefinition(dataSource: DataSource, table: DatasourceIntrospectionTableRef): Promise<string | null> {
75
+ const rows = await dataSource.query(
76
+ `
77
+ DECLARE @schemaName NVARCHAR(128) = @0;
78
+ DECLARE @tableName NVARCHAR(128) = @1;
79
+
80
+ WITH target_table AS (
81
+ SELECT t.object_id, s.name AS schema_name, t.name AS table_name
82
+ FROM sys.tables t
83
+ INNER JOIN sys.schemas s ON s.schema_id = t.schema_id
84
+ WHERE t.name = @tableName
85
+ AND (@schemaName IS NULL OR s.name = @schemaName)
86
+ ),
87
+ column_lines AS (
88
+ SELECT
89
+ c.column_id,
90
+ ' [' + c.name + '] '
91
+ + UPPER(ty.name)
92
+ + CASE
93
+ WHEN ty.name IN ('varchar', 'char', 'varbinary', 'binary') THEN
94
+ '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length AS VARCHAR(16)) END + ')'
95
+ WHEN ty.name IN ('nvarchar', 'nchar') THEN
96
+ '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length / 2 AS VARCHAR(16)) END + ')'
97
+ WHEN ty.name IN ('decimal', 'numeric') THEN
98
+ '(' + CAST(c.precision AS VARCHAR(16)) + ',' + CAST(c.scale AS VARCHAR(16)) + ')'
99
+ WHEN ty.name IN ('datetime2', 'datetimeoffset', 'time') THEN
100
+ '(' + CAST(c.scale AS VARCHAR(16)) + ')'
101
+ ELSE
102
+ ''
103
+ END
104
+ + CASE
105
+ WHEN ic.object_id IS NOT NULL THEN
106
+ ' IDENTITY(' + CAST(CONVERT(BIGINT, ic.seed_value) AS VARCHAR(32)) + ',' + CAST(CONVERT(BIGINT, ic.increment_value) AS VARCHAR(32)) + ')'
107
+ ELSE
108
+ ''
109
+ END
110
+ + CASE
111
+ WHEN dc.definition IS NOT NULL THEN ' DEFAULT ' + dc.definition
112
+ ELSE ''
113
+ END
114
+ + CASE
115
+ WHEN c.is_nullable = 1 THEN ' NULL'
116
+ ELSE ' NOT NULL'
117
+ END AS line
118
+ FROM target_table tt
119
+ INNER JOIN sys.columns c ON c.object_id = tt.object_id
120
+ INNER JOIN sys.types ty ON ty.user_type_id = c.user_type_id
121
+ LEFT JOIN sys.identity_columns ic
122
+ ON ic.object_id = c.object_id
123
+ AND ic.column_id = c.column_id
124
+ LEFT JOIN sys.default_constraints dc ON dc.object_id = c.default_object_id
125
+ ),
126
+ constraint_lines AS (
127
+ SELECT
128
+ kc.name AS constraint_name,
129
+ ' CONSTRAINT [' + kc.name + '] PRIMARY KEY ('
130
+ + STUFF((
131
+ SELECT ', [' + c.name + ']'
132
+ FROM sys.index_columns ic
133
+ INNER JOIN sys.columns c
134
+ ON c.object_id = ic.object_id
135
+ AND c.column_id = ic.column_id
136
+ WHERE ic.object_id = kc.parent_object_id
137
+ AND ic.index_id = kc.unique_index_id
138
+ ORDER BY ic.key_ordinal
139
+ FOR XML PATH(''), TYPE
140
+ ).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
141
+ + ')' AS line
142
+ FROM target_table tt
143
+ INNER JOIN sys.key_constraints kc ON kc.parent_object_id = tt.object_id
144
+ WHERE kc.type = 'PK'
145
+ )
146
+ SELECT
147
+ 'CREATE TABLE [' + tt.schema_name + '].[' + tt.table_name + '] (' + CHAR(10)
148
+ + ISNULL((
149
+ SELECT STUFF((
150
+ SELECT ',' + CHAR(10) + cl.line
151
+ FROM column_lines cl
152
+ ORDER BY cl.column_id
153
+ FOR XML PATH(''), TYPE
154
+ ).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
155
+ ), '')
156
+ + ISNULL((
157
+ SELECT CASE
158
+ WHEN EXISTS (SELECT 1 FROM constraint_lines)
159
+ THEN ',' + CHAR(10) + STUFF((
160
+ SELECT ',' + CHAR(10) + cn.line
161
+ FROM constraint_lines cn
162
+ ORDER BY cn.constraint_name
163
+ FOR XML PATH(''), TYPE
164
+ ).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
165
+ ELSE ''
166
+ END
167
+ ), '')
168
+ + CHAR(10) + ');' AS ddl
169
+ FROM target_table tt
170
+ `,
171
+ [table.tableSchema, table.tableName],
172
+ );
173
+
174
+ return rows?.[0]?.ddl ?? null;
175
+ }
176
+
177
+ private toNumberOrNull(value: unknown) {
178
+ if (value === null || value === undefined || value === "") {
179
+ return null;
180
+ }
181
+
182
+ const parsed = Number(value);
183
+ return Number.isFinite(parsed) ? parsed : null;
184
+ }
185
+ }
@@ -0,0 +1,95 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { DataSource } from "typeorm";
3
+ import {
4
+ DatasourceIntrospectionColumn,
5
+ DatasourceIntrospectionProvider,
6
+ DatasourceIntrospectionTableRef,
7
+ } from "src/interfaces/datasource-introspection-provider.interface";
8
+
9
+ @Injectable()
10
+ export class MysqlDatasourceIntrospectionProviderService implements DatasourceIntrospectionProvider {
11
+ readonly type = "mysql";
12
+
13
+ supports(type: string) {
14
+ return type?.toLowerCase() === this.type;
15
+ }
16
+
17
+ async listTables(dataSource: DataSource): Promise<DatasourceIntrospectionTableRef[]> {
18
+ const rows = await dataSource.query(`
19
+ SELECT
20
+ TABLE_SCHEMA AS tableSchema,
21
+ TABLE_NAME AS tableName
22
+ FROM INFORMATION_SCHEMA.TABLES
23
+ WHERE TABLE_TYPE = 'BASE TABLE'
24
+ AND TABLE_SCHEMA = DATABASE()
25
+ ORDER BY TABLE_SCHEMA, TABLE_NAME
26
+ `);
27
+
28
+ return rows.map((row: any) => ({
29
+ tableSchema: row.tableSchema ?? null,
30
+ tableName: row.tableName,
31
+ }));
32
+ }
33
+
34
+ async listColumns(dataSource: DataSource): Promise<DatasourceIntrospectionColumn[]> {
35
+ const rows = await dataSource.query(`
36
+ SELECT
37
+ TABLE_SCHEMA AS tableSchema,
38
+ TABLE_NAME AS tableName,
39
+ COLUMN_NAME AS columnName,
40
+ DATA_TYPE AS dataType,
41
+ EXTRA AS extra,
42
+ IS_NULLABLE AS isNullable,
43
+ COLUMN_DEFAULT AS defaultValue,
44
+ CHARACTER_MAXIMUM_LENGTH AS characterMaximumLength,
45
+ NUMERIC_PRECISION AS numericPrecision,
46
+ NUMERIC_SCALE AS numericScale,
47
+ ORDINAL_POSITION AS ordinalPosition
48
+ FROM INFORMATION_SCHEMA.COLUMNS
49
+ WHERE TABLE_SCHEMA = DATABASE()
50
+ ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
51
+ `);
52
+
53
+ return rows.map((row: any) => ({
54
+ tableSchema: row.tableSchema ?? null,
55
+ tableName: row.tableName,
56
+ columnName: row.columnName,
57
+ dataType: row.dataType,
58
+ isAutoGenerated: String(row.extra ?? "").toLowerCase().includes("auto_increment"),
59
+ isNullable: String(row.isNullable).toUpperCase() === "YES",
60
+ defaultValue: row.defaultValue ?? null,
61
+ characterMaximumLength: this.toNumberOrNull(row.characterMaximumLength),
62
+ numericPrecision: this.toNumberOrNull(row.numericPrecision),
63
+ numericScale: this.toNumberOrNull(row.numericScale),
64
+ ordinalPosition: this.toNumberOrNull(row.ordinalPosition),
65
+ }));
66
+ }
67
+
68
+ async getTableDefinition(dataSource: DataSource, table: DatasourceIntrospectionTableRef): Promise<string | null> {
69
+ const schemaName = table.tableSchema ?? dataSource.options.database;
70
+ const rows = await dataSource.query(
71
+ `SHOW CREATE TABLE \`${schemaName}\`.\`${table.tableName}\``,
72
+ );
73
+
74
+ const firstRow = rows?.[0];
75
+ if (!firstRow || typeof firstRow !== "object") {
76
+ return null;
77
+ }
78
+
79
+ const ddlKey = Object.keys(firstRow).find((key) => key.toLowerCase().startsWith("create table"));
80
+ if (!ddlKey) {
81
+ return null;
82
+ }
83
+
84
+ return String(firstRow[ddlKey] ?? "").trim() || null;
85
+ }
86
+
87
+ private toNumberOrNull(value: unknown) {
88
+ if (value === null || value === undefined || value === "") {
89
+ return null;
90
+ }
91
+
92
+ const parsed = Number(value);
93
+ return Number.isFinite(parsed) ? parsed : null;
94
+ }
95
+ }