Skip to content

Commit 45081de

Browse files
committed
client api fixes
1 parent e3cecd7 commit 45081de

10 files changed

Lines changed: 74 additions & 49 deletions

File tree

packages/react-native/src/components/survey-web-view.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
5353
const displayLanguage = getLanguageCode(props.survey, language);
5454
if (!displayLanguage) {
5555
logger.debug(
56-
`Survey "${props.survey.name}" is not available in specified language.`,
56+
`Survey "${props.survey.id}" is not available in specified language.`,
5757
);
5858
setIsSurveyRunning(false);
5959
setShowSurvey(false);
@@ -75,7 +75,7 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
7575

7676
if (props.survey.delay) {
7777
logger.debug(
78-
`Delaying survey "${props.survey.name}" by ${String(props.survey.delay)} seconds`,
78+
`Delaying survey "${props.survey.id}" by ${String(props.survey.delay)} seconds`,
7979
);
8080
const timerId = setTimeout(() => {
8181
setShowSurvey(true);
@@ -87,7 +87,7 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
8787
}
8888

8989
setShowSurvey(true);
90-
}, [props.survey.delay, isSurveyRunning, props.survey.name]);
90+
}, [props.survey.delay, isSurveyRunning, props.survey.id]);
9191

9292
if (!appConfig) {
9393
return null;

packages/react-native/src/lib/common/setup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ const updateAppConfigWithSyncedStates = (
150150
filteredSurveys,
151151
});
152152

153-
const surveyNames = filteredSurveys.map((s) => s.name);
153+
const surveyIds = filteredSurveys.map((s) => s.id);
154154
logger.debug(
155-
`Fetched ${surveyNames.length.toString()} surveys during sync: ${surveyNames.join(", ")}`,
155+
`Fetched ${surveyIds.length.toString()} surveys during sync: ${surveyIds.join(", ")}`,
156156
);
157157
};
158158

packages/react-native/src/lib/common/tests/__mocks__/config.mock.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export const mockConfig: TConfig = {
1616
surveys: [
1717
{
1818
id: mockSurveyId,
19-
name: "Onboarding Survey",
2019
welcomeCard: null,
2120
questions: [],
2221
variables: [],

packages/react-native/src/lib/common/tests/utils.test.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe("utils.ts", () => {
135135
{
136136
...baseSurvey,
137137
id: mockSurveyId1,
138-
segment: { id: mockSegmentId1 },
138+
segment: { id: mockSegmentId1, hasFilters: false },
139139
} as TSurvey,
140140
];
141141

@@ -158,14 +158,14 @@ describe("utils.ts", () => {
158158
expect(result[0].id).toBe(mockSurveyId1);
159159
});
160160

161-
test("filters out surveys that have a segment with filters if userId is not set", () => {
161+
test("filters out surveys that have a segment with filters if userId is not set (new shape: hasFilters=true)", () => {
162162
environment.data.surveys = [
163163
{
164164
...baseSurvey,
165165
id: mockSurveyId1,
166166
segment: {
167167
id: mockSegmentId1,
168-
filters: [{ type: "string", key: "name", value: "John" }],
168+
hasFilters: true,
169169
},
170170
} as TSurvey,
171171
];
@@ -174,6 +174,32 @@ describe("utils.ts", () => {
174174
expect(result).toHaveLength(0);
175175
});
176176

177+
test("filters out surveys with segment filters when cached payload uses legacy shape (filters array)", () => {
178+
// Simulates an AsyncStorage payload written by an older SDK version that
179+
// still has `segment.filters` and no `hasFilters`. The defensive check
180+
// must fall back to the legacy shape so anonymous users don't receive
181+
// segment-targeted surveys.
182+
environment.data.surveys = [
183+
{
184+
...baseSurvey,
185+
id: mockSurveyId1,
186+
segment: {
187+
id: mockSegmentId1,
188+
filters: [{ type: "attribute", value: "plan" }],
189+
},
190+
} as unknown as TSurvey,
191+
{
192+
...baseSurvey,
193+
id: mockSurveyId2,
194+
segment: { id: mockSegmentId2, filters: [] },
195+
} as unknown as TSurvey,
196+
];
197+
198+
const result = filterSurveys(environment, user);
199+
expect(result).toHaveLength(1);
200+
expect(result[0].id).toBe(mockSurveyId2);
201+
});
202+
177203
test("includes surveys without segment filters for anonymous users", () => {
178204
environment.data.surveys = [
179205
{
@@ -184,7 +210,7 @@ describe("utils.ts", () => {
184210
{
185211
...baseSurvey,
186212
id: mockSurveyId2,
187-
segment: { id: mockSegmentId1 }, // Segment but no filters
213+
segment: { id: mockSegmentId1, hasFilters: false }, // Segment but no filters
188214
} as TSurvey,
189215
];
190216

@@ -276,13 +302,13 @@ describe("utils.ts", () => {
276302
{
277303
...baseSurvey,
278304
id: mockSurveyId1,
279-
segment: { id: mockSegmentId1 },
305+
segment: { id: mockSegmentId1, hasFilters: false },
280306
displayOption: "respondMultiple",
281307
} as TSurvey,
282308
{
283309
...baseSurvey,
284310
id: mockSurveyId2,
285-
segment: { id: mockSegmentId2 },
311+
segment: { id: mockSegmentId2, hasFilters: false },
286312
displayOption: "respondMultiple",
287313
} as TSurvey,
288314
];

packages/react-native/src/lib/common/utils.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@ export const wrapThrowsAsync =
2929
}
3030
};
3131

32+
/**
33+
* Detect whether a survey's segment has filters. Handles both the current
34+
* minimal shape (`{ id, hasFilters }`) and the legacy shape with a `filters`
35+
* array — older SDKs cached the full segment in AsyncStorage and may still be
36+
* read back here within the cache window after an SDK upgrade.
37+
*/
38+
export const surveyHasSegmentFilters = (survey: TSurvey): boolean => {
39+
const segment = survey.segment as
40+
| { hasFilters?: boolean; filters?: unknown[] }
41+
| null
42+
| undefined;
43+
if (!segment) return false;
44+
if (typeof segment.hasFilters === "boolean") return segment.hasFilters;
45+
return Array.isArray(segment.filters) && segment.filters.length > 0;
46+
};
47+
3248
/**
3349
* Filters surveys based on the displayOption, recontactDays, and segments
3450
* @param environmentSate - The environment state
@@ -111,11 +127,7 @@ export const filterSurveys = (
111127
if (!userId) {
112128
// exclude surveys that have a segment with filters
113129
return filteredSurveys.filter((survey) => {
114-
const segmentFilters = survey.segment?.filters;
115-
const segmentFiltersLength = Array.isArray(segmentFilters)
116-
? segmentFilters.length
117-
: 0;
118-
return segmentFiltersLength === 0;
130+
return !surveyHasSegmentFilters(survey);
119131
});
120132
}
121133

packages/react-native/src/lib/survey/action.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const triggerSurvey = (survey: TSurvey): void => {
2727
);
2828
if (!shouldDisplaySurvey) {
2929
logger.debug(
30-
`Survey display of "${survey.name}" skipped based on displayPercentage.`,
30+
`Survey display of "${survey.id}" skipped based on displayPercentage.`,
3131
);
3232
return; // skip displaying the survey
3333
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export const mockSurveyId = "jgocyoxk9uifo6u381qahmes";
2-
export const mockSurveyName = "Test Survey";

packages/react-native/src/lib/survey/tests/action.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ vi.mock("@react-native-community/netinfo", () => ({
4545
describe("survey/action.ts", () => {
4646
const mockSurvey = {
4747
id: "survey_001",
48-
name: "Test Survey",
4948
displayPercentage: 50,
5049
triggers: [
5150
{
@@ -95,7 +94,7 @@ describe("survey/action.ts", () => {
9594
// Ensure survey is not set
9695
expect(mockSurveyStore.setSurvey).not.toHaveBeenCalled();
9796
expect(mockLogger.debug).toHaveBeenCalledWith(
98-
'Survey display of "Test Survey" skipped based on displayPercentage.',
97+
'Survey display of "survey_001" skipped based on displayPercentage.',
9998
);
10099
});
101100

packages/react-native/src/lib/survey/tests/store.test.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { beforeEach, describe, expect, test, vi } from "vitest";
22
import { SurveyStore } from "@/lib/survey/store";
3-
import {
4-
mockSurveyId,
5-
mockSurveyName,
6-
} from "@/lib/survey/tests/__mocks__/store.mock";
3+
import { mockSurveyId } from "@/lib/survey/tests/__mocks__/store.mock";
74
import type { TSurvey } from "@/types/survey";
85

96
describe("SurveyStore", () => {
@@ -32,8 +29,8 @@ describe("SurveyStore", () => {
3229
test("returns current survey when set", () => {
3330
const mockSurvey: TSurvey = {
3431
id: mockSurveyId,
35-
name: mockSurveyName,
36-
} as TSurvey;
32+
displayPercentage: 100,
33+
} as unknown as TSurvey;
3734

3835
store.setSurvey(mockSurvey);
3936
expect(store.getSurvey()).toBe(mockSurvey);
@@ -45,8 +42,8 @@ describe("SurveyStore", () => {
4542
const listener = vi.fn();
4643
const mockSurvey: TSurvey = {
4744
id: mockSurveyId,
48-
name: mockSurveyName,
49-
} as TSurvey;
45+
displayPercentage: 100,
46+
} as unknown as TSurvey;
5047

5148
store.subscribe(listener);
5249
store.setSurvey(mockSurvey);
@@ -59,8 +56,8 @@ describe("SurveyStore", () => {
5956
const listener = vi.fn();
6057
const mockSurvey: TSurvey = {
6158
id: mockSurveyId,
62-
name: mockSurveyName,
63-
} as TSurvey;
59+
displayPercentage: 100,
60+
} as unknown as TSurvey;
6461

6562
store.setSurvey(mockSurvey);
6663
store.subscribe(listener);
@@ -75,8 +72,8 @@ describe("SurveyStore", () => {
7572
const listener = vi.fn();
7673
const mockSurvey: TSurvey = {
7774
id: mockSurveyId,
78-
name: mockSurveyName,
79-
} as TSurvey;
75+
displayPercentage: 100,
76+
} as unknown as TSurvey;
8077

8178
store.setSurvey(mockSurvey);
8279
store.subscribe(listener);
@@ -101,8 +98,8 @@ describe("SurveyStore", () => {
10198
const listener = vi.fn();
10299
const mockSurvey: TSurvey = {
103100
id: mockSurveyId,
104-
name: mockSurveyName,
105-
} as TSurvey;
101+
displayPercentage: 100,
102+
} as unknown as TSurvey;
106103

107104
const unsubscribe = store.subscribe(listener);
108105
store.setSurvey(mockSurvey);
@@ -111,8 +108,8 @@ describe("SurveyStore", () => {
111108
unsubscribe();
112109
store.setSurvey({
113110
...mockSurvey,
114-
name: "Updated Survey",
115-
} as TSurvey);
111+
displayPercentage: 50,
112+
} as unknown as TSurvey);
116113
expect(listener).toHaveBeenCalledTimes(1); // Still 1, not called after unsubscribe
117114
});
118115

@@ -121,8 +118,8 @@ describe("SurveyStore", () => {
121118
const listener2 = vi.fn();
122119
const mockSurvey: TSurvey = {
123120
id: mockSurveyId,
124-
name: mockSurveyName,
125-
} as TSurvey;
121+
displayPercentage: 100,
122+
} as unknown as TSurvey;
126123

127124
store.subscribe(listener1);
128125
store.subscribe(listener2);

packages/react-native/src/types/survey.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ export interface SurveyContainerProps
8383

8484
export interface TSurvey {
8585
id: string;
86-
name: string;
8786
welcomeCard: {
8887
enabled: boolean;
8988
headline?: Record<string, string>;
@@ -283,17 +282,11 @@ export interface TSurvey {
283282
environmentId: string;
284283
};
285284
}[];
285+
// Minimal segment shape — full filter logic is evaluated server-side and must not reach the client
286286
segment?: {
287287
id: string;
288-
title: string;
289-
description: string | null;
290-
isPrivate: boolean;
291-
filters: unknown; // recursive, optional to expand
292-
environmentId: string;
293-
createdAt: string;
294-
updatedAt: string;
295-
surveys: string[];
296-
};
288+
hasFilters: boolean;
289+
} | null;
297290
displayPercentage: number;
298291
styling?: {
299292
brandColor?: {

0 commit comments

Comments
 (0)