@memberjunction/server 2.112.0 → 2.113.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/agents/skip-agent.d.ts +4 -4
- package/dist/agents/skip-agent.d.ts.map +1 -1
- package/dist/agents/skip-agent.js +951 -808
- package/dist/agents/skip-agent.js.map +1 -1
- package/dist/agents/skip-sdk.d.ts +1 -1
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +43 -53
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/apolloServer/index.js +1 -1
- package/dist/auth/AuthProviderFactory.d.ts +1 -1
- package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
- package/dist/auth/AuthProviderFactory.js +3 -1
- package/dist/auth/AuthProviderFactory.js.map +1 -1
- package/dist/auth/BaseAuthProvider.d.ts +1 -1
- package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
- package/dist/auth/BaseAuthProvider.js +2 -3
- package/dist/auth/BaseAuthProvider.js.map +1 -1
- package/dist/auth/IAuthProvider.d.ts +1 -1
- package/dist/auth/IAuthProvider.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +1 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +6 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.js +1 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/newUsers.d.ts +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +7 -7
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/auth/providers/Auth0Provider.d.ts +1 -1
- package/dist/auth/providers/Auth0Provider.d.ts.map +1 -1
- package/dist/auth/providers/Auth0Provider.js +1 -1
- package/dist/auth/providers/Auth0Provider.js.map +1 -1
- package/dist/auth/providers/CognitoProvider.d.ts +1 -1
- package/dist/auth/providers/CognitoProvider.d.ts.map +1 -1
- package/dist/auth/providers/CognitoProvider.js +6 -3
- package/dist/auth/providers/CognitoProvider.js.map +1 -1
- package/dist/auth/providers/GoogleProvider.d.ts +1 -1
- package/dist/auth/providers/GoogleProvider.d.ts.map +1 -1
- package/dist/auth/providers/GoogleProvider.js +1 -1
- package/dist/auth/providers/GoogleProvider.js.map +1 -1
- package/dist/auth/providers/MSALProvider.d.ts +1 -1
- package/dist/auth/providers/MSALProvider.d.ts.map +1 -1
- package/dist/auth/providers/MSALProvider.js +1 -1
- package/dist/auth/providers/MSALProvider.js.map +1 -1
- package/dist/auth/providers/OktaProvider.d.ts +1 -1
- package/dist/auth/providers/OktaProvider.d.ts.map +1 -1
- package/dist/auth/providers/OktaProvider.js +1 -1
- package/dist/auth/providers/OktaProvider.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -22
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +7 -9
- package/dist/context.js.map +1 -1
- package/dist/entitySubclasses/entityPermissions.server.d.ts +1 -1
- package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
- package/dist/entitySubclasses/entityPermissions.server.js +1 -1
- package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
- package/dist/generated/generated.d.ts +788 -658
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2050 -3054
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/KeyInputOutputTypes.d.ts +1 -1
- package/dist/generic/KeyInputOutputTypes.d.ts.map +1 -1
- package/dist/generic/KeyInputOutputTypes.js +1 -1
- package/dist/generic/KeyInputOutputTypes.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +10 -15
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +15 -15
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -18
- package/dist/index.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +2 -2
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +30 -28
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts +2 -2
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +50 -60
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +38 -36
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/CreateQueryResolver.js +40 -43
- package/dist/resolvers/CreateQueryResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +1 -1
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/EntityResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityResolver.js +1 -1
- package/dist/resolvers/EntityResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -1
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.js +1 -1
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +5 -5
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +6 -8
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +3 -3
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +3 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +11 -11
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/ReportResolver.js +1 -1
- package/dist/resolvers/ReportResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +28 -27
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +31 -31
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +9 -9
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +10 -10
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +14 -15
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +44 -48
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/TaskResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskResolver.js +7 -7
- package/dist/resolvers/TaskResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +12 -12
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.d.ts +1 -1
- package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +1 -1
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +16 -14
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +25 -23
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +21 -17
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
- package/dist/services/ScheduledJobsService.d.ts.map +1 -1
- package/dist/services/ScheduledJobsService.js +6 -4
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/dist/services/TaskOrchestrator.d.ts +1 -1
- package/dist/services/TaskOrchestrator.d.ts.map +1 -1
- package/dist/services/TaskOrchestrator.js +30 -30
- package/dist/services/TaskOrchestrator.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +2 -2
- package/dist/util.js.map +1 -1
- package/package.json +39 -36
- package/src/agents/skip-agent.ts +1200 -1067
- package/src/agents/skip-sdk.ts +851 -877
- package/src/apolloServer/index.ts +2 -2
- package/src/auth/AuthProviderFactory.ts +14 -8
- package/src/auth/BaseAuthProvider.ts +4 -5
- package/src/auth/IAuthProvider.ts +2 -2
- package/src/auth/exampleNewUserSubClass.ts +2 -9
- package/src/auth/index.ts +26 -31
- package/src/auth/initializeProviders.ts +3 -3
- package/src/auth/newUsers.ts +134 -166
- package/src/auth/providers/Auth0Provider.ts +5 -5
- package/src/auth/providers/CognitoProvider.ts +10 -7
- package/src/auth/providers/GoogleProvider.ts +5 -4
- package/src/auth/providers/MSALProvider.ts +5 -5
- package/src/auth/providers/OktaProvider.ts +7 -6
- package/src/config.ts +54 -63
- package/src/context.ts +30 -42
- package/src/entitySubclasses/entityPermissions.server.ts +3 -3
- package/src/generated/generated.ts +40442 -48106
- package/src/generic/KeyInputOutputTypes.ts +6 -3
- package/src/generic/ResolverBase.ts +78 -119
- package/src/generic/RunViewResolver.ts +23 -27
- package/src/index.ts +48 -66
- package/src/resolvers/ActionResolver.ts +57 -46
- package/src/resolvers/AskSkipResolver.ts +533 -607
- package/src/resolvers/ComponentRegistryResolver.ts +562 -547
- package/src/resolvers/CreateQueryResolver.ts +655 -683
- package/src/resolvers/DatasetResolver.ts +6 -5
- package/src/resolvers/EntityCommunicationsResolver.ts +1 -1
- package/src/resolvers/EntityRecordNameResolver.ts +5 -9
- package/src/resolvers/EntityResolver.ts +7 -9
- package/src/resolvers/FileCategoryResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +4 -4
- package/src/resolvers/GetDataContextDataResolver.ts +118 -106
- package/src/resolvers/GetDataResolver.ts +205 -194
- package/src/resolvers/MergeRecordsResolver.ts +5 -5
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
- package/src/resolvers/QueryResolver.ts +78 -95
- package/src/resolvers/ReportResolver.ts +2 -2
- package/src/resolvers/RunAIAgentResolver.ts +828 -818
- package/src/resolvers/RunAIPromptResolver.ts +709 -693
- package/src/resolvers/RunTemplateResolver.ts +103 -105
- package/src/resolvers/SqlLoggingConfigResolver.ts +72 -69
- package/src/resolvers/SyncDataResolver.ts +352 -386
- package/src/resolvers/SyncRolesUsersResolver.ts +350 -387
- package/src/resolvers/TaskResolver.ts +115 -110
- package/src/resolvers/TransactionGroupResolver.ts +138 -143
- package/src/resolvers/UserFavoriteResolver.ts +8 -17
- package/src/resolvers/UserViewResolver.ts +12 -17
- package/src/rest/EntityCRUDHandler.ts +268 -291
- package/src/rest/RESTEndpointHandler.ts +776 -782
- package/src/rest/ViewOperationsHandler.ts +195 -191
- package/src/scheduler/LearningCycleScheduler.ts +52 -8
- package/src/services/ScheduledJobsService.ts +132 -129
- package/src/services/TaskOrchestrator.ts +776 -792
- package/src/types.ts +9 -15
- package/src/util.ts +109 -112
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType, Resolver, PubSub, PubSubEngine } from 'type-graphql';
|
|
2
2
|
import { AppContext } from '../types.js';
|
|
3
|
-
import { LogError, RunView, UserInfo, CompositeKey, DatabaseProviderBase, LogStatus } from '@memberjunction/
|
|
3
|
+
import { LogError, RunView, UserInfo, CompositeKey, DatabaseProviderBase, LogStatus } from '@memberjunction/core';
|
|
4
4
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
5
5
|
import { QueryCategoryEntity, QueryPermissionEntity } from '@memberjunction/core-entities';
|
|
6
6
|
import { MJQueryResolver } from '../generated/generated.js';
|
|
@@ -12,827 +12,799 @@ import { QueryEntityExtended } from '@memberjunction/core-entities-server';
|
|
|
12
12
|
* Query status enumeration for GraphQL
|
|
13
13
|
*/
|
|
14
14
|
export enum QueryStatus {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
Pending = "Pending",
|
|
16
|
+
Approved = "Approved",
|
|
17
|
+
Rejected = "Rejected",
|
|
18
|
+
Expired = "Expired"
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
registerEnumType(QueryStatus, {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
name: "QueryStatus",
|
|
23
|
+
description: "Status of a query: Pending, Approved, Rejected, or Expired"
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
@InputType()
|
|
27
27
|
export class QueryPermissionInputType {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
@Field(() => String)
|
|
29
|
+
RoleID!: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
@InputType()
|
|
33
33
|
export class CreateQuerySystemUserInput {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
@Field(() => String)
|
|
35
|
+
Name!: string;
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
@Field(() => String, { nullable: true })
|
|
38
|
+
CategoryID?: string;
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
@Field(() => String, { nullable: true })
|
|
41
|
+
CategoryPath?: string;
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
@Field(() => String, { nullable: true })
|
|
44
|
+
UserQuestion?: string;
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
@Field(() => String, { nullable: true })
|
|
47
|
+
Description?: string;
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
@Field(() => String, { nullable: true })
|
|
50
|
+
SQL?: string;
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
@Field(() => String, { nullable: true })
|
|
53
|
+
TechnicalDescription?: string;
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
@Field(() => String, { nullable: true })
|
|
56
|
+
OriginalSQL?: string;
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
@Field(() => String, { nullable: true })
|
|
59
|
+
Feedback?: string;
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
@Field(() => QueryStatus, { nullable: true, defaultValue: QueryStatus.Pending })
|
|
62
|
+
Status?: QueryStatus;
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
@Field(() => Number, { nullable: true })
|
|
65
|
+
QualityRank?: number;
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
@Field(() => Number, { nullable: true })
|
|
68
|
+
ExecutionCostRank?: number;
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
@Field(() => Boolean, { nullable: true })
|
|
71
|
+
UsesTemplate?: boolean;
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
@Field(() => Boolean, { nullable: true })
|
|
74
|
+
AuditQueryRuns?: boolean;
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
@Field(() => Boolean, { nullable: true })
|
|
77
|
+
CacheEnabled?: boolean;
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
@Field(() => Number, { nullable: true })
|
|
80
|
+
CacheTTLMinutes?: number;
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
@Field(() => Number, { nullable: true })
|
|
83
|
+
CacheMaxSize?: number;
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
@Field(() => [QueryPermissionInputType], { nullable: true })
|
|
86
|
+
Permissions?: QueryPermissionInputType[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
@InputType()
|
|
90
90
|
export class UpdateQuerySystemUserInput {
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
@Field(() => String)
|
|
92
|
+
ID!: string;
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
@Field(() => String, { nullable: true })
|
|
95
|
+
Name?: string;
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
@Field(() => String, { nullable: true })
|
|
98
|
+
CategoryID?: string;
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
@Field(() => String, { nullable: true })
|
|
101
|
+
CategoryPath?: string;
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
@Field(() => String, { nullable: true })
|
|
104
|
+
UserQuestion?: string;
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
@Field(() => String, { nullable: true })
|
|
107
|
+
Description?: string;
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
@Field(() => String, { nullable: true })
|
|
110
|
+
SQL?: string;
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
@Field(() => String, { nullable: true })
|
|
113
|
+
TechnicalDescription?: string;
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
@Field(() => String, { nullable: true })
|
|
116
|
+
OriginalSQL?: string;
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
@Field(() => String, { nullable: true })
|
|
119
|
+
Feedback?: string;
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
@Field(() => QueryStatus, { nullable: true })
|
|
122
|
+
Status?: QueryStatus;
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
@Field(() => Number, { nullable: true })
|
|
125
|
+
QualityRank?: number;
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
@Field(() => Number, { nullable: true })
|
|
128
|
+
ExecutionCostRank?: number;
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
@Field(() => Boolean, { nullable: true })
|
|
131
|
+
UsesTemplate?: boolean;
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
@Field(() => Boolean, { nullable: true })
|
|
134
|
+
AuditQueryRuns?: boolean;
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
@Field(() => Boolean, { nullable: true })
|
|
137
|
+
CacheEnabled?: boolean;
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
@Field(() => Number, { nullable: true })
|
|
140
|
+
CacheTTLMinutes?: number;
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
@Field(() => Number, { nullable: true })
|
|
143
|
+
CacheMaxSize?: number;
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
@Field(() => [QueryPermissionInputType], { nullable: true })
|
|
146
|
+
Permissions?: QueryPermissionInputType[];
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
@ObjectType()
|
|
150
150
|
export class QueryFieldType {
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
@Field(() => String)
|
|
152
|
+
ID!: string;
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
@Field(() => String)
|
|
155
|
+
QueryID!: string;
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
@Field(() => String)
|
|
158
|
+
Name!: string;
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
@Field(() => String, { nullable: true })
|
|
161
|
+
Description?: string;
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
@Field(() => String, { nullable: true })
|
|
164
|
+
Type?: string;
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
@Field(() => Number)
|
|
167
|
+
Sequence!: number;
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
@Field(() => String, { nullable: true })
|
|
170
|
+
SQLBaseType?: string;
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
@Field(() => String, { nullable: true })
|
|
173
|
+
SQLFullType?: string;
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
@Field(() => Boolean)
|
|
176
|
+
IsComputed!: boolean;
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
@Field(() => String, { nullable: true })
|
|
179
|
+
ComputationDescription?: string;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
@ObjectType()
|
|
183
183
|
export class QueryParameterType {
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
@Field(() => String)
|
|
185
|
+
ID!: string;
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
@Field(() => String)
|
|
188
|
+
QueryID!: string;
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
@Field(() => String)
|
|
191
|
+
Name!: string;
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
@Field(() => String)
|
|
194
|
+
Type!: string;
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
@Field(() => String, { nullable: true })
|
|
197
|
+
DefaultValue?: string;
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
@Field(() => String, { nullable: true })
|
|
200
|
+
Comments?: string;
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
@Field(() => Boolean)
|
|
203
|
+
IsRequired!: boolean;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
@ObjectType()
|
|
207
207
|
export class QueryEntityType {
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
@Field(() => String)
|
|
209
|
+
ID!: string;
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
@Field(() => String)
|
|
212
|
+
QueryID!: string;
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
@Field(() => String)
|
|
215
|
+
EntityID!: string;
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
@Field(() => String, { nullable: true })
|
|
218
|
+
EntityName?: string;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
@ObjectType()
|
|
222
222
|
export class QueryPermissionType {
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
@Field(() => String)
|
|
224
|
+
ID!: string;
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
@Field(() => String)
|
|
227
|
+
QueryID!: string;
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
@Field(() => String)
|
|
230
|
+
RoleID!: string;
|
|
231
231
|
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
@Field(() => String, { nullable: true })
|
|
233
|
+
RoleName?: string;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
@ObjectType()
|
|
237
237
|
export class CreateQueryResultType {
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
@Field(() => Boolean)
|
|
239
|
+
Success!: boolean;
|
|
240
240
|
|
|
241
|
-
|
|
242
|
-
|
|
241
|
+
@Field(() => String, { nullable: true })
|
|
242
|
+
ErrorMessage?: string;
|
|
243
243
|
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
@Field(() => String, { nullable: true })
|
|
245
|
+
QueryData?: string;
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
@Field(() => [QueryFieldType], { nullable: true })
|
|
248
|
+
Fields?: QueryFieldType[];
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
@Field(() => [QueryParameterType], { nullable: true })
|
|
251
|
+
Parameters?: QueryParameterType[];
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
@Field(() => [QueryEntityType], { nullable: true })
|
|
254
|
+
Entities?: QueryEntityType[];
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
@Field(() => [QueryPermissionType], { nullable: true })
|
|
257
|
+
Permissions?: QueryPermissionType[];
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
@ObjectType()
|
|
261
261
|
export class UpdateQueryResultType {
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
@Field(() => Boolean)
|
|
263
|
+
Success!: boolean;
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
@Field(() => String, { nullable: true })
|
|
266
|
+
ErrorMessage?: string;
|
|
267
267
|
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
@Field(() => String, { nullable: true })
|
|
269
|
+
QueryData?: string;
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
@Field(() => [QueryFieldType], { nullable: true })
|
|
272
|
+
Fields?: QueryFieldType[];
|
|
273
273
|
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
@Field(() => [QueryParameterType], { nullable: true })
|
|
275
|
+
Parameters?: QueryParameterType[];
|
|
276
276
|
|
|
277
|
-
|
|
278
|
-
|
|
277
|
+
@Field(() => [QueryEntityType], { nullable: true })
|
|
278
|
+
Entities?: QueryEntityType[];
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
@Field(() => [QueryPermissionType], { nullable: true })
|
|
281
|
+
Permissions?: QueryPermissionType[];
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
@ObjectType()
|
|
285
285
|
export class DeleteQueryResultType {
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
@Field(() => Boolean)
|
|
287
|
+
Success!: boolean;
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
@Field(() => String, { nullable: true })
|
|
290
|
+
ErrorMessage?: string;
|
|
291
291
|
|
|
292
|
-
|
|
293
|
-
|
|
292
|
+
@Field(() => String, { nullable: true })
|
|
293
|
+
QueryData?: string;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
@Resolver()
|
|
297
297
|
export class MJQueryResolverExtended extends MJQueryResolver {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// Check for existing query with same name in the same category
|
|
320
|
-
const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
|
|
321
|
-
|
|
322
|
-
if (existingQuery) {
|
|
323
|
-
const categoryInfo = input.CategoryPath ? `category path '${input.CategoryPath}'` : `category ID '${finalCategoryID}'`;
|
|
324
|
-
return {
|
|
325
|
-
Success: false,
|
|
326
|
-
ErrorMessage: `Query with name '${input.Name}' already exists in ${categoryInfo}`,
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Use QueryEntityExtended which handles AI processing
|
|
331
|
-
const record = await provider.GetEntityObject<QueryEntityExtended>('Queries', context.userPayload.userRecord);
|
|
332
|
-
|
|
333
|
-
// Set the fields from input, handling CategoryPath resolution
|
|
334
|
-
const fieldsToSet = {
|
|
335
|
-
...input,
|
|
336
|
-
CategoryID: finalCategoryID || input.CategoryID,
|
|
337
|
-
Status: input.Status || 'Approved',
|
|
338
|
-
QualityRank: input.QualityRank || 0,
|
|
339
|
-
UsesTemplate: input.UsesTemplate || false,
|
|
340
|
-
AuditQueryRuns: input.AuditQueryRuns || false,
|
|
341
|
-
CacheEnabled: input.CacheEnabled || false,
|
|
342
|
-
CacheTTLMinutes: input.CacheTTLMinutes || null,
|
|
343
|
-
CacheMaxSize: input.CacheMaxSize || null,
|
|
344
|
-
};
|
|
345
|
-
// Remove non-database fields that we handle separately or are input-only
|
|
346
|
-
delete (fieldsToSet as any).Permissions; // Handled separately via createPermissions
|
|
347
|
-
delete (fieldsToSet as any).CategoryPath; // Input-only field, resolved to CategoryID
|
|
348
|
-
|
|
349
|
-
record.SetMany(fieldsToSet, true);
|
|
350
|
-
this.ListenForEntityMessages(record, pubSub, context.userPayload.userRecord);
|
|
351
|
-
|
|
352
|
-
// Attempt to save the query
|
|
353
|
-
const saveResult = await record.Save();
|
|
354
|
-
|
|
355
|
-
if (saveResult) {
|
|
356
|
-
// Save succeeded - fire the AfterCreate event and return all the data
|
|
357
|
-
await this.AfterCreate(provider, input); // fire event
|
|
358
|
-
const queryID = record.ID;
|
|
359
|
-
|
|
360
|
-
if (input.Permissions && input.Permissions.length > 0) {
|
|
361
|
-
await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
|
|
362
|
-
await record.RefreshRelatedMetadata(true); // force DB update since we just created new permissions
|
|
363
|
-
}
|
|
298
|
+
/**
|
|
299
|
+
* Creates a new query with the provided attributes. This mutation is restricted to system users only.
|
|
300
|
+
* @param input - CreateQuerySystemUserInput containing all the query attributes
|
|
301
|
+
* @param context - Application context containing user information
|
|
302
|
+
* @returns CreateQueryResultType with success status and query data
|
|
303
|
+
*/
|
|
304
|
+
@RequireSystemUser()
|
|
305
|
+
@Mutation(() => CreateQueryResultType)
|
|
306
|
+
async CreateQuerySystemUser(
|
|
307
|
+
@Arg('input', () => CreateQuerySystemUserInput) input: CreateQuerySystemUserInput,
|
|
308
|
+
@Ctx() context: AppContext,
|
|
309
|
+
@PubSub() pubSub: PubSubEngine
|
|
310
|
+
): Promise<CreateQueryResultType> {
|
|
311
|
+
try {
|
|
312
|
+
// Handle CategoryPath if provided
|
|
313
|
+
let finalCategoryID = input.CategoryID;
|
|
314
|
+
const provider = GetReadWriteProvider(context.providers);
|
|
315
|
+
if (input.CategoryPath) {
|
|
316
|
+
finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, provider, context.userPayload.userRecord);
|
|
317
|
+
}
|
|
364
318
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
319
|
+
// Check for existing query with same name in the same category
|
|
320
|
+
const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
|
|
321
|
+
|
|
322
|
+
if (existingQuery) {
|
|
323
|
+
const categoryInfo = input.CategoryPath ? `category path '${input.CategoryPath}'` : `category ID '${finalCategoryID}'`;
|
|
324
|
+
return {
|
|
325
|
+
Success: false,
|
|
326
|
+
ErrorMessage: `Query with name '${input.Name}' already exists in ${categoryInfo}`
|
|
327
|
+
};
|
|
328
|
+
}
|
|
368
329
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
330
|
+
// Use QueryEntityExtended which handles AI processing
|
|
331
|
+
const record = await provider.GetEntityObject<QueryEntityExtended>("Queries", context.userPayload.userRecord);
|
|
332
|
+
|
|
333
|
+
// Set the fields from input, handling CategoryPath resolution
|
|
334
|
+
const fieldsToSet = {
|
|
335
|
+
...input,
|
|
336
|
+
CategoryID: finalCategoryID || input.CategoryID,
|
|
337
|
+
Status: input.Status || 'Approved',
|
|
338
|
+
QualityRank: input.QualityRank || 0,
|
|
339
|
+
UsesTemplate: input.UsesTemplate || false,
|
|
340
|
+
AuditQueryRuns: input.AuditQueryRuns || false,
|
|
341
|
+
CacheEnabled: input.CacheEnabled || false,
|
|
342
|
+
CacheTTLMinutes: input.CacheTTLMinutes || null,
|
|
343
|
+
CacheMaxSize: input.CacheMaxSize || null
|
|
344
|
+
};
|
|
345
|
+
// Remove non-database fields that we handle separately or are input-only
|
|
346
|
+
delete (fieldsToSet as any).Permissions; // Handled separately via createPermissions
|
|
347
|
+
delete (fieldsToSet as any).CategoryPath; // Input-only field, resolved to CategoryID
|
|
348
|
+
|
|
349
|
+
record.SetMany(fieldsToSet, true);
|
|
350
|
+
this.ListenForEntityMessages(record, pubSub, context.userPayload.userRecord);
|
|
351
|
+
|
|
352
|
+
// Attempt to save the query
|
|
353
|
+
const saveResult = await record.Save();
|
|
354
|
+
|
|
355
|
+
if (saveResult) {
|
|
356
|
+
// Save succeeded - fire the AfterCreate event and return all the data
|
|
357
|
+
await this.AfterCreate(provider, input); // fire event
|
|
358
|
+
const queryID = record.ID;
|
|
359
|
+
|
|
360
|
+
if (input.Permissions && input.Permissions.length > 0) {
|
|
361
|
+
await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
|
|
362
|
+
await record.RefreshRelatedMetadata(true); // force DB update since we just created new permissions
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Refresh metadata cache to include the newly created query
|
|
366
|
+
// This ensures subsequent operations can find the query without additional DB calls
|
|
367
|
+
await provider.Refresh();
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
Success: true,
|
|
371
|
+
QueryData: JSON.stringify(record.GetAll()),
|
|
372
|
+
Fields: record.QueryFields,
|
|
373
|
+
Parameters: record.QueryParameters,
|
|
374
|
+
Entities: record.QueryEntities.map(e => {
|
|
375
|
+
return {
|
|
376
|
+
...e,
|
|
377
|
+
EntityName: e.Entity // alias this to fix variable name mismatch
|
|
378
|
+
}
|
|
379
|
+
}),
|
|
380
|
+
Permissions: record.QueryPermissions
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
// Save failed - check if another request created the same query (race condition)
|
|
385
|
+
// Always recheck regardless of error type to handle all duplicate scenarios
|
|
386
|
+
const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
|
|
387
|
+
|
|
388
|
+
if (existingQuery) {
|
|
389
|
+
// Found the query that was created by another request
|
|
390
|
+
// Return it as if we created it (it has the same name/category)
|
|
391
|
+
LogStatus(`[CreateQuery] Unique constraint detected for query '${input.Name}'. Using existing query (ID: ${existingQuery.ID}) created by concurrent request.`);
|
|
392
|
+
return {
|
|
393
|
+
Success: true,
|
|
394
|
+
QueryData: JSON.stringify(existingQuery),
|
|
395
|
+
Fields: existingQuery.Fields || [],
|
|
396
|
+
Parameters: existingQuery.Parameters || [],
|
|
397
|
+
Entities: existingQuery.Entities?.map(e => ({
|
|
398
|
+
ID: e.ID,
|
|
399
|
+
QueryID: e.QueryID,
|
|
400
|
+
EntityID: e.EntityID,
|
|
401
|
+
EntityName: e.Entity
|
|
402
|
+
})) || [],
|
|
403
|
+
Permissions: existingQuery.Permissions || []
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Genuine failure - couldn't find an existing query with the same name
|
|
408
|
+
const errorMessage = record.LatestResult?.Message || '';
|
|
409
|
+
return {
|
|
410
|
+
Success: false,
|
|
411
|
+
ErrorMessage: `Failed to create query: ${errorMessage || 'Unknown error'}`
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
LogError(err);
|
|
375
417
|
return {
|
|
376
|
-
|
|
377
|
-
|
|
418
|
+
Success: false,
|
|
419
|
+
ErrorMessage: `MJQueryResolverExtended::CreateQuerySystemUser --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
|
|
378
420
|
};
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
//
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
protected async createPermissions(p: DatabaseProviderBase, permissions: QueryPermissionInputType[], queryID: string, contextUser: UserInfo): Promise<QueryPermissionType[]> {
|
|
425
|
+
// Create permissions if provided
|
|
426
|
+
const createdPermissions: QueryPermissionType[] = [];
|
|
427
|
+
if (permissions && permissions.length > 0) {
|
|
428
|
+
for (const perm of permissions) {
|
|
429
|
+
const permissionEntity = await p.GetEntityObject<QueryPermissionEntity>('Query Permissions', contextUser);
|
|
430
|
+
if (permissionEntity) {
|
|
431
|
+
permissionEntity.QueryID = queryID;
|
|
432
|
+
permissionEntity.RoleID = perm.RoleID;
|
|
433
|
+
|
|
434
|
+
const saveResult = await permissionEntity.Save();
|
|
435
|
+
if (saveResult) {
|
|
436
|
+
createdPermissions.push({
|
|
437
|
+
ID: permissionEntity.ID,
|
|
438
|
+
QueryID: permissionEntity.QueryID,
|
|
439
|
+
RoleID: permissionEntity.RoleID,
|
|
440
|
+
RoleName: permissionEntity.Role // The view includes the Role name
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return createdPermissions;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Updates an existing query with the provided attributes. This mutation is restricted to system users only.
|
|
451
|
+
* @param input - UpdateQuerySystemUserInput containing the query ID and fields to update
|
|
452
|
+
* @param context - Application context containing user information
|
|
453
|
+
* @returns UpdateQueryResultType with success status and updated query data including related entities
|
|
454
|
+
*/
|
|
455
|
+
@RequireSystemUser()
|
|
456
|
+
@Mutation(() => UpdateQueryResultType)
|
|
457
|
+
async UpdateQuerySystemUser(
|
|
458
|
+
@Arg('input', () => UpdateQuerySystemUserInput) input: UpdateQuerySystemUserInput,
|
|
459
|
+
@Ctx() context: AppContext,
|
|
460
|
+
@PubSub() pubSub: PubSubEngine
|
|
461
|
+
): Promise<UpdateQueryResultType> {
|
|
462
|
+
try {
|
|
463
|
+
// Load the existing query using QueryEntityExtended
|
|
464
|
+
const provider = GetReadWriteProvider(context.providers);
|
|
465
|
+
const queryEntity = await provider.GetEntityObject<QueryEntityExtended>('Queries', context.userPayload.userRecord);
|
|
466
|
+
if (!queryEntity || !await queryEntity.Load(input.ID)) {
|
|
467
|
+
return {
|
|
468
|
+
Success: false,
|
|
469
|
+
ErrorMessage: `Query with ID ${input.ID} not found`
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Handle CategoryPath if provided
|
|
474
|
+
let finalCategoryID = input.CategoryID;
|
|
475
|
+
if (input.CategoryPath) {
|
|
476
|
+
finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, provider, context.userPayload.userRecord);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// now make sure there is NO existing query by the same name in the specified category
|
|
480
|
+
const existingQueryResult = await provider.RunView({
|
|
481
|
+
EntityName: 'Queries',
|
|
482
|
+
ExtraFilter: `Name='${input.Name}' AND CategoryID='${finalCategoryID}'`
|
|
483
|
+
}, context.userPayload.userRecord);
|
|
484
|
+
if (existingQueryResult.Success && existingQueryResult.Results?.length > 0) {
|
|
485
|
+
// we have a match! Let's return an error
|
|
486
|
+
return {
|
|
487
|
+
Success: false,
|
|
488
|
+
ErrorMessage: `Query with name '${input.Name}' already exists in the specified ${input.CategoryID ? 'category' : 'categoryPath'}`
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Update fields that were provided
|
|
493
|
+
const updateFields: Record<string, any> = {};
|
|
494
|
+
if (input.Name !== undefined) updateFields.Name = input.Name;
|
|
495
|
+
if (finalCategoryID !== undefined) updateFields.CategoryID = finalCategoryID;
|
|
496
|
+
if (input.UserQuestion !== undefined) updateFields.UserQuestion = input.UserQuestion;
|
|
497
|
+
if (input.Description !== undefined) updateFields.Description = input.Description;
|
|
498
|
+
if (input.SQL !== undefined) updateFields.SQL = input.SQL;
|
|
499
|
+
if (input.TechnicalDescription !== undefined) updateFields.TechnicalDescription = input.TechnicalDescription;
|
|
500
|
+
if (input.OriginalSQL !== undefined) updateFields.OriginalSQL = input.OriginalSQL;
|
|
501
|
+
if (input.Feedback !== undefined) updateFields.Feedback = input.Feedback;
|
|
502
|
+
if (input.Status !== undefined) updateFields.Status = input.Status;
|
|
503
|
+
if (input.QualityRank !== undefined) updateFields.QualityRank = input.QualityRank;
|
|
504
|
+
if (input.ExecutionCostRank !== undefined) updateFields.ExecutionCostRank = input.ExecutionCostRank;
|
|
505
|
+
if (input.UsesTemplate !== undefined) updateFields.UsesTemplate = input.UsesTemplate;
|
|
506
|
+
if (input.AuditQueryRuns !== undefined) updateFields.AuditQueryRuns = input.AuditQueryRuns;
|
|
507
|
+
if (input.CacheEnabled !== undefined) updateFields.CacheEnabled = input.CacheEnabled;
|
|
508
|
+
if (input.CacheTTLMinutes !== undefined) updateFields.CacheTTLMinutes = input.CacheTTLMinutes;
|
|
509
|
+
if (input.CacheMaxSize !== undefined) updateFields.CacheMaxSize = input.CacheMaxSize;
|
|
510
|
+
|
|
511
|
+
// Use SetMany to update all fields at once
|
|
512
|
+
queryEntity.SetMany(updateFields);
|
|
513
|
+
|
|
514
|
+
// Save the updated query
|
|
515
|
+
const saveResult = await queryEntity.Save();
|
|
516
|
+
if (!saveResult) {
|
|
517
|
+
return {
|
|
518
|
+
Success: false,
|
|
519
|
+
ErrorMessage: `Failed to update query: ${queryEntity.LatestResult?.Message || 'Unknown error'}`
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const queryID = queryEntity.ID;
|
|
524
|
+
|
|
525
|
+
// Handle permissions update if provided
|
|
526
|
+
if (input.Permissions !== undefined) {
|
|
527
|
+
// Delete existing permissions
|
|
528
|
+
const rv = new RunView();
|
|
529
|
+
const existingPermissions = await rv.RunView<QueryPermissionEntity>({
|
|
530
|
+
EntityName: 'Query Permissions',
|
|
531
|
+
ExtraFilter: `QueryID='${queryID}'`,
|
|
532
|
+
ResultType: 'entity_object'
|
|
533
|
+
}, context.userPayload.userRecord);
|
|
534
|
+
|
|
535
|
+
if (existingPermissions.Success && existingPermissions.Results) {
|
|
536
|
+
for (const perm of existingPermissions.Results) {
|
|
537
|
+
await perm.Delete();
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Create new permissions
|
|
542
|
+
await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
|
|
543
|
+
|
|
544
|
+
// Refresh the metadata to get updated permissions
|
|
545
|
+
await queryEntity.RefreshRelatedMetadata(true);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Use the properties from QueryEntityExtended instead of manual loading
|
|
549
|
+
const fields: QueryFieldType[] = queryEntity.QueryFields.map(f => ({
|
|
550
|
+
ID: f.ID,
|
|
551
|
+
QueryID: f.QueryID,
|
|
552
|
+
Name: f.Name,
|
|
553
|
+
Description: f.Description || undefined,
|
|
554
|
+
Type: f.SQLBaseType || undefined,
|
|
555
|
+
Sequence: f.Sequence,
|
|
556
|
+
SQLBaseType: f.SQLBaseType || undefined,
|
|
557
|
+
SQLFullType: f.SQLFullType || undefined,
|
|
558
|
+
IsComputed: f.IsComputed,
|
|
559
|
+
ComputationDescription: f.ComputationDescription || undefined
|
|
560
|
+
}));
|
|
561
|
+
|
|
562
|
+
const parameters: QueryParameterType[] = queryEntity.QueryParameters.map(p => ({
|
|
563
|
+
ID: p.ID,
|
|
564
|
+
QueryID: p.QueryID,
|
|
565
|
+
Name: p.Name,
|
|
566
|
+
Type: p.Type,
|
|
567
|
+
DefaultValue: p.DefaultValue || undefined,
|
|
568
|
+
Comments: '', // Not available in QueryParameterInfo
|
|
569
|
+
IsRequired: p.IsRequired
|
|
570
|
+
}));
|
|
571
|
+
|
|
572
|
+
const entities: QueryEntityType[] = queryEntity.QueryEntities.map(e => ({
|
|
400
573
|
ID: e.ID,
|
|
401
574
|
QueryID: e.QueryID,
|
|
402
575
|
EntityID: e.EntityID,
|
|
403
|
-
EntityName: e.Entity,
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
576
|
+
EntityName: e.Entity || undefined // Property is called Entity, not EntityName
|
|
577
|
+
}));
|
|
578
|
+
|
|
579
|
+
const permissions: QueryPermissionType[] = queryEntity.QueryPermissions.map(p => ({
|
|
580
|
+
ID: p.ID,
|
|
581
|
+
QueryID: p.QueryID,
|
|
582
|
+
RoleID: p.RoleID,
|
|
583
|
+
RoleName: p.Role || undefined // Property is called Role, not RoleName
|
|
584
|
+
}));
|
|
408
585
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
protected async createPermissions(
|
|
426
|
-
p: DatabaseProviderBase,
|
|
427
|
-
permissions: QueryPermissionInputType[],
|
|
428
|
-
queryID: string,
|
|
429
|
-
contextUser: UserInfo
|
|
430
|
-
): Promise<QueryPermissionType[]> {
|
|
431
|
-
// Create permissions if provided
|
|
432
|
-
const createdPermissions: QueryPermissionType[] = [];
|
|
433
|
-
if (permissions && permissions.length > 0) {
|
|
434
|
-
for (const perm of permissions) {
|
|
435
|
-
const permissionEntity = await p.GetEntityObject<QueryPermissionEntity>('Query Permissions', contextUser);
|
|
436
|
-
if (permissionEntity) {
|
|
437
|
-
permissionEntity.QueryID = queryID;
|
|
438
|
-
permissionEntity.RoleID = perm.RoleID;
|
|
439
|
-
|
|
440
|
-
const saveResult = await permissionEntity.Save();
|
|
441
|
-
if (saveResult) {
|
|
442
|
-
createdPermissions.push({
|
|
443
|
-
ID: permissionEntity.ID,
|
|
444
|
-
QueryID: permissionEntity.QueryID,
|
|
445
|
-
RoleID: permissionEntity.RoleID,
|
|
446
|
-
RoleName: permissionEntity.Role, // The view includes the Role name
|
|
447
|
-
});
|
|
448
|
-
}
|
|
586
|
+
return {
|
|
587
|
+
Success: true,
|
|
588
|
+
QueryData: JSON.stringify(queryEntity.GetAll()),
|
|
589
|
+
Fields: fields,
|
|
590
|
+
Parameters: parameters,
|
|
591
|
+
Entities: entities,
|
|
592
|
+
Permissions: permissions
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
} catch (err) {
|
|
596
|
+
LogError(err);
|
|
597
|
+
return {
|
|
598
|
+
Success: false,
|
|
599
|
+
ErrorMessage: `MJQueryResolverExtended::UpdateQuerySystemUser --- Error updating query: ${err instanceof Error ? err.message : String(err)}`
|
|
600
|
+
};
|
|
449
601
|
}
|
|
450
|
-
}
|
|
451
602
|
}
|
|
452
|
-
return createdPermissions;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Updates an existing query with the provided attributes. This mutation is restricted to system users only.
|
|
457
|
-
* @param input - UpdateQuerySystemUserInput containing the query ID and fields to update
|
|
458
|
-
* @param context - Application context containing user information
|
|
459
|
-
* @returns UpdateQueryResultType with success status and updated query data including related entities
|
|
460
|
-
*/
|
|
461
|
-
@RequireSystemUser()
|
|
462
|
-
@Mutation(() => UpdateQueryResultType)
|
|
463
|
-
async UpdateQuerySystemUser(
|
|
464
|
-
@Arg('input', () => UpdateQuerySystemUserInput) input: UpdateQuerySystemUserInput,
|
|
465
|
-
@Ctx() context: AppContext,
|
|
466
|
-
@PubSub() pubSub: PubSubEngine
|
|
467
|
-
): Promise<UpdateQueryResultType> {
|
|
468
|
-
try {
|
|
469
|
-
// Load the existing query using QueryEntityExtended
|
|
470
|
-
const provider = GetReadWriteProvider(context.providers);
|
|
471
|
-
const queryEntity = await provider.GetEntityObject<QueryEntityExtended>('Queries', context.userPayload.userRecord);
|
|
472
|
-
if (!queryEntity || !(await queryEntity.Load(input.ID))) {
|
|
473
|
-
return {
|
|
474
|
-
Success: false,
|
|
475
|
-
ErrorMessage: `Query with ID ${input.ID} not found`,
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Handle CategoryPath if provided
|
|
480
|
-
let finalCategoryID = input.CategoryID;
|
|
481
|
-
if (input.CategoryPath) {
|
|
482
|
-
finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, provider, context.userPayload.userRecord);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// now make sure there is NO existing query by the same name in the specified category
|
|
486
|
-
const existingQueryResult = await provider.RunView(
|
|
487
|
-
{
|
|
488
|
-
EntityName: 'Queries',
|
|
489
|
-
ExtraFilter: `Name='${input.Name}' AND CategoryID='${finalCategoryID}'`,
|
|
490
|
-
},
|
|
491
|
-
context.userPayload.userRecord
|
|
492
|
-
);
|
|
493
|
-
if (existingQueryResult.Success && existingQueryResult.Results?.length > 0) {
|
|
494
|
-
// we have a match! Let's return an error
|
|
495
|
-
return {
|
|
496
|
-
Success: false,
|
|
497
|
-
ErrorMessage: `Query with name '${input.Name}' already exists in the specified ${input.CategoryID ? 'category' : 'categoryPath'}`,
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Update fields that were provided
|
|
502
|
-
const updateFields: Record<string, any> = {};
|
|
503
|
-
if (input.Name !== undefined) updateFields.Name = input.Name;
|
|
504
|
-
if (finalCategoryID !== undefined) updateFields.CategoryID = finalCategoryID;
|
|
505
|
-
if (input.UserQuestion !== undefined) updateFields.UserQuestion = input.UserQuestion;
|
|
506
|
-
if (input.Description !== undefined) updateFields.Description = input.Description;
|
|
507
|
-
if (input.SQL !== undefined) updateFields.SQL = input.SQL;
|
|
508
|
-
if (input.TechnicalDescription !== undefined) updateFields.TechnicalDescription = input.TechnicalDescription;
|
|
509
|
-
if (input.OriginalSQL !== undefined) updateFields.OriginalSQL = input.OriginalSQL;
|
|
510
|
-
if (input.Feedback !== undefined) updateFields.Feedback = input.Feedback;
|
|
511
|
-
if (input.Status !== undefined) updateFields.Status = input.Status;
|
|
512
|
-
if (input.QualityRank !== undefined) updateFields.QualityRank = input.QualityRank;
|
|
513
|
-
if (input.ExecutionCostRank !== undefined) updateFields.ExecutionCostRank = input.ExecutionCostRank;
|
|
514
|
-
if (input.UsesTemplate !== undefined) updateFields.UsesTemplate = input.UsesTemplate;
|
|
515
|
-
if (input.AuditQueryRuns !== undefined) updateFields.AuditQueryRuns = input.AuditQueryRuns;
|
|
516
|
-
if (input.CacheEnabled !== undefined) updateFields.CacheEnabled = input.CacheEnabled;
|
|
517
|
-
if (input.CacheTTLMinutes !== undefined) updateFields.CacheTTLMinutes = input.CacheTTLMinutes;
|
|
518
|
-
if (input.CacheMaxSize !== undefined) updateFields.CacheMaxSize = input.CacheMaxSize;
|
|
519
|
-
|
|
520
|
-
// Use SetMany to update all fields at once
|
|
521
|
-
queryEntity.SetMany(updateFields);
|
|
522
|
-
|
|
523
|
-
// Save the updated query
|
|
524
|
-
const saveResult = await queryEntity.Save();
|
|
525
|
-
if (!saveResult) {
|
|
526
|
-
return {
|
|
527
|
-
Success: false,
|
|
528
|
-
ErrorMessage: `Failed to update query: ${queryEntity.LatestResult?.Message || 'Unknown error'}`,
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const queryID = queryEntity.ID;
|
|
533
|
-
|
|
534
|
-
// Handle permissions update if provided
|
|
535
|
-
if (input.Permissions !== undefined) {
|
|
536
|
-
// Delete existing permissions
|
|
537
|
-
const rv = new RunView();
|
|
538
|
-
const existingPermissions = await rv.RunView<QueryPermissionEntity>(
|
|
539
|
-
{
|
|
540
|
-
EntityName: 'Query Permissions',
|
|
541
|
-
ExtraFilter: `QueryID='${queryID}'`,
|
|
542
|
-
ResultType: 'entity_object',
|
|
543
|
-
},
|
|
544
|
-
context.userPayload.userRecord
|
|
545
|
-
);
|
|
546
|
-
|
|
547
|
-
if (existingPermissions.Success && existingPermissions.Results) {
|
|
548
|
-
for (const perm of existingPermissions.Results) {
|
|
549
|
-
await perm.Delete();
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
603
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
QueryID: p.QueryID,
|
|
577
|
-
Name: p.Name,
|
|
578
|
-
Type: p.Type,
|
|
579
|
-
DefaultValue: p.DefaultValue || undefined,
|
|
580
|
-
Comments: '', // Not available in QueryParameterInfo
|
|
581
|
-
IsRequired: p.IsRequired,
|
|
582
|
-
}));
|
|
583
|
-
|
|
584
|
-
const entities: QueryEntityType[] = queryEntity.QueryEntities.map((e) => ({
|
|
585
|
-
ID: e.ID,
|
|
586
|
-
QueryID: e.QueryID,
|
|
587
|
-
EntityID: e.EntityID,
|
|
588
|
-
EntityName: e.Entity || undefined, // Property is called Entity, not EntityName
|
|
589
|
-
}));
|
|
590
|
-
|
|
591
|
-
const permissions: QueryPermissionType[] = queryEntity.QueryPermissions.map((p) => ({
|
|
592
|
-
ID: p.ID,
|
|
593
|
-
QueryID: p.QueryID,
|
|
594
|
-
RoleID: p.RoleID,
|
|
595
|
-
RoleName: p.Role || undefined, // Property is called Role, not RoleName
|
|
596
|
-
}));
|
|
597
|
-
|
|
598
|
-
return {
|
|
599
|
-
Success: true,
|
|
600
|
-
QueryData: JSON.stringify(queryEntity.GetAll()),
|
|
601
|
-
Fields: fields,
|
|
602
|
-
Parameters: parameters,
|
|
603
|
-
Entities: entities,
|
|
604
|
-
Permissions: permissions,
|
|
605
|
-
};
|
|
606
|
-
} catch (err) {
|
|
607
|
-
LogError(err);
|
|
608
|
-
return {
|
|
609
|
-
Success: false,
|
|
610
|
-
ErrorMessage: `MJQueryResolverExtended::UpdateQuerySystemUser --- Error updating query: ${err instanceof Error ? err.message : String(err)}`,
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/**
|
|
616
|
-
* Deletes a query by ID. This mutation is restricted to system users only.
|
|
617
|
-
* @param ID - The ID of the query to delete
|
|
618
|
-
* @param options - Delete options controlling action execution
|
|
619
|
-
* @param context - Application context containing user information
|
|
620
|
-
* @returns DeleteQueryResultType with success status and deleted query data
|
|
621
|
-
*/
|
|
622
|
-
@RequireSystemUser()
|
|
623
|
-
@Mutation(() => DeleteQueryResultType)
|
|
624
|
-
async DeleteQuerySystemResolver(
|
|
625
|
-
@Arg('ID', () => String) ID: string,
|
|
626
|
-
@Arg('options', () => DeleteOptionsInput, { nullable: true }) options: DeleteOptionsInput | null,
|
|
627
|
-
@Ctx() context: AppContext,
|
|
628
|
-
@PubSub() pubSub: PubSubEngine
|
|
629
|
-
): Promise<DeleteQueryResultType> {
|
|
630
|
-
try {
|
|
631
|
-
// Validate ID is not null/undefined/empty
|
|
632
|
-
if (!ID || ID.trim() === '') {
|
|
633
|
-
return {
|
|
634
|
-
Success: false,
|
|
635
|
-
ErrorMessage: 'MJQueryResolverExtended::DeleteQuerySystemResolver --- Invalid query ID: ID cannot be null or empty',
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const provider = GetReadWriteProvider(context.providers);
|
|
640
|
-
const key = new CompositeKey([{ FieldName: 'ID', Value: ID }]);
|
|
641
|
-
|
|
642
|
-
// Provide default options if none provided
|
|
643
|
-
const deleteOptions = options || {
|
|
644
|
-
SkipEntityAIActions: false,
|
|
645
|
-
SkipEntityActions: false,
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
// Use inherited DeleteRecord method from ResolverBase
|
|
649
|
-
const deletedQuery = await this.DeleteRecord('Queries', key, deleteOptions, provider, context.userPayload, pubSub);
|
|
650
|
-
|
|
651
|
-
if (deletedQuery) {
|
|
652
|
-
return {
|
|
653
|
-
Success: true,
|
|
654
|
-
QueryData: JSON.stringify(deletedQuery),
|
|
655
|
-
};
|
|
656
|
-
} else {
|
|
657
|
-
return {
|
|
658
|
-
Success: false,
|
|
659
|
-
ErrorMessage: 'Failed to delete query using DeleteRecord method',
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
} catch (err) {
|
|
663
|
-
LogError(err);
|
|
664
|
-
return {
|
|
665
|
-
Success: false,
|
|
666
|
-
ErrorMessage: `MJQueryResolverExtended::DeleteQuerySystemResolver --- Error deleting query: ${err instanceof Error ? err.message : String(err)}`,
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
/**
|
|
672
|
-
* Finds or creates a category hierarchy based on the provided path.
|
|
673
|
-
* Path format: "Parent/Child/Grandchild" - case insensitive lookup and creation.
|
|
674
|
-
* @param categoryPath - Slash-separated category path
|
|
675
|
-
* @param md - Metadata instance
|
|
676
|
-
* @param contextUser - User context for operations
|
|
677
|
-
* @returns The ID of the final category in the path
|
|
678
|
-
*/
|
|
679
|
-
private async findOrCreateCategoryPath(categoryPath: string, p: DatabaseProviderBase, contextUser: UserInfo): Promise<string> {
|
|
680
|
-
if (!categoryPath || categoryPath.trim() === '') {
|
|
681
|
-
throw new Error('CategoryPath cannot be empty');
|
|
682
|
-
}
|
|
604
|
+
/**
|
|
605
|
+
* Deletes a query by ID. This mutation is restricted to system users only.
|
|
606
|
+
* @param ID - The ID of the query to delete
|
|
607
|
+
* @param options - Delete options controlling action execution
|
|
608
|
+
* @param context - Application context containing user information
|
|
609
|
+
* @returns DeleteQueryResultType with success status and deleted query data
|
|
610
|
+
*/
|
|
611
|
+
@RequireSystemUser()
|
|
612
|
+
@Mutation(() => DeleteQueryResultType)
|
|
613
|
+
async DeleteQuerySystemResolver(
|
|
614
|
+
@Arg('ID', () => String) ID: string,
|
|
615
|
+
@Arg('options', () => DeleteOptionsInput, { nullable: true }) options: DeleteOptionsInput | null,
|
|
616
|
+
@Ctx() context: AppContext,
|
|
617
|
+
@PubSub() pubSub: PubSubEngine
|
|
618
|
+
): Promise<DeleteQueryResultType> {
|
|
619
|
+
try {
|
|
620
|
+
// Validate ID is not null/undefined/empty
|
|
621
|
+
if (!ID || ID.trim() === '') {
|
|
622
|
+
return {
|
|
623
|
+
Success: false,
|
|
624
|
+
ErrorMessage: 'MJQueryResolverExtended::DeleteQuerySystemResolver --- Invalid query ID: ID cannot be null or empty'
|
|
625
|
+
};
|
|
626
|
+
}
|
|
683
627
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
628
|
+
const provider = GetReadWriteProvider(context.providers);
|
|
629
|
+
const key = new CompositeKey([{FieldName: 'ID', Value: ID}]);
|
|
630
|
+
|
|
631
|
+
// Provide default options if none provided
|
|
632
|
+
const deleteOptions = options || {
|
|
633
|
+
SkipEntityAIActions: false,
|
|
634
|
+
SkipEntityActions: false
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// Use inherited DeleteRecord method from ResolverBase
|
|
638
|
+
const deletedQuery = await this.DeleteRecord('Queries', key, deleteOptions, provider, context.userPayload, pubSub);
|
|
639
|
+
|
|
640
|
+
if (deletedQuery) {
|
|
641
|
+
return {
|
|
642
|
+
Success: true,
|
|
643
|
+
QueryData: JSON.stringify(deletedQuery)
|
|
644
|
+
};
|
|
645
|
+
} else {
|
|
646
|
+
return {
|
|
647
|
+
Success: false,
|
|
648
|
+
ErrorMessage: 'Failed to delete query using DeleteRecord method'
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
} catch (err) {
|
|
653
|
+
LogError(err);
|
|
654
|
+
return {
|
|
655
|
+
Success: false,
|
|
656
|
+
ErrorMessage: `MJQueryResolverExtended::DeleteQuerySystemResolver --- Error deleting query: ${err instanceof Error ? err.message : String(err)}`
|
|
657
|
+
};
|
|
658
|
+
}
|
|
690
659
|
}
|
|
691
660
|
|
|
692
|
-
|
|
693
|
-
|
|
661
|
+
/**
|
|
662
|
+
* Finds or creates a category hierarchy based on the provided path.
|
|
663
|
+
* Path format: "Parent/Child/Grandchild" - case insensitive lookup and creation.
|
|
664
|
+
* @param categoryPath - Slash-separated category path
|
|
665
|
+
* @param md - Metadata instance
|
|
666
|
+
* @param contextUser - User context for operations
|
|
667
|
+
* @returns The ID of the final category in the path
|
|
668
|
+
*/
|
|
669
|
+
private async findOrCreateCategoryPath(categoryPath: string, p: DatabaseProviderBase, contextUser: UserInfo): Promise<string> {
|
|
670
|
+
if (!categoryPath || categoryPath.trim() === '') {
|
|
671
|
+
throw new Error('CategoryPath cannot be empty');
|
|
672
|
+
}
|
|
694
673
|
|
|
695
|
-
|
|
696
|
-
|
|
674
|
+
const pathParts = categoryPath.split('/').map(part => part.trim()).filter(part => part.length > 0);
|
|
675
|
+
if (pathParts.length === 0) {
|
|
676
|
+
throw new Error('CategoryPath must contain at least one valid category name');
|
|
677
|
+
}
|
|
697
678
|
|
|
698
|
-
|
|
699
|
-
|
|
679
|
+
let currentParentID: string | null = null;
|
|
680
|
+
let currentCategoryID: string | null = null;
|
|
681
|
+
|
|
682
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
683
|
+
const categoryName = pathParts[i];
|
|
684
|
+
|
|
685
|
+
// Look for existing category at this level
|
|
686
|
+
const existingCategory = await this.findCategoryByNameAndParent(p, categoryName, currentParentID, contextUser);
|
|
687
|
+
|
|
688
|
+
if (existingCategory) {
|
|
689
|
+
currentCategoryID = existingCategory.ID;
|
|
690
|
+
currentParentID = existingCategory.ID;
|
|
691
|
+
} else {
|
|
692
|
+
try {
|
|
693
|
+
// Create new category
|
|
694
|
+
const newCategory = await p.GetEntityObject<QueryCategoryEntity>("Query Categories", contextUser);
|
|
695
|
+
if (!newCategory) {
|
|
696
|
+
throw new Error(`Failed to create entity object for Query Categories`);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
newCategory.Name = categoryName;
|
|
700
|
+
newCategory.ParentID = currentParentID;
|
|
701
|
+
newCategory.UserID = contextUser.ID;
|
|
702
|
+
newCategory.Description = `Auto-created category from path: ${categoryPath}`;
|
|
703
|
+
|
|
704
|
+
const saveResult = await newCategory.Save();
|
|
705
|
+
if (!saveResult) {
|
|
706
|
+
// Save failed - always recheck if another request created the same category
|
|
707
|
+
const recheckExisting = await this.findCategoryByNameAndParent(p, categoryName, currentParentID, contextUser);
|
|
708
|
+
if (recheckExisting) {
|
|
709
|
+
// Another request created it - use that one
|
|
710
|
+
LogStatus(`[CreateQuery] Unique constraint detected for category '${categoryName}'. Using existing category (ID: ${recheckExisting.ID}) created by concurrent request.`);
|
|
711
|
+
currentCategoryID = recheckExisting.ID;
|
|
712
|
+
currentParentID = recheckExisting.ID;
|
|
713
|
+
} else {
|
|
714
|
+
// Genuine failure (not a duplicate)
|
|
715
|
+
const errorMessage = newCategory.LatestResult?.Message || '';
|
|
716
|
+
throw new Error(`Failed to create category '${categoryName}': ${errorMessage || 'Unknown error'}`);
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
currentCategoryID = newCategory.ID;
|
|
720
|
+
currentParentID = newCategory.ID;
|
|
721
|
+
}
|
|
722
|
+
} catch (error) {
|
|
723
|
+
// On error, double-check if category exists (race condition handling)
|
|
724
|
+
const recheckExisting = await this.findCategoryByNameAndParent(p, categoryName, currentParentID, contextUser);
|
|
725
|
+
if (recheckExisting) {
|
|
726
|
+
// Category exists, another request created it
|
|
727
|
+
LogStatus(`[CreateQuery] Exception during category creation for '${categoryName}'. Using existing category (ID: ${recheckExisting.ID}) created by concurrent request.`);
|
|
728
|
+
currentCategoryID = recheckExisting.ID;
|
|
729
|
+
currentParentID = recheckExisting.ID;
|
|
730
|
+
} else {
|
|
731
|
+
throw new Error(`Failed to create category '${categoryName}': ${error instanceof Error ? error.message : String(error)}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (!currentCategoryID) {
|
|
738
|
+
throw new Error('Failed to determine final category ID');
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return currentCategoryID;
|
|
742
|
+
}
|
|
700
743
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
744
|
+
/**
|
|
745
|
+
* Finds an existing query by name and category ID using RunView.
|
|
746
|
+
* Bypasses metadata cache to ensure we get the latest data from database.
|
|
747
|
+
* @param provider - Database provider
|
|
748
|
+
* @param queryName - Name of the query to find
|
|
749
|
+
* @param categoryID - Category ID (can be null)
|
|
750
|
+
* @param contextUser - User context for database operations
|
|
751
|
+
* @returns The matching query info or null if not found
|
|
752
|
+
*/
|
|
753
|
+
private async findExistingQuery(
|
|
754
|
+
provider: DatabaseProviderBase,
|
|
755
|
+
queryName: string,
|
|
756
|
+
categoryID: string | null,
|
|
757
|
+
contextUser: UserInfo
|
|
758
|
+
): Promise<any | null> {
|
|
705
759
|
try {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
const saveResult = await newCategory.Save();
|
|
718
|
-
if (!saveResult) {
|
|
719
|
-
// Save failed - always recheck if another request created the same category
|
|
720
|
-
const recheckExisting = await this.findCategoryByNameAndParent(p, categoryName, currentParentID, contextUser);
|
|
721
|
-
if (recheckExisting) {
|
|
722
|
-
// Another request created it - use that one
|
|
723
|
-
LogStatus(
|
|
724
|
-
`[CreateQuery] Unique constraint detected for category '${categoryName}'. Using existing category (ID: ${recheckExisting.ID}) created by concurrent request.`
|
|
725
|
-
);
|
|
726
|
-
currentCategoryID = recheckExisting.ID;
|
|
727
|
-
currentParentID = recheckExisting.ID;
|
|
728
|
-
} else {
|
|
729
|
-
// Genuine failure (not a duplicate)
|
|
730
|
-
const errorMessage = newCategory.LatestResult?.Message || '';
|
|
731
|
-
throw new Error(`Failed to create category '${categoryName}': ${errorMessage || 'Unknown error'}`);
|
|
760
|
+
// Query database directly to avoid cache staleness issues
|
|
761
|
+
const categoryFilter = categoryID ? `CategoryID='${categoryID}'` : 'CategoryID IS NULL';
|
|
762
|
+
const nameFilter = `LOWER(Name) = LOWER('${queryName.replace(/'/g, "''")}')`;
|
|
763
|
+
|
|
764
|
+
const result = await provider.RunView({
|
|
765
|
+
EntityName: 'Queries',
|
|
766
|
+
ExtraFilter: `${nameFilter} AND ${categoryFilter}`
|
|
767
|
+
}, contextUser);
|
|
768
|
+
|
|
769
|
+
if (result.Success && result.Results && result.Results.length > 0) {
|
|
770
|
+
return result.Results[0];
|
|
732
771
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
currentParentID = newCategory.ID;
|
|
736
|
-
}
|
|
772
|
+
|
|
773
|
+
return null;
|
|
737
774
|
} catch (error) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (recheckExisting) {
|
|
741
|
-
// Category exists, another request created it
|
|
742
|
-
LogStatus(
|
|
743
|
-
`[CreateQuery] Exception during category creation for '${categoryName}'. Using existing category (ID: ${recheckExisting.ID}) created by concurrent request.`
|
|
744
|
-
);
|
|
745
|
-
currentCategoryID = recheckExisting.ID;
|
|
746
|
-
currentParentID = recheckExisting.ID;
|
|
747
|
-
} else {
|
|
748
|
-
throw new Error(`Failed to create category '${categoryName}': ${error instanceof Error ? error.message : String(error)}`);
|
|
749
|
-
}
|
|
775
|
+
// If query fails, return null (query doesn't exist)
|
|
776
|
+
return null;
|
|
750
777
|
}
|
|
751
|
-
}
|
|
752
778
|
}
|
|
753
779
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
780
|
+
/**
|
|
781
|
+
* Finds a category by name and parent ID using RunView.
|
|
782
|
+
* Bypasses metadata cache to ensure we get the latest data from database.
|
|
783
|
+
* @param categoryName - Name of the category to find
|
|
784
|
+
* @param parentID - Parent category ID (null for root level)
|
|
785
|
+
* @param contextUser - User context for database operations
|
|
786
|
+
* @returns The matching category entity or null if not found
|
|
787
|
+
*/
|
|
788
|
+
private async findCategoryByNameAndParent(provider: DatabaseProviderBase, categoryName: string, parentID: string | null, contextUser: UserInfo): Promise<QueryCategoryEntity | null> {
|
|
789
|
+
try {
|
|
790
|
+
// Query database directly to avoid cache staleness issues
|
|
791
|
+
const parentFilter = parentID ? `ParentID='${parentID}'` : 'ParentID IS NULL';
|
|
792
|
+
const nameFilter = `LOWER(Name) = LOWER('${categoryName.replace(/'/g, "''")}')`; // Escape single quotes
|
|
793
|
+
|
|
794
|
+
const result = await provider.RunView<QueryCategoryEntity>({
|
|
795
|
+
EntityName: 'Query Categories',
|
|
796
|
+
ExtraFilter: `${nameFilter} AND ${parentFilter}`,
|
|
797
|
+
ResultType: 'entity_object'
|
|
798
|
+
}, contextUser);
|
|
799
|
+
|
|
800
|
+
if (result.Success && result.Results && result.Results.length > 0) {
|
|
801
|
+
return result.Results[0];
|
|
802
|
+
}
|
|
757
803
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
* Bypasses metadata cache to ensure we get the latest data from database.
|
|
764
|
-
* @param provider - Database provider
|
|
765
|
-
* @param queryName - Name of the query to find
|
|
766
|
-
* @param categoryID - Category ID (can be null)
|
|
767
|
-
* @param contextUser - User context for database operations
|
|
768
|
-
* @returns The matching query info or null if not found
|
|
769
|
-
*/
|
|
770
|
-
private async findExistingQuery(
|
|
771
|
-
provider: DatabaseProviderBase,
|
|
772
|
-
queryName: string,
|
|
773
|
-
categoryID: string | null,
|
|
774
|
-
contextUser: UserInfo
|
|
775
|
-
): Promise<any | null> {
|
|
776
|
-
try {
|
|
777
|
-
// Query database directly to avoid cache staleness issues
|
|
778
|
-
const categoryFilter = categoryID ? `CategoryID='${categoryID}'` : 'CategoryID IS NULL';
|
|
779
|
-
const nameFilter = `LOWER(Name) = LOWER('${queryName.replace(/'/g, "''")}')`;
|
|
780
|
-
|
|
781
|
-
const result = await provider.RunView(
|
|
782
|
-
{
|
|
783
|
-
EntityName: 'Queries',
|
|
784
|
-
ExtraFilter: `${nameFilter} AND ${categoryFilter}`,
|
|
785
|
-
},
|
|
786
|
-
contextUser
|
|
787
|
-
);
|
|
788
|
-
|
|
789
|
-
if (result.Success && result.Results && result.Results.length > 0) {
|
|
790
|
-
return result.Results[0];
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
return null;
|
|
794
|
-
} catch (error) {
|
|
795
|
-
// If query fails, return null (query doesn't exist)
|
|
796
|
-
return null;
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Finds a category by name and parent ID using RunView.
|
|
802
|
-
* Bypasses metadata cache to ensure we get the latest data from database.
|
|
803
|
-
* @param categoryName - Name of the category to find
|
|
804
|
-
* @param parentID - Parent category ID (null for root level)
|
|
805
|
-
* @param contextUser - User context for database operations
|
|
806
|
-
* @returns The matching category entity or null if not found
|
|
807
|
-
*/
|
|
808
|
-
private async findCategoryByNameAndParent(
|
|
809
|
-
provider: DatabaseProviderBase,
|
|
810
|
-
categoryName: string,
|
|
811
|
-
parentID: string | null,
|
|
812
|
-
contextUser: UserInfo
|
|
813
|
-
): Promise<QueryCategoryEntity | null> {
|
|
814
|
-
try {
|
|
815
|
-
// Query database directly to avoid cache staleness issues
|
|
816
|
-
const parentFilter = parentID ? `ParentID='${parentID}'` : 'ParentID IS NULL';
|
|
817
|
-
const nameFilter = `LOWER(Name) = LOWER('${categoryName.replace(/'/g, "''")}')`; // Escape single quotes
|
|
818
|
-
|
|
819
|
-
const result = await provider.RunView<QueryCategoryEntity>(
|
|
820
|
-
{
|
|
821
|
-
EntityName: 'Query Categories',
|
|
822
|
-
ExtraFilter: `${nameFilter} AND ${parentFilter}`,
|
|
823
|
-
ResultType: 'entity_object',
|
|
824
|
-
},
|
|
825
|
-
contextUser
|
|
826
|
-
);
|
|
827
|
-
|
|
828
|
-
if (result.Success && result.Results && result.Results.length > 0) {
|
|
829
|
-
return result.Results[0];
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
return null;
|
|
833
|
-
} catch (error) {
|
|
834
|
-
// If query fails, return null
|
|
835
|
-
return null;
|
|
804
|
+
return null;
|
|
805
|
+
} catch (error) {
|
|
806
|
+
// If query fails, return null
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
836
809
|
}
|
|
837
|
-
|
|
838
|
-
}
|
|
810
|
+
}
|