mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 16:31:47 +00:00
Code Insights: [FE] Update insight creation intro page designs and layouts (#28460)
* Add insight promo cards storybook stories * Update designs and layouts for the intro creation insights page * Fix Monaco field ref warning in the console * Fix nested link tags problems * Skip insight extension images in Percy snapshotting
This commit is contained in:
parent
3c6a7761fe
commit
0efd46d451
@ -0,0 +1,40 @@
|
||||
import React, { useRef, useState } from 'react'
|
||||
|
||||
import { Button, ProductStatusBadge } from '@sourcegraph/wildcard/src'
|
||||
|
||||
import { FeedbackPromptContent } from '../../../../nav/Feedback/FeedbackPrompt'
|
||||
import { flipRightPosition } from '../context-menu/utils'
|
||||
import { Popover } from '../popover/Popover'
|
||||
|
||||
import styles from './BetaFeedbackPanel.module.scss'
|
||||
|
||||
export const BetaFeedbackPanel: React.FunctionComponent = () => {
|
||||
const buttonReference = useRef<HTMLButtonElement>(null)
|
||||
const [isVisible, setVisibility] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-center">
|
||||
<a href="https://docs.sourcegraph.com/code_insights#code-insights-beta" target="_blank" rel="noopener">
|
||||
<ProductStatusBadge status="beta" className="text-uppercase" />
|
||||
</a>
|
||||
|
||||
<Button ref={buttonReference} variant="link" size="sm">
|
||||
Share feedback
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
isOpen={isVisible}
|
||||
target={buttonReference}
|
||||
position={flipRightPosition}
|
||||
onVisibilityChange={setVisibility}
|
||||
className={styles.feedbackPrompt}
|
||||
>
|
||||
<FeedbackPromptContent
|
||||
closePrompt={() => setVisibility(false)}
|
||||
textPrefix="Code Insights: "
|
||||
routeMatch="/insights/dashboards"
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
import { noop } from 'lodash'
|
||||
import * as Monaco from 'monaco-editor'
|
||||
import React, { forwardRef, InputHTMLAttributes, useMemo } from 'react'
|
||||
import React, { forwardRef, InputHTMLAttributes, useImperativeHandle, useMemo } from 'react'
|
||||
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
|
||||
@ -31,7 +31,7 @@ interface MonacoFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, '
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
export const MonacoField: React.FunctionComponent<MonacoFieldProps> = forwardRef(props => {
|
||||
export const MonacoField: React.FunctionComponent<MonacoFieldProps> = forwardRef((props, reference) => {
|
||||
const {
|
||||
value,
|
||||
className,
|
||||
@ -42,6 +42,11 @@ export const MonacoField: React.FunctionComponent<MonacoFieldProps> = forwardRef
|
||||
patternType = SearchPatternType.regexp,
|
||||
} = props
|
||||
|
||||
// Monaco doesn't have any native input elements, so we mock
|
||||
// ref here to avoid React warnings in console about zero usage of
|
||||
// element ref with forward ref call.
|
||||
useImperativeHandle(reference, () => null)
|
||||
|
||||
const { enhancedThemePreference } = useTheme()
|
||||
const monacoOptions = useMemo(() => ({ ...MONACO_OPTIONS, readOnly: disabled }), [disabled])
|
||||
|
||||
|
||||
@ -173,3 +173,82 @@ export const PieChart: React.FunctionComponent<React.SVGProps<SVGSVGElement>> =
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CaptureGroupInsight: React.FunctionComponent<React.SVGProps<SVGSVGElement>> = props => (
|
||||
<svg
|
||||
width="185"
|
||||
height="126"
|
||||
viewBox="0 0 185 126"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
className={classNames(styles.chart, props.className)}
|
||||
>
|
||||
<rect x="6" y="39" width="137" height="1" />
|
||||
<rect x="6" y="16" width="137" height="1" />
|
||||
<rect x="6" y="62" width="137" height="1" />
|
||||
<rect x="6" y="85" width="137" height="1" />
|
||||
<rect x="6" y="108" width="137" height="1" />
|
||||
<rect x="170" y="16.5" width="15" height="1" />
|
||||
<circle cx="165" cy="17" r="2" fill="var(--orange)" />
|
||||
<rect x="170" y="52.5" width="15" height="1" />
|
||||
<circle cx="165" cy="53" r="2" fill="var(--orange)" />
|
||||
<rect x="170" y="88.5" width="15" height="1" />
|
||||
<circle cx="165" cy="89" r="2" fill="var(--orange)" />
|
||||
<rect x="170" y="61.5" width="15" height="1" />
|
||||
<circle cx="165" cy="62" r="2" fill="var(--pink)" />
|
||||
<rect x="170" y="25.5" width="15" height="1" />
|
||||
<circle cx="165" cy="26" r="2" fill="var(--pink)" />
|
||||
<rect x="170" y="70.5" width="15" height="1" />
|
||||
<circle cx="165" cy="71" r="2" fill="var(--dark-blue)" />
|
||||
<rect x="170" y="97.5" width="15" height="1" />
|
||||
<circle cx="165" cy="98" r="2" fill="var(--pink)" />
|
||||
<rect x="170" y="79.5" width="15" height="1" />
|
||||
<circle cx="165" cy="80" r="2" fill="var(--light-green)" />
|
||||
<rect x="170" y="34.5" width="15" height="1" />
|
||||
<circle cx="165" cy="35" r="2" fill="var(--dark-blue)" />
|
||||
<rect x="170" y="106.5" width="15" height="1" />
|
||||
<circle cx="165" cy="107" r="2" fill="var(--dark-blue)" />
|
||||
<rect x="170" y="43.5" width="15" height="1" />
|
||||
<circle cx="165" cy="44" r="2" fill="var(--light-green)" />
|
||||
<circle cx="7.5" cy="39.5" r="1.5" fill="var(--pink)" />
|
||||
<circle cx="30" cy="39.5" r="1.5" fill="var(--pink)" />
|
||||
<circle cx="51" cy="79" r="1.5" fill="var(--pink)" />
|
||||
<circle cx="74" cy="85" r="1.5" fill="var(--pink)" />
|
||||
<circle cx="95" cy="85" r="1.5" fill="var(--pink)" />
|
||||
<circle cx="116.5" cy="74.7" r="1.5" fill="var(--pink)" />
|
||||
<circle cx="139" cy="74.7" r="1.5" fill="var(--pink)" />
|
||||
<circle cx="29.5" cy="85.5" r="1.5" fill="var(--dark-blue)" />
|
||||
<circle cx="7.5" cy="107.5" r="1.5" fill="var(--dark-blue)" />
|
||||
<circle cx="7.5" cy="94.5" r="1.5" fill="var(--light-green)" />
|
||||
<circle cx="31.5" cy="94.5" r="1.5" fill="var(--light-green)" />
|
||||
<circle cx="52" cy="108.5" r="1.5" fill="var(--light-green)" />
|
||||
<circle cx="75" cy="99" r="1.5" fill="var(--light-green)" />
|
||||
<circle cx="96.5" cy="98.5" r="1.5" fill="var(--light-green)" />
|
||||
<circle cx="117" cy="62" r="1.5" fill="var(--light-green)" />
|
||||
<circle cx="140.5" cy="62" r="1.5" fill="var(--light-green)" />
|
||||
<path
|
||||
d="M6.5 108.5L29.5 85.5H51.5L73.8164 64.7647H95.3333L118 39.5H140.5"
|
||||
stroke="var(--dark-blue)"
|
||||
strokeWidth="1.2"
|
||||
/>
|
||||
<path d="M7 39.5H30.2115L51 79.5L74 85H95.5769L116 75H139.5" stroke="var(--pink)" strokeWidth="1.2" />
|
||||
<path d="M8 94.5H31.2115L52 108.5L75 99H96.5769L117 62H140.5" stroke="var(--light-green)" strokeWidth="1.2" />
|
||||
<path
|
||||
d="M8 87L29 17.5L51.6667 40.5H73.8164L92 17.5H116.5L141.5 40.5"
|
||||
stroke="var(--orange)"
|
||||
strokeWidth="1.2"
|
||||
/>
|
||||
<circle cx="8" cy="87" r="1.5" fill="var(--orange)" />
|
||||
<circle cx="29" cy="17.5" r="1.5" fill="var(--orange)" />
|
||||
<circle cx="51.5" cy="40.5" r="1.5" fill="var(--orange)" />
|
||||
<circle cx="73.5" cy="40.5" r="1.5" fill="var(--orange)" />
|
||||
<circle cx="91.5" cy="18" r="1.5" fill="var(--orange)" />
|
||||
<circle cx="116.5" cy="17.5" r="1.5" fill="var(--orange)" />
|
||||
<circle cx="140.5" cy="39.5" r="1.5" fill="var(--orange)" />
|
||||
<circle cx="118" cy="39.5" r="1.5" fill="var(--dark-blue)" />
|
||||
<circle cx="95.5" cy="64.5" r="1.5" fill="var(--dark-blue)" />
|
||||
<circle cx="73.5" cy="65" r="1.5" fill="var(--dark-blue)" />
|
||||
<circle cx="51" cy="85.5" r="1.5" fill="var(--dark-blue)" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
@ -1,21 +1,18 @@
|
||||
import PlusIcon from 'mdi-react/PlusIcon'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useRouteMatch } from 'react-router'
|
||||
import { Redirect } from 'react-router-dom'
|
||||
|
||||
import { Link } from '@sourcegraph/shared/src/components/Link'
|
||||
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { Button, PageHeader, ProductStatusBadge } from '@sourcegraph/wildcard'
|
||||
import { PageHeader } from '@sourcegraph/wildcard'
|
||||
|
||||
import { Page } from '../../../../../components/Page'
|
||||
import { FeedbackPromptContent } from '../../../../../nav/Feedback/FeedbackPrompt'
|
||||
import { CodeInsightsIcon } from '../../../components'
|
||||
import { flipRightPosition } from '../../../components/context-menu/utils'
|
||||
import { Popover } from '../../../components/popover/Popover'
|
||||
import { BetaFeedbackPanel } from '../../../components/beta-feedback-panel/BetaFeedbackPanel'
|
||||
import { ALL_INSIGHTS_DASHBOARD_ID } from '../../../core/types/dashboard/virtual-dashboard'
|
||||
|
||||
import { DashboardsContent } from './components/dashboards-content/DashboardsContent'
|
||||
import styles from './DashboardPage.module.scss'
|
||||
|
||||
export interface DashboardsPageProps extends TelemetryProps {
|
||||
/**
|
||||
@ -52,8 +49,8 @@ export const DashboardsPage: React.FunctionComponent<DashboardsPageProps> = prop
|
||||
<div className="w-100">
|
||||
<Page>
|
||||
<PageHeader
|
||||
annotation={<PageAnnotation />}
|
||||
path={[{ icon: CodeInsightsIcon, text: 'Insights' }]}
|
||||
annotation={<BetaFeedbackPanel />}
|
||||
path={[{ icon: CodeInsightsIcon }, { text: 'Insights' }]}
|
||||
actions={
|
||||
<>
|
||||
<Link to="/insights/add-dashboard" className="btn btn-outline-secondary mr-2">
|
||||
@ -76,34 +73,3 @@ export const DashboardsPage: React.FunctionComponent<DashboardsPageProps> = prop
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const PageAnnotation: React.FunctionComponent = () => {
|
||||
const buttonReference = useRef<HTMLButtonElement>(null)
|
||||
const [isVisible, setVisibility] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-center">
|
||||
<a href="https://docs.sourcegraph.com/code_insights#code-insights-beta" target="_blank" rel="noopener">
|
||||
<ProductStatusBadge status="beta" className="text-uppercase" />
|
||||
</a>
|
||||
|
||||
<Button ref={buttonReference} variant="link" size="sm">
|
||||
Share feedback
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
isOpen={isVisible}
|
||||
target={buttonReference}
|
||||
position={flipRightPosition}
|
||||
onVisibilityChange={setVisibility}
|
||||
className={styles.feedbackPrompt}
|
||||
>
|
||||
<FeedbackPromptContent
|
||||
closePrompt={() => setVisibility(false)}
|
||||
textPrefix="Code Insights: "
|
||||
routeMatch="/insights/dashboards"
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,32 +1,14 @@
|
||||
.create-intro-page {
|
||||
&__insights {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
grid-gap: 1rem;
|
||||
}
|
||||
|
||||
&__insight-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__insight-button {
|
||||
margin-top: auto;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
&__chart-container {
|
||||
height: 17rem;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__chart {
|
||||
// stretch chart over all parent block
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.container {
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import { Meta } from '@storybook/react'
|
||||
import React from 'react'
|
||||
|
||||
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
@ -7,12 +7,14 @@ import { WebStory } from '../../../../../../components/WebStory'
|
||||
|
||||
import { IntroCreationPage } from './IntroCreationPage'
|
||||
|
||||
const { add } = storiesOf('web/insights/CreationInsightIntroPage', module)
|
||||
.addDecorator(story => <WebStory>{() => story()}</WebStory>)
|
||||
.addParameters({
|
||||
export default {
|
||||
title: 'web/insights/creation-ui/IntroPage',
|
||||
decorators: [story => <WebStory>{() => story()}</WebStory>],
|
||||
parameters: {
|
||||
chromatic: {
|
||||
viewports: [320, 576, 978, 1440],
|
||||
viewports: [576, 978, 1440],
|
||||
},
|
||||
})
|
||||
},
|
||||
} as Meta
|
||||
|
||||
add('Page', () => <IntroCreationPage telemetryService={NOOP_TELEMETRY_SERVICE} />)
|
||||
export const InsightIntroPageExample = () => <IntroCreationPage telemetryService={NOOP_TELEMETRY_SERVICE} />
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import classNames from 'classnames'
|
||||
import React, { useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import { useHistory } from 'react-router'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { PageHeader } from '@sourcegraph/wildcard'
|
||||
|
||||
import { Page } from '../../../../../../components/Page'
|
||||
import { PageTitle } from '../../../../../../components/PageTitle'
|
||||
import { LineChart } from '../../../../../../views/components/view/content/chart-view-content/charts/line/LineChart'
|
||||
import { PieChart } from '../../../../../../views/components/view/content/chart-view-content/charts/pie/PieChart'
|
||||
import { LinkWithQuery } from '../../../../components/link-with-query'
|
||||
import { CodeInsightsIcon } from '../../../../../../insights/Icons'
|
||||
import { BetaFeedbackPanel } from '../../../../components/beta-feedback-panel/BetaFeedbackPanel'
|
||||
import { CodeInsightsBackendContext } from '../../../../core/backend/code-insights-backend-context'
|
||||
import { CodeInsightsGqlBackend } from '../../../../core/backend/gql-api/code-insights-gql-backend'
|
||||
|
||||
import { LINE_CHART_DATA, PIE_CHART_DATA } from './charts-mock'
|
||||
import {
|
||||
CaptureGroupInsightCard,
|
||||
ExtensionInsightsCard,
|
||||
LangStatsInsightCard,
|
||||
SearchInsightCard,
|
||||
} from './cards/InsightCards'
|
||||
import styles from './IntroCreationPage.module.scss'
|
||||
|
||||
interface IntroCreationPageProps extends TelemetryProps {}
|
||||
@ -20,113 +26,80 @@ interface IntroCreationPageProps extends TelemetryProps {}
|
||||
export const IntroCreationPage: React.FunctionComponent<IntroCreationPageProps> = props => {
|
||||
const { telemetryService } = props
|
||||
|
||||
const logCreateSearchBasedInsightClick = (): void => {
|
||||
const history = useHistory()
|
||||
const { search } = useLocation()
|
||||
const api = useContext(CodeInsightsBackendContext)
|
||||
|
||||
const handleCreateSearchBasedInsightClick = (): void => {
|
||||
telemetryService.log('CodeInsightsCreateSearchBasedInsightClick')
|
||||
history.push(`/insights/create/search${search}`)
|
||||
}
|
||||
|
||||
const logCreateCodeStatsInsightClick = (): void => {
|
||||
const handleCaptureGroupInsightClick = (): void => {
|
||||
telemetryService.log('CodeInsightsCreateCaptureGroupInsightClick')
|
||||
history.push(`/insights/create/capture-group${search}`)
|
||||
}
|
||||
|
||||
const handleCreateCodeStatsInsightClick = (): void => {
|
||||
telemetryService.log('CodeInsightsCreateCodeStatsInsightClick')
|
||||
history.push(`/insights/create/lang-stats${search}`)
|
||||
}
|
||||
|
||||
const logExploreExtensionsClick = (): void => {
|
||||
const handleExploreExtensionsClick = (): void => {
|
||||
telemetryService.log('CodeInsightsExploreInsightExtensionsClick')
|
||||
history.push('/extensions?query=category:Insights&experimental=true')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
telemetryService.logViewEvent('CodeInsightsCreationPage')
|
||||
}, [telemetryService])
|
||||
|
||||
const isGqlApi = api instanceof CodeInsightsGqlBackend
|
||||
|
||||
return (
|
||||
<Page className="col-8">
|
||||
<PageTitle title="Create code insights" />
|
||||
<Page className={classNames('container pb-5', styles.container)}>
|
||||
<PageHeader
|
||||
annotation={<BetaFeedbackPanel />}
|
||||
path={[{ icon: CodeInsightsIcon }, { text: 'Create new code insight' }]}
|
||||
description={
|
||||
<>
|
||||
Insights analyze your code based on any search query.{' '}
|
||||
<a href="https://docs.sourcegraph.com/code_insights" target="_blank" rel="noopener">
|
||||
Learn more.
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
className={styles.header}
|
||||
/>
|
||||
|
||||
<div className="mb-5">
|
||||
<h2>Create new insight</h2>
|
||||
<div className={styles.sectionContent}>
|
||||
<SearchInsightCard data-testid="create-search-insights" onClick={handleCreateSearchBasedInsightClick} />
|
||||
|
||||
<p className="text-muted">
|
||||
Code insights analyze your code based on any search query.{' '}
|
||||
<a href="https://docs.sourcegraph.com/code_insights" target="_blank" rel="noopener">
|
||||
Learn more.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{isGqlApi && (
|
||||
<CaptureGroupInsightCard
|
||||
data-testid="create-capture-group-insight"
|
||||
onClick={handleCaptureGroupInsightClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={classNames(styles.createIntroPageInsights, 'pb-5')}>
|
||||
<section
|
||||
className={classNames(styles.createIntroPageInsightCard, 'card card-body p-3')}
|
||||
data-testid="create-search-insights"
|
||||
>
|
||||
<h3>Based on your search query</h3>
|
||||
|
||||
<p>
|
||||
Search-based insights let you create a time series data visualization about your code based on a
|
||||
custom search query.
|
||||
</p>
|
||||
|
||||
<LinkWithQuery
|
||||
to="/insights/create/search"
|
||||
onClick={logCreateSearchBasedInsightClick}
|
||||
className={classNames(styles.createIntroPageInsightButton, 'btn', 'btn-primary')}
|
||||
>
|
||||
Create search insight
|
||||
</LinkWithQuery>
|
||||
|
||||
<hr className="ml-n3 mr-n3 mt-4 mb-3" />
|
||||
|
||||
<p className="text-muted">Example:</p>
|
||||
<div className={styles.createIntroPageChartContainer}>
|
||||
<ParentSize className={styles.createIntroPageChart}>
|
||||
{({ width, height }) => <LineChart width={width} height={height} {...LINE_CHART_DATA} />}
|
||||
</ParentSize>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={classNames(styles.createIntroPageInsightCard, 'card card-body p-3')}
|
||||
<LangStatsInsightCard
|
||||
data-testid="create-lang-usage-insight"
|
||||
>
|
||||
<h3>Language usage</h3>
|
||||
onClick={handleCreateCodeStatsInsightClick}
|
||||
/>
|
||||
|
||||
<p>Shows language usage in your repository by lines of code.</p>
|
||||
|
||||
<LinkWithQuery
|
||||
to="/insights/create/lang-stats"
|
||||
onClick={logCreateCodeStatsInsightClick}
|
||||
className={classNames(styles.createIntroPageInsightButton, 'btn', 'btn-primary')}
|
||||
>
|
||||
Create language usage insight
|
||||
</LinkWithQuery>
|
||||
|
||||
<hr className="ml-n3 mr-n3 mt-4 mb-3" />
|
||||
|
||||
<p className="text-muted">Example:</p>
|
||||
<div className={styles.createIntroPageChartContainer}>
|
||||
<ParentSize className={styles.createIntroPageChart}>
|
||||
{({ width, height }) => <PieChart width={width} height={height} {...PIE_CHART_DATA} />}
|
||||
</ParentSize>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={classNames(styles.createIntroPageInsightCard, 'card card-body p-3')}
|
||||
data-testid="explore-extensions"
|
||||
>
|
||||
<h3>Based on Sourcegraph extensions</h3>
|
||||
|
||||
<p>
|
||||
Enable an extension that creates code insights, then follow its README.md to learn how to set up
|
||||
code insights for that extension.
|
||||
</p>
|
||||
|
||||
<Link
|
||||
to="/extensions?query=category:Insights&experimental=true"
|
||||
onClick={logExploreExtensionsClick}
|
||||
className={classNames(styles.createIntroPageInsightButton, 'btn', 'btn-secondary')}
|
||||
>
|
||||
Explore the extensions
|
||||
</Link>
|
||||
</section>
|
||||
<ExtensionInsightsCard data-testid="explore-extensions" onClick={handleExploreExtensionsClick} />
|
||||
</div>
|
||||
|
||||
<footer className="mt-3">
|
||||
Not sure which insight type to choose? Learn more about the{' '}
|
||||
<a
|
||||
href="https://docs.sourcegraph.com/code_insights/references/common_use_cases"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
use cases
|
||||
</a>
|
||||
</footer>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
// Card component common styles
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: var(--box-shadow);
|
||||
font-size: 0.75rem;
|
||||
text-align: start;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--secondary-2);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: var(--secondary-2);
|
||||
box-shadow: var(--focus-box-shadow);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&--extension-card {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: transparent;
|
||||
margin-top: 1rem;
|
||||
padding: 0;
|
||||
|
||||
b {
|
||||
display: contents;
|
||||
|
||||
// A bootstrap CSS class .card brings custom color styles that we should change
|
||||
// for b elements within a card accroding to our designs for this card.
|
||||
// Since bootstrap classes works with !important we should override this also
|
||||
// with !important statement.
|
||||
color: var(--body-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// chart specific styles
|
||||
.chart {
|
||||
overflow: hidden;
|
||||
height: 10rem;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.images {
|
||||
display: flex;
|
||||
height: 10rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
width: 6rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.image {
|
||||
object-fit: contain;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.capture-chart-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.capture-chart-icon {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.capture-group-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-bg-3);
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import React from 'react'
|
||||
|
||||
import { WebStory } from '../../../../../../../components/WebStory'
|
||||
|
||||
import { CaptureGroupInsightCard, LangStatsInsightCard, SearchInsightCard } from './InsightCards'
|
||||
|
||||
export default {
|
||||
title: 'web/insights/insight-cards',
|
||||
decorators: [story => <WebStory>{() => <div className="p-3 container web-content">{story()}</div>}</WebStory>],
|
||||
} as Meta
|
||||
|
||||
export { SearchInsightCard, LangStatsInsightCard, CaptureGroupInsightCard }
|
||||
@ -0,0 +1,119 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { CaptureGroupInsight, PieChart, ThreeLineChart } from '../../../../../modals/components/MediaCharts'
|
||||
|
||||
import styles from './InsightCards.module.scss'
|
||||
|
||||
interface CardProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
footerText?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Low-level styled component for building insight link card for
|
||||
* the creation page gallery.
|
||||
*/
|
||||
const Card: React.FunctionComponent<CardProps> = props => {
|
||||
const { children, footerText, ...otherProps } = props
|
||||
|
||||
return (
|
||||
<button {...otherProps} type="button" className={classNames(styles.card, 'card p-3', otherProps.className)}>
|
||||
{children}
|
||||
|
||||
{footerText && (
|
||||
<footer className="d-flex flex-column mt-3">
|
||||
<small className="text-muted">Example use</small>
|
||||
<span>{footerText}</span>
|
||||
</footer>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
const CardBody: React.FunctionComponent<{ title: string }> = props => {
|
||||
const { title, children } = props
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.cardBody, 'card-body flex-1')}>
|
||||
<h3 className="mb-3">{title}</h3>
|
||||
|
||||
<p className="d-flex flex-column text-muted m-0">{children}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const SearchInsightCard: React.FunctionComponent<CardProps> = props => (
|
||||
<Card {...props} footerText="Redis, PostgreSQL and SQLite database usage.">
|
||||
<ThreeLineChart viewBox="0 0 169 148" className={styles.chart} />
|
||||
<CardBody title="Track">
|
||||
Insight <b>based on a custom Sourcegraph search query</b> that creates visualization of the data series you
|
||||
will define <b>manually.</b>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
|
||||
export const LangStatsInsightCard: React.FunctionComponent<CardProps> = props => (
|
||||
<Card {...props}>
|
||||
<PieChart viewBox="0 0 169 148" className={styles.chart} />
|
||||
<CardBody title="Language usage">
|
||||
Shows usage of languages in your repository based on number of lines of code.
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
|
||||
export const CaptureGroupInsightCard: React.FunctionComponent<CardProps> = props => (
|
||||
<Card {...props} footerText="Detecting and tracking language or package versions.">
|
||||
<div className={styles.captureChartWrapper}>
|
||||
<CaptureGroupInsight className={styles.chart} />
|
||||
<CaptureGroupIcon className={styles.captureChartIcon} />
|
||||
</div>
|
||||
|
||||
<CardBody title="Detect and track">
|
||||
Data series will be generated dynamically for each unique value from the
|
||||
<b> regular expression capture group </b> included in the search query. Chart will be updated as new values
|
||||
appear in the code base.
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
|
||||
const CaptureGroupIcon: React.FunctionComponent<React.HTMLAttributes<HTMLElement>> = props => (
|
||||
<div className={classNames(props.className, styles.captureGroupIcon)}>
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M9.76238 9.82535C9.49591 9.86573 9.22943 9.88995 8.95488 9.88995C8.68033 9.88995 8.41386 9.86573 8.14738 9.82535V6.99103L6.12863 8.99363C5.72488 8.6787 5.32113 8.27495 5.00621 7.8712L7.00881 5.85245H4.17448C4.13411 5.58598 4.10988 5.3195 4.10988 5.04495C4.10988 4.7704 4.13411 4.50393 4.17448 4.23745H7.00881L5.00621 2.2187C5.15963 2.01683 5.32113 1.81495 5.53108 1.62115C5.72488 1.4112 5.92676 1.2497 6.12863 1.09628L8.14738 3.09888V0.264551C8.41386 0.224176 8.68033 0.199951 8.95488 0.199951C9.22943 0.199951 9.49591 0.224176 9.76238 0.264551V3.09888L11.7811 1.09628C12.1849 1.4112 12.5886 1.81495 12.9036 2.2187L10.901 4.23745H13.7353C13.7757 4.50393 13.7999 4.7704 13.7999 5.04495C13.7999 5.3195 13.7757 5.58598 13.7353 5.85245H10.901L12.9036 7.8712C12.7501 8.07308 12.5886 8.27495 12.3787 8.46875C12.1849 8.6787 11.983 8.8402 11.7811 8.99363L9.76238 6.99103V9.82535ZM0.879883 11.505C0.879883 11.0766 1.05003 10.6658 1.35291 10.363C1.65578 10.0601 2.06656 9.88995 2.49488 9.88995C2.92321 9.88995 3.33399 10.0601 3.63686 10.363C3.93973 10.6658 4.10988 11.0766 4.10988 11.505C4.10988 11.9333 3.93973 12.3441 3.63686 12.6469C3.33399 12.9498 2.92321 13.12 2.49488 13.12C2.06656 13.12 1.65578 12.9498 1.35291 12.6469C1.05003 12.3441 0.879883 11.9333 0.879883 11.505Z"
|
||||
fill="var(--body-color)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const ExtensionInsightsCard: React.FunctionComponent<CardProps> = props => (
|
||||
<Card {...props} className={styles.cardExtensionCard}>
|
||||
<div className={styles.images}>
|
||||
<img
|
||||
className={styles.image}
|
||||
src={`${window.context?.assetsRoot || ''}/img/codecov.png`}
|
||||
data-skip-percy={true}
|
||||
alt="Codecov logo"
|
||||
/>
|
||||
<img
|
||||
className={styles.image}
|
||||
src={`${window.context?.assetsRoot || ''}/img/eslint.png`}
|
||||
data-skip-percy={true}
|
||||
alt="Eslint logo"
|
||||
/>
|
||||
<img
|
||||
className={styles.image}
|
||||
src={`${window.context?.assetsRoot || ''}/img/snyk.png`}
|
||||
data-skip-percy={true}
|
||||
alt="Snyk logo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CardBody title="Based on Sourcegraph extensions">
|
||||
Enable the extension and go to the README.md to learn how to set up code insights for selected Sourcegraph
|
||||
extensions. <Link to="/extensions?query=category:Insights&experimental=true">Explore the extensions</Link>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
@ -1,97 +0,0 @@
|
||||
import { LineChartContent, PieChartContent } from 'sourcegraph'
|
||||
|
||||
/** Mock data for search-based insight preview for create-intro page. */
|
||||
export const LINE_CHART_DATA: LineChartContent<any, string> = {
|
||||
chart: 'line',
|
||||
data: [
|
||||
{
|
||||
date: 1597698000000,
|
||||
'Imports of old GQL.* types': 250,
|
||||
'Imports of new graphql-operations types': 61,
|
||||
},
|
||||
{
|
||||
date: 1601326800000,
|
||||
'Imports of old GQL.* types': 183,
|
||||
'Imports of new graphql-operations types': 207,
|
||||
},
|
||||
{
|
||||
date: 1604955600000,
|
||||
'Imports of old GQL.* types': 178,
|
||||
'Imports of new graphql-operations types': 242,
|
||||
},
|
||||
{
|
||||
date: 1608584400000,
|
||||
'Imports of old GQL.* types': 139,
|
||||
'Imports of new graphql-operations types': 329,
|
||||
},
|
||||
{
|
||||
date: 1612213200000,
|
||||
'Imports of old GQL.* types': 139,
|
||||
'Imports of new graphql-operations types': 341,
|
||||
},
|
||||
{
|
||||
date: 1615842000000,
|
||||
'Imports of old GQL.* types': 139,
|
||||
'Imports of new graphql-operations types': 360,
|
||||
},
|
||||
{ date: 1619470800000, 'Imports of old GQL.* types': 130, 'Imports of new graphql-operations types': 396 },
|
||||
],
|
||||
series: [
|
||||
{
|
||||
dataKey: 'Imports of old GQL.* types',
|
||||
name: 'Imports of old GQL.* types',
|
||||
stroke: 'var(--oc-red-7)',
|
||||
},
|
||||
{
|
||||
dataKey: 'Imports of new graphql-operations types',
|
||||
name: 'Imports of new graphql-operations types',
|
||||
stroke: 'var(--oc-blue-7)',
|
||||
},
|
||||
],
|
||||
xAxis: { dataKey: 'date', type: 'number', scale: 'time' },
|
||||
}
|
||||
|
||||
/** Mock data for lang-stats insight preview for create-intro page. */
|
||||
export const PIE_CHART_DATA: PieChartContent<any> = {
|
||||
chart: 'pie',
|
||||
pies: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
name: 'Go',
|
||||
totalLines: 377693,
|
||||
fill: '#00ADD8',
|
||||
},
|
||||
{
|
||||
name: 'HTML',
|
||||
totalLines: 224972,
|
||||
fill: '#e34c26',
|
||||
},
|
||||
{
|
||||
name: 'TypeScript',
|
||||
totalLines: 165086,
|
||||
fill: '#2b7489',
|
||||
},
|
||||
{
|
||||
name: 'Markdown',
|
||||
totalLines: 48327,
|
||||
fill: '#083fa1',
|
||||
},
|
||||
{
|
||||
name: 'YAML',
|
||||
totalLines: 26305,
|
||||
fill: '#cb171e',
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
totalLines: 59884,
|
||||
fill: 'gray',
|
||||
},
|
||||
],
|
||||
dataKey: 'totalLines',
|
||||
nameKey: 'name',
|
||||
fillKey: 'fill',
|
||||
linkURLKey: 'linkURL',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -14,9 +14,7 @@ describe('Code insights page', () => {
|
||||
let testContext: WebIntegrationTestContext
|
||||
|
||||
before(async () => {
|
||||
driver = await createDriverForTest({
|
||||
devtools: true,
|
||||
})
|
||||
driver = await createDriverForTest()
|
||||
})
|
||||
|
||||
after(() => driver?.close())
|
||||
|
||||
@ -49,7 +49,9 @@ const ColorSchemeToMonacoEditorClassName: Record<ColorScheme, string> = {
|
||||
*/
|
||||
export const convertImgSourceHttpToBase64 = async (page: Page): Promise<void> => {
|
||||
await page.evaluate(() => {
|
||||
const imgs = document.querySelectorAll('img')
|
||||
// Skip images with data-skip-percy
|
||||
// See https://github.com/sourcegraph/sourcegraph/issues/28949
|
||||
const imgs = document.querySelectorAll<HTMLImageElement>('img:not([data-skip-percy])')
|
||||
|
||||
for (const img of imgs) {
|
||||
if (img.src.startsWith('data:image')) {
|
||||
|
||||
BIN
ui/assets/img/codecov.png
Normal file
BIN
ui/assets/img/codecov.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
BIN
ui/assets/img/eslint.png
Normal file
BIN
ui/assets/img/eslint.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
ui/assets/img/snyk.png
Normal file
BIN
ui/assets/img/snyk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Loading…
Reference in New Issue
Block a user