Refine insight workspace global filters (#285)
This commit is contained in:
parent
2addc93814
commit
8b95a0d764
@ -47,12 +47,6 @@ const defaultRecord = <T,>(value: T): Record<QueryLanguage, T> => ({
|
||||
traceql: value
|
||||
})
|
||||
|
||||
const orgOptions = ['global-org', 'ecommerce', 'payments']
|
||||
const projectOptions = ['observability', 'checkout', 'growth']
|
||||
const envOptions = ['production', 'staging', 'dev']
|
||||
const regionOptions = ['us-west-2', 'eu-central-1', 'ap-southeast-1']
|
||||
const timeRanges = ['15m', '1h', '6h', '24h', '7d']
|
||||
|
||||
export function ExploreBuilder({
|
||||
state,
|
||||
updateState,
|
||||
@ -236,18 +230,9 @@ export function ExploreBuilder({
|
||||
{!isCollapsed && (
|
||||
<div className="mt-4 flex flex-1 flex-col gap-4">
|
||||
{inputMode === 'menu' ? (
|
||||
<div className="grid gap-3 text-xs text-slate-200 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<MenuSelect label="Org" value={state.org} onChange={value => updateState({ org: value })} options={orgOptions} />
|
||||
<MenuSelect
|
||||
label="Project"
|
||||
value={state.project}
|
||||
onChange={value => updateState({ project: value })}
|
||||
options={projectOptions}
|
||||
/>
|
||||
<MenuSelect label="Environment" value={state.env} onChange={value => updateState({ env: value })} options={envOptions} />
|
||||
<MenuSelect label="Region" value={state.region} onChange={value => updateState({ region: value })} options={regionOptions} />
|
||||
<MenuTimeRange value={state.timeRange} onChange={value => updateState({ timeRange: value })} />
|
||||
<div className="sm:col-span-2 lg:col-span-4">
|
||||
<div className="space-y-3 text-xs text-slate-200">
|
||||
<ContextSummary state={state} />
|
||||
<div>
|
||||
<p className="text-[11px] uppercase tracking-wide text-slate-500">Query preview</p>
|
||||
<pre className="mt-2 max-h-32 overflow-auto rounded-2xl border border-slate-800 bg-slate-950/60 p-3 text-xs text-emerald-200">
|
||||
{state.queries[language] || meta.placeholder}
|
||||
@ -328,63 +313,29 @@ export function ExploreBuilder({
|
||||
return <div className="space-y-4">{panels}</div>
|
||||
}
|
||||
|
||||
interface MenuSelectProps {
|
||||
label: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
options: string[]
|
||||
}
|
||||
function ContextSummary({ state }: { state: InsightState }) {
|
||||
const context = [
|
||||
{ label: 'Org', value: state.org },
|
||||
{ label: 'Environment', value: state.env },
|
||||
{ label: 'Region', value: state.region },
|
||||
{ label: 'Project', value: state.project },
|
||||
{ label: 'Time range', value: state.timeRange }
|
||||
]
|
||||
|
||||
function MenuSelect({ label, value, onChange, options }: MenuSelectProps) {
|
||||
return (
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-[11px] uppercase tracking-wide text-slate-500">{label}</span>
|
||||
<select
|
||||
value={value}
|
||||
onChange={event => onChange(event.target.value)}
|
||||
className="rounded-xl border border-slate-800 bg-slate-950/60 px-3 py-2 text-sm text-slate-200"
|
||||
>
|
||||
{options.map(option => (
|
||||
<option key={option} value={option} className="bg-slate-900 text-slate-200">
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
interface MenuTimeRangeProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
function MenuTimeRange({ value, onChange }: MenuTimeRangeProps) {
|
||||
return (
|
||||
<label className="flex flex-col gap-1 sm:col-span-2 lg:col-span-4">
|
||||
<span className="text-[11px] uppercase tracking-wide text-slate-500">Time range</span>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{timeRanges.map(range => (
|
||||
<button
|
||||
key={range}
|
||||
type="button"
|
||||
onClick={() => onChange(range)}
|
||||
className={`rounded-xl px-3 py-1 text-xs font-medium transition ${
|
||||
value === range
|
||||
? 'bg-emerald-500/20 text-emerald-200'
|
||||
: 'border border-slate-800 bg-slate-950/60 text-slate-400 hover:text-slate-100'
|
||||
}`}
|
||||
<div className="rounded-2xl border border-slate-800 bg-slate-950/60 p-3">
|
||||
<p className="text-[11px] uppercase tracking-wide text-slate-500">Global context</p>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2">
|
||||
{context.map(item => (
|
||||
<span
|
||||
key={item.label}
|
||||
className="flex items-center gap-2 rounded-full border border-slate-800 bg-slate-900/70 px-3 py-1 text-xs text-slate-200"
|
||||
>
|
||||
{range}
|
||||
</button>
|
||||
<span className="text-slate-500">{item.label}</span>
|
||||
<span className="font-medium text-slate-100">{item.value}</span>
|
||||
</span>
|
||||
))}
|
||||
<input
|
||||
value={value}
|
||||
onChange={event => onChange(event.target.value)}
|
||||
className="w-32 rounded-xl border border-slate-800 bg-slate-950/60 px-3 py-1.5 text-xs text-slate-200"
|
||||
placeholder="Custom"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from 'react'
|
||||
import { InsightState } from '../../insight/store/urlState'
|
||||
import { TimeRangePicker } from './TimeRangePicker'
|
||||
|
||||
interface BreadcrumbBarProps {
|
||||
state: InsightState
|
||||
@ -28,17 +29,19 @@ export function BreadcrumbBar({ state, updateState, shareableLink }: BreadcrumbB
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-3 text-sm text-slate-200">
|
||||
<div className="flex w-full flex-wrap items-center gap-3 text-sm text-slate-200">
|
||||
<Selector label="Org" value={state.org} options={orgs} onChange={org => updateState({ org })} />
|
||||
<Separator />
|
||||
<Selector label="Environment" value={state.env} options={envs} onChange={env => updateState({ env })} />
|
||||
<Separator />
|
||||
<Selector label="Region" value={state.region} options={regions} onChange={region => updateState({ region })} />
|
||||
<Separator />
|
||||
<Selector label="Project" value={state.project} options={projects} onChange={project => updateState({ project })} />
|
||||
<Separator />
|
||||
<Selector label="Env" value={state.env} options={envs} onChange={env => updateState({ env })} />
|
||||
<Separator />
|
||||
<Selector label="Region" value={state.region} options={regions} onChange={region => updateState({ region })} />
|
||||
<TimeRangePicker state={state} updateState={updateState} />
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="ml-auto flex items-center gap-2 rounded-xl border border-slate-700 px-3 py-1.5 text-xs font-medium text-slate-200 hover:bg-slate-800 transition"
|
||||
className="ml-auto flex items-center gap-2 rounded-xl border border-slate-700 px-3 py-1.5 text-xs font-medium text-slate-200 transition hover:bg-slate-800"
|
||||
>
|
||||
{copied ? 'Link copied!' : 'Copy share link'}
|
||||
</button>
|
||||
@ -58,12 +61,12 @@ function Selector({
|
||||
onChange: (value: string) => void
|
||||
}) {
|
||||
return (
|
||||
<label className="flex items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/80 px-3 py-1.5 shadow-inner">
|
||||
<label className="flex min-w-[160px] flex-1 items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/80 px-3 py-1.5 shadow-inner">
|
||||
<span className="text-xs uppercase tracking-wide text-slate-500">{label}</span>
|
||||
<select
|
||||
value={value}
|
||||
onChange={event => onChange(event.target.value)}
|
||||
className="bg-transparent text-sm font-medium focus:outline-none"
|
||||
className="w-full bg-transparent text-sm font-medium focus:outline-none"
|
||||
>
|
||||
{options.map(option => (
|
||||
<option key={option} value={option} className="bg-slate-900">
|
||||
|
||||
@ -12,12 +12,12 @@ const ranges = ['15m', '1h', '6h', '24h', '7d']
|
||||
|
||||
export function TimeRangePicker({ state, updateState }: TimeRangePickerProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/80 px-3 py-2 text-sm text-slate-200 shadow-inner">
|
||||
<span className="text-xs uppercase tracking-wide text-slate-500">Time Range</span>
|
||||
<label className="flex min-w-[160px] flex-1 items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/80 px-3 py-1.5 text-sm text-slate-200 shadow-inner">
|
||||
<span className="text-xs uppercase tracking-wide text-slate-500">Time range</span>
|
||||
<select
|
||||
value={state.timeRange}
|
||||
onChange={event => updateState({ timeRange: event.target.value })}
|
||||
className="bg-transparent focus:outline-none text-sm font-medium"
|
||||
className="w-full bg-transparent text-sm font-medium focus:outline-none"
|
||||
>
|
||||
{ranges.map(range => (
|
||||
<option key={range} value={range} className="bg-slate-900">
|
||||
@ -28,6 +28,6 @@ export function TimeRangePicker({ state, updateState }: TimeRangePickerProps) {
|
||||
Custom window
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
@ -10,14 +10,8 @@ interface NetworkTopologyPanelProps {
|
||||
updateState: (partial: Partial<InsightState>) => void
|
||||
}
|
||||
|
||||
const orgOptions = ['global-org', 'ecommerce', 'payments']
|
||||
const projectOptions = ['observability', 'checkout', 'growth']
|
||||
const envOptions = ['production', 'staging', 'dev']
|
||||
const regionOptions = ['us-west-2', 'eu-central-1', 'ap-southeast-1']
|
||||
const timeRanges = ['15m', '1h', '6h', '24h', '7d']
|
||||
|
||||
export function NetworkTopologyPanel({ state, updateState }: NetworkTopologyPanelProps) {
|
||||
const [filtersCollapsed, setFiltersCollapsed] = useState(false)
|
||||
const [advancedVisible, setAdvancedVisible] = useState(false)
|
||||
|
||||
const subtitle = useMemo(
|
||||
() => `${state.env} · ${state.region} · ${state.org}/${state.project}`,
|
||||
@ -37,10 +31,10 @@ export function NetworkTopologyPanel({ state, updateState }: NetworkTopologyPane
|
||||
<div className="ml-auto flex flex-wrap items-center gap-2 text-xs text-slate-300">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFiltersCollapsed(prev => !prev)}
|
||||
onClick={() => setAdvancedVisible(prev => !prev)}
|
||||
className="rounded-xl border border-slate-800 px-3 py-1 transition hover:border-slate-700 hover:text-slate-100"
|
||||
>
|
||||
{filtersCollapsed ? 'Expand filters' : 'Collapse filters'}
|
||||
{advancedVisible ? 'Hide advanced filters' : 'Show advanced filters'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@ -52,20 +46,7 @@ export function NetworkTopologyPanel({ state, updateState }: NetworkTopologyPane
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="mt-4 grid gap-3 text-xs text-slate-200 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<SelectField label="Org" value={state.org} onChange={value => updateState({ org: value })} options={orgOptions} />
|
||||
<SelectField
|
||||
label="Project"
|
||||
value={state.project}
|
||||
onChange={value => updateState({ project: value })}
|
||||
options={projectOptions}
|
||||
/>
|
||||
<SelectField label="Environment" value={state.env} onChange={value => updateState({ env: value })} options={envOptions} />
|
||||
<SelectField label="Region" value={state.region} onChange={value => updateState({ region: value })} options={regionOptions} />
|
||||
<TimeRangeControls value={state.timeRange} onChange={value => updateState({ timeRange: value })} />
|
||||
</div>
|
||||
|
||||
{!filtersCollapsed && (
|
||||
{advancedVisible && (
|
||||
<div className="mt-4 flex flex-wrap gap-3 text-xs text-slate-300">
|
||||
<TextField
|
||||
label="Namespace"
|
||||
@ -79,12 +60,6 @@ export function NetworkTopologyPanel({ state, updateState }: NetworkTopologyPane
|
||||
placeholder="all services"
|
||||
onChange={value => updateState({ service: value })}
|
||||
/>
|
||||
<TextField
|
||||
label="Time window"
|
||||
value={state.timeRange}
|
||||
placeholder="1h"
|
||||
onChange={value => updateState({ timeRange: value })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -95,67 +70,6 @@ export function NetworkTopologyPanel({ state, updateState }: NetworkTopologyPane
|
||||
)
|
||||
}
|
||||
|
||||
interface SelectFieldProps {
|
||||
label: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
options: string[]
|
||||
}
|
||||
|
||||
function SelectField({ label, value, onChange, options }: SelectFieldProps) {
|
||||
return (
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-[11px] uppercase tracking-wide text-slate-500">{label}</span>
|
||||
<select
|
||||
value={value}
|
||||
onChange={event => onChange(event.target.value)}
|
||||
className="rounded-xl border border-slate-800 bg-slate-950/60 px-3 py-2 text-sm text-slate-200"
|
||||
>
|
||||
{options.map(option => (
|
||||
<option key={option} value={option} className="bg-slate-900 text-slate-200">
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
interface TimeRangeControlsProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
function TimeRangeControls({ value, onChange }: TimeRangeControlsProps) {
|
||||
return (
|
||||
<label className="flex flex-col gap-1 sm:col-span-2 lg:col-span-4">
|
||||
<span className="text-[11px] uppercase tracking-wide text-slate-500">Time range</span>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{timeRanges.map(range => (
|
||||
<button
|
||||
key={range}
|
||||
type="button"
|
||||
onClick={() => onChange(range)}
|
||||
className={`rounded-xl px-3 py-1 text-xs font-medium transition ${
|
||||
value === range
|
||||
? 'bg-emerald-500/20 text-emerald-200'
|
||||
: 'border border-slate-800 bg-slate-950/60 text-slate-400 hover:text-slate-100'
|
||||
}`}
|
||||
>
|
||||
{range}
|
||||
</button>
|
||||
))}
|
||||
<input
|
||||
value={value}
|
||||
onChange={event => onChange(event.target.value)}
|
||||
className="w-32 rounded-xl border border-slate-800 bg-slate-950/60 px-3 py-1.5 text-xs text-slate-200"
|
||||
placeholder="Custom"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
interface TextFieldProps {
|
||||
label: string
|
||||
value: string
|
||||
|
||||
Loading…
Reference in New Issue
Block a user