@solidstarters/solid-core 1.2.135 → 1.2.136

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 (195) hide show
  1. package/dist/controllers/ai-interaction.controller.d.ts +2 -1
  2. package/dist/controllers/ai-interaction.controller.d.ts.map +1 -1
  3. package/dist/controllers/ai-interaction.controller.js +15 -3
  4. package/dist/controllers/ai-interaction.controller.js.map +1 -1
  5. package/dist/controllers/dashboard-question-sql-dataset-config.controller.d.ts +43 -0
  6. package/dist/controllers/dashboard-question-sql-dataset-config.controller.d.ts.map +1 -0
  7. package/dist/controllers/{question-sql-dataset-config.controller.js → dashboard-question-sql-dataset-config.controller.js} +30 -30
  8. package/dist/controllers/dashboard-question-sql-dataset-config.controller.js.map +1 -0
  9. package/dist/controllers/dashboard-question.controller.d.ts +45 -0
  10. package/dist/controllers/dashboard-question.controller.d.ts.map +1 -0
  11. package/dist/controllers/{question.controller.js → dashboard-question.controller.js} +31 -31
  12. package/dist/controllers/dashboard-question.controller.js.map +1 -0
  13. package/dist/dtos/{create-question-sql-dataset-config.dto.d.ts → create-dashboard-question-sql-dataset-config.dto.d.ts} +2 -2
  14. package/dist/dtos/create-dashboard-question-sql-dataset-config.dto.d.ts.map +1 -0
  15. package/dist/dtos/{create-question-sql-dataset-config.dto.js → create-dashboard-question-sql-dataset-config.dto.js} +13 -13
  16. package/dist/dtos/create-dashboard-question-sql-dataset-config.dto.js.map +1 -0
  17. package/dist/dtos/create-dashboard-question.dto.d.ts +17 -0
  18. package/dist/dtos/create-dashboard-question.dto.d.ts.map +1 -0
  19. package/dist/dtos/{create-question.dto.js → create-dashboard-question.dto.js} +25 -19
  20. package/dist/dtos/create-dashboard-question.dto.js.map +1 -0
  21. package/dist/dtos/create-dashboard.dto.d.ts +2 -2
  22. package/dist/dtos/create-dashboard.dto.d.ts.map +1 -1
  23. package/dist/dtos/create-dashboard.dto.js +3 -3
  24. package/dist/dtos/create-dashboard.dto.js.map +1 -1
  25. package/dist/dtos/{update-question-sql-dataset-config.dto.d.ts → update-dashboard-question-sql-dataset-config.dto.d.ts} +2 -2
  26. package/dist/dtos/update-dashboard-question-sql-dataset-config.dto.d.ts.map +1 -0
  27. package/dist/dtos/{update-question-sql-dataset-config.dto.js → update-dashboard-question-sql-dataset-config.dto.js} +14 -14
  28. package/dist/dtos/update-dashboard-question-sql-dataset-config.dto.js.map +1 -0
  29. package/dist/dtos/update-dashboard-question.dto.d.ts +18 -0
  30. package/dist/dtos/update-dashboard-question.dto.d.ts.map +1 -0
  31. package/dist/dtos/{update-question.dto.js → update-dashboard-question.dto.js} +26 -20
  32. package/dist/dtos/update-dashboard-question.dto.js.map +1 -0
  33. package/dist/dtos/update-dashboard.dto.d.ts +2 -2
  34. package/dist/dtos/update-dashboard.dto.d.ts.map +1 -1
  35. package/dist/dtos/update-dashboard.dto.js +3 -3
  36. package/dist/dtos/update-dashboard.dto.js.map +1 -1
  37. package/dist/entities/dashboard-question-sql-dataset-config.entity.d.ts +13 -0
  38. package/dist/entities/dashboard-question-sql-dataset-config.entity.d.ts.map +1 -0
  39. package/dist/entities/{question-sql-dataset-config.entity.js → dashboard-question-sql-dataset-config.entity.js} +19 -19
  40. package/dist/entities/dashboard-question-sql-dataset-config.entity.js.map +1 -0
  41. package/dist/entities/dashboard-question.entity.d.ts +16 -0
  42. package/dist/entities/dashboard-question.entity.d.ts.map +1 -0
  43. package/dist/entities/{question.entity.js → dashboard-question.entity.js} +23 -19
  44. package/dist/entities/dashboard-question.entity.js.map +1 -0
  45. package/dist/entities/dashboard.entity.d.ts +2 -2
  46. package/dist/entities/dashboard.entity.d.ts.map +1 -1
  47. package/dist/entities/dashboard.entity.js +3 -3
  48. package/dist/entities/dashboard.entity.js.map +1 -1
  49. package/dist/index.d.ts +7 -2
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +7 -2
  52. package/dist/index.js.map +1 -1
  53. package/dist/interfaces.d.ts +16 -2
  54. package/dist/interfaces.d.ts.map +1 -1
  55. package/dist/interfaces.js.map +1 -1
  56. package/dist/jobs/database/trigger-mcp-client-publisher-database.service.d.ts +11 -0
  57. package/dist/jobs/database/trigger-mcp-client-publisher-database.service.d.ts.map +1 -0
  58. package/dist/jobs/database/trigger-mcp-client-publisher-database.service.js +39 -0
  59. package/dist/jobs/database/trigger-mcp-client-publisher-database.service.js.map +1 -0
  60. package/dist/jobs/database/trigger-mcp-client-queue-options.d.ts +8 -0
  61. package/dist/jobs/database/trigger-mcp-client-queue-options.d.ts.map +1 -0
  62. package/dist/jobs/database/trigger-mcp-client-queue-options.js +10 -0
  63. package/dist/jobs/database/trigger-mcp-client-queue-options.js.map +1 -0
  64. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts +16 -0
  65. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts.map +1 -0
  66. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js +71 -0
  67. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -0
  68. package/dist/repository/dashboard.repository.d.ts +1 -3
  69. package/dist/repository/dashboard.repository.d.ts.map +1 -1
  70. package/dist/repository/dashboard.repository.js +42 -37
  71. package/dist/repository/dashboard.repository.js.map +1 -1
  72. package/dist/seeders/seed-data/solid-core-metadata.json +88 -131
  73. package/dist/services/ai-interaction.service.d.ts +10 -2
  74. package/dist/services/ai-interaction.service.d.ts.map +1 -1
  75. package/dist/services/ai-interaction.service.js +53 -18
  76. package/dist/services/ai-interaction.service.js.map +1 -1
  77. package/dist/services/chatter-message.service.d.ts.map +1 -1
  78. package/dist/services/chatter-message.service.js +8 -6
  79. package/dist/services/chatter-message.service.js.map +1 -1
  80. package/dist/services/{question-sql-dataset-config.service.d.ts → dashboard-question-sql-dataset-config.service.d.ts} +5 -5
  81. package/dist/services/dashboard-question-sql-dataset-config.service.d.ts.map +1 -0
  82. package/dist/services/{question-sql-dataset-config.service.js → dashboard-question-sql-dataset-config.service.js} +9 -9
  83. package/dist/services/dashboard-question-sql-dataset-config.service.js.map +1 -0
  84. package/dist/services/{question.service.d.ts → dashboard-question.service.d.ts} +5 -5
  85. package/dist/services/dashboard-question.service.d.ts.map +1 -0
  86. package/dist/services/{question.service.js → dashboard-question.service.js} +10 -10
  87. package/dist/services/dashboard-question.service.js.map +1 -0
  88. package/dist/services/field-metadata.service.d.ts.map +1 -1
  89. package/dist/services/field-metadata.service.js +1 -0
  90. package/dist/services/field-metadata.service.js.map +1 -1
  91. package/dist/services/import-transaction.service.d.ts +3 -1
  92. package/dist/services/import-transaction.service.d.ts.map +1 -1
  93. package/dist/services/import-transaction.service.js +22 -0
  94. package/dist/services/import-transaction.service.js.map +1 -1
  95. package/dist/services/list-of-values.service.d.ts +1 -0
  96. package/dist/services/list-of-values.service.d.ts.map +1 -1
  97. package/dist/services/list-of-values.service.js +3 -0
  98. package/dist/services/list-of-values.service.js.map +1 -1
  99. package/dist/services/question-data-providers/chartjs-sql-data-provider.service.d.ts +2 -2
  100. package/dist/services/question-data-providers/chartjs-sql-data-provider.service.d.ts.map +1 -1
  101. package/dist/services/question-data-providers/chartjs-sql-data-provider.service.js +11 -7
  102. package/dist/services/question-data-providers/chartjs-sql-data-provider.service.js.map +1 -1
  103. package/dist/services/question-data-providers/helpers.d.ts +5 -2
  104. package/dist/services/question-data-providers/helpers.d.ts.map +1 -1
  105. package/dist/services/question-data-providers/helpers.js +18 -3
  106. package/dist/services/question-data-providers/helpers.js.map +1 -1
  107. package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.d.ts +2 -2
  108. package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.d.ts.map +1 -1
  109. package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.js +10 -4
  110. package/dist/services/question-data-providers/prime-react-datatable-sql-data-provider.service.js.map +1 -1
  111. package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.d.ts +2 -2
  112. package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.d.ts.map +1 -1
  113. package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.js +11 -3
  114. package/dist/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.js.map +1 -1
  115. package/dist/services/queues/publisher-factory.service.js +1 -1
  116. package/dist/services/queues/publisher-factory.service.js.map +1 -1
  117. package/dist/solid-core.module.d.ts.map +1 -1
  118. package/dist/solid-core.module.js +52 -39
  119. package/dist/solid-core.module.js.map +1 -1
  120. package/dist/subscribers/{question-sql-dataset-config.subscriber.d.ts → dashboard-question-sql-dataset-config.subscriber.d.ts} +6 -6
  121. package/dist/subscribers/dashboard-question-sql-dataset-config.subscriber.d.ts.map +1 -0
  122. package/dist/subscribers/{question-sql-dataset-config.subscriber.js → dashboard-question-sql-dataset-config.subscriber.js} +10 -10
  123. package/dist/subscribers/dashboard-question-sql-dataset-config.subscriber.js.map +1 -0
  124. package/dist/subscribers/{question.subscriber.d.ts → dashboard-question.subscriber.d.ts} +7 -7
  125. package/dist/subscribers/dashboard-question.subscriber.d.ts.map +1 -0
  126. package/dist/subscribers/{question.subscriber.js → dashboard-question.subscriber.js} +8 -8
  127. package/dist/subscribers/dashboard-question.subscriber.js.map +1 -0
  128. package/dist/tsconfig.tsbuildinfo +1 -1
  129. package/package.json +1 -1
  130. package/src/controllers/ai-interaction.controller.ts +8 -2
  131. package/src/controllers/{question-sql-dataset-config.controller.ts → dashboard-question-sql-dataset-config.controller.ts} +10 -10
  132. package/src/controllers/{question.controller.ts → dashboard-question.controller.ts} +10 -10
  133. package/src/dtos/{create-question-sql-dataset-config.dto.ts → create-dashboard-question-sql-dataset-config.dto.ts} +1 -1
  134. package/src/dtos/{create-question.dto.ts → create-dashboard-question.dto.ts} +9 -4
  135. package/src/dtos/create-dashboard.dto.ts +3 -3
  136. package/src/dtos/{update-question-sql-dataset-config.dto.ts → update-dashboard-question-sql-dataset-config.dto.ts} +1 -1
  137. package/src/dtos/{update-question.dto.ts → update-dashboard-question.dto.ts} +9 -4
  138. package/src/dtos/update-dashboard.dto.ts +3 -3
  139. package/src/entities/{question-sql-dataset-config.entity.ts → dashboard-question-sql-dataset-config.entity.ts} +5 -5
  140. package/src/entities/{question.entity.ts → dashboard-question.entity.ts} +8 -5
  141. package/src/entities/dashboard.entity.ts +3 -3
  142. package/src/index.ts +7 -4
  143. package/src/interfaces.ts +18 -2
  144. package/src/jobs/database/trigger-mcp-client-publisher-database.service.ts +22 -0
  145. package/src/jobs/database/trigger-mcp-client-queue-options.ts +9 -0
  146. package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +71 -0
  147. package/src/repository/dashboard.repository.ts +54 -53
  148. package/src/seeders/seed-data/solid-core-metadata.json +88 -131
  149. package/src/services/ai-interaction.service.ts +68 -32
  150. package/src/services/chatter-message.service.ts +12 -6
  151. package/src/services/{question-sql-dataset-config.service.ts → dashboard-question-sql-dataset-config.service.ts} +5 -5
  152. package/src/services/{question.service.ts → dashboard-question.service.ts} +6 -6
  153. package/src/services/field-metadata.service.ts +1 -0
  154. package/src/services/import-transaction.service.ts +29 -1
  155. package/src/services/list-of-values.service.ts +5 -0
  156. package/src/services/question-data-providers/chartjs-sql-data-provider.service.ts +14 -10
  157. package/src/services/question-data-providers/helpers.ts +23 -4
  158. package/src/services/question-data-providers/prime-react-datatable-sql-data-provider.service.ts +12 -6
  159. package/src/services/question-data-providers/prime-react-meter-group-sql-data-provider.service.ts +14 -5
  160. package/src/services/question-data-providers/test.sql +1 -0
  161. package/src/services/queues/publisher-factory.service.ts +1 -1
  162. package/src/solid-core.module.ts +54 -39
  163. package/src/subscribers/{question-sql-dataset-config.subscriber.ts → dashboard-question-sql-dataset-config.subscriber.ts} +8 -10
  164. package/src/subscribers/{question.subscriber.ts → dashboard-question.subscriber.ts} +7 -10
  165. package/dist/controllers/question-sql-dataset-config.controller.d.ts +0 -43
  166. package/dist/controllers/question-sql-dataset-config.controller.d.ts.map +0 -1
  167. package/dist/controllers/question-sql-dataset-config.controller.js.map +0 -1
  168. package/dist/controllers/question.controller.d.ts +0 -45
  169. package/dist/controllers/question.controller.d.ts.map +0 -1
  170. package/dist/controllers/question.controller.js.map +0 -1
  171. package/dist/dtos/create-question-sql-dataset-config.dto.d.ts.map +0 -1
  172. package/dist/dtos/create-question-sql-dataset-config.dto.js.map +0 -1
  173. package/dist/dtos/create-question.dto.d.ts +0 -16
  174. package/dist/dtos/create-question.dto.d.ts.map +0 -1
  175. package/dist/dtos/create-question.dto.js.map +0 -1
  176. package/dist/dtos/update-question-sql-dataset-config.dto.d.ts.map +0 -1
  177. package/dist/dtos/update-question-sql-dataset-config.dto.js.map +0 -1
  178. package/dist/dtos/update-question.dto.d.ts +0 -17
  179. package/dist/dtos/update-question.dto.d.ts.map +0 -1
  180. package/dist/dtos/update-question.dto.js.map +0 -1
  181. package/dist/entities/question-sql-dataset-config.entity.d.ts +0 -13
  182. package/dist/entities/question-sql-dataset-config.entity.d.ts.map +0 -1
  183. package/dist/entities/question-sql-dataset-config.entity.js.map +0 -1
  184. package/dist/entities/question.entity.d.ts +0 -15
  185. package/dist/entities/question.entity.d.ts.map +0 -1
  186. package/dist/entities/question.entity.js.map +0 -1
  187. package/dist/services/question-sql-dataset-config.service.d.ts.map +0 -1
  188. package/dist/services/question-sql-dataset-config.service.js.map +0 -1
  189. package/dist/services/question.service.d.ts.map +0 -1
  190. package/dist/services/question.service.js.map +0 -1
  191. package/dist/subscribers/question-sql-dataset-config.subscriber.d.ts.map +0 -1
  192. package/dist/subscribers/question-sql-dataset-config.subscriber.js.map +0 -1
  193. package/dist/subscribers/question.subscriber.d.ts.map +0 -1
  194. package/dist/subscribers/question.subscriber.js.map +0 -1
  195. package/src/services/1. Create a context menu option i.py +0 -12
@@ -1,4 +1,4 @@
1
- import { BadRequestException, Injectable } from '@nestjs/common';
1
+ import { BadRequestException, Logger, Injectable } from '@nestjs/common';
2
2
  import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
3
3
  import { DiscoveryService, ModuleRef } from "@nestjs/core";
4
4
  import { EntityManager, Repository } from 'typeorm';
@@ -12,22 +12,15 @@ import { CrudHelperService } from 'src/services/crud-helper.service';
12
12
  import { spawn } from 'child_process';
13
13
  import { AiInteraction } from '../entities/ai-interaction.entity';
14
14
  import * as fs from 'fs/promises';
15
- import * as path from 'path';
16
-
17
- interface McpResponse {
18
- success: boolean;
19
- request: string;
20
- response: string;
21
- model?: string;
22
- tools_invoked?: string[];
23
- tool_calls?: any[];
24
- duration_ms?: number;
25
- errors?: string[];
26
- trace?: string[];
27
- }
15
+ import { McpResponse, TriggerMcpClientOptions } from 'src/interfaces';
16
+ import { PublisherFactory } from './queues/publisher-factory.service';
17
+ import { RequestContextService } from './request-context.service';
18
+ import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
28
19
 
29
20
  @Injectable()
30
21
  export class AiInteractionService extends CRUDService<AiInteraction> {
22
+ private readonly logger = new Logger(AiInteractionService.name);
23
+
31
24
  constructor(
32
25
  readonly modelMetadataService: ModelMetadataService,
33
26
  readonly moduleMetadataService: ModuleMetadataService,
@@ -39,18 +32,45 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
39
32
  readonly entityManager: EntityManager,
40
33
  @InjectRepository(AiInteraction, 'default')
41
34
  readonly repo: Repository<AiInteraction>,
42
- readonly moduleRef: ModuleRef
35
+ readonly moduleRef: ModuleRef,
36
+ readonly publisherFactory: PublisherFactory<TriggerMcpClientOptions>,
37
+ readonly requestContextService: RequestContextService,
43
38
 
44
39
  ) {
45
40
  super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'aiInteraction', 'solid-core', moduleRef);
46
41
  }
47
42
 
43
+ async triggerMcpClientJob(prompt: string): Promise<string> {
44
+ const activeUser: ActiveUserData = this.requestContextService.getActiveUser();
45
+
46
+ const aiInteraction = await this.create({
47
+ userId: activeUser.sub,
48
+ threadId: `thread-${activeUser.sub}`,
49
+ role: 'human',
50
+ message: prompt,
51
+ contentType: '',
52
+ errorMessage: '',
53
+ modelUsed: '',
54
+ responseTimeMs: 0,
55
+ metadata: ''
56
+ });
57
+ const m = {
58
+ payload: {
59
+ aiInteractionId: aiInteraction.id,
60
+ },
61
+ parentEntity: 'aiInteraction',
62
+ parentEntityId: aiInteraction.id,
63
+ };
64
+
65
+ return await this.publisherFactory.publish(m, 'TriggerMcpClientPublisher');
66
+ }
67
+
48
68
  /**
49
69
  * Runs the Python MCP client with a prompt and returns the parsed JSON embedded in the 'response'.
50
70
  * @param prompt - The question or instruction to send to the MCP client.
51
71
  * @returns The parsed object inside the 'response' field of the JSON output.
52
72
  */
53
- async runMcpPrompt(prompt: string): Promise<any> {
73
+ async runMcpPrompt(prompt: string): Promise<McpResponse> {
54
74
  const pythonExecutable = process.env.MCP_PYTHON_EXECUTABLE;
55
75
  const mcpClient = process.env.MCP_CLIENT;
56
76
 
@@ -78,7 +98,10 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
78
98
  throw new BadRequestException(`Invalid MCP executable or client path: ${err.message}`);
79
99
  }
80
100
 
101
+ // TODO: Refactor to use the command.service.ts instead...
81
102
  return new Promise((resolve, reject) => {
103
+ this.logger.log(`Attempting to run command:`)
104
+ this.logger.log(`${pythonExecutable} ${mcpClient} ${prompt}`);
82
105
 
83
106
  const python = spawn(pythonExecutable, [mcpClient, prompt]);
84
107
 
@@ -94,34 +117,47 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
94
117
  });
95
118
 
96
119
  python.on('close', (code) => {
120
+ this.logger.log(`Python script exited with code ${code}`);
121
+
97
122
  if (code !== 0) {
98
- return reject(new Error(`Python script exited with code ${code}: ${stderr}`));
123
+ this.logger.error(`Python script exited with a non-zero exit code: ${stderr}`);
124
+ return reject(new Error(`Python script exited with a non-zero exit code: ${stderr}`));
99
125
  }
100
126
 
101
127
  try {
128
+ this.logger.log(`Python script exited with zero exit code: ${stdout}`);
102
129
  const raw: McpResponse = JSON.parse(stdout);
103
130
 
104
- if (!raw.success) {
105
- return reject(new Error(`MCP error: ${raw.errors?.join(', ')}`));
106
- }
107
-
108
- let cleaned = raw.response.trim();
131
+ // if (!raw.success) {
132
+ // return reject(new Error(`MCP error: ${raw.errors?.join(', ')}`));
133
+ // }
134
+ // let cleaned = raw.response.trim();
109
135
 
110
- // Remove markdown-style code block wrapper
111
- if (cleaned.startsWith('```json')) {
112
- cleaned = cleaned.replace(/^```json/, '').trim();
113
- }
114
- if (cleaned.endsWith('```')) {
115
- cleaned = cleaned.replace(/```$/, '').trim();
116
- }
136
+ // Don't need to re-parse this...
137
+ // const parsed = JSON.parse(cleaned);
138
+ // resolve(cleaned);
117
139
 
118
- const parsed = JSON.parse(cleaned);
119
- resolve(parsed);
140
+ resolve(raw);
120
141
  } catch (err: any) {
121
- reject(new Error(`Failed to parse JSON: ${err.message}`));
142
+ reject(new Error(`Mcp Invocation Failed: ${err.message}`));
122
143
  }
123
144
  });
124
145
  });
125
146
  }
126
147
 
148
+ cleanResponse(response: string) {
149
+ this.logger.log(`mcp server response is: ${response}`);
150
+
151
+ // Remove markdown-style code block wrapper
152
+ if (response.startsWith('```json')) {
153
+ response = response.replace(/^```json/, '').trim();
154
+ }
155
+ if (response.endsWith('```')) {
156
+ response = response.replace(/```$/, '').trim();
157
+ }
158
+ this.logger.log(`mcp server response after removing doc tags is: ${response}`);
159
+
160
+ return response;
161
+ }
162
+
127
163
  }
@@ -125,7 +125,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
125
125
 
126
126
  for (const field of auditFields) {
127
127
  const fieldValue = entity[field.name];
128
- if (fieldValue !== undefined && fieldValue !== null) {
128
+ if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
129
129
  const messageDetail = new ChatterMessageDetails();
130
130
  messageDetail.chatterMessage = savedMessage;
131
131
  messageDetail.fieldName = field.name;
@@ -289,19 +289,25 @@ private formatFieldValueDisplay(field: any, value: any): string {
289
289
  }
290
290
 
291
291
  private hasValueChanged(newValue: any, oldValue: any): boolean {
292
- if (newValue === oldValue) {
292
+ if (
293
+ (newValue === null || newValue === undefined) &&
294
+ (oldValue === null || oldValue === undefined)
295
+ ) {
293
296
  return false;
294
297
  }
295
298
 
296
- if (newValue === null && oldValue === null) {
299
+ if (newValue === oldValue) {
297
300
  return false;
298
301
  }
299
302
 
300
- if (newValue === undefined && oldValue === undefined) {
301
- return false;
303
+ if (Array.isArray(newValue) && Array.isArray(oldValue)) {
304
+ return JSON.stringify(newValue) !== JSON.stringify(oldValue);
302
305
  }
303
306
 
304
- if (Array.isArray(newValue) && Array.isArray(oldValue)) {
307
+ if (
308
+ typeof newValue === 'object' && newValue !== null &&
309
+ typeof oldValue === 'object' && oldValue !== null
310
+ ) {
305
311
  return JSON.stringify(newValue) !== JSON.stringify(oldValue);
306
312
  }
307
313
 
@@ -11,10 +11,10 @@ import { FileService } from 'src/services/file.service';
11
11
  import { CrudHelperService } from 'src/services/crud-helper.service';
12
12
 
13
13
 
14
- import { QuestionSqlDatasetConfig } from '../entities/question-sql-dataset-config.entity';
14
+ import { DashboardQuestionSqlDatasetConfig } from '../entities/dashboard-question-sql-dataset-config.entity';
15
15
 
16
16
  @Injectable()
17
- export class QuestionSqlDatasetConfigService extends CRUDService<QuestionSqlDatasetConfig>{
17
+ export class DashboardQuestionSqlDatasetConfigService extends CRUDService<DashboardQuestionSqlDatasetConfig>{
18
18
  constructor(
19
19
  readonly modelMetadataService: ModelMetadataService,
20
20
  readonly moduleMetadataService: ModuleMetadataService,
@@ -24,11 +24,11 @@ export class QuestionSqlDatasetConfigService extends CRUDService<QuestionSqlData
24
24
  readonly crudHelperService: CrudHelperService,
25
25
  @InjectEntityManager()
26
26
  readonly entityManager: EntityManager,
27
- @InjectRepository(QuestionSqlDatasetConfig, 'default')
28
- readonly repo: Repository<QuestionSqlDatasetConfig>,
27
+ @InjectRepository(DashboardQuestionSqlDatasetConfig, 'default')
28
+ readonly repo: Repository<DashboardQuestionSqlDatasetConfig>,
29
29
  readonly moduleRef: ModuleRef
30
30
 
31
31
  ) {
32
- super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService,entityManager, repo, 'questionSqlDatasetConfig', 'solid-core', moduleRef);
32
+ super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService,entityManager, repo, 'dashboardQuestionSqlDatasetConfig', 'solid-core', moduleRef);
33
33
  }
34
34
  }
@@ -13,7 +13,7 @@ import { ModuleMetadataService } from 'src/services/module-metadata.service';
13
13
 
14
14
  import { DashboardVariable } from 'src/entities/dashboard-variable.entity';
15
15
  import { SolidRegistry } from 'src/helpers/solid-registry';
16
- import { Question } from '../entities/question.entity';
16
+ import { DashboardQuestion } from '../entities/dashboard-question.entity';
17
17
  import { SqlExpression, SqlExpressionOperator } from './question-data-providers/chartjs-sql-data-provider.service';
18
18
  import { DashboardService } from './dashboard.service';
19
19
  import { Dashboard } from 'src/entities/dashboard.entity';
@@ -28,7 +28,7 @@ const PRIME_REACT_METER_GROUP_SQL_DATA_PROVIDER_NAME = 'PrimeReactMeterGroupSqlD
28
28
  const PRIME_REACT_DATATABLE_SQL_DATA_PROVIDER_NAME = 'PrimeReactDatatableSqlDataProvider';
29
29
 
30
30
  @Injectable()
31
- export class QuestionService extends CRUDService<Question> {
31
+ export class DashboardQuestionService extends CRUDService<DashboardQuestion> {
32
32
  private readonly logger = new Logger(this.constructor.name);
33
33
  constructor(
34
34
  readonly modelMetadataService: ModelMetadataService,
@@ -39,12 +39,12 @@ export class QuestionService extends CRUDService<Question> {
39
39
  readonly crudHelperService: CrudHelperService,
40
40
  @InjectEntityManager()
41
41
  readonly entityManager: EntityManager,
42
- @InjectRepository(Question, 'default')
43
- readonly repo: Repository<Question>,
42
+ @InjectRepository(DashboardQuestion, 'default')
43
+ readonly repo: Repository<DashboardQuestion>,
44
44
  readonly moduleRef: ModuleRef,
45
45
  readonly solidRegistry: SolidRegistry, // Assuming solidRegistry is injected for data providers
46
46
  ) {
47
- super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'question', 'solid-core', moduleRef);
47
+ super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'dashboardQuestion', 'solid-core', moduleRef);
48
48
  }
49
49
 
50
50
  // Get the data for a specific question
@@ -100,7 +100,7 @@ export class QuestionService extends CRUDService<Question> {
100
100
  }
101
101
 
102
102
  private async loadQuestion(id: number) {
103
- const repo = this.entityManager.getRepository(Question);
103
+ const repo = this.entityManager.getRepository(DashboardQuestion);
104
104
 
105
105
  // Load the dashboard record using the field
106
106
  const question = await repo.findOne({
@@ -929,6 +929,7 @@ export class FieldMetadataService implements OnApplicationBootstrap {
929
929
  "type",
930
930
  "ormType",
931
931
  "isSystem",
932
+ "computedFieldValueType",
932
933
  "computedFieldTriggerConfig",
933
934
  "computedFieldValueProvider",
934
935
  "computedFieldValueProviderCtxt",
@@ -496,7 +496,7 @@ export class ImportTransactionService extends CRUDService<ImportTransaction> {
496
496
  };
497
497
  }
498
498
 
499
- private async createErrorLogEntry(importTransaction: ImportTransaction, record: Record<string, any>, error: any) {
499
+ async createErrorLogEntry(importTransaction: ImportTransaction, record: Record<string, any>, error: any) {
500
500
  const importTransactionRepo = this.entityManager.getRepository(ImportTransactionErrorLog);
501
501
  // Create a new ImportTransactionErrorLog entry
502
502
  const rowNumber = uuidv4(); // Generate a unique row number or use page.rowNumber if available
@@ -574,11 +574,39 @@ export class ImportTransactionService extends CRUDService<ImportTransaction> {
574
574
  return this.populateDtoForNumber(dtoRecord, fieldMetadata, record, key);
575
575
  case SolidFieldType.boolean:
576
576
  return this.populateDtoForBoolean(dtoRecord, fieldMetadata, record, key);
577
+ case SolidFieldType.selectionStatic:
578
+ case SolidFieldType.selectionDynamic:
579
+ return this.populateDtoForSelectionValues(dtoRecord, fieldMetadata, record, key);
577
580
  default:
578
581
  dtoRecord[fieldMetadata.name] = record[key];
579
582
  return dtoRecord;
580
583
  }
581
584
  }
585
+
586
+ private populateDtoForSelectionValues(dtoRecord: Record<string, any>, fieldMetadata: FieldMetadata, record: Record<string, any>, key: string) {
587
+ const rawValue = record[key];
588
+
589
+ if (rawValue == null) {
590
+ dtoRecord[fieldMetadata.name] = null;
591
+ return dtoRecord;
592
+ }
593
+
594
+ const isMultipleSelection = fieldMetadata.isMultiSelect;
595
+
596
+ if (isMultipleSelection) {
597
+ const selectionValues = String(rawValue)
598
+ .split(',')
599
+ .map(value => value.trim())
600
+ .filter(value => value !== '');
601
+
602
+ dtoRecord[fieldMetadata.name] = JSON.stringify(selectionValues);
603
+ } else {
604
+ dtoRecord[fieldMetadata.name] = rawValue; // Single select: assign directly
605
+ }
606
+
607
+ return dtoRecord;
608
+ }
609
+
582
610
 
583
611
  private populateDtoForBoolean(dtoRecord: Record<string, any>, fieldMetadata: FieldMetadata, record: Record<string, any>, key: string) {
584
612
  const booleanValue = Boolean(record[key]);
@@ -39,6 +39,11 @@ export class ListOfValuesService extends CRUDService<ListOfValues> {
39
39
  },
40
40
  });
41
41
  }
42
+
43
+ async findAll(): Promise<ListOfValues[]> {
44
+ return await this.repo.find();
45
+ }
46
+
42
47
  async upsert(updateListOfValuesDto: any) {
43
48
  // First check if module already exists using name
44
49
  const existingListOfValue = await this.repo.findOne({
@@ -1,11 +1,10 @@
1
- import { Injectable } from "@nestjs/common";
1
+ import { Injectable, Logger } from "@nestjs/common";
2
2
  import { DashboardQuestionDataProvider } from "src/decorators/dashboard-question-data-provider.decorator";
3
- import { Question } from "src/entities/question.entity";
3
+ import { DashboardQuestion } from "src/entities/dashboard-question.entity";
4
4
  import { IDashboardQuestionDataProvider } from "src/interfaces";
5
5
  import { EntityManager } from "typeorm";
6
6
  import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
7
- import { Logger } from '@nestjs/common';
8
- import { getLabels } from "./helpers";
7
+ import { getKpi, getLabels } from "./helpers";
9
8
 
10
9
  export interface QuestionSqlDataProviderContext {
11
10
  // questionSqlDatasetConfig: QuestionSqlDatasetConfig;
@@ -50,7 +49,7 @@ export class ChartJsSqlDataProvider implements IDashboardQuestionDataProvider<Qu
50
49
  return "ChartJsSqlDataProvider";
51
50
  }
52
51
 
53
- async getData(question: Question, expressions?: SqlExpression[], context?: QuestionSqlDataProviderContext): Promise<any> {
52
+ async getData(question: DashboardQuestion, expressions?: SqlExpression[], context?: QuestionSqlDataProviderContext): Promise<any> {
54
53
  // TODO: put some validation to check if the results of each SQL in each dataset returns the same number of rows
55
54
 
56
55
  // This is what we have to return.
@@ -80,7 +79,8 @@ export class ChartJsSqlDataProvider implements IDashboardQuestionDataProvider<Qu
80
79
  let datasetIdx = 0;
81
80
  const datasets = [];
82
81
 
83
- const labels: string[] = await getLabels(question, this.entityManager);
82
+ const labels: string[] = await getLabels(question, expressions, this.entityManager, this.sqlExpressionResolver);
83
+ const kpi: string = await getKpi(question, expressions, this.entityManager, this.sqlExpressionResolver);
84
84
 
85
85
  // const question = context.question;
86
86
  for (const questionSqlDatasetConfig of question.questionSqlDatasetConfigs) {
@@ -91,8 +91,8 @@ export class ChartJsSqlDataProvider implements IDashboardQuestionDataProvider<Qu
91
91
  }
92
92
 
93
93
  const sqlReplacementResult = this.sqlExpressionResolver.resolveSqlWithExpressions(sql, expressions || []);
94
- this.logger.log(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is query=[${sqlReplacementResult.rawSql}]`);
95
- this.logger.log(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is parameters=[${JSON.stringify(sqlReplacementResult.parameters)}]`);
94
+ this.logger.debug(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is query=[${sqlReplacementResult.rawSql}]`);
95
+ this.logger.debug(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is parameters=[${JSON.stringify(sqlReplacementResult.parameters)}]`);
96
96
  const results = await this.entityManager.query(sqlReplacementResult.rawSql, sqlReplacementResult.parameters);
97
97
 
98
98
  // Also for each data set we create the dataset object as is expected by ChartJs.
@@ -111,8 +111,12 @@ export class ChartJsSqlDataProvider implements IDashboardQuestionDataProvider<Qu
111
111
  }
112
112
 
113
113
  return {
114
- labels,
115
- datasets
114
+ kpi,
115
+ visualizedAs: question.visualisedAs,
116
+ visualizationData: {
117
+ labels,
118
+ datasets
119
+ }
116
120
  };
117
121
 
118
122
  }
@@ -1,11 +1,30 @@
1
- import { Question } from "src/entities/question.entity";
1
+ import { DashboardQuestion } from "src/entities/dashboard-question.entity";
2
2
  import { EntityManager } from "typeorm";
3
+ import { SqlExpression } from "./chartjs-sql-data-provider.service";
4
+ import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
3
5
 
4
- export async function getLabels(question: Question, entityManager: EntityManager): Promise<string[]> {
5
- const labelSql = question.labelSql;
6
- const labelResults = await this.entityManager.query(labelSql);
6
+ export async function getLabels(question: DashboardQuestion, expressions: SqlExpression[], entityManager: EntityManager, sqlExpressionResolver: SqlExpressionResolverService): Promise<string[]> {
7
+ const sql = question.labelSql;
8
+ if (!sql) {
9
+ return [];
10
+ }
11
+ const sqlReplacementResult = sqlExpressionResolver.resolveSqlWithExpressions(sql, expressions || []);
12
+
13
+ const labelResults = await entityManager.query(sqlReplacementResult.rawSql, sqlReplacementResult.parameters);
14
+
7
15
  // Assuming labelResults has a single row with a 'label' field
8
16
  // Map the label results to the labels array
9
17
  const labels: string[] = labelResults.map((result: { [x: string]: string; }) => result['label']);
10
18
  return labels;
19
+ }
20
+
21
+ export async function getKpi(question: DashboardQuestion, expressions: SqlExpression[], entityManager: EntityManager, sqlExpressionResolver: SqlExpressionResolverService): Promise<string> {
22
+ const sql = question.kpiSql;
23
+ if (!sql) {
24
+ return "";
25
+ }
26
+ const sqlReplacementResult = sqlExpressionResolver.resolveSqlWithExpressions(sql, expressions || []);
27
+ const result = await entityManager.query(sqlReplacementResult.rawSql, sqlReplacementResult.parameters);
28
+ const kpiResult = result.pop();
29
+ return kpiResult?.kpi || "";
11
30
  }
@@ -1,11 +1,12 @@
1
1
  import { Injectable } from "@nestjs/common";
2
2
  import { DashboardQuestionDataProvider } from "src/decorators/dashboard-question-data-provider.decorator";
3
- import { Question } from "src/entities/question.entity";
3
+ import { DashboardQuestion } from "src/entities/dashboard-question.entity";
4
4
  import { IDashboardQuestionDataProvider } from "src/interfaces";
5
5
  import { EntityManager } from "typeorm";
6
6
  import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
7
7
  import { Logger } from '@nestjs/common';
8
8
  import { SqlExpression } from "./chartjs-sql-data-provider.service";
9
+ import { getKpi } from "./helpers";
9
10
 
10
11
  export interface QuestionSqlDataProviderContext {
11
12
  // questionSqlDatasetConfig: QuestionSqlDatasetConfig;
@@ -28,11 +29,12 @@ export class PrimeReactDatatableSqlDataProvider implements IDashboardQuestionDat
28
29
  return "PrimeReactDatatableSqlDataProvider";
29
30
  }
30
31
 
31
- async getData(question: Question, expressions?: SqlExpression[], context?: QuestionSqlDataProviderContext): Promise<any> {
32
+ async getData(question: DashboardQuestion, expressions?: SqlExpression[], context?: QuestionSqlDataProviderContext): Promise<any> {
32
33
  // TODO: put some validation to check if the results of each SQL in each dataset returns the same number of rows
33
34
 
34
35
  // Check the expected response for prime react data tables to understand what is going on here...
35
36
 
37
+ const kpi: string = await getKpi(question, expressions, this.entityManager, this.sqlExpressionResolver);
36
38
  // TODO: Load the set of labels by using a separate field on the question entity.
37
39
  const labelSql = question.labelSql;
38
40
  const labelResults = await this.entityManager.query(labelSql);
@@ -59,13 +61,17 @@ export class PrimeReactDatatableSqlDataProvider implements IDashboardQuestionDat
59
61
  }
60
62
 
61
63
  const sqlReplacementResult = this.sqlExpressionResolver.resolveSqlWithExpressions(sql, expressions || []);
62
- this.logger.log(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is query=[${sqlReplacementResult.rawSql}]`);
63
- this.logger.log(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is parameters=[${JSON.stringify(sqlReplacementResult.parameters)}]`);
64
+ this.logger.debug(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is query=[${sqlReplacementResult.rawSql}]`);
65
+ this.logger.debug(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is parameters=[${JSON.stringify(sqlReplacementResult.parameters)}]`);
64
66
  const results = await this.entityManager.query(sqlReplacementResult.rawSql, sqlReplacementResult.parameters);
65
67
 
66
68
  return {
67
- columns,
68
- data: results,
69
+ kpi,
70
+ visualisedAs: question.visualisedAs,
71
+ visualizationData: {
72
+ columns,
73
+ rows: results,
74
+ }
69
75
  };
70
76
 
71
77
  }
@@ -1,11 +1,12 @@
1
1
  import { Injectable } from "@nestjs/common";
2
2
  import { DashboardQuestionDataProvider } from "src/decorators/dashboard-question-data-provider.decorator";
3
- import { Question } from "src/entities/question.entity";
3
+ import { DashboardQuestion } from "src/entities/dashboard-question.entity";
4
4
  import { IDashboardQuestionDataProvider } from "src/interfaces";
5
5
  import { EntityManager } from "typeorm";
6
6
  import { SqlExpressionResolverService } from "../sql-expression-resolver.service";
7
7
  import { Logger } from '@nestjs/common';
8
8
  import { SqlExpression } from "./chartjs-sql-data-provider.service";
9
+ import { getKpi } from "./helpers";
9
10
 
10
11
  export interface QuestionSqlDataProviderContext {
11
12
  // questionSqlDatasetConfig: QuestionSqlDatasetConfig;
@@ -57,7 +58,7 @@ export class PrimeReactMeterGroupSqlDataProvider implements IDashboardQuestionDa
57
58
  return colors;
58
59
  }
59
60
 
60
- async getData(question: Question, expressions?: SqlExpression[], context?: QuestionSqlDataProviderContext): Promise<any> {
61
+ async getData(question: DashboardQuestion, expressions?: SqlExpression[], context?: QuestionSqlDataProviderContext): Promise<any> {
61
62
  // TODO: put some validation to check if the results of each SQL in each dataset returns the same number of rows
62
63
 
63
64
  // This is what we have to return.
@@ -70,6 +71,8 @@ export class PrimeReactMeterGroupSqlDataProvider implements IDashboardQuestionDa
70
71
 
71
72
  // TODO: Load the set of labels by using a separate field on the question entity.
72
73
 
74
+ const kpi: string = await getKpi(question, expressions, this.entityManager, this.sqlExpressionResolver);
75
+
73
76
  // Load the chart options as a JSON
74
77
  const chartOptions = JSON.parse(question.chartOptions || '{}');
75
78
 
@@ -84,8 +87,8 @@ export class PrimeReactMeterGroupSqlDataProvider implements IDashboardQuestionDa
84
87
  }
85
88
 
86
89
  const sqlReplacementResult = this.sqlExpressionResolver.resolveSqlWithExpressions(sql, expressions || []);
87
- this.logger.log(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is query=[${sqlReplacementResult.rawSql}]`);
88
- this.logger.log(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is parameters=[${JSON.stringify(sqlReplacementResult.parameters)}]`);
90
+ this.logger.debug(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is query=[${sqlReplacementResult.rawSql}]`);
91
+ this.logger.debug(`Final Sql query for dataset [${questionSqlDatasetConfig.datasetName}] is parameters=[${JSON.stringify(sqlReplacementResult.parameters)}]`);
89
92
  const results = await this.entityManager.query(sqlReplacementResult.rawSql, sqlReplacementResult.parameters);
90
93
 
91
94
  const colors = this.generateDistinctColors(results.length);
@@ -104,7 +107,13 @@ export class PrimeReactMeterGroupSqlDataProvider implements IDashboardQuestionDa
104
107
  })
105
108
  }
106
109
 
107
- return values;
110
+ return {
111
+ kpi,
112
+ visualizedAs: question.visualisedAs,
113
+ visualizationData: {
114
+ dataset: values
115
+ },
116
+ };
108
117
 
109
118
  }
110
119
  }
@@ -0,0 +1 @@
1
+ SELECT TO_CHAR(DATE_TRUNC('month', created_at), 'Mon-YYYY') AS label,FROM public.sapphire_clientwhere created_at >= $1 GROUP BY DATE_TRUNC('month', created_at) ORDER BY DATE_TRUNC('month', created_at)
@@ -16,7 +16,7 @@ export class PublisherFactory<T> {
16
16
  }
17
17
 
18
18
  async publish(message: QueueMessage<T>, publisherName: string, brokerToUse?: string): Promise<string> {
19
- let defaultBrokerToUse = brokerToUse || process.env.QUEUES_DEFAULT_BROKER;
19
+ let defaultBrokerToUse = brokerToUse || process.env.QUEUES_DEFAULT_BROKER || "database";
20
20
  let resolvedPublisherName = `${publisherName}${classify(defaultBrokerToUse)}`;
21
21
 
22
22
  // Register all ISolidDatabaseModules implementations