Skip to content

Commit 427a6db

Browse files
authored
feat: handle edge cases in env var rendering (#1786)
- allow the dialog to scroll when there are a _lot_ of env vars - set a min width when one of the env vars is super long <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Enhances environment variable dialog UI to handle many variables and long names, refactoring components for better status handling. > > - **UI Enhancements**: > - `EnvironmentVariablesDialog` in `env-vars.tsx` now supports scrolling for dialogs with many environment variables and sets a minimum width for long variable names. > - Replaces `Dialog` and `DialogContent` with `EnvironmentVariablesDialog` in `prompt-preview/index.tsx` and `side-bar/index.tsx`. > - **Refactoring**: > - Introduces `EnvVarStatus` component in `env-vars.tsx` to handle status display of environment variables. > - Renames `EnvVars` to `EnvironmentVariablesPanel` and `EnvironmentVariablesDialog` in `env-vars.tsx`. > - **Storybook**: > - Updates `EnvVars.stories.tsx` to include scenarios for dialogs with many variables and long variable names. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for 3cda293. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN -->
1 parent 7c1e2b7 commit 427a6db

5 files changed

Lines changed: 104 additions & 44 deletions

File tree

typescript/playground-common/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,5 @@ export { default as CustomErrorBoundary } from './utils/ErrorFallback'
77
export { CodeMirrorViewer } from './shared/baml-project-panel/codemirror-panel/code-mirror-viewer'
88
export { JotaiProvider } from './baml_wasm_web/JotaiProvider'
99
export { PromptPreview }
10-
export { default as EnvVars } from './shared/baml-project-panel/playground-panel/side-bar/env-vars'
1110
//wasm
1211
// export { default as lint, type LinterSourceFile, type LinterError, type LinterInput } from "./wasm/lint";

typescript/playground-common/src/shared/baml-project-panel/playground-panel/prompt-preview/index.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import { ResizablePanel } from '@/components/ui/resizable'
1313
import { useAtom, useAtomValue } from 'jotai'
1414
import { areTestsRunningAtom, showEnvDialogAtom } from '../atoms'
1515
import { ThemeProvider } from '../../theme/ThemeProvider'
16-
import { Dialog, DialogContent } from '@/components/ui/dialog'
17-
import EnvVars from '../side-bar/env-vars'
16+
import { EnvironmentVariablesDialog } from '../side-bar/env-vars'
17+
1818
const PromptPreview = ({ isEmbed = false }: { isEmbed?: boolean }) => {
1919
const areTestsRunning = useAtomValue(areTestsRunningAtom)
2020
const ref = useRef<ImperativePanelHandle>(null)
@@ -45,11 +45,7 @@ const PromptPreview = ({ isEmbed = false }: { isEmbed?: boolean }) => {
4545
className='flex overflow-x-auto flex-col justify-start items-start pr-2 w-full h-full'
4646
style={{ minHeight: '530px' }}
4747
>
48-
<Dialog open={showEnvDialog} onOpenChange={setShowEnvDialog}>
49-
<DialogContent className='mt-12 sm:max-w-[825px]'>
50-
<EnvVars />
51-
</DialogContent>
52-
</Dialog>
48+
<EnvironmentVariablesDialog showEnvDialog={showEnvDialog} setShowEnvDialog={setShowEnvDialog} />
5349
<ResizablePanelGroup autoSaveId={'prompt-preview'} direction='vertical' className='py-2 h-full'>
5450
<ResizablePanel defaultSize={areTestsRunning ? 40 : 80} className='flex flex-col gap-4 px-4'>
5551
<PreviewToolbar />

typescript/playground-common/src/shared/baml-project-panel/playground-panel/side-bar/env-vars.tsx

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,21 @@ import { motion } from 'motion/react'
2929

3030
const envVarVisibilityAtom = atom<Record<string, boolean>>({})
3131

32-
const REQUIRED_ENV_VAR_UNSET_WARNING = 'Clients may fail if this is not set'
32+
const REQUIRED_ENV_VAR_UNSET_WARNING = 'Your BAML clients may fail if this is not set'
3333

34-
const renderedEnvVarsAtom = atom((get) => {
35-
const envVars = get(envVarsAtom)
34+
interface EnvVarEntry {
35+
key: string
36+
value: string | undefined
37+
required: boolean
38+
hidden: boolean
39+
}
40+
41+
const renderedEnvVarsAtom = atom<EnvVarEntry[]>((get) => {
42+
const envVars = get(envVarsAtom) as Record<string, string>
3643
const requiredEnvVars = get(requiredEnvVarsAtom)
3744
const visibility = get(envVarVisibilityAtom)
3845

39-
const vars = Object.entries(envVars)
46+
const vars: EnvVarEntry[] = Object.entries(envVars)
4047
.filter(([key]) => key !== 'BOUNDARY_PROXY_URL')
4148
.map(([key, value]) => ({
4249
key,
@@ -89,7 +96,41 @@ const unescapeValue = (value: string): string => {
8996
})
9097
}
9198

92-
export default function EnvVariablesManager() {
99+
function EnvVarStatus({ value, required }: { value?: string; required: boolean }) {
100+
if (!value || value === '') {
101+
return (
102+
<TooltipProvider delayDuration={300}>
103+
<Tooltip>
104+
<TooltipTrigger asChild>
105+
<AlertTriangle className='h-4 w-4 text-orange-500 flex-shrink-0' />
106+
</TooltipTrigger>
107+
<TooltipContent side='top' className='text-xs'>
108+
{value ? 'Click to edit' : REQUIRED_ENV_VAR_UNSET_WARNING}
109+
</TooltipContent>
110+
</Tooltip>
111+
</TooltipProvider>
112+
)
113+
}
114+
115+
if (required) {
116+
return (
117+
<TooltipProvider delayDuration={300}>
118+
<Tooltip>
119+
<TooltipTrigger asChild>
120+
<Check className='h-4 w-4 text-green-500 flex-shrink-0' />
121+
</TooltipTrigger>
122+
<TooltipContent side='top' className='text-xs'>
123+
Used by one of your BAML clients
124+
</TooltipContent>
125+
</Tooltip>
126+
</TooltipProvider>
127+
)
128+
}
129+
130+
return <div />
131+
}
132+
133+
export const EnvironmentVariablesPanel: React.FC = () => {
93134
const envVars = useAtomValue(renderedEnvVarsAtom)
94135
const setEnvVars = useSetAtom(envVarsAtom)
95136
const setVisibility = useSetAtom(envVarVisibilityAtom)
@@ -229,20 +270,7 @@ export default function EnvVariablesManager() {
229270
<td className='pl-2 pr-0.5 py-0.5'>
230271
<div className='flex items-center gap-2 justify-between'>
231272
<code className='font-mono text-xs text-muted-foreground'>{env.key}</code>
232-
{!env.value || env.value === '' ? (
233-
<TooltipProvider key={env.key} delayDuration={300}>
234-
<Tooltip>
235-
<TooltipTrigger asChild>
236-
<AlertTriangle className='h-4 w-4 text-orange-500 flex-shrink-0' />
237-
</TooltipTrigger>
238-
<TooltipContent side='top' className='text-xs'>
239-
{env.value ? 'Click to edit' : REQUIRED_ENV_VAR_UNSET_WARNING}
240-
</TooltipContent>
241-
</Tooltip>
242-
</TooltipProvider>
243-
) : (
244-
<div />
245-
)}
273+
<EnvVarStatus value={env.value} required={env.required} />
246274
</div>
247275
</td>
248276
<td className='px-0.5 py-0.5'>
@@ -253,7 +281,7 @@ export default function EnvVariablesManager() {
253281
type={env.hidden ? 'password' : 'text'}
254282
value={typeof env.value === 'string' ? escapeValue(env.value) : ''}
255283
onChange={(e) => updateEnvVar(index, unescapeValue(e.target.value))}
256-
className='h-6 text-xs font-mono placeholder:font-sans'
284+
className='h-6 text-xs font-mono placeholder:font-sans min-w-32'
257285
placeholder={env.required && !env.value ? '<unset>' : undefined}
258286
autoComplete='off'
259287
data-1p-ignore
@@ -376,3 +404,16 @@ export default function EnvVariablesManager() {
376404
</div>
377405
)
378406
}
407+
408+
export const EnvironmentVariablesDialog: React.FC<{
409+
showEnvDialog: boolean
410+
setShowEnvDialog: (show: boolean) => void
411+
}> = ({ showEnvDialog, setShowEnvDialog }) => {
412+
return (
413+
<Dialog open={showEnvDialog} onOpenChange={setShowEnvDialog}>
414+
<DialogContent className='mt-12 max-h-[80vh] overflow-y-auto sm:max-w-none w-fit'>
415+
<EnvironmentVariablesPanel />
416+
</DialogContent>
417+
</Dialog>
418+
)
419+
}

typescript/playground-common/src/shared/baml-project-panel/playground-panel/side-bar/index.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-floating-promises */
22
import { Input } from '@/components/ui/input'
3-
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
43
import { ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
54
import { cn } from '@/lib/utils'
6-
import { Dialog, DialogContent } from '@radix-ui/react-dialog'
75
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
86
import {
97
AlertTriangle,
@@ -13,7 +11,6 @@ import {
1311
FlaskConical,
1412
Play,
1513
Search,
16-
Settings,
1714
XCircle,
1815
} from 'lucide-react'
1916
import { AnimatePresence, motion } from 'motion/react'
@@ -27,7 +24,7 @@ import {
2724
} from '../prompt-preview/test-panel/atoms'
2825
import { useRunBamlTests } from '../prompt-preview/test-panel/test-runner'
2926
import { getStatus } from '../prompt-preview/test-panel/testStateUtils'
30-
import EnvVars from './env-vars'
27+
import { EnvironmentVariablesDialog, EnvironmentVariablesPanel } from './env-vars'
3128
import { ScrollArea } from '@/components/ui/scroll-area'
3229
import { atomWithStorage } from 'jotai/utils'
3330
import { vscode } from '../../vscode'
@@ -38,10 +35,6 @@ interface FunctionData {
3835
tests: string[]
3936
}
4037

41-
interface SidepanelProps {
42-
functions: FunctionData[]
43-
searchTerm: string
44-
}
4538
const functionsAtom = atom((get) => {
4639
const runtimeState = get(runtimeStateAtom)
4740
if (runtimeState === undefined) {
@@ -150,12 +143,6 @@ export default function CustomSidebar({ isEmbed = false }: { isEmbed?: boolean }
150143
</div>
151144
</ScrollArea>
152145
</ResizablePanel>
153-
{/* <ResizableHandle withHandle />
154-
<ResizablePanel defaultSize={25}>
155-
<ScrollArea className="h-full" type="always">
156-
<EnvVars />
157-
</ScrollArea>
158-
</ResizablePanel> */}
159146
</ResizablePanelGroup>
160147
</div>
161148
</div>

typescript/vscode-ext/packages/web-panel/src/stories/EnvVars.stories.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ import { expect } from '@storybook/test'
22
import { DevTools } from 'jotai-devtools'
33
import 'jotai-devtools/styles.css'
44
import { atom, createStore, useAtomValue, useSetAtom } from 'jotai'
5-
import { default as EnvVars } from '../shared/baml-project-panel/playground-panel/side-bar/env-vars'
5+
import {
6+
EnvironmentVariablesDialog,
7+
EnvironmentVariablesPanel,
8+
} from '../shared/baml-project-panel/playground-panel/side-bar/env-vars'
69
import { Provider as JotaiProvider } from 'jotai'
710
import { ThemeProvider } from 'next-themes'
811
import '../App.css'
912
import { envVarsAtom } from '../shared/baml-project-panel/atoms'
1013
import { useState } from 'react'
14+
import { Dialog, DialogContent } from '@/components/ui/dialog'
1115

1216
interface JotaiProviderProps {
1317
envVars: Record<string, string>
@@ -26,7 +30,7 @@ const WrappedEnvVars: React.FC = () => {
2630
<div>
2731
<ThemeProvider attribute='class' defaultTheme='dark' enableSystem={false} disableTransitionOnChange={true}>
2832
<div className='flex gap-8 items-start'>
29-
<EnvVars />
33+
<EnvironmentVariablesPanel />
3034
<div className='p-4 bg-[#1e1e1e] rounded-lg min-w-[300px]'>
3135
<h3 className='mb-2 text-sm font-mono'>JSON.stringify(useAtomValue(envVarsAtom))</h3>
3236
<pre className='text-xs'>{JSON.stringify(envVars, null, 2)}</pre>
@@ -137,3 +141,36 @@ export const TableWith100EnvVars = {
137141
),
138142
],
139143
}
144+
145+
export const TableWith100EnvVarsInDialog = {
146+
decorators: [
147+
(Story: React.FC) => (
148+
<JotaiStorybookProvider
149+
envVars={{
150+
ANTHROPIC_API_KEY: 'sk-ant456',
151+
COHERE_API_KEY: 'sk-coh789',
152+
OPENAI_API_KEY: 'sk-test123',
153+
...Object.fromEntries(Array.from({ length: 100 }, (_, i) => `VAR_${i}`).map((key) => [key, `value_${key}`])),
154+
}}
155+
>
156+
<EnvironmentVariablesDialog showEnvDialog={true} setShowEnvDialog={() => {}} />
157+
</JotaiStorybookProvider>
158+
),
159+
],
160+
}
161+
162+
export const VeryLongEnvVarNameInDialog = {
163+
decorators: [
164+
(Story: React.FC) => (
165+
<JotaiStorybookProvider
166+
envVars={{
167+
ANTHROPIC_API_KEY: 'line1\nline2\nline3',
168+
LONG_ENV_VAR_NAME_THAT_EXCEEDS_MAX_WIDTH_OF_THE_TABLE_CELL: 'sk-test123',
169+
OPENAI_API_KEY: 'sk-test123',
170+
}}
171+
>
172+
<EnvironmentVariablesDialog showEnvDialog={true} setShowEnvDialog={() => {}} />
173+
</JotaiStorybookProvider>
174+
),
175+
],
176+
}

0 commit comments

Comments
 (0)