Skip to content

Commit fe0ec2a

Browse files
[FSSDK-12444] getQualifiedSegments improvement
1 parent 6d634b7 commit fe0ec2a

2 files changed

Lines changed: 51 additions & 34 deletions

File tree

src/utils/helpers.spec.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,18 @@ describe('getQualifiedSegments', () => {
3434
});
3535

3636
const mockFetchResponse = (body: any, ok = true) => {
37-
global.fetch = vi.fn().mockResolvedValue({
38-
ok,
39-
json: () => Promise.resolve(body),
40-
});
37+
vi.stubGlobal(
38+
'fetch',
39+
vi.fn().mockResolvedValue({
40+
ok,
41+
json: () => Promise.resolve(body),
42+
})
43+
);
4144
};
4245

4346
afterEach(() => {
4447
vi.restoreAllMocks();
48+
vi.unstubAllGlobals();
4549
});
4650

4751
it('returns null when datafile is invalid or missing ODP integration', async () => {
@@ -95,7 +99,7 @@ describe('getQualifiedSegments', () => {
9599

96100
it('returns null when fetch fails or response is not ok', async () => {
97101
// network error
98-
global.fetch = vi.fn().mockRejectedValue(new Error('network error'));
102+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network error')));
99103

100104
expect(await utils.getQualifiedSegments('user-1', makeDatafile())).toBeNull();
101105

@@ -105,6 +109,25 @@ describe('getQualifiedSegments', () => {
105109
expect(await utils.getQualifiedSegments('user-1', makeDatafile())).toBeNull();
106110
});
107111

112+
it('skips audiences with malformed conditions string without throwing', async () => {
113+
mockFetchResponse({
114+
data: {
115+
customer: {
116+
audiences: {
117+
edges: [{ node: { name: 'seg1', state: 'qualified' } }],
118+
},
119+
},
120+
},
121+
});
122+
123+
const datafile = makeDatafile({
124+
typedAudiences: [{ conditions: '{bad json' }, { conditions: ['or', { match: 'qualified', value: 'seg1' }] }],
125+
});
126+
127+
const result = await utils.getQualifiedSegments('user-1', datafile);
128+
expect(result).toEqual(['seg1']);
129+
});
130+
108131
it('returns null when response contains GraphQL errors or missing edges', async () => {
109132
// GraphQL errors
110133
mockFetchResponse({ errors: [{ message: 'something went wrong' }] });

src/utils/helpers.ts

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,13 @@ const QUALIFIED = 'qualified';
6262
* Looks for conditions with `match: 'qualified'` and collects their values.
6363
*/
6464
function extractSegmentsFromConditions(condition: any): string[] {
65-
if (typeof condition === 'string') {
66-
return [];
67-
}
68-
6965
if (Array.isArray(condition)) {
70-
const segments: string[] = [];
71-
condition.forEach((c) => segments.push(...extractSegmentsFromConditions(c)));
72-
return segments;
66+
return condition.flatMap(extractSegmentsFromConditions);
7367
}
7468

75-
if (condition && typeof condition === 'object' && condition['match'] === 'qualified') {
76-
return [condition['value']];
69+
if (condition && typeof condition === 'object' && condition['match'] === QUALIFIED) {
70+
const value = condition['value'];
71+
return typeof value === 'string' && value.length > 0 ? [value] : [];
7772
}
7873

7974
return [];
@@ -83,8 +78,9 @@ function extractSegmentsFromConditions(condition: any): string[] {
8378
* Builds the GraphQL query payload for fetching audience segments from ODP.
8479
*/
8580
function buildGraphQLQuery(userId: string, segmentsToCheck: string[]): string {
86-
const segmentsList = segmentsToCheck.map((s) => `\\"${s}\\"`).join(',');
87-
return `{"query" : "query {customer(fs_user_id : \\"${userId}\\") {audiences(subset: [${segmentsList}]) {edges {node {name state}}}}}"}`;
81+
const segmentsList = segmentsToCheck.map((s) => `"${s}"`).join(',');
82+
const query = `query {customer(fs_user_id : "${userId}") {audiences(subset: [${segmentsList}]) {edges {node {name state}}}}}`;
83+
return JSON.stringify({ query });
8884
}
8985

9086
/**
@@ -121,29 +117,21 @@ export async function getQualifiedSegments(
121117
} catch {
122118
return null;
123119
}
124-
} else if (typeof datafile === 'object') {
120+
} else if (typeof datafile === 'object' && datafile !== null) {
125121
datafileObj = datafile;
126122
} else {
127123
return null;
128124
}
129125

130126
// Extract ODP integration config from datafile
131-
let apiKey = '';
132-
let apiHost = '';
133-
let odpIntegrated = false;
134-
135-
if (Array.isArray(datafileObj.integrations)) {
136-
for (const integration of datafileObj.integrations) {
137-
if (integration.key === 'odp') {
138-
odpIntegrated = true;
139-
apiKey = integration.publicKey || '';
140-
apiHost = integration.host || '';
141-
break;
142-
}
143-
}
144-
}
127+
const odpIntegration = Array.isArray(datafileObj.integrations)
128+
? datafileObj.integrations.find((i: Record<string, unknown>) => i.key === 'odp')
129+
: undefined;
145130

146-
if (!odpIntegrated || !apiKey || !apiHost) {
131+
const apiKey = odpIntegration?.publicKey;
132+
const apiHost = odpIntegration?.host;
133+
134+
if (!apiKey || !apiHost) {
147135
return null;
148136
}
149137

@@ -153,8 +141,14 @@ export async function getQualifiedSegments(
153141

154142
for (const audience of audiences) {
155143
if (audience.conditions) {
156-
const conditions =
157-
typeof audience.conditions === 'string' ? JSON.parse(audience.conditions) : audience.conditions;
144+
let conditions = audience.conditions;
145+
if (typeof conditions === 'string') {
146+
try {
147+
conditions = JSON.parse(conditions);
148+
} catch {
149+
continue;
150+
}
151+
}
158152
extractSegmentsFromConditions(conditions).forEach((s) => allSegments.add(s));
159153
}
160154
}

0 commit comments

Comments
 (0)