Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/react-native/src/components/survey-web-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {
const displayLanguage = getLanguageCode(props.survey, language);
if (!displayLanguage) {
logger.debug(
`Survey "${props.survey.name}" is not available in specified language.`,
`Survey "${props.survey.id}" is not available in specified language.`,
);
setIsSurveyRunning(false);
setShowSurvey(false);
Expand All @@ -75,7 +75,7 @@ export function SurveyWebView(props: SurveyWebViewProps): JSX.Element | null {

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

setShowSurvey(true);
}, [props.survey.delay, isSurveyRunning, props.survey.name]);
}, [props.survey.delay, isSurveyRunning, props.survey.id]);

if (!appConfig) {
return null;
Expand Down
4 changes: 2 additions & 2 deletions packages/react-native/src/lib/common/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ const updateAppConfigWithSyncedStates = (
filteredSurveys,
});

const surveyNames = filteredSurveys.map((s) => s.name);
const surveyIds = filteredSurveys.map((s) => s.id);
logger.debug(
`Fetched ${surveyNames.length.toString()} surveys during sync: ${surveyNames.join(", ")}`,
`Fetched ${surveyIds.length.toString()} surveys during sync: ${surveyIds.join(", ")}`,
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export const mockConfig: TConfig = {
surveys: [
{
id: mockSurveyId,
name: "Onboarding Survey",
welcomeCard: null,
questions: [],
variables: [],
Expand Down
38 changes: 32 additions & 6 deletions packages/react-native/src/lib/common/tests/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ describe("utils.ts", () => {
{
...baseSurvey,
id: mockSurveyId1,
segment: { id: mockSegmentId1 },
segment: { id: mockSegmentId1, hasFilters: false },
} as TSurvey,
];

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

test("filters out surveys that have a segment with filters if userId is not set", () => {
test("filters out surveys that have a segment with filters if userId is not set (new shape: hasFilters=true)", () => {
workspace.data.surveys = [
{
...baseSurvey,
id: mockSurveyId1,
segment: {
id: mockSegmentId1,
filters: [{ type: "string", key: "name", value: "John" }],
hasFilters: true,
},
} as TSurvey,
];
Expand All @@ -170,6 +170,32 @@ describe("utils.ts", () => {
expect(result).toHaveLength(0);
});

test("filters out surveys with segment filters when cached payload uses legacy shape (filters array)", () => {
// Simulates an AsyncStorage payload written by an older SDK version that
// still has `segment.filters` and no `hasFilters`. The defensive check
// must fall back to the legacy shape so anonymous users don't receive
// segment-targeted surveys.
workspace.data.surveys = [
{
...baseSurvey,
id: mockSurveyId1,
segment: {
id: mockSegmentId1,
filters: [{ type: "attribute", value: "plan" }],
},
} as unknown as TSurvey,
{
...baseSurvey,
id: mockSurveyId2,
segment: { id: mockSegmentId2, filters: [] },
} as unknown as TSurvey,
];

const result = filterSurveys(workspace, user);
expect(result).toHaveLength(1);
expect(result[0].id).toBe(mockSurveyId2);
});

test("includes surveys without segment filters for anonymous users", () => {
workspace.data.surveys = [
{
Expand All @@ -180,7 +206,7 @@ describe("utils.ts", () => {
{
...baseSurvey,
id: mockSurveyId2,
segment: { id: mockSegmentId1 }, // Segment but no filters
segment: { id: mockSegmentId1, hasFilters: false }, // Segment but no filters
} as TSurvey,
];

Expand Down Expand Up @@ -272,13 +298,13 @@ describe("utils.ts", () => {
{
...baseSurvey,
id: mockSurveyId1,
segment: { id: mockSegmentId1 },
segment: { id: mockSegmentId1, hasFilters: false },
displayOption: "respondMultiple",
} as TSurvey,
{
...baseSurvey,
id: mockSurveyId2,
segment: { id: mockSegmentId2 },
segment: { id: mockSegmentId2, hasFilters: false },
displayOption: "respondMultiple",
} as TSurvey,
];
Expand Down
22 changes: 17 additions & 5 deletions packages/react-native/src/lib/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ export const wrapThrowsAsync =
}
};

/**
* Detect whether a survey's segment has filters. Handles both the current
* minimal shape (`{ id, hasFilters }`) and the legacy shape with a `filters`
* array — older SDKs cached the full segment in AsyncStorage and may still be
* read back here within the cache window after an SDK upgrade.
*/
export const surveyHasSegmentFilters = (survey: TSurvey): boolean => {
const segment = survey.segment as
| { hasFilters?: boolean; filters?: unknown[] }
| null
| undefined;
if (!segment) return false;
if (typeof segment.hasFilters === "boolean") return segment.hasFilters;
return Array.isArray(segment.filters) && segment.filters.length > 0;
};

/**
* Filters surveys based on the displayOption, recontactDays, and segments
* @param workspace - The workspace state
Expand Down Expand Up @@ -112,11 +128,7 @@ export const filterSurveys = (
if (!userId) {
// exclude surveys that have a segment with filters
return filteredSurveys.filter((survey) => {
const segmentFilters = survey.segment?.filters;
const segmentFiltersLength = Array.isArray(segmentFilters)
? segmentFilters.length
: 0;
return segmentFiltersLength === 0;
return !surveyHasSegmentFilters(survey);
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/src/lib/survey/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const triggerSurvey = (survey: TSurvey): void => {
);
if (!shouldDisplaySurvey) {
logger.debug(
`Survey display of "${survey.name}" skipped based on displayPercentage.`,
`Survey display of "${survey.id}" skipped based on displayPercentage.`,
);
return; // skip displaying the survey
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export const mockSurveyId = "jgocyoxk9uifo6u381qahmes";
export const mockSurveyName = "Test Survey";
3 changes: 1 addition & 2 deletions packages/react-native/src/lib/survey/tests/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ vi.mock("@react-native-community/netinfo", () => ({
describe("survey/action.ts", () => {
const mockSurvey = {
id: "survey_001",
name: "Test Survey",
displayPercentage: 50,
triggers: [
{
Expand Down Expand Up @@ -95,7 +94,7 @@ describe("survey/action.ts", () => {
// Ensure survey is not set
expect(mockSurveyStore.setSurvey).not.toHaveBeenCalled();
expect(mockLogger.debug).toHaveBeenCalledWith(
'Survey display of "Test Survey" skipped based on displayPercentage.',
'Survey display of "survey_001" skipped based on displayPercentage.',
);
});

Expand Down
33 changes: 15 additions & 18 deletions packages/react-native/src/lib/survey/tests/store.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { SurveyStore } from "@/lib/survey/store";
import {
mockSurveyId,
mockSurveyName,
} from "@/lib/survey/tests/__mocks__/store.mock";
import { mockSurveyId } from "@/lib/survey/tests/__mocks__/store.mock";
import type { TSurvey } from "@/types/survey";

describe("SurveyStore", () => {
Expand Down Expand Up @@ -32,8 +29,8 @@ describe("SurveyStore", () => {
test("returns current survey when set", () => {
const mockSurvey: TSurvey = {
id: mockSurveyId,
name: mockSurveyName,
} as TSurvey;
displayPercentage: 100,
} as unknown as TSurvey;

store.setSurvey(mockSurvey);
expect(store.getSurvey()).toBe(mockSurvey);
Expand All @@ -45,8 +42,8 @@ describe("SurveyStore", () => {
const listener = vi.fn();
const mockSurvey: TSurvey = {
id: mockSurveyId,
name: mockSurveyName,
} as TSurvey;
displayPercentage: 100,
} as unknown as TSurvey;

store.subscribe(listener);
store.setSurvey(mockSurvey);
Expand All @@ -59,8 +56,8 @@ describe("SurveyStore", () => {
const listener = vi.fn();
const mockSurvey: TSurvey = {
id: mockSurveyId,
name: mockSurveyName,
} as TSurvey;
displayPercentage: 100,
} as unknown as TSurvey;

store.setSurvey(mockSurvey);
store.subscribe(listener);
Expand All @@ -75,8 +72,8 @@ describe("SurveyStore", () => {
const listener = vi.fn();
const mockSurvey: TSurvey = {
id: mockSurveyId,
name: mockSurveyName,
} as TSurvey;
displayPercentage: 100,
} as unknown as TSurvey;

store.setSurvey(mockSurvey);
store.subscribe(listener);
Expand All @@ -101,8 +98,8 @@ describe("SurveyStore", () => {
const listener = vi.fn();
const mockSurvey: TSurvey = {
id: mockSurveyId,
name: mockSurveyName,
} as TSurvey;
displayPercentage: 100,
} as unknown as TSurvey;

const unsubscribe = store.subscribe(listener);
store.setSurvey(mockSurvey);
Expand All @@ -111,8 +108,8 @@ describe("SurveyStore", () => {
unsubscribe();
store.setSurvey({
...mockSurvey,
name: "Updated Survey",
} as TSurvey);
displayPercentage: 50,
} as unknown as TSurvey);
expect(listener).toHaveBeenCalledTimes(1); // Still 1, not called after unsubscribe
});

Expand All @@ -121,8 +118,8 @@ describe("SurveyStore", () => {
const listener2 = vi.fn();
const mockSurvey: TSurvey = {
id: mockSurveyId,
name: mockSurveyName,
} as TSurvey;
displayPercentage: 100,
} as unknown as TSurvey;

store.subscribe(listener1);
store.subscribe(listener2);
Expand Down
13 changes: 3 additions & 10 deletions packages/react-native/src/types/survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export interface SurveyContainerProps

export interface TSurvey {
id: string;
name: string;
welcomeCard: {
enabled: boolean;
headline?: Record<string, string>;
Expand Down Expand Up @@ -285,17 +284,11 @@ export interface TSurvey {
environmentId: string;
};
}[];
// Minimal segment shape — full filter logic is evaluated server-side and must not reach the client
segment?: {
id: string;
title: string;
description: string | null;
isPrivate: boolean;
filters: unknown; // recursive, optional to expand
environmentId: string;
createdAt: string;
updatedAt: string;
surveys: string[];
};
hasFilters: boolean;
} | null;
displayPercentage: number;
styling?: {
brandColor?: {
Expand Down
Loading