@memberjunction/server 2.73.0 → 2.75.0
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/dist/generated/generated.d.ts +81 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +837 -368
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts +1 -0
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +10 -0
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts +33 -0
- package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -0
- package/dist/resolvers/CreateQueryResolver.js +251 -0
- package/dist/resolvers/CreateQueryResolver.js.map +1 -0
- package/dist/resolvers/QueryResolver.d.ts +8 -5
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +151 -46
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +6 -2
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/package.json +34 -34
- package/src/generated/generated.ts +662 -369
- package/src/index.ts +2 -1
- package/src/resolvers/AskSkipResolver.ts +15 -0
- package/src/resolvers/CreateQueryResolver.ts +257 -0
- package/src/resolvers/QueryResolver.ts +147 -41
- package/src/resolvers/RunAIPromptResolver.ts +5 -1
package/src/index.ts
CHANGED
|
@@ -85,13 +85,14 @@ export * from './resolvers/DatasetResolver.js';
|
|
|
85
85
|
export * from './resolvers/EntityRecordNameResolver.js';
|
|
86
86
|
export * from './resolvers/MergeRecordsResolver.js';
|
|
87
87
|
export * from './resolvers/ReportResolver.js';
|
|
88
|
+
export * from './resolvers/QueryResolver.js';
|
|
88
89
|
export * from './resolvers/SqlLoggingConfigResolver.js';
|
|
89
90
|
export * from './resolvers/SyncRolesUsersResolver.js';
|
|
90
91
|
export * from './resolvers/SyncDataResolver.js';
|
|
91
92
|
export * from './resolvers/GetDataResolver.js';
|
|
92
93
|
export * from './resolvers/GetDataContextDataResolver.js';
|
|
93
94
|
export * from './resolvers/TransactionGroupResolver.js';
|
|
94
|
-
|
|
95
|
+
export * from './resolvers/CreateQueryResolver.js';
|
|
95
96
|
export { GetReadOnlyDataSource, GetReadWriteDataSource } from './util.js';
|
|
96
97
|
|
|
97
98
|
export * from './generated/generated.js';
|
|
@@ -1396,6 +1396,20 @@ cycle.`);
|
|
|
1396
1396
|
);
|
|
1397
1397
|
}
|
|
1398
1398
|
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* Recursively builds the category path for a query
|
|
1402
|
+
* @param md
|
|
1403
|
+
* @param categoryID
|
|
1404
|
+
*/
|
|
1405
|
+
protected buildQueryCategoryPath(md: Metadata, categoryID: string): string {
|
|
1406
|
+
const cat = md.QueryCategories.find((c) => c.ID === categoryID);
|
|
1407
|
+
if (!cat) return '';
|
|
1408
|
+
if (!cat.ParentID) return cat.Name; // base case, no parent, just return the name
|
|
1409
|
+
const parentPath = this.buildQueryCategoryPath(md, cat.ParentID); // build the path recursively
|
|
1410
|
+
return parentPath ? `${parentPath}/${cat.Name}` : cat.Name;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1399
1413
|
/**
|
|
1400
1414
|
* Packages up queries from the metadata based on their status
|
|
1401
1415
|
* Used to provide Skip with information about available queries
|
|
@@ -1412,6 +1426,7 @@ cycle.`);
|
|
|
1412
1426
|
name: q.Name,
|
|
1413
1427
|
description: q.Description,
|
|
1414
1428
|
category: q.Category,
|
|
1429
|
+
categoryPath: this.buildQueryCategoryPath(md, q.CategoryID),
|
|
1415
1430
|
sql: q.SQL,
|
|
1416
1431
|
originalSQL: q.OriginalSQL,
|
|
1417
1432
|
feedback: q.Feedback,
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType } from 'type-graphql';
|
|
2
|
+
import { AppContext } from '../types.js';
|
|
3
|
+
import { LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
|
|
4
|
+
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
5
|
+
import { QueryEntity, QueryCategoryEntity } from '@memberjunction/core-entities';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Query status enumeration for GraphQL
|
|
9
|
+
*/
|
|
10
|
+
export enum QueryStatus {
|
|
11
|
+
Pending = "Pending",
|
|
12
|
+
Approved = "Approved",
|
|
13
|
+
Rejected = "Rejected",
|
|
14
|
+
Expired = "Expired"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
registerEnumType(QueryStatus, {
|
|
18
|
+
name: "QueryStatus",
|
|
19
|
+
description: "Status of a query: Pending, Approved, Rejected, or Expired"
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
@InputType()
|
|
23
|
+
export class CreateQueryInputType {
|
|
24
|
+
@Field(() => String)
|
|
25
|
+
Name!: string;
|
|
26
|
+
|
|
27
|
+
@Field(() => String, { nullable: true })
|
|
28
|
+
CategoryID?: string;
|
|
29
|
+
|
|
30
|
+
@Field(() => String, { nullable: true })
|
|
31
|
+
CategoryPath?: string;
|
|
32
|
+
|
|
33
|
+
@Field(() => String, { nullable: true })
|
|
34
|
+
UserQuestion?: string;
|
|
35
|
+
|
|
36
|
+
@Field(() => String, { nullable: true })
|
|
37
|
+
Description?: string;
|
|
38
|
+
|
|
39
|
+
@Field(() => String, { nullable: true })
|
|
40
|
+
SQL?: string;
|
|
41
|
+
|
|
42
|
+
@Field(() => String, { nullable: true })
|
|
43
|
+
TechnicalDescription?: string;
|
|
44
|
+
|
|
45
|
+
@Field(() => String, { nullable: true })
|
|
46
|
+
OriginalSQL?: string;
|
|
47
|
+
|
|
48
|
+
@Field(() => String, { nullable: true })
|
|
49
|
+
Feedback?: string;
|
|
50
|
+
|
|
51
|
+
@Field(() => QueryStatus, { nullable: true, defaultValue: QueryStatus.Pending })
|
|
52
|
+
Status?: QueryStatus;
|
|
53
|
+
|
|
54
|
+
@Field(() => Number, { nullable: true })
|
|
55
|
+
QualityRank?: number;
|
|
56
|
+
|
|
57
|
+
@Field(() => Number, { nullable: true })
|
|
58
|
+
ExecutionCostRank?: number;
|
|
59
|
+
|
|
60
|
+
@Field(() => Boolean, { nullable: true })
|
|
61
|
+
UsesTemplate?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@ObjectType()
|
|
65
|
+
export class CreateQueryResultType {
|
|
66
|
+
@Field(() => Boolean)
|
|
67
|
+
Success!: boolean;
|
|
68
|
+
|
|
69
|
+
@Field(() => String, { nullable: true })
|
|
70
|
+
ErrorMessage?: string;
|
|
71
|
+
|
|
72
|
+
@Field(() => String, { nullable: true })
|
|
73
|
+
QueryData?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class CreateQueryResolver {
|
|
77
|
+
/**
|
|
78
|
+
* Creates a new query with the provided attributes. This mutation is restricted to system users only.
|
|
79
|
+
* @param input - CreateQueryInputType containing all the query attributes
|
|
80
|
+
* @param context - Application context containing user information
|
|
81
|
+
* @returns CreateQueryResultType with success status and query data
|
|
82
|
+
*/
|
|
83
|
+
@RequireSystemUser()
|
|
84
|
+
@Mutation(() => CreateQueryResultType)
|
|
85
|
+
async CreateQuery(
|
|
86
|
+
@Arg('input', () => CreateQueryInputType) input: CreateQueryInputType,
|
|
87
|
+
@Ctx() context: AppContext
|
|
88
|
+
): Promise<CreateQueryResultType> {
|
|
89
|
+
try {
|
|
90
|
+
const md = new Metadata();
|
|
91
|
+
const newQuery = await md.GetEntityObject<QueryEntity>("Queries", context.userPayload.userRecord);
|
|
92
|
+
|
|
93
|
+
// Handle CategoryPath if provided
|
|
94
|
+
let finalCategoryID = input.CategoryID;
|
|
95
|
+
if (input.CategoryPath) {
|
|
96
|
+
finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Populate the query fields from input
|
|
100
|
+
newQuery.Name = input.Name;
|
|
101
|
+
|
|
102
|
+
if (finalCategoryID != null) {
|
|
103
|
+
newQuery.CategoryID = finalCategoryID;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (input.UserQuestion != null) {
|
|
107
|
+
newQuery.UserQuestion = input.UserQuestion;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (input.Description != null) {
|
|
111
|
+
newQuery.Description = input.Description;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (input.SQL != null) {
|
|
115
|
+
newQuery.SQL = input.SQL;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (input.TechnicalDescription != null) {
|
|
119
|
+
newQuery.TechnicalDescription = input.TechnicalDescription;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (input.OriginalSQL != null) {
|
|
123
|
+
newQuery.OriginalSQL = input.OriginalSQL;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (input.Feedback != null) {
|
|
127
|
+
newQuery.Feedback = input.Feedback;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (input.Status != null) {
|
|
131
|
+
newQuery.Status = input.Status;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (input.QualityRank != null) {
|
|
135
|
+
newQuery.QualityRank = input.QualityRank;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (input.ExecutionCostRank != null) {
|
|
139
|
+
newQuery.ExecutionCostRank = input.ExecutionCostRank;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (input.UsesTemplate != null) {
|
|
143
|
+
newQuery.UsesTemplate = input.UsesTemplate;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Save the query
|
|
147
|
+
const saveResult = await newQuery.Save();
|
|
148
|
+
|
|
149
|
+
if (saveResult) {
|
|
150
|
+
return {
|
|
151
|
+
Success: true,
|
|
152
|
+
QueryData: JSON.stringify(newQuery.GetAll())
|
|
153
|
+
};
|
|
154
|
+
} else {
|
|
155
|
+
return {
|
|
156
|
+
Success: false,
|
|
157
|
+
ErrorMessage: `Failed to save query: ${newQuery.LatestResult?.Message || 'Unknown error'}`
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
} catch (err) {
|
|
162
|
+
LogError(err);
|
|
163
|
+
return {
|
|
164
|
+
Success: false,
|
|
165
|
+
ErrorMessage: `CreateQueryResolver::CreateQuery --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Finds or creates a category hierarchy based on the provided path.
|
|
172
|
+
* Path format: "Parent/Child/Grandchild" - case insensitive lookup and creation.
|
|
173
|
+
* @param categoryPath - Slash-separated category path
|
|
174
|
+
* @param md - Metadata instance
|
|
175
|
+
* @param contextUser - User context for operations
|
|
176
|
+
* @returns The ID of the final category in the path
|
|
177
|
+
*/
|
|
178
|
+
private async findOrCreateCategoryPath(categoryPath: string, md: Metadata, contextUser: UserInfo): Promise<string> {
|
|
179
|
+
if (!categoryPath || categoryPath.trim() === '') {
|
|
180
|
+
throw new Error('CategoryPath cannot be empty');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const pathParts = categoryPath.split('/').map(part => part.trim()).filter(part => part.length > 0);
|
|
184
|
+
if (pathParts.length === 0) {
|
|
185
|
+
throw new Error('CategoryPath must contain at least one valid category name');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let currentParentID: string | null = null;
|
|
189
|
+
let currentCategoryID: string | null = null;
|
|
190
|
+
|
|
191
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
192
|
+
const categoryName = pathParts[i];
|
|
193
|
+
|
|
194
|
+
// Look for existing category at this level
|
|
195
|
+
const existingCategory = await this.findCategoryByNameAndParent(categoryName, currentParentID, contextUser);
|
|
196
|
+
|
|
197
|
+
if (existingCategory) {
|
|
198
|
+
currentCategoryID = existingCategory.ID;
|
|
199
|
+
currentParentID = existingCategory.ID;
|
|
200
|
+
} else {
|
|
201
|
+
// Create new category
|
|
202
|
+
const newCategory = await md.GetEntityObject<QueryCategoryEntity>("Query Categories", contextUser);
|
|
203
|
+
newCategory.Name = categoryName;
|
|
204
|
+
newCategory.ParentID = currentParentID;
|
|
205
|
+
newCategory.UserID = contextUser.ID;
|
|
206
|
+
newCategory.Description = `Auto-created category from path: ${categoryPath}`;
|
|
207
|
+
|
|
208
|
+
const saveResult = await newCategory.Save();
|
|
209
|
+
if (!saveResult) {
|
|
210
|
+
throw new Error(`Failed to create category '${categoryName}': ${newCategory.LatestResult?.Message || 'Unknown error'}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
currentCategoryID = newCategory.ID;
|
|
214
|
+
currentParentID = newCategory.ID;
|
|
215
|
+
|
|
216
|
+
// Refresh metadata after each category creation to ensure it's available for subsequent lookups
|
|
217
|
+
await md.Refresh();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!currentCategoryID) {
|
|
222
|
+
throw new Error('Failed to determine final category ID');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return currentCategoryID;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Finds a category by name and parent ID using case-insensitive comparison via RunView.
|
|
230
|
+
* @param categoryName - Name of the category to find
|
|
231
|
+
* @param parentID - Parent category ID (null for root level)
|
|
232
|
+
* @param contextUser - User context for database operations
|
|
233
|
+
* @returns The matching category entity or null if not found
|
|
234
|
+
*/
|
|
235
|
+
private async findCategoryByNameAndParent(categoryName: string, parentID: string | null, contextUser: UserInfo): Promise<QueryCategoryEntity | null> {
|
|
236
|
+
try {
|
|
237
|
+
const rv = new RunView();
|
|
238
|
+
const parentFilter = parentID ? `ParentID='${parentID}'` : 'ParentID IS NULL';
|
|
239
|
+
const nameFilter = `LOWER(Name) = LOWER('${categoryName.replace(/'/g, "''")}')`; // Escape single quotes
|
|
240
|
+
|
|
241
|
+
const result = await rv.RunView<QueryCategoryEntity>({
|
|
242
|
+
EntityName: 'Query Categories',
|
|
243
|
+
ExtraFilter: `${nameFilter} AND ${parentFilter}`,
|
|
244
|
+
ResultType: 'entity_object'
|
|
245
|
+
}, contextUser);
|
|
246
|
+
|
|
247
|
+
if (result.Success && result.Results && result.Results.length > 0) {
|
|
248
|
+
return result.Results[0];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return null;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
LogError(error);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import { RunQuery } from '@memberjunction/core';
|
|
3
|
-
import { Arg, Ctx, Field, Int, ObjectType, Query, Resolver } from 'type-graphql';
|
|
1
|
+
import { Arg, Ctx, ObjectType, Query, Resolver, Field, Int } from 'type-graphql';
|
|
2
|
+
import { RunQuery, QueryInfo } from '@memberjunction/core';
|
|
4
3
|
import { AppContext } from '../types.js';
|
|
5
4
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
5
|
+
import { GraphQLJSONObject } from 'graphql-type-json';
|
|
6
|
+
import { QueryEntity } from '@memberjunction/core-entities';
|
|
7
|
+
import { Metadata } from '@memberjunction/core';
|
|
6
8
|
|
|
7
9
|
@ObjectType()
|
|
8
10
|
export class RunQueryResultType {
|
|
@@ -21,37 +23,104 @@ export class RunQueryResultType {
|
|
|
21
23
|
@Field()
|
|
22
24
|
RowCount: number;
|
|
23
25
|
|
|
26
|
+
@Field()
|
|
27
|
+
TotalRowCount: number;
|
|
28
|
+
|
|
24
29
|
@Field()
|
|
25
30
|
ExecutionTime: number;
|
|
26
31
|
|
|
27
32
|
@Field()
|
|
28
33
|
ErrorMessage: string;
|
|
34
|
+
|
|
35
|
+
@Field(() => String, { nullable: true })
|
|
36
|
+
AppliedParameters?: string;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
@Resolver(
|
|
32
|
-
export class
|
|
39
|
+
@Resolver()
|
|
40
|
+
export class RunQueryResolver {
|
|
41
|
+
private async findQuery(QueryID: string, QueryName?: string, CategoryID?: string, CategoryName?: string, refreshMetadataIfNotFound: boolean = false): Promise<QueryInfo | null> {
|
|
42
|
+
const md = new Metadata();
|
|
43
|
+
|
|
44
|
+
// Filter queries based on provided criteria
|
|
45
|
+
const queries = md.Queries.filter(q => {
|
|
46
|
+
if (QueryID) {
|
|
47
|
+
return q.ID.trim().toLowerCase() === QueryID.trim().toLowerCase();
|
|
48
|
+
} else if (QueryName) {
|
|
49
|
+
let matches = q.Name.trim().toLowerCase() === QueryName.trim().toLowerCase();
|
|
50
|
+
if (CategoryID) {
|
|
51
|
+
matches = matches && q.CategoryID?.trim().toLowerCase() === CategoryID.trim().toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
if (CategoryName) {
|
|
54
|
+
matches = matches && q.Category?.trim().toLowerCase() === CategoryName.trim().toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
return matches;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (queries.length === 0) {
|
|
62
|
+
if (refreshMetadataIfNotFound) {
|
|
63
|
+
// If we didn't find the query, refresh metadata and try again
|
|
64
|
+
await md.Refresh();
|
|
65
|
+
return this.findQuery(QueryID, QueryName, CategoryID, CategoryName, false); // change the refresh flag to false so we don't loop infinitely
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
return null; // No query found and not refreshing metadata
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
return queries[0];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
33
75
|
@Query(() => RunQueryResultType)
|
|
34
76
|
async GetQueryData(@Arg('QueryID', () => String) QueryID: string,
|
|
35
77
|
@Ctx() context: AppContext,
|
|
36
78
|
@Arg('CategoryID', () => String, {nullable: true}) CategoryID?: string,
|
|
37
|
-
@Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string
|
|
79
|
+
@Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string,
|
|
80
|
+
@Arg('Parameters', () => GraphQLJSONObject, {nullable: true}) Parameters?: Record<string, any>,
|
|
81
|
+
@Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
|
|
82
|
+
@Arg('StartRow', () => Int, {nullable: true}) StartRow?: number): Promise<RunQueryResultType> {
|
|
38
83
|
const runQuery = new RunQuery();
|
|
84
|
+
console.log('GetQueryData called with:', { QueryID, Parameters, MaxRows, StartRow });
|
|
39
85
|
const result = await runQuery.RunQuery(
|
|
40
86
|
{
|
|
41
87
|
QueryID: QueryID,
|
|
42
88
|
CategoryID: CategoryID,
|
|
43
|
-
CategoryName: CategoryName
|
|
89
|
+
CategoryName: CategoryName,
|
|
90
|
+
Parameters: Parameters,
|
|
91
|
+
MaxRows: MaxRows,
|
|
92
|
+
StartRow: StartRow
|
|
44
93
|
},
|
|
45
94
|
context.userPayload.userRecord);
|
|
95
|
+
console.log('RunQuery result:', {
|
|
96
|
+
Success: result.Success,
|
|
97
|
+
ErrorMessage: result.ErrorMessage,
|
|
98
|
+
AppliedParameters: result.AppliedParameters
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// If QueryName is not populated by the provider, use efficient lookup
|
|
102
|
+
let queryName = result.QueryName;
|
|
103
|
+
if (!queryName) {
|
|
104
|
+
try {
|
|
105
|
+
const queryInfo = await this.findQuery(QueryID, undefined, CategoryID, CategoryName, true);
|
|
106
|
+
if (queryInfo) {
|
|
107
|
+
queryName = queryInfo.Name;
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('Error finding query to get name:', error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
46
113
|
|
|
47
114
|
return {
|
|
48
115
|
QueryID: QueryID,
|
|
49
|
-
QueryName:
|
|
50
|
-
Success: result.Success,
|
|
51
|
-
Results: JSON.stringify(result.Results),
|
|
52
|
-
RowCount: result.RowCount,
|
|
53
|
-
|
|
54
|
-
|
|
116
|
+
QueryName: queryName || 'Unknown Query',
|
|
117
|
+
Success: result.Success ?? false,
|
|
118
|
+
Results: JSON.stringify(result.Results ?? null),
|
|
119
|
+
RowCount: result.RowCount ?? 0,
|
|
120
|
+
TotalRowCount: result.TotalRowCount ?? 0,
|
|
121
|
+
ExecutionTime: result.ExecutionTime ?? 0,
|
|
122
|
+
ErrorMessage: result.ErrorMessage || '',
|
|
123
|
+
AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined
|
|
55
124
|
};
|
|
56
125
|
}
|
|
57
126
|
|
|
@@ -59,24 +128,32 @@ export class ReportResolver {
|
|
|
59
128
|
async GetQueryDataByName(@Arg('QueryName', () => String) QueryName: string,
|
|
60
129
|
@Ctx() context: AppContext,
|
|
61
130
|
@Arg('CategoryID', () => String, {nullable: true}) CategoryID?: string,
|
|
62
|
-
@Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string
|
|
131
|
+
@Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string,
|
|
132
|
+
@Arg('Parameters', () => GraphQLJSONObject, {nullable: true}) Parameters?: Record<string, any>,
|
|
133
|
+
@Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
|
|
134
|
+
@Arg('StartRow', () => Int, {nullable: true}) StartRow?: number): Promise<RunQueryResultType> {
|
|
63
135
|
const runQuery = new RunQuery();
|
|
64
136
|
const result = await runQuery.RunQuery(
|
|
65
137
|
{
|
|
66
138
|
QueryName: QueryName,
|
|
67
139
|
CategoryID: CategoryID,
|
|
68
|
-
CategoryName: CategoryName
|
|
140
|
+
CategoryName: CategoryName,
|
|
141
|
+
Parameters: Parameters,
|
|
142
|
+
MaxRows: MaxRows,
|
|
143
|
+
StartRow: StartRow
|
|
69
144
|
},
|
|
70
145
|
context.userPayload.userRecord);
|
|
71
146
|
|
|
72
147
|
return {
|
|
73
|
-
QueryID: result.QueryID,
|
|
148
|
+
QueryID: result.QueryID || '',
|
|
74
149
|
QueryName: QueryName,
|
|
75
|
-
Success: result.Success,
|
|
76
|
-
Results: JSON.stringify(result.Results),
|
|
77
|
-
RowCount: result.RowCount,
|
|
78
|
-
|
|
79
|
-
|
|
150
|
+
Success: result.Success ?? false,
|
|
151
|
+
Results: JSON.stringify(result.Results ?? null),
|
|
152
|
+
RowCount: result.RowCount ?? 0,
|
|
153
|
+
TotalRowCount: result.TotalRowCount ?? 0,
|
|
154
|
+
ExecutionTime: result.ExecutionTime ?? 0,
|
|
155
|
+
ErrorMessage: result.ErrorMessage || '',
|
|
156
|
+
AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined
|
|
80
157
|
};
|
|
81
158
|
}
|
|
82
159
|
|
|
@@ -85,24 +162,45 @@ export class ReportResolver {
|
|
|
85
162
|
async GetQueryDataSystemUser(@Arg('QueryID', () => String) QueryID: string,
|
|
86
163
|
@Ctx() context: AppContext,
|
|
87
164
|
@Arg('CategoryID', () => String, {nullable: true}) CategoryID?: string,
|
|
88
|
-
@Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string
|
|
165
|
+
@Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string,
|
|
166
|
+
@Arg('Parameters', () => GraphQLJSONObject, {nullable: true}) Parameters?: Record<string, any>,
|
|
167
|
+
@Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
|
|
168
|
+
@Arg('StartRow', () => Int, {nullable: true}) StartRow?: number): Promise<RunQueryResultType> {
|
|
89
169
|
const runQuery = new RunQuery();
|
|
90
170
|
const result = await runQuery.RunQuery(
|
|
91
171
|
{
|
|
92
172
|
QueryID: QueryID,
|
|
93
173
|
CategoryID: CategoryID,
|
|
94
|
-
CategoryName: CategoryName
|
|
174
|
+
CategoryName: CategoryName,
|
|
175
|
+
Parameters: Parameters,
|
|
176
|
+
MaxRows: MaxRows,
|
|
177
|
+
StartRow: StartRow
|
|
95
178
|
},
|
|
96
179
|
context.userPayload.userRecord);
|
|
97
180
|
|
|
181
|
+
// If QueryName is not populated by the provider, use efficient lookup
|
|
182
|
+
let queryName = result.QueryName;
|
|
183
|
+
if (!queryName) {
|
|
184
|
+
try {
|
|
185
|
+
const queryInfo = await this.findQuery(QueryID, undefined, CategoryID, CategoryName, true);
|
|
186
|
+
if (queryInfo) {
|
|
187
|
+
queryName = queryInfo.Name;
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('Error finding query to get name:', error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
98
194
|
return {
|
|
99
195
|
QueryID: QueryID,
|
|
100
|
-
QueryName:
|
|
101
|
-
Success: result.Success,
|
|
102
|
-
Results: JSON.stringify(result.Results),
|
|
103
|
-
RowCount: result.RowCount,
|
|
104
|
-
|
|
105
|
-
|
|
196
|
+
QueryName: queryName || 'Unknown Query',
|
|
197
|
+
Success: result.Success ?? false,
|
|
198
|
+
Results: JSON.stringify(result.Results ?? null),
|
|
199
|
+
RowCount: result.RowCount ?? 0,
|
|
200
|
+
TotalRowCount: result.TotalRowCount ?? 0,
|
|
201
|
+
ExecutionTime: result.ExecutionTime ?? 0,
|
|
202
|
+
ErrorMessage: result.ErrorMessage || '',
|
|
203
|
+
AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined
|
|
106
204
|
};
|
|
107
205
|
}
|
|
108
206
|
|
|
@@ -111,24 +209,32 @@ export class ReportResolver {
|
|
|
111
209
|
async GetQueryDataByNameSystemUser(@Arg('QueryName', () => String) QueryName: string,
|
|
112
210
|
@Ctx() context: AppContext,
|
|
113
211
|
@Arg('CategoryID', () => String, {nullable: true}) CategoryID?: string,
|
|
114
|
-
@Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string
|
|
212
|
+
@Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string,
|
|
213
|
+
@Arg('Parameters', () => GraphQLJSONObject, {nullable: true}) Parameters?: Record<string, any>,
|
|
214
|
+
@Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
|
|
215
|
+
@Arg('StartRow', () => Int, {nullable: true}) StartRow?: number): Promise<RunQueryResultType> {
|
|
115
216
|
const runQuery = new RunQuery();
|
|
116
217
|
const result = await runQuery.RunQuery(
|
|
117
218
|
{
|
|
118
|
-
QueryName: QueryName,
|
|
219
|
+
QueryName: QueryName,
|
|
119
220
|
CategoryID: CategoryID,
|
|
120
|
-
CategoryName: CategoryName
|
|
121
|
-
|
|
221
|
+
CategoryName: CategoryName,
|
|
222
|
+
Parameters: Parameters,
|
|
223
|
+
MaxRows: MaxRows,
|
|
224
|
+
StartRow: StartRow
|
|
225
|
+
},
|
|
122
226
|
context.userPayload.userRecord);
|
|
123
|
-
|
|
227
|
+
|
|
124
228
|
return {
|
|
125
|
-
QueryID: result.QueryID,
|
|
229
|
+
QueryID: result.QueryID || '',
|
|
126
230
|
QueryName: QueryName,
|
|
127
|
-
Success: result.Success,
|
|
128
|
-
Results: JSON.stringify(result.Results),
|
|
129
|
-
RowCount: result.RowCount,
|
|
130
|
-
|
|
131
|
-
|
|
231
|
+
Success: result.Success ?? false,
|
|
232
|
+
Results: JSON.stringify(result.Results ?? null),
|
|
233
|
+
RowCount: result.RowCount ?? 0,
|
|
234
|
+
TotalRowCount: result.TotalRowCount ?? 0,
|
|
235
|
+
ExecutionTime: result.ExecutionTime ?? 0,
|
|
236
|
+
ErrorMessage: result.ErrorMessage || '',
|
|
237
|
+
AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined
|
|
132
238
|
};
|
|
133
239
|
}
|
|
134
|
-
}
|
|
240
|
+
}
|
|
@@ -62,7 +62,9 @@ export class RunAIPromptResolver extends ResolverBase {
|
|
|
62
62
|
@Arg('stopSequences', () => [String], { nullable: true }) stopSequences?: string[],
|
|
63
63
|
@Arg('includeLogProbs', { nullable: true }) includeLogProbs?: boolean,
|
|
64
64
|
@Arg('topLogProbs', () => Int, { nullable: true }) topLogProbs?: number,
|
|
65
|
-
@Arg('messages', { nullable: true }) messages?: string
|
|
65
|
+
@Arg('messages', { nullable: true }) messages?: string,
|
|
66
|
+
@Arg('rerunFromPromptRunID', { nullable: true }) rerunFromPromptRunID?: string,
|
|
67
|
+
@Arg('systemPromptOverride', { nullable: true }) systemPromptOverride?: string
|
|
66
68
|
): Promise<AIPromptRunResult> {
|
|
67
69
|
const startTime = Date.now();
|
|
68
70
|
|
|
@@ -141,6 +143,8 @@ export class RunAIPromptResolver extends ResolverBase {
|
|
|
141
143
|
promptParams.configurationId = configurationId;
|
|
142
144
|
promptParams.contextUser = currentUser;
|
|
143
145
|
promptParams.skipValidation = skipValidation || false;
|
|
146
|
+
promptParams.rerunFromPromptRunID = rerunFromPromptRunID;
|
|
147
|
+
promptParams.systemPromptOverride = systemPromptOverride;
|
|
144
148
|
|
|
145
149
|
// Set override if model or vendor ID provided
|
|
146
150
|
if (overrideModelId || overrideVendorId) {
|