Skip to content

Commit 2a3fcbb

Browse files
Import/export state (#97)
* initial, get state from url * querystring hydration hooks * wip * main menu * share links, fix a bunch of firefox and chrome messaging bugs * fix state import, ui stuff * import/export, refine share, general ui * additional hydration (payload, logs) in embed_script, ui * put/patch payload via UI * devtools -> devtools-panel * fix patch method, etc * ui changes * encode source for hydrated states * bump versions, readme * cleanup
1 parent 69be34e commit 2a3fcbb

30 files changed

Lines changed: 1235 additions & 173 deletions

README.md

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

6565
## Changelog
6666

67+
*1.1.0* (2025-03-11)
68+
- Share DevTools session by link
69+
- Import/export DevTools session by JSON blob
70+
- Preview draft features & experiments not in SDK payload
71+
- Bug fixes (occasional attributes tab crashes, mislabeled experiment overrides, theme flickering)
72+
6773
*1.0.4* (2025-03-05)
6874
- Ability to clear individual attribute overrides
6975
- Bug fixes (legacy SDK support, prevent attribute caching, search)

devtools.d.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type {
22
Experiment,
33
FeatureDefinition,
44
ExperimentOverride,
5-
StickyAssignmentsDocument,
5+
StickyAssignmentsDocument, FeatureApiResponse,
66
} from "@growthbook/growthbook";
77
import {
88
FetchVisualChangesetPayload,
@@ -167,6 +167,21 @@ type PullOverrides = {
167167
type: "GB_REQUEST_OVERRIDES";
168168
};
169169

170+
type SetPayload = {
171+
type: "SET_PAYLOAD";
172+
data: FeatureApiResponse;
173+
};
174+
175+
type PatchPayload = {
176+
type: "PATCH_PAYLOAD";
177+
data: FeatureApiResponse;
178+
};
179+
180+
type CopyToClipboard = {
181+
type: "COPY_TO_CLIPBOARD";
182+
value: string;
183+
};
184+
170185
// Messages sent to content script
171186
export type Message =
172187
| RequestRefreshMessage
@@ -182,7 +197,10 @@ export type Message =
182197
| TransformCopyRequestMessage
183198
| TransformCopyResponseMessage
184199
| UpdateTabState
185-
| PullOverrides;
200+
| PullOverrides
201+
| SetPayload
202+
| PatchPayload
203+
| CopyToClipboard;
186204

187205
export type BGLoadVisualChangsetMessage = {
188206
type: "BG_LOAD_VISUAL_CHANGESET";

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.0.4",
3+
"version": "1.1.0",
44
"private": true,
55
"scripts": {
66
"dev": "webpack --config webpack/webpack.dev.js --watch",

public/devtools_panel.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<link rel="stylesheet" href="css/popup.css">
77
</head>
88
<body style="overflow-y: hidden; min-height: 100vh;">
9-
<div id="root"></div>
9+
<div id="root" data-is-devtools-panel="1"></div>
1010
<script src="js/popup.js"></script>
1111
</body>
1212
</html>

public/manifest.chrome.json

Lines changed: 2 additions & 2 deletions
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.0.4",
6+
"version": "1.1.0",
77

88
"content_scripts": [
99
{
@@ -30,7 +30,7 @@
3030
"service_worker": "js/background.js"
3131
},
3232

33-
"permissions": ["storage"],
33+
"permissions": ["storage", "clipboardWrite"],
3434

3535
"host_permissions": ["<all_urls>"],
3636

public/manifest.firefox.json

Lines changed: 2 additions & 2 deletions
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.0.4",
6+
"version": "1.1.0",
77

88
"browser_specific_settings": {
99
"gecko": {
@@ -37,7 +37,7 @@
3737
"scripts": ["js/background.js"]
3838
},
3939

40-
"permissions": ["storage"],
40+
"permissions": ["storage", "clipboardWrite"],
4141

4242
"host_permissions": ["<all_urls>"],
4343

src/app/components/AppMenu.tsx

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { Button, DropdownMenu, Flex, Tooltip } from "@radix-ui/themes";
2+
import {
3+
PiArrowSquareInBold,
4+
PiBellFill,
5+
PiCircleFill,
6+
PiCircleHalfBold,
7+
PiGearSixFill,
8+
PiList,
9+
PiMoonBold,
10+
PiShareBold,
11+
PiSunBold,
12+
} from "react-icons/pi";
13+
import React from "react";
14+
import { Theme } from "@/app";
15+
16+
export function AppMenu({
17+
apiKeyReady,
18+
apiKey,
19+
theme,
20+
setTheme,
21+
setSettingsOpen,
22+
setShareOpen,
23+
setImportExportOpen,
24+
}: {
25+
apiKeyReady: boolean;
26+
apiKey: string;
27+
theme: "system" | "light" | "dark";
28+
setTheme: (t: Theme) => void;
29+
setSettingsOpen: (b: boolean) => void;
30+
setShareOpen: (b: boolean) => void;
31+
setImportExportOpen: (b: boolean) => void;
32+
}) {
33+
return (
34+
<DropdownMenu.Root>
35+
<DropdownMenu.Trigger>
36+
{apiKeyReady && !apiKey ? (
37+
<div>
38+
<Tooltip content="Enter an Access Token for improved functionality">
39+
<Button
40+
variant="ghost"
41+
radius="small"
42+
size="2"
43+
style={{ marginRight: 0 }}
44+
>
45+
<div className="relative mr-1">
46+
<PiCircleFill
47+
size={11}
48+
className="absolute text-red-600 bg-surface rounded-full border border-surface"
49+
style={{ right: -4, top: -4 }}
50+
/>
51+
<PiList size={18} />
52+
</div>
53+
Menu
54+
</Button>
55+
</Tooltip>
56+
</div>
57+
) : (
58+
<Button
59+
variant="ghost"
60+
radius="small"
61+
size="2"
62+
style={{ marginRight: 0 }}
63+
>
64+
<PiList size={18} className="mr-1" />
65+
Menu
66+
</Button>
67+
)}
68+
</DropdownMenu.Trigger>
69+
<DropdownMenu.Content variant="soft">
70+
<DropdownMenu.Sub>
71+
<DropdownMenu.SubTrigger>
72+
{theme === "system" ? (
73+
<PiCircleHalfBold />
74+
) : theme === "dark" ? (
75+
<PiMoonBold />
76+
) : (
77+
<PiSunBold />
78+
)}
79+
Theme
80+
</DropdownMenu.SubTrigger>
81+
<DropdownMenu.SubContent>
82+
<DropdownMenu.Item onSelect={() => setTheme("system")}>
83+
<PiCircleHalfBold />
84+
System default
85+
</DropdownMenu.Item>
86+
<DropdownMenu.Item onSelect={() => setTheme("light")}>
87+
<PiSunBold />
88+
Light
89+
</DropdownMenu.Item>
90+
<DropdownMenu.Item onSelect={() => setTheme("dark")}>
91+
<PiMoonBold />
92+
Dark
93+
</DropdownMenu.Item>
94+
</DropdownMenu.SubContent>
95+
</DropdownMenu.Sub>
96+
<DropdownMenu.Item onSelect={() => setShareOpen(true)}>
97+
<PiShareBold />
98+
Share...
99+
</DropdownMenu.Item>
100+
<DropdownMenu.Item onSelect={() => setImportExportOpen(true)}>
101+
<PiArrowSquareInBold />
102+
Import / Export
103+
</DropdownMenu.Item>
104+
<DropdownMenu.Separator />
105+
<DropdownMenu.Item
106+
className="flex justify-between items-center"
107+
onSelect={() => setSettingsOpen(true)}
108+
>
109+
<Flex gap="2" align="center">
110+
<PiGearSixFill />
111+
Settings
112+
</Flex>
113+
{apiKeyReady && !apiKey ? (
114+
<Tooltip content="Enter an Access Token for improved functionality">
115+
<div className="p-1 text-red-600">
116+
<PiBellFill />
117+
</div>
118+
</Tooltip>
119+
) : null}
120+
</DropdownMenu.Item>
121+
</DropdownMenu.Content>
122+
</DropdownMenu.Root>
123+
);
124+
}

src/app/components/ArchetypesList.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
PiCaretDownFill,
66
PiCheck,
77
PiPencilSimple,
8-
PiUser,
98
PiUserCircle,
109
} from "react-icons/pi";
1110
import useApi from "@/app/hooks/useApi";
@@ -70,7 +69,7 @@ export default function ArchetypesList() {
7069
className="block text-nowrap overflow-hidden overflow-ellipsis"
7170
style={{ maxWidth: "calc(100vw - 120px - 20px - 20px)" }}
7271
>
73-
<PiUserCircle className="inline-block mr-1" />
72+
<PiUserCircle className="inline-block mr-1 mb-0.5" />
7473
{selectedArchetype?.name || "Current User"}
7574
</Link>
7675
<PiCaretDownFill className="ml-0.5 text-violet-a11" size={12} />

src/app/components/AttributesForm/index.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,16 @@ export default function AttributesForm({
258258
) : (
259259
<>
260260
<div
261-
className="rt-TextAreaRoot rt-r-size-2 rt-variant-surface mb-2"
261+
className={clsx(
262+
"rt-TextAreaRoot rt-r-size-2 rt-variant-surface mb-2",
263+
{
264+
"border border-red-700": textareaError,
265+
},
266+
)}
262267
style={{ minHeight: "unset !important" }}
263268
>
264269
<TextareaAutosize
265-
className={clsx("rt-reset rt-TextAreaInput mono", {
266-
"border-red-700": textareaError,
267-
})}
270+
className="rt-reset rt-TextAreaInput mono"
268271
name={"__JSON_attributes__"}
269272
value={textareaAttributes}
270273
onChange={(e) => {

src/app/components/ExperimentDetail.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { MW, NAV_H } from "@/app";
2-
import { Button, IconButton, Link, RadioCards } from "@radix-ui/themes";
2+
import {
3+
Button,
4+
Callout,
5+
IconButton,
6+
Link,
7+
RadioCards,
8+
Tooltip,
9+
} from "@radix-ui/themes";
310
import {
411
PiArrowSquareOut,
512
PiCaretRightFill,
@@ -28,6 +35,7 @@ import { SelectedExperiment } from "@/app/components/ExperimentsTab";
2835
import { AutoExperimentVariation, isURLTargeted } from "@growthbook/growthbook";
2936
import clsx from "clsx";
3037
import DebugLogger, { DebugLogAccordion } from "@/app/components/DebugLogger";
38+
import { TbEyeSearch } from "react-icons/tb";
3139

3240
export default function ExperimentDetail({
3341
selectedEid,
@@ -161,6 +169,21 @@ export default function ExperimentDetail({
161169
</div>
162170

163171
<div className="content">
172+
{selectedExperiment?.experiment?.isDraft ? (
173+
<Callout.Root
174+
color="cyan"
175+
size="1"
176+
className="py-1.5 px-2 mt-2 mb-4"
177+
>
178+
<Tooltip content="You are previewing a draft state of this experiment. Reload the current page to reset.">
179+
<span className="text-sm">
180+
<TbEyeSearch className="inline-block mr-1 mb-0.5" />
181+
Previewing draft
182+
</span>
183+
</Tooltip>
184+
</Callout.Root>
185+
) : null}
186+
164187
<div className="my-1">
165188
<div className="mt-2 mb-3">
166189
<div className="label font-semibold">Enrollment Status</div>

0 commit comments

Comments
 (0)