Skip to content

Commit b672b56

Browse files
Backend support (#100)
1 parent bdfc0c0 commit b672b56

25 files changed

+1391
-369
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ Some notes
6464

6565
## Changelog
6666

67+
*1.2.0* (2025-04-07)
68+
- Support for backend SDK debugging (overrides, logs)
69+
- Feature and Experiment views now show both simulated and live evaluations when available
70+
- Ability to inject a "debugging SDK" onto pages with no front-end SDKs
71+
- UI updates
72+
- Bug fixes
73+
6774
*1.1.2* (2025-03-25)
6875
- Persist feature and experiment search filter
6976
- Bug fix: Improve association of debug logs to feature rules

devtools.d.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type {
1+
import {
22
Experiment,
33
FeatureDefinition,
44
ExperimentOverride,
5-
StickyAssignmentsDocument, FeatureApiResponse,
5+
StickyAssignmentsDocument, FeatureApiResponse, Attributes,
66
} from "@growthbook/growthbook";
77
import {
88
FetchVisualChangesetPayload,
@@ -78,6 +78,16 @@ export type RequestRefreshMessage = {
7878
type: "GB_REQUEST_REFRESH";
7979
};
8080

81+
export type InjectSdkMessage = {
82+
type: "GB_INJECT_SDK";
83+
clientKey: string;
84+
apiHost: string;
85+
autoInject: boolean;
86+
};
87+
export type ClearInjectedSdkMessage = {
88+
type: "GB_CLEAR_INJECTED_SDK";
89+
};
90+
8191
export type SetOverridesMessage = {
8292
type: "GB_SET_OVERRIDES";
8393
variations: Record<string, number>;
@@ -200,6 +210,8 @@ export type Message =
200210
| PullOverrides
201211
| SetPayload
202212
| PatchPayload
213+
| InjectSdkMessage
214+
| ClearInjectedSdkMessage
203215
| CopyToClipboard;
204216

205217
export type BGLoadVisualChangsetMessage = {
@@ -226,14 +238,25 @@ export type BGTransformCopyMessage = {
226238
};
227239
};
228240

229-
type SDKHealthCheckResult = {
241+
export type ExternalSdkInfo = {
242+
apiHost: string;
243+
clientKey: string;
244+
version?: string;
245+
payload?: FeatureApiResponse;
246+
attributes?: Attributes;
247+
};
248+
249+
export type SDKHealthCheckResult = {
230250
canConnect: boolean;
231251
hasPayload: boolean;
232252
hasClientKey?: boolean;
233253
errorMessage?: string;
234254
version?: string;
235255
hasWindowConfig?: boolean;
236256
sdkFound?: boolean;
257+
sdkInjected?: boolean;
258+
sdkAutoInjected?: boolean;
259+
externalSdks?: Record<string, ExternalSdkInfo>;
237260
clientKey?: string;
238261
isLoading?: boolean;
239262
payload?: Record<string, any>;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gb-devtools",
3-
"version": "1.1.2",
3+
"version": "1.2.0",
44
"private": true,
55
"scripts": {
66
"dev": "webpack --config webpack/webpack.dev.js --watch",

public/manifest.chrome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
"name": "GrowthBook DevTools",
55
"description": "GrowthBook DevTools helps you debug feature flags and experiments and design A/B tests visually — all from your browser.",
6-
"version": "1.1.2",
6+
"version": "1.2.0",
77

88
"content_scripts": [
99
{

public/manifest.firefox.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
"name": "GrowthBook DevTools",
55
"description": "GrowthBook DevTools helps you debug feature flags and experiments and design A/B tests visually — all from your browser.",
6-
"version": "1.1.2",
6+
"version": "1.2.0",
77

88
"browser_specific_settings": {
99
"gecko": {

src/app/components/DebugLogger.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ export default function DebugLogger({
3636
size={12}
3737
/>
3838
Debug log
39-
{showCount ? (<>
40-
{" "}({logs?.length || 0})
41-
</>) : null}
39+
{showCount ? <> ({logs?.length || 0})</> : null}
4240
</Link>
4341
</div>
4442
{!collapsed && (

src/app/components/EditableValueField.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
33
import ValueField, { ValueType } from "./ValueField";
44
import { Button, RadioGroup, Link, DropdownMenu } from "@radix-ui/themes";
55
import TextareaAutosize from "react-textarea-autosize";
6-
import { PiCaretDownFill, PiPencilSimple } from "react-icons/pi";
6+
import { PiCaretDownFill } from "react-icons/pi";
77

88
export default function EditableValueField({
99
value,

src/app/components/ExperimentDetail.tsx

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import {
1111
PiArrowSquareOut,
1212
PiCaretRightFill,
1313
PiFlagFill,
14+
PiInfo,
1415
PiLinkBold,
15-
PiMonitorBold,
16+
PiDesktopFill,
1617
PiXBold,
1718
} from "react-icons/pi";
1819
import ValueField from "@/app/components/ValueField";
@@ -22,7 +23,7 @@ import {
2223
getVariationColor,
2324
} from "@/app/components/Rule";
2425
import * as Accordion from "@radix-ui/react-accordion";
25-
import React, { CSSProperties, useEffect, useState } from "react";
26+
import React, { CSSProperties, useEffect, useMemo, useState } from "react";
2627
import {
2728
ExperimentWithFeatures,
2829
HEADER_H,
@@ -36,8 +37,11 @@ import { AutoExperimentVariation, isURLTargeted } from "@growthbook/growthbook";
3637
import clsx from "clsx";
3738
import DebugLogger, { DebugLogAccordion } from "@/app/components/DebugLogger";
3839
import { TbEyeSearch } from "react-icons/tb";
39-
import useApi from "@/app/hooks/useApi";
40-
import { SDKAttribute } from "@/app/gbTypes";
40+
import {
41+
Evaluation,
42+
EvaluationSourceViewer,
43+
} from "@/app/components/FeatureDetail";
44+
import { LogUnionWithSource } from "@/app/utils/logs";
4145

4246
export default function ExperimentDetail({
4347
selectedEid,
@@ -82,6 +86,58 @@ export default function ExperimentDetail({
8286
setOverrideExperiment(false);
8387
};
8488

89+
const [logEvents] = useTabState<LogUnionWithSource[] | undefined>(
90+
"logEvents",
91+
undefined,
92+
);
93+
94+
const [viewEvaluationSource, setViewEvaluationSource] = useState<
95+
string | undefined
96+
>(undefined);
97+
const evaluations = useMemo(() => {
98+
if (!selectedFid) return [];
99+
const evaluationsMap: Record<string, Evaluation> = {};
100+
let logs = [...(logEvents || [])]
101+
.filter(
102+
(log) =>
103+
log.logType === "experiment" && log.experiment.key === selectedEid,
104+
)
105+
.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
106+
logs.forEach((log) => {
107+
const key = (log.source || "local") + "__" + (log.clientKey || "");
108+
if (!(key in evaluationsMap) && "result" in log) {
109+
evaluationsMap[key] = {
110+
result: log.result,
111+
context: {
112+
source: log.source || "front-end",
113+
clientKey: log.clientKey,
114+
timestamp: log.timestamp,
115+
},
116+
};
117+
}
118+
});
119+
return Object.entries(evaluationsMap).sort((a, b) => {
120+
if (a[0] === "local") return 1;
121+
return a[0].localeCompare(b[0]);
122+
});
123+
}, [logEvents, selectedEid]);
124+
125+
useEffect(() => {
126+
if (!selectedEid || !evaluations.length) {
127+
setViewEvaluationSource(undefined);
128+
}
129+
if (viewEvaluationSource === undefined && evaluations.length) {
130+
setViewEvaluationSource(evaluations?.[0]?.[0]);
131+
}
132+
if (
133+
viewEvaluationSource !== undefined &&
134+
evaluations.length &&
135+
!evaluations.find((e) => e[0] === viewEvaluationSource)
136+
) {
137+
setViewEvaluationSource(evaluations?.[0]?.[0]);
138+
}
139+
}, [selectedEid, viewEvaluationSource, evaluations]);
140+
85141
const { types } = selectedExperiment || {};
86142

87143
const { variations, weights, hashAttribute, coverage, namespace } =
@@ -211,7 +267,23 @@ export default function ExperimentDetail({
211267

212268
<div className="flex items-center justify-between my-2">
213269
<div className="label font-semibold">
214-
{overrideExperiment ? "Forced variation" : "Current variation"}
270+
<Tooltip
271+
content={
272+
!overrideExperiment
273+
? "Value is simulated by DevTools"
274+
: "Value is overridden and is applied to live SDK(s)"
275+
}
276+
>
277+
<span>
278+
{overrideExperiment
279+
? "Forced variation"
280+
: "Current variation"}
281+
<PiInfo
282+
size={12}
283+
className="text-indigo-9 inline-block ml-1"
284+
/>
285+
</span>
286+
</Tooltip>
215287
</div>
216288
{overrideExperiment && (
217289
<Button
@@ -257,7 +329,7 @@ export default function ExperimentDetail({
257329
<Link
258330
size="2"
259331
role="button"
260-
className="hover:underline"
332+
className="hover:underline decoration-violet-a6"
261333
>
262334
<PiCaretRightFill
263335
className="caret mr-0.5"
@@ -294,6 +366,15 @@ export default function ExperimentDetail({
294366
customPrismOuterStyle={{ marginTop: 4 }}
295367
/>
296368

369+
{evaluations.length ? (
370+
<EvaluationSourceViewer
371+
evaluations={evaluations}
372+
viewEvaluationSource={viewEvaluationSource}
373+
setViewEvaluationSource={setViewEvaluationSource}
374+
isExperiment={true}
375+
/>
376+
) : null}
377+
297378
<div className="mt-4 mb-1 text-md font-semibold">
298379
Implementation
299380
{(types?.redirect ? 1 : 0) +
@@ -312,7 +393,7 @@ export default function ExperimentDetail({
312393
) : null}
313394
{types?.visual ? (
314395
<div className="text-sm">
315-
<PiMonitorBold className="inline-block mr-1" />
396+
<PiDesktopFill className="inline-block mr-1" />
316397
Visual Editor
317398
</div>
318399
) : null}
@@ -402,7 +483,11 @@ export default function ExperimentDetail({
402483
>
403484
<Accordion.Item value="feature-definition">
404485
<Accordion.Trigger className="trigger mb-0.5">
405-
<Link size="2" role="button" className="hover:underline">
486+
<Link
487+
size="2"
488+
role="button"
489+
className="hover:underline decoration-violet-a6"
490+
>
406491
<PiCaretRightFill className="caret mr-0.5" size={12} />
407492
Full experiment definition
408493
</Link>

src/app/components/ExperimentsTab.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
AutoExperiment,
44
Experiment,
55
FeatureDefinition,
6-
LogUnion,
76
} from "@growthbook/growthbook";
87
import useTabState from "../hooks/useTabState";
98
import useGBSandboxEval, {
@@ -24,6 +23,7 @@ import { ValueType } from "@/app/components/ValueField";
2423
import FeatureExperimentStatusIcon from "@/app/components/FeatureExperimentStatusIcon";
2524
import { useResponsiveContext } from "../hooks/useResponsive";
2625
import { TbEyeSearch } from "react-icons/tb";
26+
import { LogUnionWithSource } from "@/app/utils/logs";
2727

2828
export type ExperimentWithFeatures = (AutoExperiment | Experiment<any>) & {
2929
features?: string[];
@@ -93,7 +93,7 @@ export default function ExperimentsTab() {
9393

9494
const { evaluatedExperiments } = useGBSandboxEval();
9595

96-
const [logEvents] = useTabState<LogUnion[] | undefined>(
96+
const [logEvents] = useTabState<LogUnionWithSource[] | undefined>(
9797
"logEvents",
9898
undefined,
9999
);
@@ -109,7 +109,7 @@ export default function ExperimentsTab() {
109109

110110
const [searchValue, setSearchValue] = useTabState<string>(
111111
"experimentsSearchValue",
112-
""
112+
"",
113113
);
114114

115115
const {

0 commit comments

Comments
 (0)