diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 99aaeebe6e1f4..4366f56b139a2 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -98,6 +98,7 @@ Emmett Folger Erfi Anugrah Eric Kharitonashvili Etienne Stalmans +Evan Cummack Eyal Ehrlich Fabrizio Fenoglio Fady A diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/TokenCapabilities/EndpointRow.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/TokenCapabilities/EndpointRow.tsx index bec181ee5175d..ccaf55a485292 100644 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/TokenCapabilities/EndpointRow.tsx +++ b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/TokenCapabilities/EndpointRow.tsx @@ -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 @@ -33,23 +30,12 @@ export const EndpointRow = ({ const { prefix, distinguishing } = splitEndpointPath(path, sharedPrefix) const pathContainerRef = useRef(null) - const [panDistance, setPanDistance] = useState(0) - const [isRevealed, setIsRevealed] = useState(false) const [isCopied, setIsCopied] = useState(false) const copiedTimerRef = useRef | 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) @@ -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" > @@ -80,27 +62,35 @@ export const EndpointRow = ({ 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 !== '' && ( - {prefix} + 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%))]' )} - {distinguishing} + > + + {prefix !== '' && ( + + {prefix} + + )} + {distinguishing} + diff --git a/apps/studio/components/interfaces/App/AppBannerWrapper.tsx b/apps/studio/components/interfaces/App/AppBannerWrapper.tsx index 832f2769a756d..dbb1d6cef9601 100644 --- a/apps/studio/components/interfaces/App/AppBannerWrapper.tsx +++ b/apps/studio/components/interfaces/App/AppBannerWrapper.tsx @@ -30,7 +30,7 @@ export const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => { id: 'tos-update-banner', isDismissed: false, content: , - priority: 2, + priority: 0, }) } else { dismissBanner('tos-update-banner') diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx index 03691172a714d..2eeb77a318fc3 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx @@ -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) diff --git a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts index a30be45c94144..607337c5384a3 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts +++ b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts @@ -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(() => { @@ -49,7 +48,7 @@ export const useFeaturePreviews = (): FeaturePreview[] => { enabled: true, isNew: true, isPlatformOnly: true, - isDefaultOptIn: unifiedLogsDefaultOptIn, + isDefaultOptIn: true, getRoute: (ref?: string) => `/project/${ref}/logs`, }, { @@ -142,7 +141,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => { return previews.sort((a, b) => Number(b.isNew) - Number(a.isNew)) }, [ - unifiedLogsDefaultOptIn, isSqlEditorManualSaveForced, isPlatformWebhooksEnabled, jitDbAccessEnabled, diff --git a/apps/studio/components/interfaces/Explorer/QueryEditor/DisplaySettingsButton.tsx b/apps/studio/components/interfaces/Explorer/QueryEditor/DisplaySettingsButton.tsx index b8eb3785b0e6b..e093b3c2ddcac 100644 --- a/apps/studio/components/interfaces/Explorer/QueryEditor/DisplaySettingsButton.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryEditor/DisplaySettingsButton.tsx @@ -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 @@ -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, @@ -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]) @@ -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 ( } tooltip="Result settings" /> - +

Result display settings @@ -147,12 +157,17 @@ export const DisplaySettingsButton = ({

- + - - + + + + {columns.map((x) => ( + = MAX_CHART_Y_COLUMNS && !y_columns.includes(x) + } + > + {x} + + ))} + + + - +