-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDataRetrievalService.ts
More file actions
147 lines (129 loc) · 5.41 KB
/
DataRetrievalService.ts
File metadata and controls
147 lines (129 loc) · 5.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { FixedOptionValueElement, ValueElement } from '../service/Element.js';
import { logger } from '../index.js';
export interface DataRetrievalRequest {
userId?: string;
fields: string[];
/** Current form session data — enables session-aware conditional logic in the data retrieval API. */
sessionData?: Record<string, string>;
}
/** Option item for a multi-value field returned by the data retrieval API. */
export interface FieldOption {
text: string;
value?: string;
valueText?: string;
hint?: string;
}
/**
* Structured response from the data retrieval API.
* Also accepts the legacy flat shape (Record<string, unknown>) for backward compatibility.
*/
export interface DataRetrievalResponse {
/** Scalar values keyed by field name. */
values?: Record<string, string>;
/** Option lists for multi-value fields, keyed by field name. */
options?: Record<string, FieldOption[]>;
/** Legacy flat shape — field name mapped directly to its value. */
[key: string]: unknown;
}
export class DataRetrievalService {
public async enrichData(
externalUrl: string,
allElements: ValueElement[],
data: Record<string, unknown>,
userId?: string,
): Promise<void> {
logger.info(`Enriching data with external API: ${externalUrl}`);
try {
const propertiesToFill = this.getPropertiesToFill(allElements, data);
if (!propertiesToFill.length) {
logger.debug(`No fields to fill.`);
return;
}
logger.debug(`Enriching fields:`, propertiesToFill);
const requestData: DataRetrievalRequest = {
...(userId && { userId }),
fields: propertiesToFill,
sessionData: data as Record<string, string>,
};
const responseData = await this.makeRequest(externalUrl, requestData);
// Structured response: apply scalar values and dynamic options separately
if ((responseData.values && typeof responseData.values === 'object')
|| (responseData.options && typeof responseData.values === 'object')) {
if (responseData.values) {
Object.assign(data, responseData.values);
}
if (responseData.options) {
for (const element of allElements) {
const dynamicOptions = responseData.options[element.name];
if (dynamicOptions && 'options' in element) {
(element as unknown as FixedOptionValueElement).options = dynamicOptions;
}
}
}
}
else {
// Legacy flat response — merge directly into data for backward compatibility
Object.assign(data, responseData);
}
logger.info(`Successfully enriched data`);
} catch (error) {
logger.error('Failed to retrieve data from external API:', error);
}
}
private async makeRequest(url: string, requestData: DataRetrievalRequest): Promise<DataRetrievalResponse> {
logger.debug(`Sending request to external API`, requestData);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData),
});
if (response.ok) {
const responseData = await response.json();
logger.debug(`Response from external API for fields:`, responseData);
return responseData as DataRetrievalResponse;
} else {
const errorBody = await response.text();
const errorMessage = `Data retrieval failed with status ${response.status}: ${response.statusText}. Response body: ${errorBody}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
}
private getPropertiesToFill(allElements: ValueElement[], data: Record<string, unknown>): string[] {
const propertiesToFill: string[] = [];
allElements.forEach((element) => {
if (element.type === 'DatePickerField') {
const dayProperty = `${element.name}-day`;
const monthProperty = `${element.name}-month`;
const yearProperty = `${element.name}-year`;
if (!data[dayProperty]) propertiesToFill.push(dayProperty);
if (!data[monthProperty]) propertiesToFill.push(monthProperty);
if (!data[yearProperty]) propertiesToFill.push(yearProperty);
} else if (element.type === 'AddressField') {
const line1Property = `${element.name}-line1`;
const line2Property = `${element.name}-line2`;
const townProperty = `${element.name}-town`;
const countyProperty = `${element.name}-county`;
const postcodeProperty = `${element.name}-postcode`;
if (!data[line1Property]) propertiesToFill.push(line1Property);
if (!data[line2Property]) propertiesToFill.push(line2Property);
if (!data[townProperty]) propertiesToFill.push(townProperty);
if (!data[countyProperty]) propertiesToFill.push(countyProperty);
if (!data[postcodeProperty]) propertiesToFill.push(postcodeProperty);
} else {
const hasNoValue = !data[element.name];
// Always fetch a field whose options list is empty — it needs dynamic options
// from the API even when a scalar value already exists in session.
const hasEmptyOptions =
'options' in element &&
Array.isArray((element as unknown as { options: unknown[] }).options) &&
(element as unknown as { options: unknown[] }).options.length === 0;
if (hasNoValue || hasEmptyOptions) {
propertiesToFill.push(element.name);
}
}
});
return propertiesToFill;
}
}