Skip to content

Commit 5427fdc

Browse files
authored
client api fixes (#62)
1 parent 80a313f commit 5427fdc

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
@@ -18,7 +18,6 @@ export const mockConfig: TConfig = {
1818
surveys: [
1919
{
2020
id: mockSurveyId,
21-
name: "Onboarding Survey",
2221
welcomeCard: null,
2322
questions: [],
2423
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
@@ -131,7 +131,7 @@ describe("utils.ts", () => {
131131
{
132132
...baseSurvey,
133133
id: mockSurveyId1,
134-
segment: { id: mockSegmentId1 },
134+
segment: { id: mockSegmentId1, hasFilters: false },
135135
} as TSurvey,
136136
];
137137

@@ -154,14 +154,14 @@ describe("utils.ts", () => {
154154
expect(result[0].id).toBe(mockSurveyId1);
155155
});
156156

157-
test("filters out surveys that have a segment with filters if userId is not set", () => {
157+
test("filters out surveys that have a segment with filters if userId is not set (new shape: hasFilters=true)", () => {
158158
workspace.data.surveys = [
159159
{
160160
...baseSurvey,
161161
id: mockSurveyId1,
162162
segment: {
163163
id: mockSegmentId1,
164-
filters: [{ type: "string", key: "name", value: "John" }],
164+
hasFilters: true,
165165
},
166166
} as TSurvey,
167167
];
@@ -170,6 +170,32 @@ describe("utils.ts", () => {
170170
expect(result).toHaveLength(0);
171171
});
172172

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

@@ -272,13 +298,13 @@ describe("utils.ts", () => {
272298
{
273299
...baseSurvey,
274300
id: mockSurveyId1,
275-
segment: { id: mockSegmentId1 },
301+
segment: { id: mockSegmentId1, hasFilters: false },
276302
displayOption: "respondMultiple",
277303
} as TSurvey,
278304
{
279305
...baseSurvey,
280306
id: mockSurveyId2,
281-
segment: { id: mockSegmentId2 },
307+
segment: { id: mockSegmentId2, hasFilters: false },
282308
displayOption: "respondMultiple",
283309
} as TSurvey,
284310
];

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 workspace - The workspace state
@@ -112,11 +128,7 @@ export const filterSurveys = (
112128
if (!userId) {
113129
// exclude surveys that have a segment with filters
114130
return filteredSurveys.filter((survey) => {
115-
const segmentFilters = survey.segment?.filters;
116-
const segmentFiltersLength = Array.isArray(segmentFilters)
117-
? segmentFilters.length
118-
: 0;
119-
return segmentFiltersLength === 0;
131+
return !surveyHasSegmentFilters(survey);
120132
});
121133
}
122134

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
@@ -85,7 +85,6 @@ export interface SurveyContainerProps
8585

8686
export interface TSurvey {
8787
id: string;
88-
name: string;
8988
welcomeCard: {
9089
enabled: boolean;
9190
headline?: Record<string, string>;
@@ -285,17 +284,11 @@ export interface TSurvey {
285284
environmentId: string;
286285
};
287286
}[];
287+
// Minimal segment shape — full filter logic is evaluated server-side and must not reach the client
288288
segment?: {
289289
id: string;
290-
title: string;
291-
description: string | null;
292-
isPrivate: boolean;
293-
filters: unknown; // recursive, optional to expand
294-
environmentId: string;
295-
createdAt: string;
296-
updatedAt: string;
297-
surveys: string[];
298-
};
290+
hasFilters: boolean;
291+
} | null;
299292
displayPercentage: number;
300293
styling?: {
301294
brandColor?: {

0 commit comments

Comments
 (0)