14
14
* limitations under the License.
15
15
*/
16
16
17
- import { ErrorHandler , LogHandler , LogLevel } from '../../modules/logging' ;
18
- import { Response } from './odp_types' ;
19
- import { IOdpClient , OdpClient } from './odp_client' ;
17
+ import { LogHandler , LogLevel } from '../../modules/logging' ;
20
18
import { validate } from '../../utils/json_schema_validator' ;
21
19
import { OdpResponseSchema } from './odp_response_schema' ;
22
- import { QuerySegmentsParameters } from './query_segments_parameters' ;
23
- import { RequestHandlerFactory } from '../../utils/http_request_handler/request_handler_factory' ;
20
+ import { ODP_USER_KEY } from '../../utils/enums' ;
21
+ import { RequestHandler , Response as HttpResponse } from '../../utils/http_request_handler/http' ;
22
+ import { Response as GraphQLResponse } from './odp_types' ;
24
23
25
24
/**
26
25
* Expected value for a qualified/valid segment
@@ -34,102 +33,145 @@ const EMPTY_SEGMENTS_COLLECTION: string[] = [];
34
33
* Return value for scenarios with no valid JSON
35
34
*/
36
35
const EMPTY_JSON_RESPONSE = null ;
36
+ /**
37
+ * Standard message for audience querying fetch errors
38
+ */
39
+ const AUDIENCE_FETCH_FAILURE_MESSAGE = 'Audience segments fetch failed' ;
37
40
38
41
/**
39
42
* Manager for communicating with the Optimizely Data Platform GraphQL endpoint
40
43
*/
41
44
export interface IGraphQLManager {
42
- fetchSegments ( apiKey : string , apiHost : string , userKey : string , userValue : string , segmentsToCheck : string [ ] ) : Promise < string [ ] > ;
45
+ fetchSegments ( apiKey : string , apiHost : string , userKey : string , userValue : string , segmentsToCheck : string [ ] ) : Promise < string [ ] | null > ;
43
46
}
44
47
45
48
/**
46
- * Concrete implementation for communicating with the Optimizely Data Platform GraphQL endpoint
49
+ * Concrete implementation for communicating with the ODP GraphQL endpoint
47
50
*/
48
- export class GraphqlManager implements IGraphQLManager {
49
- private readonly _errorHandler : ErrorHandler ;
50
- private readonly _logger : LogHandler ;
51
- private readonly _odpClient : IOdpClient ;
51
+ export class GraphQLManager implements IGraphQLManager {
52
+ private readonly logger : LogHandler ;
53
+ private readonly requestHandler : RequestHandler ;
52
54
53
55
/**
54
- * Retrieves the audience segments from the Optimizely Data Platform (ODP)
55
- * @param errorHandler Handler to record exceptions
56
+ * Communicates with Optimizely Data Platform's GraphQL endpoint
57
+ * @param requestHandler Desired request handler for testing
56
58
* @param logger Collect and record events/errors for this GraphQL implementation
57
- * @param client Client to use to send queries to ODP
58
59
*/
59
- constructor ( errorHandler : ErrorHandler , logger : LogHandler , client ?: IOdpClient ) {
60
- this . _errorHandler = errorHandler ;
61
- this . _logger = logger ;
62
-
63
- this . _odpClient = client ?? new OdpClient ( this . _errorHandler ,
64
- this . _logger ,
65
- RequestHandlerFactory . createHandler ( this . _logger ) ) ;
60
+ constructor ( requestHandler : RequestHandler , logger : LogHandler ) {
61
+ this . requestHandler = requestHandler ;
62
+ this . logger = logger ;
66
63
}
67
64
68
65
/**
69
66
* Retrieves the audience segments from ODP
70
67
* @param apiKey ODP public key
71
- * @param apiHost Fully-qualified URL of ODP
68
+ * @param apiHost Host of ODP endpoint
72
69
* @param userKey 'vuid' or 'fs_user_id key'
73
70
* @param userValue Associated value to query for the user key
74
71
* @param segmentsToCheck Audience segments to check for experiment inclusion
75
72
*/
76
- public async fetchSegments ( apiKey : string , apiHost : string , userKey : string , userValue : string , segmentsToCheck : string [ ] ) : Promise < string [ ] > {
77
- const parameters = new QuerySegmentsParameters ( {
78
- apiKey,
79
- apiHost,
80
- userKey,
81
- userValue,
82
- segmentsToCheck,
83
- } ) ;
84
- const segmentsResponse = await this . _odpClient . querySegments ( parameters ) ;
85
- if ( ! segmentsResponse ) {
86
- this . _logger . log ( LogLevel . ERROR , 'Audience segments fetch failed (network error)' ) ;
73
+ public async fetchSegments ( apiKey : string , apiHost : string , userKey : ODP_USER_KEY , userValue : string , segmentsToCheck : string [ ] ) : Promise < string [ ] | null > {
74
+ if ( ! apiKey || ! apiHost ) {
75
+ this . logger . log ( LogLevel . ERROR , `${ AUDIENCE_FETCH_FAILURE_MESSAGE } (Parameters apiKey or apiHost invalid)` ) ;
76
+ return null ;
77
+ }
78
+
79
+ if ( segmentsToCheck ?. length === 0 ) {
87
80
return EMPTY_SEGMENTS_COLLECTION ;
88
81
}
89
82
83
+ const endpoint = `${ apiHost } /v3/graphql` ;
84
+ const query = this . toGraphQLJson ( userKey , userValue , segmentsToCheck ) ;
85
+
86
+ const segmentsResponse = await this . querySegments ( apiKey , endpoint , userKey , userValue , query ) ;
87
+ if ( ! segmentsResponse ) {
88
+ this . logger . log ( LogLevel . ERROR , `${ AUDIENCE_FETCH_FAILURE_MESSAGE } (network error)` ) ;
89
+ return null ;
90
+ }
91
+
90
92
const parsedSegments = this . parseSegmentsResponseJson ( segmentsResponse ) ;
91
93
if ( ! parsedSegments ) {
92
- this . _logger . log ( LogLevel . ERROR , 'Audience segments fetch failed (decode error)' ) ;
93
- return EMPTY_SEGMENTS_COLLECTION ;
94
+ this . logger . log ( LogLevel . ERROR , ` ${ AUDIENCE_FETCH_FAILURE_MESSAGE } (decode error)` ) ;
95
+ return null ;
94
96
}
95
97
96
98
if ( parsedSegments . errors ?. length > 0 ) {
97
99
const errors = parsedSegments . errors . map ( ( e ) => e . message ) . join ( '; ' ) ;
98
100
99
- this . _logger . log ( LogLevel . WARNING , `Audience segments fetch failed (${ errors } )` ) ;
101
+ this . logger . log ( LogLevel . ERROR , `${ AUDIENCE_FETCH_FAILURE_MESSAGE } (${ errors } )` ) ;
100
102
101
- return EMPTY_SEGMENTS_COLLECTION ;
103
+ return null ;
102
104
}
103
105
104
106
const edges = parsedSegments ?. data ?. customer ?. audiences ?. edges ;
105
107
if ( ! edges ) {
106
- this . _logger . log ( LogLevel . WARNING , 'Audience segments fetch failed (decode error)' ) ;
107
- return EMPTY_SEGMENTS_COLLECTION ;
108
+ this . logger . log ( LogLevel . ERROR , ` ${ AUDIENCE_FETCH_FAILURE_MESSAGE } (decode error)` ) ;
109
+ return null ;
108
110
}
109
111
110
112
return edges . filter ( edge => edge . node . state == QUALIFIED ) . map ( edge => edge . node . name ) ;
111
113
}
112
114
115
+ /**
116
+ * Converts the query parameters to a GraphQL JSON payload
117
+ * @returns GraphQL JSON string
118
+ */
119
+ private toGraphQLJson = ( userKey : string , userValue : string , segmentsToCheck : string [ ] ) : string => ( [
120
+ '{"query" : "query {customer"' ,
121
+ `(${ userKey } : "${ userValue } ") ` ,
122
+ '{audiences' ,
123
+ '(subset: [' ,
124
+ ...segmentsToCheck ?. map ( ( segment , index ) =>
125
+ `\\"${ segment } \\"${ index < segmentsToCheck . length - 1 ? ',' : '' } ` ,
126
+ ) || '' ,
127
+ '] {edges {node {name state}}}}}"}' ,
128
+ ] . join ( '' ) ) ;
129
+
130
+ /**
131
+ * Handler for querying the ODP GraphQL endpoint
132
+ * @param apiKey ODP API key
133
+ * @param endpoint Fully-qualified GraphQL endpoint URL
134
+ * @param userKey 'vuid' or 'fs_user_id'
135
+ * @param userValue userKey's value
136
+ * @param query GraphQL formatted query string
137
+ * @returns JSON response string from ODP or null
138
+ */
139
+ private async querySegments ( apiKey : string , endpoint : string , userKey : string , userValue : string , query : string ) : Promise < string | null > {
140
+ const method = 'POST' ;
141
+ const url = endpoint ;
142
+ const headers = {
143
+ 'Content-Type' : 'application/json' ,
144
+ 'x-api-key' : apiKey ,
145
+ } ;
146
+
147
+ let response : HttpResponse ;
148
+ try {
149
+ const request = this . requestHandler . makeRequest ( url , headers , method , query ) ;
150
+ response = await request . responsePromise ;
151
+ } catch {
152
+ return null ;
153
+ }
154
+
155
+ return response . body ;
156
+ }
157
+
113
158
/**
114
159
* Parses JSON response
115
160
* @param jsonResponse JSON response from ODP
116
161
* @private
117
162
* @returns Response Strongly-typed ODP Response object
118
163
*/
119
- private parseSegmentsResponseJson ( jsonResponse : string ) : Response | null {
164
+ private parseSegmentsResponseJson ( jsonResponse : string ) : GraphQLResponse | null {
120
165
let jsonObject = { } ;
121
166
122
167
try {
123
168
jsonObject = JSON . parse ( jsonResponse ) ;
124
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
- } catch ( error : any ) {
126
- this . _errorHandler . handleError ( error ) ;
127
- this . _logger . log ( LogLevel . ERROR , 'Attempted to parse invalid segment response JSON.' ) ;
169
+ } catch {
128
170
return EMPTY_JSON_RESPONSE ;
129
171
}
130
172
131
173
if ( validate ( jsonObject , OdpResponseSchema , false ) ) {
132
- return jsonObject as Response ;
174
+ return jsonObject as GraphQLResponse ;
133
175
}
134
176
135
177
return EMPTY_JSON_RESPONSE ;
0 commit comments