mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:51:43 +00:00
Code Insights: Create insight example section (#30300)
* Restructure view components for future reuses * Implement code insights example layouts * Add storybook story * Update sg config yaml URL var
This commit is contained in:
parent
3e3b168840
commit
6f2203f5fb
@ -0,0 +1,50 @@
|
||||
.section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
flex: 1;
|
||||
flex-basis: 25rem;
|
||||
}
|
||||
|
||||
.chart {
|
||||
min-height: 15rem;
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
flex-basis: 18rem;
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
|
||||
&--horizontal {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
background-color: var(--gray-01);
|
||||
padding: 0.25rem 0.5rem;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.capture-group {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import React from 'react'
|
||||
|
||||
import { WebStory } from '../../../../components/WebStory'
|
||||
|
||||
import { CodeInsightsExamples } from './CodeInsightsExamples'
|
||||
|
||||
export default {
|
||||
title: 'web/insights/getting-started/CodeInsightExamples',
|
||||
decorators: [story => <WebStory>{() => story()}</WebStory>],
|
||||
} as Meta
|
||||
|
||||
export const StandardExample = () => <CodeInsightsExamples />
|
||||
@ -0,0 +1,155 @@
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
import { LineChartContent, LineChartContent as LineChartContentType, LineChartSeries } from 'sourcegraph'
|
||||
|
||||
import * as View from '../../../../views'
|
||||
import { LegendBlock, LegendItem } from '../../../../views'
|
||||
import {
|
||||
getLineStroke,
|
||||
LineChart,
|
||||
} from '../../../../views/components/view/content/chart-view-content/charts/line/components/LineChartContent'
|
||||
|
||||
import styles from './CodeInsightsExamples.module.scss'
|
||||
|
||||
export const CodeInsightsExamples: React.FunctionComponent = () => (
|
||||
<section className={styles.section}>
|
||||
<CodeInsightSearchExample className={styles.card} />
|
||||
<CodeInsightCaptureExample className={styles.card} />
|
||||
</section>
|
||||
)
|
||||
|
||||
interface ExampleCardProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
interface SeriesWithQuery extends LineChartSeries<any> {
|
||||
query: string
|
||||
}
|
||||
|
||||
type Content = Omit<LineChartContentType<any, string>, 'chart' | 'series'> & { series: SeriesWithQuery[] }
|
||||
|
||||
const SEARCH_INSIGHT_EXAMPLES_DATA: Content = {
|
||||
data: [
|
||||
{ x: 1588965700286 - 4 * 24 * 60 * 60 * 1000, a: null, b: null },
|
||||
{ x: 1588965700286 - 3 * 24 * 60 * 60 * 1000, a: null, b: null },
|
||||
{ x: 1588965700286 - 2 * 24 * 60 * 60 * 1000, a: 94, b: 200 },
|
||||
{ x: 1588965700286 - 1.5 * 24 * 60 * 60 * 1000, a: 134, b: null },
|
||||
{ x: 1588965700286 - 1.3 * 24 * 60 * 60 * 1000, a: null, b: 150 },
|
||||
{ x: 1588965700286 - 1 * 24 * 60 * 60 * 1000, a: 134, b: 190 },
|
||||
{ x: 1588965700286, a: 123, b: 170 },
|
||||
],
|
||||
series: [
|
||||
{
|
||||
dataKey: 'a',
|
||||
name: 'A metric',
|
||||
stroke: 'var(--blue)',
|
||||
query: 'file:README archived:no fork:no',
|
||||
},
|
||||
{
|
||||
dataKey: 'b',
|
||||
name: 'B metric',
|
||||
stroke: 'var(--warning)',
|
||||
query: '-file:README archived:no fork:no',
|
||||
},
|
||||
],
|
||||
xAxis: {
|
||||
dataKey: 'x',
|
||||
scale: 'time',
|
||||
type: 'number',
|
||||
},
|
||||
}
|
||||
|
||||
export const CodeInsightSearchExample: React.FunctionComponent<ExampleCardProps> = props => {
|
||||
const { className } = props
|
||||
|
||||
return (
|
||||
<View.Root
|
||||
title="Repos with READMEs / without READMEs"
|
||||
subtitle={<InlineCodeBlock className="mt-1">All repositories</InlineCodeBlock>}
|
||||
className={classNames(className)}
|
||||
>
|
||||
<div className={styles.chart}>
|
||||
<ParentSize>
|
||||
{({ width, height }) => (
|
||||
<LineChart {...SEARCH_INSIGHT_EXAMPLES_DATA} width={width} height={height} />
|
||||
)}
|
||||
</ParentSize>
|
||||
</div>
|
||||
|
||||
<LegendBlock className={styles.legend}>
|
||||
{SEARCH_INSIGHT_EXAMPLES_DATA.series.map(line => (
|
||||
<LegendItem key={line.dataKey.toString()} color={getLineStroke<any>(line)}>
|
||||
<span className="flex-shrink-0 mr-2">{line.name}</span>
|
||||
<InlineCodeBlock>{line.query}</InlineCodeBlock>
|
||||
</LegendItem>
|
||||
))}
|
||||
</LegendBlock>
|
||||
</View.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const CAPTURE_INSIGHT_EXAMPLES_DATA: LineChartContent<any, string> = {
|
||||
chart: 'line' as const,
|
||||
data: [
|
||||
{ x: 1588965700286 - 6 * 24 * 60 * 60 * 1000, a: 20, b: 200 },
|
||||
{ x: 1588965700286 - 5 * 24 * 60 * 60 * 1000, a: 40, b: 177 },
|
||||
{ x: 1588965700286 - 4 * 24 * 60 * 60 * 1000, a: 110, b: 150 },
|
||||
{ x: 1588965700286 - 3 * 24 * 60 * 60 * 1000, a: 105, b: 165 },
|
||||
{ x: 1588965700286 - 2 * 24 * 60 * 60 * 1000, a: 160, b: 100 },
|
||||
{ x: 1588965700286 - 1 * 24 * 60 * 60 * 1000, a: 184, b: 85 },
|
||||
{ x: 1588965700286, a: 200, b: 50 },
|
||||
],
|
||||
series: [
|
||||
{
|
||||
dataKey: 'a',
|
||||
name: 'Go 1.11',
|
||||
stroke: 'var(--oc-indigo-7)',
|
||||
},
|
||||
{
|
||||
dataKey: 'b',
|
||||
name: 'Go 1.12',
|
||||
stroke: 'var(--oc-orange-7)',
|
||||
},
|
||||
],
|
||||
xAxis: {
|
||||
dataKey: 'x',
|
||||
scale: 'time' as const,
|
||||
type: 'number',
|
||||
},
|
||||
}
|
||||
|
||||
export const CodeInsightCaptureExample: React.FunctionComponent<ExampleCardProps> = props => {
|
||||
const { className } = props
|
||||
|
||||
return (
|
||||
<View.Root
|
||||
title="Node.js versions (present or most popular)"
|
||||
subtitle={<InlineCodeBlock className="mt-1">repo:github.com/awesomeOrg/examplerepo</InlineCodeBlock>}
|
||||
className={classNames(className)}
|
||||
>
|
||||
<div className={styles.captureGroup}>
|
||||
<div className={styles.chart}>
|
||||
<ParentSize className={styles.chartContent}>
|
||||
{({ width, height }) => (
|
||||
<LineChart {...CAPTURE_INSIGHT_EXAMPLES_DATA} width={width} height={height} />
|
||||
)}
|
||||
</ParentSize>
|
||||
</div>
|
||||
|
||||
<LegendBlock className={styles.legendHorizontal}>
|
||||
{CAPTURE_INSIGHT_EXAMPLES_DATA.series.map(line => (
|
||||
<LegendItem key={line.dataKey.toString()} color={getLineStroke<any>(line)}>
|
||||
{line.name}
|
||||
</LegendItem>
|
||||
))}
|
||||
</LegendBlock>
|
||||
</div>
|
||||
<InlineCodeBlock className="mt-2">nvm install ([0-9]+\.[0-9]+) archived:no fork:no</InlineCodeBlock>
|
||||
</View.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const InlineCodeBlock: React.FunctionComponent<React.HTMLAttributes<HTMLElement>> = props => (
|
||||
<code className={classNames(styles.code, props.className)}>{props.children}</code>
|
||||
)
|
||||
@ -14,7 +14,7 @@ type ViewCardElementProps = React.DetailedHTMLProps<Omit<React.HTMLAttributes<HT
|
||||
|
||||
export interface ViewCardProps extends ViewCardElementProps {
|
||||
title?: string
|
||||
subtitle?: string
|
||||
subtitle?: ReactNode
|
||||
innerRef?: React.Ref<HTMLElement>
|
||||
|
||||
/**
|
||||
@ -35,7 +35,6 @@ export const View: React.FunctionComponent<PropsWithChildren<ViewCardProps>> = p
|
||||
<Card
|
||||
as="section"
|
||||
{...otherProps}
|
||||
/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */
|
||||
tabIndex={0}
|
||||
ref={innerRef}
|
||||
className={classNames(otherProps.className, styles.view)}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import { EventEmitterProvider } from '@visx/xychart'
|
||||
import classNames from 'classnames'
|
||||
import React, { ReactElement, useContext } from 'react'
|
||||
|
||||
import { getLineStroke, LineChartContent, LineChartContentProps } from './components/LineChartContent'
|
||||
import { getLineStroke, LineChart as LineChartContent, LineChartContentProps } from './components/LineChartContent'
|
||||
import { ScrollBox } from './components/scroll-box/ScrollBox'
|
||||
import { MINIMAL_HORIZONTAL_LAYOUT_WIDTH, MINIMAL_SERIES_FOR_ASIDE_LEGEND } from './constants'
|
||||
import { LineChartLayoutOrientation, LineChartSettingsContext } from './line-chart-settings-provider'
|
||||
@ -34,39 +33,51 @@ export function LineChart<Datum extends object>(props: LineChartProps<Datum>): R
|
||||
hasViewManySeries && hasEnoughXSpace
|
||||
|
||||
return (
|
||||
<EventEmitterProvider>
|
||||
<div
|
||||
aria-label="Line chart"
|
||||
/* eslint-disable-next-line react/forbid-dom-props */
|
||||
style={hasChartParentFixedSize ? { width, height } : undefined}
|
||||
className={classNames(styles.lineChart, { [styles.lineChartHorizontal]: isHorizontal })}
|
||||
>
|
||||
{/*
|
||||
In case if we have a legend to render we have to have responsive container for chart
|
||||
just to calculate right sizes for chart content = rootContainerSizes - legendSizes
|
||||
*/}
|
||||
<ParentSize className={styles.contentParentSize} data-line-chart-size-root="">
|
||||
{({ width, height }) => <LineChartContent {...otherProps} width={width} height={height} />}
|
||||
</ParentSize>
|
||||
<div
|
||||
aria-label="Line chart"
|
||||
/* eslint-disable-next-line react/forbid-dom-props */
|
||||
style={hasChartParentFixedSize ? { width, height } : undefined}
|
||||
className={classNames(styles.lineChart, { [styles.lineChartHorizontal]: isHorizontal })}
|
||||
>
|
||||
{/*
|
||||
In case if we have a legend to render we have to have responsive container for chart
|
||||
just to calculate right sizes for chart content = rootContainerSizes - legendSizes
|
||||
*/}
|
||||
<ParentSize className={styles.contentParentSize} data-line-chart-size-root="">
|
||||
{({ width, height }) => <LineChartContent {...otherProps} width={width} height={height} />}
|
||||
</ParentSize>
|
||||
|
||||
<ScrollBox
|
||||
aria-hidden={true}
|
||||
className={classNames(styles.legend, { [styles.legendHorizontal]: isHorizontal })}
|
||||
>
|
||||
<ul className={classNames(styles.legendList, { [styles.legendListHorizontal]: isHorizontal })}>
|
||||
{props.series.map(line => (
|
||||
<li key={line.dataKey.toString()} className={styles.legendItem}>
|
||||
<div
|
||||
/* eslint-disable-next-line react/forbid-dom-props */
|
||||
style={{ backgroundColor: getLineStroke(line) }}
|
||||
className={styles.legendMark}
|
||||
/>
|
||||
{line.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</ScrollBox>
|
||||
</div>
|
||||
</EventEmitterProvider>
|
||||
<ScrollBox
|
||||
aria-hidden={true}
|
||||
className={classNames(styles.legend, { [styles.legendHorizontal]: isHorizontal })}
|
||||
>
|
||||
<LegendBlock className={classNames({ [styles.legendListHorizontal]: isHorizontal })}>
|
||||
{props.series.map(line => (
|
||||
<LegendItem key={line.dataKey.toString()} color={getLineStroke(line)}>
|
||||
{line.name}
|
||||
</LegendItem>
|
||||
))}
|
||||
</LegendBlock>
|
||||
</ScrollBox>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const LegendBlock: React.FunctionComponent<React.HTMLAttributes<HTMLUListElement>> = props => (
|
||||
<ul className={classNames(styles.legendList, props.className)}>{props.children}</ul>
|
||||
)
|
||||
|
||||
interface LegendItem {
|
||||
color: string
|
||||
}
|
||||
|
||||
export const LegendItem: React.FunctionComponent<LegendItem> = props => (
|
||||
<li className={styles.legendItem}>
|
||||
<div
|
||||
/* eslint-disable-next-line react/forbid-dom-props */
|
||||
style={{ backgroundColor: props.color }}
|
||||
className={styles.legendMark}
|
||||
/>
|
||||
{props.children}
|
||||
</li>
|
||||
)
|
||||
|
||||
@ -1,10 +1,4 @@
|
||||
.content {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
&--with-cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
import { curveLinear } from '@visx/curve'
|
||||
import { GridRows } from '@visx/grid'
|
||||
import { Group } from '@visx/group'
|
||||
import { Axis, DataProvider, GlyphSeries, LineSeries, Tooltip, TooltipProvider, XYChart } from '@visx/xychart'
|
||||
import {
|
||||
Axis,
|
||||
DataProvider,
|
||||
GlyphSeries,
|
||||
LineSeries,
|
||||
Tooltip,
|
||||
TooltipProvider,
|
||||
XYChart,
|
||||
EventEmitterProvider,
|
||||
} from '@visx/xychart'
|
||||
import { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip'
|
||||
import { XYCHART_EVENT_SOURCE } from '@visx/xychart/lib/constants'
|
||||
import isValidNumber from '@visx/xychart/lib/typeguards/isValidNumber'
|
||||
@ -9,7 +18,7 @@ import { EventHandlerParams } from '@visx/xychart/lib/types'
|
||||
import classNames from 'classnames'
|
||||
import React, { ReactElement, useCallback, useMemo, useState, MouseEvent, useRef } from 'react'
|
||||
import { noop } from 'rxjs'
|
||||
import { LineChartContent as LineChartContentType } from 'sourcegraph'
|
||||
import { LineChartContent as LineChartContentType, LineChartSeries } from 'sourcegraph'
|
||||
|
||||
import { DEFAULT_LINE_STROKE } from '../constants'
|
||||
import { generateAccessors } from '../helpers/generate-accessors'
|
||||
@ -43,9 +52,8 @@ const SCALES_CONFIG = {
|
||||
}
|
||||
|
||||
// Line color accessor
|
||||
export const getLineStroke = <Datum extends object>(
|
||||
line: LineChartContentType<Datum, keyof Datum>['series'][number]
|
||||
): string => line?.stroke ?? DEFAULT_LINE_STROKE
|
||||
export const getLineStroke = <Datum extends object>(line: LineChartSeries<Datum>): string =>
|
||||
line?.stroke ?? DEFAULT_LINE_STROKE
|
||||
|
||||
const stopPropagation = (event: React.MouseEvent): void => event.stopPropagation()
|
||||
|
||||
@ -70,6 +78,14 @@ export interface LineChartContentProps<Datum extends object>
|
||||
onDatumLinkClick?: (event: React.MouseEvent) => void
|
||||
}
|
||||
|
||||
export function LineChart<Datum extends object>(props: LineChartContentProps<Datum>): ReactElement {
|
||||
return (
|
||||
<EventEmitterProvider>
|
||||
<LineChartContent {...props} />
|
||||
</EventEmitterProvider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays line chart content - line chart, tooltip, active point
|
||||
*/
|
||||
@ -220,7 +236,7 @@ export function LineChartContent<Datum extends object>(props: LineChartContentPr
|
||||
const hoveredDatumLink = hoveredDatum
|
||||
? hoveredDatumLinks[+hoveredDatum.datum.x] ?? hoveredDatumLinks[hoveredDatum.index]
|
||||
: null
|
||||
const rootClasses = classNames(styles.content, { [styles.contentWithCursor]: !!hoveredDatumLink })
|
||||
const rootClasses = classNames({ [styles.contentWithCursor]: !!hoveredDatumLink })
|
||||
|
||||
return (
|
||||
<div className={classNames(rootClasses, 'percy-inactive-element')} data-testid="line-chart__content">
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export { LineChart } from './LineChart'
|
||||
export { LineChart, LegendBlock, LegendItem } from './LineChart'
|
||||
export { LineChartSettingsContext, LineChartLayoutOrientation } from './line-chart-settings-provider'
|
||||
export { EMPTY_DATA_POINT_VALUE, DEFAULT_LINE_STROKE } from './constants'
|
||||
export { getLineStroke } from './components/LineChartContent'
|
||||
|
||||
@ -317,7 +317,7 @@ commands:
|
||||
install: yarn --no-progress
|
||||
env:
|
||||
WEBPACK_SERVE_INDEX: true
|
||||
SOURCEGRAPH_API_URL: https://sourcegraph.com
|
||||
SOURCEGRAPH_API_URL: https://k8s.sgdev.org
|
||||
|
||||
web-standalone-http-prod:
|
||||
cmd: yarn workspace @sourcegraph/web serve:prod
|
||||
|
||||
Loading…
Reference in New Issue
Block a user