Skip to content

Commit 51b4338

Browse files
committed
introduce: in-use API for use with React
1 parent dcbe3ad commit 51b4338

File tree

6 files changed

+62
-43
lines changed

6 files changed

+62
-43
lines changed

packages/browser-sdk/src/feature/features.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -399,29 +399,24 @@ export class FeaturesClient {
399399
private triggerFeaturesChanged() {
400400
const mergedFeatures: RawFeatures = {};
401401

402-
// merge fetched features with overrides into `this.features`
403-
for (const key in this.fetchedFeatures) {
404-
const fetchedFeature = this.fetchedFeatures[key];
405-
if (!fetchedFeature) continue;
406-
const isEnabledOverride = this.featureOverrides[key] ?? null;
407-
mergedFeatures[key] = {
408-
...fetchedFeature,
409-
isEnabledOverride,
410-
inUse: false,
411-
};
412-
}
402+
const allKeys = new Set([
403+
...Object.keys(this.fetchedFeatures),
404+
...Object.keys(this.featureInUse),
405+
...Object.keys(this.featureOverrides),
406+
]);
413407

414-
for (const key in this.featureInUse) {
415-
const inUse = this.featureInUse[key] ?? false;
408+
for (const key of allKeys) {
416409
mergedFeatures[key] = {
417-
...mergedFeatures[key],
418-
key,
419-
inUse,
410+
...(this.fetchedFeatures[key] ?? {
411+
key,
412+
isEnabled: false,
413+
}),
414+
isEnabledOverride: this.featureOverrides[key] ?? null,
415+
inUse: this.featureInUse[key] ?? false,
420416
};
421417
}
422418

423419
this.features = mergedFeatures;
424-
425420
this.eventTarget.dispatchEvent(new Event(FEATURES_UPDATED_EVENT));
426421
}
427422

packages/browser-sdk/src/toolbar/Features.css

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@
4040
.features-table {
4141
width: 100%;
4242
border-collapse: collapse;
43+
a {
44+
color: var(--text-color);
45+
text-decoration: none;
46+
&:hover {
47+
text-decoration: underline;
48+
}
49+
}
50+
.in-use a {
51+
color: var(--brand400);
52+
}
4353
}
4454

4555
.feature-name-cell {
@@ -50,14 +60,6 @@
5060
padding: 6px 6px 6px 0;
5161
}
5262

53-
.feature-link {
54-
color: var(--text-color);
55-
text-decoration: none;
56-
&:hover {
57-
text-decoration: underline;
58-
}
59-
}
60-
6163
.feature-reset-cell {
6264
width: 30px;
6365
padding: 6px 0;

packages/browser-sdk/src/toolbar/Features.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ function FeatureRow({
4141
setEnabledOverride: (key: string, value: boolean | null) => void;
4242
}) {
4343
return (
44-
<tr key={feature.key}>
45-
<td class="feature-name-cell in-use">
44+
<tr key={feature.key} class={feature.inUse ? "in-use" : ""}>
45+
<td class="feature-name-cell">
4646
<a
4747
href={`${appBaseUrl}/envs/current/features/by-key/${feature.key}`}
4848
target="_blank"

packages/browser-sdk/src/toolbar/Toolbar.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,3 @@
161161
box-shadow: var(--border-color) -1px 1px 1px 0px;
162162
}
163163
}
164-
165-
.in-use-show-all {
166-
cursor: pointer;
167-
}

packages/browser-sdk/test/features.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { afterAll, beforeEach, describe, expect, test, vi } from "vitest";
1+
import { afterAll, beforeEach, describe, expect, it, test, vi } from "vitest";
22

33
import { version } from "../package.json";
44
import {
@@ -384,4 +384,24 @@ describe("FeaturesClient", () => {
384384
expect(updated).toBe(true);
385385
expect(client.getFeatures().featureC).toBeUndefined();
386386
});
387+
388+
describe("in use", () => {
389+
it("handled in use", async () => {
390+
// change the response so we can validate that we'll serve the stale cache
391+
const { newFeaturesClient } = featuresClientFactory();
392+
393+
// localStorage.clear();
394+
const client = newFeaturesClient(undefined);
395+
await client.initialize();
396+
397+
client.setInUse("featureC", true);
398+
expect(client.getFeatures().featureC.isEnabled).toBe(false);
399+
expect(client.getFeatures().featureC.isEnabledOverride).toBe(null);
400+
401+
client.setFeatureOverride("featureC", true);
402+
403+
expect(client.getFeatures().featureC.isEnabled).toBe(false);
404+
expect(client.getFeatures().featureC.isEnabledOverride).toBe(true);
405+
});
406+
});
387407
});

packages/react-sdk/dev/plain/app.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
declare module "../../src" {
1616
interface Features {
1717
huddles: { optInCopy: string };
18+
"voice-chat": { optInCopy: string };
1819
}
1920
}
2021

@@ -164,6 +165,9 @@ function Feedback() {
164165

165166
// App.tsx
166167
function Demos() {
168+
const [showVoiceChatOptIn, setShowVoiceChatOptIn] = useState(true);
169+
const [showHuddleOptIn, setShowHuddleOptIn] = useState(true);
170+
167171
return (
168172
<main>
169173
<h1>React SDK</h1>
@@ -176,7 +180,14 @@ function Demos() {
176180
<code>optin-huddles IS TRUE</code>. Hit the checkbox below to opt-in/out
177181
of the feature.
178182
</div>
179-
<FeatureOptIn featureKey={"huddles"} featureName={"Huddles"} />
183+
{showHuddleOptIn && <FeatureOptIn featureKey={"huddles"} />}
184+
<button onClick={() => setShowHuddleOptIn((prev) => !prev)}>
185+
Toggle voice chat opt-in
186+
</button>
187+
{showVoiceChatOptIn && <FeatureOptIn featureKey={"voice-chat"} />}
188+
<button onClick={() => setShowVoiceChatOptIn((prev) => !prev)}>
189+
Toggle voice chat opt-in
190+
</button>
180191

181192
<UpdateContext />
182193
<Feedback />
@@ -185,27 +196,22 @@ function Demos() {
185196
);
186197
}
187198

188-
// type KeysOfValue<T, TCondition> = {
189-
// [K in keyof T]: T[K] extends TCondition ? K : never;
190-
// }[keyof T];
191-
192-
// type OptInFeature = KeysOfValue<Features, { optInCopy: any }>;
193-
194199
function FeatureOptIn<TKey extends FeatureKey>({
195200
featureKey,
196-
featureName,
197201
}: {
198202
featureKey: TKey;
199-
featureName: string;
200203
}) {
201204
const updateUser = useUpdateUser();
202205
const [sendingUpdate, setSendingUpdate] = useState(false);
203206
const { isEnabled, config } = useFeature(featureKey);
204207

205208
return (
206209
<div>
207-
<label htmlFor="huddlesOptIn">Opt-in to {featureName} feature</label>
208-
<span>{config.payload?.optInCopy ?? "Hit the checkbox to opt-in"}</span>
210+
<label htmlFor="huddlesOptIn">
211+
{config.payload?.optInCopy ?? "Hit the checkbox to opt-in"}:{" "}
212+
{featureKey}
213+
</label>
214+
209215
<input
210216
disabled={sendingUpdate}
211217
id="huddlesOptIn"

0 commit comments

Comments
 (0)