Skip to content

Commit

Permalink
feat(llm-observability): Sleek(ish) dashboard (#27510)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Twixes and github-actions[bot] authored Jan 14, 2025
1 parent dff31cb commit 0220d54
Show file tree
Hide file tree
Showing 21 changed files with 381 additions and 136 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 1 addition & 8 deletions frontend/src/lib/components/Cards/CardMeta.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,6 @@
background: var(--bg-light);
border-radius: var(--radius);

&--with-details {
.CardMeta__top {
// Reduced height so that, considering the padding set above, CardMeta__top doesn't have too much margin
height: 1.5rem;
}
}

h5 {
margin-bottom: 0;
overflow: hidden;
Expand Down Expand Up @@ -163,7 +156,7 @@
align-items: center;
align-self: stretch;
justify-content: space-between;
height: 2rem;
height: 1.5rem;

.LemonButton {
height: 1.75rem;
Expand Down
13 changes: 3 additions & 10 deletions frontend/src/lib/components/Cards/CardMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface CardMetaProps extends Pick<React.HTMLAttributes<HTMLDivElement>
showDetailsControls?: boolean
refresh?: () => void
refreshDisabledReason?: string
meta?: JSX.Element | null
content?: JSX.Element | null
metaDetails?: JSX.Element | null
moreButtons?: JSX.Element | null
topHeading?: JSX.Element | null
Expand All @@ -40,7 +40,7 @@ export function CardMeta({
showDetailsControls,
refresh,
refreshDisabledReason,
meta,
content: meta,
metaDetails,
moreButtons,
topHeading,
Expand All @@ -55,14 +55,7 @@ export function CardMeta({
const showDetailsButtonLabel = !!primaryWidth && primaryWidth > 480

return (
<div
className={clsx(
'CardMeta',
className,
showDetailsControls && 'CardMeta--with-details',
areDetailsShown && 'CardMeta--details-shown'
)}
>
<div className={clsx('CardMeta', className, areDetailsShown && 'CardMeta--details-shown')}>
<div className="CardMeta__primary" ref={primaryRef}>
{ribbonColor &&
ribbonColor !==
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/lib/components/Cards/InsightCard/InsightCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
import { ResizeHandle1D, ResizeHandle2D } from '../handles'
import { InsightMeta } from './InsightMeta'

export interface InsightCardProps extends Resizeable, React.HTMLAttributes<HTMLDivElement> {
export interface InsightCardProps extends Resizeable {
/** Insight to display. */
insight: QueryBasedInsightModel
/** id of the dashboard the card is on (when the card is being displayed on a dashboard) **/
Expand Down Expand Up @@ -63,6 +63,8 @@ export interface InsightCardProps extends Resizeable, React.HTMLAttributes<HTMLD
doNotLoad?: boolean
/** Dashboard variables to override the ones in the insight */
variablesOverride?: Record<string, HogQLVariable>
className?: string
style?: React.CSSProperties
}

function InsightCardInternal(
Expand All @@ -88,7 +90,6 @@ function InsightCardInternal(
duplicate,
moveToDashboard,
className,
children,
moreButtons,
placement,
loadPriority,
Expand Down Expand Up @@ -168,7 +169,6 @@ function InsightCardInternal(
{canResizeWidth ? <ResizeHandle2D /> : null}
</>
)}
{children /* Extras, such as resize handles */}
</ErrorBoundary>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Template: StoryFn<{ insight: InsightModel }> = ({ insight: legacyInsight }
const insight = getQueryBasedInsightModel(legacyInsight)
return (
<div className="bg-bg-light w-[24rem] p-4 rounded">
<InsightDetailsComponent insight={insight} />
<InsightDetailsComponent query={insight.query} footerInfo={insight} />
</div>
)
}
Expand Down
93 changes: 53 additions & 40 deletions frontend/src/lib/components/Cards/InsightCard/InsightDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
isTrendsQuery,
isValidBreakdown,
} from '~/queries/utils'
import { AnyPropertyFilter, FilterLogicalOperator, PropertyGroupFilter, QueryBasedInsightModel } from '~/types'
import { AnyPropertyFilter, FilterLogicalOperator, PropertyGroupFilter, UserBasicType } from '~/types'

import { PropertyKeyInfo } from '../../PropertyKeyInfo'
import { TZLabel } from '../../TZLabel'
Expand Down Expand Up @@ -387,46 +387,59 @@ export function BreakdownSummary({ query }: { query: InsightQueryNode }): JSX.El
)
}

function InsightDetailsInternal(
{ insight }: { insight: QueryBasedInsightModel },
ref: React.Ref<HTMLDivElement>
): JSX.Element {
const { created_at, created_by, query } = insight
interface InsightDetailsProps {
query: Node | null
footerInfo?: {
created_at: string
created_by: UserBasicType | null
last_modified_by: UserBasicType | null
last_modified_at: string
last_refresh: string | null
}
}

// TODO: Implement summaries for HogQL query insights
return (
<div className="InsightDetails" ref={ref}>
{isInsightVizNode(query) && (
<>
<SeriesSummary query={query.source} />
<PropertiesSummary properties={query.source.properties} />
<BreakdownSummary query={query.source} />
</>
)}
<div className="InsightDetails__footer">
<div>
<h5>Created by</h5>
<section>
<ProfilePicture user={created_by} showName size="md" /> <TZLabel time={created_at} />
</section>
</div>
<div>
<h5>Last modified by</h5>
<section>
<ProfilePicture user={insight.last_modified_by} showName size="md" />{' '}
<TZLabel time={insight.last_modified_at} />
</section>
</div>
{insight.last_refresh && (
<div>
<h5>Last computed</h5>
<section>
<TZLabel time={insight.last_refresh} />
</section>
export const InsightDetails = React.memo(
React.forwardRef<HTMLDivElement, InsightDetailsProps>(function InsightDetailsInternal(
{ query, footerInfo },
ref
): JSX.Element {
// TODO: Implement summaries for HogQL query insights
return (
<div className="InsightDetails" ref={ref}>
{isInsightVizNode(query) && (
<>
<SeriesSummary query={query.source} />
<PropertiesSummary properties={query.source.properties} />
<BreakdownSummary query={query.source} />
</>
)}
{footerInfo && (
<div className="InsightDetails__footer">
<div>
<h5>Created by</h5>
<section>
<ProfilePicture user={footerInfo.created_by} showName size="md" />{' '}
<TZLabel time={footerInfo.created_at} />
</section>
</div>
<div>
<h5>Last modified by</h5>
<section>
<ProfilePicture user={footerInfo.last_modified_by} showName size="md" />{' '}
<TZLabel time={footerInfo.last_modified_at} />
</section>
</div>
{footerInfo.last_refresh && (
<div>
<h5>Last computed</h5>
<section>
<TZLabel time={footerInfo.last_refresh} />
</section>
</div>
)}
</div>
)}
</div>
</div>
)
}
export const InsightDetails = React.memo(React.forwardRef(InsightDetailsInternal))
)
})
)
88 changes: 59 additions & 29 deletions frontend/src/lib/components/Cards/InsightCard/InsightMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,36 +99,17 @@ export function InsightMeta({
setAreDetailsShown={setAreDetailsShown}
areDetailsShown={areDetailsShown}
topHeading={<TopHeading query={insight.query} />}
meta={
<>
<Link to={urls.insightView(short_id, dashboardId, variablesOverride)}>
<h4 title={name} data-attr="insight-card-title">
{name || <i>{summary}</i>}
{loading && (
<Tooltip
title="This insight is queued to check for newer results. It will be updated soon."
placement="top-end"
>
<span className="text-primary text-sm font-medium ml-1.5">
<Spinner className="mr-1.5 text-base" />
Refreshing
</span>
</Tooltip>
)}
</h4>
</Link>

{!!insight.description && (
<LemonMarkdown className="CardMeta__description" lowKeyHeadings>
{insight.description}
</LemonMarkdown>
)}
{insight.tags && insight.tags.length > 0 && <ObjectTags tags={insight.tags} staticOnly />}

{loading && <LemonTableLoader loading={true} />}
</>
content={
<InsightMetaContent
link={urls.insightView(short_id, dashboardId, variablesOverride)}
title={name}
fallbackTitle={summary}
description={insight.description}
loading={loading}
tags={insight.tags}
/>
}
metaDetails={<InsightDetails insight={insight} />}
metaDetails={<InsightDetails query={insight.query} footerInfo={insight} />}
samplingFactor={samplingFactor}
moreButtons={
<>
Expand Down Expand Up @@ -282,3 +263,52 @@ export function InsightMeta({
/>
)
}

export function InsightMetaContent({
title,
fallbackTitle,
description,
link,
loading,
tags,
}: {
title: string
fallbackTitle?: string
description?: string
link?: string
loading?: boolean
tags?: string[]
}): JSX.Element {
let titleEl: JSX.Element = (
<h4 title={title} data-attr="insight-card-title">
{title || <i>{fallbackTitle || 'Untitled'}</i>}
{loading && (
<Tooltip
title="This insight is queued to check for newer results. It will be updated soon."
placement="top-end"
>
<span className="text-primary text-sm font-medium ml-1.5">
<Spinner className="mr-1.5 text-base" />
Refreshing
</span>
</Tooltip>
)}
</h4>
)
if (link) {
titleEl = <Link to={link}>{titleEl}</Link>
}

return (
<>
{titleEl}
{!!description && (
<LemonMarkdown className="CardMeta__description" lowKeyHeadings>
{description}
</LemonMarkdown>
)}
{tags && tags.length > 0 && <ObjectTags tags={tags} staticOnly />}
<LemonTableLoader loading={loading} />
</>
)
}
73 changes: 73 additions & 0 deletions frontend/src/lib/components/Cards/InsightCard/QueryCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import clsx from 'clsx'
import { useValues } from 'kea'
import { CardMeta } from 'lib/components/Cards/CardMeta'
import { LemonMenuItemList } from 'lib/lemon-ui/LemonMenu/LemonMenu'
import React, { useState } from 'react'
import { urls } from 'scenes/urls'

import { ErrorBoundary } from '~/layout/ErrorBoundary'
import { themeLogic } from '~/layout/navigation-3000/themeLogic'
import { Query } from '~/queries/Query/Query'
import { Node } from '~/queries/schema'

import { InsightCardProps } from './InsightCard'
import { InsightDetails } from './InsightDetails'
import { InsightMetaContent } from './InsightMeta'
import { TopHeading } from './TopHeading'

export interface QueryCardProps extends Pick<InsightCardProps, 'highlighted' | 'ribbonColor' | 'className' | 'style'> {
query: Node
title: string
description?: string
}

/** This is like InsightCard, except for presentation of queries that aren't saved insights. */
export const QueryCard = React.forwardRef<HTMLDivElement, QueryCardProps>(function QueryCard(
{ query, title, description, highlighted, ribbonColor, className, ...divProps },
ref
): JSX.Element {
const { theme } = useValues(themeLogic)

const [areDetailsShown, setAreDetailsShown] = useState(false)

return (
<div
className={clsx('InsightCard border', highlighted && 'InsightCard--highlighted', className)}
data-attr="insight-card"
{...divProps}
// eslint-disable-next-line react/forbid-dom-props
style={{ ...(divProps?.style ?? {}), ...(theme?.boxStyle ?? {}) }}
ref={ref}
>
<ErrorBoundary tags={{ feature: 'insight' }}>
<CardMeta
ribbonColor={ribbonColor}
setAreDetailsShown={setAreDetailsShown}
areDetailsShown={areDetailsShown}
topHeading={<TopHeading query={query} />}
content={<InsightMetaContent title={title} description={description} />}
metaDetails={<InsightDetails query={query} />}
samplingFactor={
'samplingFactor' in query && typeof query.samplingFactor === 'number'
? query.samplingFactor
: undefined
}
moreButtons={
<LemonMenuItemList
items={[
{
label: 'Open as new insight',
to: urls.insightNew(undefined, undefined, query),
},
]}
/>
}
showEditingControls
/>
<div className="InsightCard__viz">
<Query query={query} readOnly embedded />
</div>
</ErrorBoundary>
</div>
)
})
Loading

0 comments on commit 0220d54

Please sign in to comment.