@memberjunction/server 2.111.0 → 2.112.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 +808 -951
- 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 +53 -43
- 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 +1 -3
- 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 +3 -2
- 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 +3 -6
- 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 +22 -10
- 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 +9 -7
- 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 +648 -648
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2986 -1133
- 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 +15 -10
- 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 +18 -9
- 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 +28 -30
- 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 +60 -50
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +36 -38
- 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 +43 -40
- 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 +8 -6
- 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 +27 -28
- 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 +15 -14
- 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 +48 -44
- 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 +14 -16
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +23 -25
- 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 +17 -21
- 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 +4 -6
- 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 +0 -1
- 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 +36 -37
- package/src/agents/skip-agent.ts +1067 -1200
- package/src/agents/skip-sdk.ts +877 -851
- package/src/apolloServer/index.ts +2 -2
- package/src/auth/AuthProviderFactory.ts +8 -14
- package/src/auth/BaseAuthProvider.ts +5 -4
- package/src/auth/IAuthProvider.ts +2 -2
- package/src/auth/exampleNewUserSubClass.ts +9 -2
- package/src/auth/index.ts +31 -26
- package/src/auth/initializeProviders.ts +3 -3
- package/src/auth/newUsers.ts +166 -134
- package/src/auth/providers/Auth0Provider.ts +5 -5
- package/src/auth/providers/CognitoProvider.ts +7 -10
- package/src/auth/providers/GoogleProvider.ts +4 -5
- package/src/auth/providers/MSALProvider.ts +5 -5
- package/src/auth/providers/OktaProvider.ts +6 -7
- package/src/config.ts +63 -54
- package/src/context.ts +42 -30
- package/src/entitySubclasses/entityPermissions.server.ts +3 -3
- package/src/generated/generated.ts +48130 -39930
- package/src/generic/KeyInputOutputTypes.ts +3 -6
- package/src/generic/ResolverBase.ts +119 -78
- package/src/generic/RunViewResolver.ts +27 -23
- package/src/index.ts +66 -42
- package/src/resolvers/ActionResolver.ts +46 -57
- package/src/resolvers/AskSkipResolver.ts +607 -533
- package/src/resolvers/ComponentRegistryResolver.ts +547 -562
- package/src/resolvers/CreateQueryResolver.ts +683 -655
- package/src/resolvers/DatasetResolver.ts +5 -6
- package/src/resolvers/EntityCommunicationsResolver.ts +1 -1
- package/src/resolvers/EntityRecordNameResolver.ts +9 -5
- package/src/resolvers/EntityResolver.ts +9 -7
- package/src/resolvers/FileCategoryResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +4 -4
- package/src/resolvers/GetDataContextDataResolver.ts +106 -118
- package/src/resolvers/GetDataResolver.ts +194 -205
- package/src/resolvers/MergeRecordsResolver.ts +5 -5
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
- package/src/resolvers/QueryResolver.ts +95 -78
- package/src/resolvers/ReportResolver.ts +2 -2
- package/src/resolvers/RunAIAgentResolver.ts +818 -828
- package/src/resolvers/RunAIPromptResolver.ts +693 -709
- package/src/resolvers/RunTemplateResolver.ts +105 -103
- package/src/resolvers/SqlLoggingConfigResolver.ts +69 -72
- package/src/resolvers/SyncDataResolver.ts +386 -352
- package/src/resolvers/SyncRolesUsersResolver.ts +387 -350
- package/src/resolvers/TaskResolver.ts +110 -115
- package/src/resolvers/TransactionGroupResolver.ts +143 -138
- package/src/resolvers/UserFavoriteResolver.ts +17 -8
- package/src/resolvers/UserViewResolver.ts +17 -12
- package/src/rest/EntityCRUDHandler.ts +291 -268
- package/src/rest/RESTEndpointHandler.ts +782 -776
- package/src/rest/ViewOperationsHandler.ts +191 -195
- package/src/scheduler/LearningCycleScheduler.ts +8 -52
- package/src/services/ScheduledJobsService.ts +129 -132
- package/src/services/TaskOrchestrator.ts +792 -776
- package/src/types.ts +15 -9
- package/src/util.ts +112 -109
|
@@ -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/global';
|
|
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,799 +12,827 @@ 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
|
-
|
|
320
|
-
|
|
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
|
+
}
|
|
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
|
+
}
|
|
321
364
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
Success: false,
|
|
326
|
-
ErrorMessage: `Query with name '${input.Name}' already exists in ${categoryInfo}`
|
|
327
|
-
};
|
|
328
|
-
}
|
|
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();
|
|
329
368
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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);
|
|
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) => {
|
|
417
375
|
return {
|
|
418
|
-
|
|
419
|
-
|
|
376
|
+
...e,
|
|
377
|
+
EntityName: e.Entity, // alias this to fix variable name mismatch
|
|
420
378
|
};
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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 => ({
|
|
379
|
+
}),
|
|
380
|
+
Permissions: record.QueryPermissions,
|
|
381
|
+
};
|
|
382
|
+
} else {
|
|
383
|
+
// Save failed - check if another request created the same query (race condition)
|
|
384
|
+
// Always recheck regardless of error type to handle all duplicate scenarios
|
|
385
|
+
const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
|
|
386
|
+
|
|
387
|
+
if (existingQuery) {
|
|
388
|
+
// Found the query that was created by another request
|
|
389
|
+
// Return it as if we created it (it has the same name/category)
|
|
390
|
+
LogStatus(
|
|
391
|
+
`[CreateQuery] Unique constraint detected for query '${input.Name}'. Using existing query (ID: ${existingQuery.ID}) created by concurrent request.`
|
|
392
|
+
);
|
|
393
|
+
return {
|
|
394
|
+
Success: true,
|
|
395
|
+
QueryData: JSON.stringify(existingQuery),
|
|
396
|
+
Fields: existingQuery.Fields || [],
|
|
397
|
+
Parameters: existingQuery.Parameters || [],
|
|
398
|
+
Entities:
|
|
399
|
+
existingQuery.Entities?.map((e) => ({
|
|
573
400
|
ID: e.ID,
|
|
574
401
|
QueryID: e.QueryID,
|
|
575
402
|
EntityID: e.EntityID,
|
|
576
|
-
EntityName: e.Entity
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
ID: p.ID,
|
|
581
|
-
QueryID: p.QueryID,
|
|
582
|
-
RoleID: p.RoleID,
|
|
583
|
-
RoleName: p.Role || undefined // Property is called Role, not RoleName
|
|
584
|
-
}));
|
|
585
|
-
|
|
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
|
-
};
|
|
403
|
+
EntityName: e.Entity,
|
|
404
|
+
})) || [],
|
|
405
|
+
Permissions: existingQuery.Permissions || [],
|
|
406
|
+
};
|
|
601
407
|
}
|
|
602
|
-
}
|
|
603
408
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
}
|
|
627
|
-
|
|
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
|
-
}
|
|
409
|
+
// Genuine failure - couldn't find an existing query with the same name
|
|
410
|
+
const errorMessage = record.LatestResult?.Message || '';
|
|
411
|
+
return {
|
|
412
|
+
Success: false,
|
|
413
|
+
ErrorMessage: `Failed to create query: ${errorMessage || 'Unknown error'}`,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
} catch (err) {
|
|
417
|
+
LogError(err);
|
|
418
|
+
return {
|
|
419
|
+
Success: false,
|
|
420
|
+
ErrorMessage: `MJQueryResolverExtended::CreateQuerySystemUser --- Error creating query: ${err instanceof Error ? err.message : String(err)}`,
|
|
421
|
+
};
|
|
659
422
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
+
}
|
|
677
449
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
450
|
+
}
|
|
451
|
+
}
|
|
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
|
+
}
|
|
735
551
|
}
|
|
736
552
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
553
|
+
// Create new permissions
|
|
554
|
+
await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
|
|
555
|
+
|
|
556
|
+
// Refresh the metadata to get updated permissions
|
|
557
|
+
await queryEntity.RefreshRelatedMetadata(true);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Use the properties from QueryEntityExtended instead of manual loading
|
|
561
|
+
const fields: QueryFieldType[] = queryEntity.QueryFields.map((f) => ({
|
|
562
|
+
ID: f.ID,
|
|
563
|
+
QueryID: f.QueryID,
|
|
564
|
+
Name: f.Name,
|
|
565
|
+
Description: f.Description || undefined,
|
|
566
|
+
Type: f.SQLBaseType || undefined,
|
|
567
|
+
Sequence: f.Sequence,
|
|
568
|
+
SQLBaseType: f.SQLBaseType || undefined,
|
|
569
|
+
SQLFullType: f.SQLFullType || undefined,
|
|
570
|
+
IsComputed: f.IsComputed,
|
|
571
|
+
ComputationDescription: f.ComputationDescription || undefined,
|
|
572
|
+
}));
|
|
573
|
+
|
|
574
|
+
const parameters: QueryParameterType[] = queryEntity.QueryParameters.map((p) => ({
|
|
575
|
+
ID: p.ID,
|
|
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
|
+
}
|
|
740
683
|
|
|
741
|
-
|
|
684
|
+
const pathParts = categoryPath
|
|
685
|
+
.split('/')
|
|
686
|
+
.map((part) => part.trim())
|
|
687
|
+
.filter((part) => part.length > 0);
|
|
688
|
+
if (pathParts.length === 0) {
|
|
689
|
+
throw new Error('CategoryPath must contain at least one valid category name');
|
|
742
690
|
}
|
|
743
691
|
|
|
744
|
-
|
|
745
|
-
|
|
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> {
|
|
759
|
-
try {
|
|
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, "''")}')`;
|
|
692
|
+
let currentParentID: string | null = null;
|
|
693
|
+
let currentCategoryID: string | null = null;
|
|
763
694
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
ExtraFilter: `${nameFilter} AND ${categoryFilter}`
|
|
767
|
-
}, contextUser);
|
|
695
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
696
|
+
const categoryName = pathParts[i];
|
|
768
697
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
}
|
|
698
|
+
// Look for existing category at this level
|
|
699
|
+
const existingCategory = await this.findCategoryByNameAndParent(p, categoryName, currentParentID, contextUser);
|
|
772
700
|
|
|
773
|
-
|
|
701
|
+
if (existingCategory) {
|
|
702
|
+
currentCategoryID = existingCategory.ID;
|
|
703
|
+
currentParentID = existingCategory.ID;
|
|
704
|
+
} else {
|
|
705
|
+
try {
|
|
706
|
+
// Create new category
|
|
707
|
+
const newCategory = await p.GetEntityObject<QueryCategoryEntity>('Query Categories', contextUser);
|
|
708
|
+
if (!newCategory) {
|
|
709
|
+
throw new Error(`Failed to create entity object for Query Categories`);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
newCategory.Name = categoryName;
|
|
713
|
+
newCategory.ParentID = currentParentID;
|
|
714
|
+
newCategory.UserID = contextUser.ID;
|
|
715
|
+
newCategory.Description = `Auto-created category from path: ${categoryPath}`;
|
|
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'}`);
|
|
732
|
+
}
|
|
733
|
+
} else {
|
|
734
|
+
currentCategoryID = newCategory.ID;
|
|
735
|
+
currentParentID = newCategory.ID;
|
|
736
|
+
}
|
|
774
737
|
} catch (error) {
|
|
775
|
-
|
|
776
|
-
|
|
738
|
+
// On error, double-check if category exists (race condition handling)
|
|
739
|
+
const recheckExisting = await this.findCategoryByNameAndParent(p, categoryName, currentParentID, contextUser);
|
|
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
|
+
}
|
|
777
750
|
}
|
|
751
|
+
}
|
|
778
752
|
}
|
|
779
753
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
-
}
|
|
754
|
+
if (!currentCategoryID) {
|
|
755
|
+
throw new Error('Failed to determine final category ID');
|
|
756
|
+
}
|
|
803
757
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
758
|
+
return currentCategoryID;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Finds an existing query by name and category ID using RunView.
|
|
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;
|
|
809
797
|
}
|
|
810
|
-
}
|
|
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;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|