Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/public/humans.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Emmett Folger
Erfi Anugrah
Eric Kharitonashvili
Etienne Stalmans
Evan Cummack
Eyal Ehrlich
Fabrizio Fenoglio
Fady A
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ interface EndpointRowProps {
methodColumnWidth: string
}

/** Pan slowly enough to read while revealing (~70px/s), but never snap for short distances. */
const panDurationMs = (distance: number) => Math.max(300, Math.round(distance * 14))

/**
* One copyable endpoint. The muted prefix span shrinks with an end-ellipsis while the
* distinguishing segment stays fixed-width — visually equivalent to truncating the full path in
Expand All @@ -33,23 +30,12 @@ export const EndpointRow = ({
const { prefix, distinguishing } = splitEndpointPath(path, sharedPrefix)

const pathContainerRef = useRef<HTMLSpanElement>(null)
const [panDistance, setPanDistance] = useState(0)
const [isRevealed, setIsRevealed] = useState(false)
const [isCopied, setIsCopied] = useState(false)
const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
const [showCopiedIcon, setShowCopiedIcon] = useState(false)

useEffect(() => () => clearTimeout(copiedTimerRef.current), [])

// Measured when the reveal starts, not on mount: rows live inside accordion content that mounts
// collapsed, so resting measurements are taken before the row has its real width.
const handleRevealStart = () => {
const container = pathContainerRef.current
if (container) setPanDistance(Math.max(0, container.scrollWidth - container.clientWidth))
setIsRevealed(true)
}
const handleRevealEnd = () => setIsRevealed(false)

const handleCopy = () => {
copyToClipboard(path, () => {
setIsCopied(true)
Expand All @@ -64,10 +50,6 @@ export const EndpointRow = ({
type="button"
tabIndex={0}
onClick={handleCopy}
onMouseEnter={handleRevealStart}
onMouseLeave={handleRevealEnd}
onFocus={handleRevealStart}
onBlur={handleRevealEnd}
aria-label={`Copy ${method} ${path}`}
className="group flex w-full items-center gap-2 py-1.5 text-left"
>
Expand All @@ -80,27 +62,35 @@ export const EndpointRow = ({
<span
ref={pathContainerRef}
className={cn(
'flex min-w-0 flex-1 overflow-hidden whitespace-nowrap font-mono text-xs',
// The fade only paints over text that reaches the container's right edge, so short rows
// are unaffected; dropped while panning so the revealed tail stays readable.
!isRevealed && '[mask-image:linear-gradient(to_right,black_calc(100%-2rem),transparent)]'
'block inline-full overflow-hidden',
// group allows to have the hover effect on children (see below)
'group',
// container to allow hover effect size computations
'@container',
'[mask-image:linear-gradient(to_right,black_calc(100%-2rem),transparent)]'
)}
>
<span
className="flex min-w-0 transition-transform ease-linear motion-reduce:transition-none"
style={{
transform:
isRevealed && panDistance > 0 ? `translateX(-${panDistance}px)` : 'translateX(0)',
// Pausing before panning lets a quick mouse pass leave the row untouched; the pan
// back starts immediately and faster, so the row settles as soon as it's left.
transitionDuration: isRevealed ? `${panDurationMs(panDistance)}ms` : '200ms',
transitionDelay: isRevealed ? '400ms' : '0ms',
}}
>
{prefix !== '' && (
<span className="overflow-hidden text-ellipsis text-foreground-lighter">{prefix}</span>
className={cn(
'font-mono text-xs',
// This is necessary for the reveal on hover animation
'block inline-[max-content] whitespace-nowrap text-nowrap',
'transition-transform ease-linear motion-reduce:transition-none duration-1000 delay-400',
// This ensure no GPU jump when non-scrolling items are hovered
'translate-0',
// If the content (102% to have a small right margin) exceeds the container size (100cqi), the calc result will be a negative number
// This ensures small items don't translate
'group-hover:translate-x-[min(0px,calc(100cqi-102%))]'
)}
<span className="shrink-0 text-foreground">{distinguishing}</span>
>
<span>
{prefix !== '' && (
<span className="overflow-hidden text-ellipsis text-foreground-lighter">
{prefix}
</span>
)}
<span className="shrink-0 text-foreground">{distinguishing}</span>
</span>
</span>
</span>
<span className="shrink-0 pl-2">
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/components/interfaces/App/AppBannerWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => {
id: 'tos-update-banner',
isDismissed: false,
content: <BannerTOSUpdate />,
priority: 2,
priority: 0,
})
} else {
dismissBanner('tos-update-banner')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,13 @@ export const useIsColumnLevelPrivilegesEnabled = () => {
}

export const useUnifiedLogsPreview = () => {
const unifiedLogsDefaultOptIn = useFlag('unifiedLogsDefaultOptIn')
const { flags, isInitialized, onUpdateFlag } = useFeaturePreviewContext()

const isLoading = !isInitialized
const isEnabled = IS_PLATFORM && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS]

const hasToggledPreview = !!safeLocalStorage.getItem(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS)
const isDefaultOptIn =
IS_PLATFORM && isInitialized && unifiedLogsDefaultOptIn && !hasToggledPreview
const isDefaultOptIn = IS_PLATFORM && !hasToggledPreview

const enable = () => onUpdateFlag(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, true)
const disable = () => onUpdateFlag(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
const isDatabaseConnectionsEnabled = useFlag('topForPostgres')

const unifiedLogsDefaultOptIn = useFlag('unifiedLogsDefaultOptIn')
const isSqlEditorManualSaveForced = useFlag('sqlEditorManualSaveForced')

return useMemo(() => {
Expand All @@ -49,7 +48,7 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
enabled: true,
isNew: true,
isPlatformOnly: true,
isDefaultOptIn: unifiedLogsDefaultOptIn,
isDefaultOptIn: true,
getRoute: (ref?: string) => `/project/${ref}/logs`,
},
{
Expand Down Expand Up @@ -142,7 +141,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {

return previews.sort((a, b) => Number(b.isNew) - Number(a.isNew))
}, [
unifiedLogsDefaultOptIn,
isSqlEditorManualSaveForced,
isPlatformWebhooksEnabled,
jitDbAccessEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@ import {
TooltipTrigger,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import {
MultiSelector,
MultiSelectorContent,
MultiSelectorItem,
MultiSelectorList,
MultiSelectorTrigger,
} from 'ui-patterns/multi-select'

import { ExplorerToolbarAction } from '../ExplorerToolbar'
import { type QueryDisplay, type QueryResult } from '../types'
import { checkHasNonPositiveValues } from '@/components/ui/QueryBlock/QueryBlock.utils'
import { type ChartConfig } from '@/data/content/notebooks/notebook-schema'
import { MAX_CHART_Y_COLUMNS, type ChartConfig } from '@/data/content/notebooks/notebook-schema'

interface DisplaySettingsButtonProps {
display: QueryDisplay
Expand All @@ -32,9 +39,11 @@ interface DisplaySettingsButtonProps {
onChange: (display: QueryDisplay) => void
}

// [Joshen] TODO support multiple y axis charts
// [Joshen] TODO onUpdateChartConfig can likely be shifted into the notebook-state
// so this component doesn't need to know about other cells
const getLogScaleDisabledReason = (y_columns: string[]) => {
if (y_columns.length === 0) return 'Select a column for the Y axis first'
if (y_columns.length > 1) return 'Only available with a single Y axis column'
return 'Data contains zero or negative values'
}

export const DisplaySettingsButton = ({
display,
Expand All @@ -58,8 +67,9 @@ export const DisplaySettingsButton = ({
[result, y_columns]
)

// Logarithmic scale only applies to a single series.
const canToggleLogScale = useMemo(() => {
if (y_columns.length === 0 || !result || (result.rows ?? []).length === 0) return false
if (y_columns.length !== 1 || !result || (result.rows ?? []).length === 0) return false
return !hasNonPositiveValues
}, [hasNonPositiveValues, result, y_columns.length])

Expand Down Expand Up @@ -87,17 +97,17 @@ export const DisplaySettingsButton = ({
})

useEffect(() => {
if (hasNonPositiveValues && scale === 'log') {
if (scale === 'log' && (hasNonPositiveValues || y_columns.length > 1)) {
resetToLinearScale()
}
}, [hasNonPositiveValues, scale])
}, [hasNonPositiveValues, scale, y_columns.length])

return (
<Popover>
<PopoverTrigger asChild>
<ExplorerToolbarAction disabled={disabled} icon={<Settings2 />} tooltip="Result settings" />
</PopoverTrigger>
<PopoverContent side="bottom" className="flex flex-col gap-y-3 p-0 py-3 mr-8">
<PopoverContent side="bottom" className="flex flex-col gap-y-3 p-0 py-3 mr-8 w-80">
<div className="flex flex-col gap-y-3 px-3">
<p className="text-xs tracking-tighter uppercase font-mono text-foreground-lighter">
Result display settings
Expand Down Expand Up @@ -147,12 +157,17 @@ export const DisplaySettingsButton = ({
</FormItemLayout>

<div className="px-3 flex flex-col gap-y-2">
<FormItemLayout isReactForm={false} layout="flex-row-reverse" label="X axis">
<FormItemLayout
isReactForm={false}
layout="flex-row-reverse"
label="X axis"
className="[&>div:first-child]:xl:w-3/5"
>
<Select
value={x_column}
onValueChange={(x_column) => onUpdateChartConfig({ x_column })}
>
<SelectTrigger className="w-32">
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a column" />
</SelectTrigger>
<SelectContent>
Expand All @@ -165,32 +180,59 @@ export const DisplaySettingsButton = ({
</Select>
</FormItemLayout>

<FormItemLayout isReactForm={false} layout="flex-row-reverse" label="Y Axis">
<Select
value={y_columns?.[0]}
onValueChange={(y_column) => onUpdateChartConfig({ y_columns: [y_column] })}
<FormItemLayout
isReactForm={false}
layout="flex-row-reverse"
label="Y Axis"
className="[&>div:first-child]:xl:w-3/5"
>
<MultiSelector
values={y_columns}
onValuesChange={(values) => {
if (values.length > MAX_CHART_Y_COLUMNS) return
onUpdateChartConfig({ y_columns: values })
}}
className="w-full"
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Select a column" />
</SelectTrigger>
<SelectContent>
{columns.map((x) => (
<SelectItem key={x} value={x}>
{x}
</SelectItem>
))}
</SelectContent>
</Select>
<MultiSelectorTrigger
mode="inline-combobox"
label={`Select up to ${MAX_CHART_Y_COLUMNS} columns`}
deletableBadge
badgeLimit="wrap"
showIcon={false}
className="min-w-32!"
/>
<MultiSelectorContent>
<MultiSelectorList>
{columns.map((x) => (
<MultiSelectorItem
key={x}
value={x}
disabled={
y_columns.length >= MAX_CHART_Y_COLUMNS && !y_columns.includes(x)
}
>
{x}
</MultiSelectorItem>
))}
</MultiSelectorList>
</MultiSelectorContent>
</MultiSelector>
</FormItemLayout>

<FormItemLayout isReactForm={false} layout="flex-row-reverse" label="Scale">
<FormItemLayout
isReactForm={false}
layout="flex-row-reverse"
label="Scale"
className="[&>div:first-child]:xl:w-3/5"
>
<Select
value={scale}
onValueChange={(scale) =>
onUpdateChartConfig({ scale: scale as 'log' | 'linear' })
}
>
<SelectTrigger className="w-32">
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a column" />
</SelectTrigger>
<SelectContent>
Expand All @@ -207,9 +249,7 @@ export const DisplaySettingsButton = ({
</TooltipTrigger>
{!canToggleLogScale && (
<TooltipContent side="left">
{y_columns.length === 0
? 'Select a column for the Y axis first'
: 'Data contains zero or negative values'}
{getLogScaleDisabledReason(y_columns)}
</TooltipContent>
)}
</Tooltip>
Expand Down
Loading
Loading