mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:31:43 +00:00
Code Insights: Connect UI and compute insight, assemble creation UI page (#38762)
* Extract series sanitizer to a separate shared module * Add compute insight FE model * Update sanitisers re-export paths * Support compute-powered insight in createInsight gql handler * Adjust search insight with new series sanitizer function * Add submit, clear and cancel actions to compute insight creation UI * Integrate live preview and compute creation form * Fix compute live preview legend rendering * Hide colour pickers and sum up series values grouped by series names
This commit is contained in:
parent
77a13fd4dc
commit
6c8e51cdde
@ -26,10 +26,23 @@ export interface FormSeriesProps {
|
||||
* solution, see https://github.com/sourcegraph/sourcegraph/issues/38236
|
||||
*/
|
||||
queryFieldDescription?: ReactNode
|
||||
|
||||
/**
|
||||
* This prop hides color picker from the series form. This field is needed for
|
||||
* compute powered insight creation UI, see https://github.com/sourcegraph/sourcegraph/issues/38832
|
||||
* for more details compute doesn't have series colors
|
||||
*/
|
||||
showColorPicker?: boolean
|
||||
}
|
||||
|
||||
export const FormSeries: FC<FormSeriesProps> = props => {
|
||||
const { seriesField, showValidationErrorsOnMount, repositories, queryFieldDescription } = props
|
||||
const {
|
||||
seriesField,
|
||||
showValidationErrorsOnMount,
|
||||
repositories,
|
||||
queryFieldDescription,
|
||||
showColorPicker = true,
|
||||
} = props
|
||||
|
||||
const { licensed } = useUiFeatures()
|
||||
const { series, changeSeries, editRequest, editCommit, cancelEdit, deleteSeries } = useEditableSeries(seriesField)
|
||||
@ -47,6 +60,7 @@ export const FormSeries: FC<FormSeriesProps> = props => {
|
||||
autofocus={line.autofocus}
|
||||
repositories={repositories}
|
||||
queryFieldDescription={queryFieldDescription}
|
||||
showColorPicker={showColorPicker}
|
||||
className={classNames('p-3', styles.formSeriesItem)}
|
||||
onSubmit={editCommit}
|
||||
onCancel={() => cancelEdit(line.id)}
|
||||
@ -60,6 +74,7 @@ export const FormSeries: FC<FormSeriesProps> = props => {
|
||||
onEdit={() => editRequest(line.id)}
|
||||
onRemove={() => deleteSeries(line.id)}
|
||||
className={styles.formSeriesItem}
|
||||
showColor={showColorPicker}
|
||||
{...line}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -36,6 +36,13 @@ interface FormSeriesInputProps {
|
||||
*/
|
||||
queryFieldDescription?: ReactNode
|
||||
|
||||
/**
|
||||
* This prop hides color picker from the series form. This field is needed for
|
||||
* compute powered insight creation UI, see https://github.com/sourcegraph/sourcegraph/issues/38832
|
||||
* for more details whe compute doesn't have series colors
|
||||
*/
|
||||
showColorPicker: boolean
|
||||
|
||||
/** Enable autofocus behavior of the first input element of series form. */
|
||||
autofocus?: boolean
|
||||
|
||||
@ -65,6 +72,7 @@ export const FormSeriesInput: FC<FormSeriesInputProps> = props => {
|
||||
autofocus = true,
|
||||
repositories,
|
||||
queryFieldDescription,
|
||||
showColorPicker,
|
||||
onCancel = noop,
|
||||
onSubmit = noop,
|
||||
onChange = noop,
|
||||
@ -148,13 +156,15 @@ export const FormSeriesInput: FC<FormSeriesInputProps> = props => {
|
||||
{...getDefaultInputProps(queryField)}
|
||||
/>
|
||||
|
||||
<FormColorInput
|
||||
name={`color group of ${index} series`}
|
||||
title="Color"
|
||||
className="mt-4"
|
||||
value={colorField.input.value}
|
||||
onChange={colorField.input.onChange}
|
||||
/>
|
||||
{showColorPicker && (
|
||||
<FormColorInput
|
||||
name={`color group of ${index} series`}
|
||||
title="Color"
|
||||
className="mt-4"
|
||||
value={colorField.input.value}
|
||||
onChange={colorField.input.onChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="mt-4">
|
||||
<Button
|
||||
|
||||
@ -17,6 +17,14 @@ interface SeriesCardProps {
|
||||
query: string
|
||||
/** Color value of series. */
|
||||
stroke?: string
|
||||
|
||||
/**
|
||||
* This prop hides color picker from the series form. This field is needed for
|
||||
* compute powered insight creation UI, see https://github.com/sourcegraph/sourcegraph/issues/38832
|
||||
* for more details whe compute doesn't have series colors
|
||||
*/
|
||||
showColor?: boolean
|
||||
|
||||
/** Custom class name for root button element. */
|
||||
className?: string
|
||||
/** Edit handler. */
|
||||
@ -29,7 +37,16 @@ interface SeriesCardProps {
|
||||
* Renders series card component, visual list item of series (name, color, query)
|
||||
* */
|
||||
export function SeriesCard(props: SeriesCardProps): ReactElement {
|
||||
const { disabled, name, query, stroke: color = DEFAULT_DATA_SERIES_COLOR, className, onEdit, onRemove } = props
|
||||
const {
|
||||
disabled,
|
||||
name,
|
||||
query,
|
||||
stroke: color = DEFAULT_DATA_SERIES_COLOR,
|
||||
showColor = true,
|
||||
className,
|
||||
onEdit,
|
||||
onRemove,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<Card
|
||||
@ -41,12 +58,14 @@ export function SeriesCard(props: SeriesCardProps): ReactElement {
|
||||
>
|
||||
<div className={styles.cardInfo}>
|
||||
<div className={classNames('mb-1 ', styles.cardTitle)}>
|
||||
<div
|
||||
data-testid="series-color-mark"
|
||||
/* eslint-disable-next-line react/forbid-dom-props */
|
||||
style={{ color: disabled ? 'var(--icon-muted)' : color }}
|
||||
className={styles.cardColorMark}
|
||||
/>
|
||||
{showColor && (
|
||||
<div
|
||||
data-testid="series-color-mark"
|
||||
/* eslint-disable-next-line react/forbid-dom-props */
|
||||
style={{ color: disabled ? 'var(--icon-muted)' : color }}
|
||||
className={styles.cardColorMark}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
data-testid="series-name"
|
||||
title={name}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
export * from './live-preview'
|
||||
export * from './creation-ui-layout/CreationUiLayout'
|
||||
|
||||
export { getSanitizedRepositories } from './sanitizers/repositories'
|
||||
export { getSanitizedRepositories, getSanitizedSeries } from './sanitizers'
|
||||
|
||||
export { CodeInsightDashboardsVisibility } from './CodeInsightDashboardsVisibility'
|
||||
export { CodeInsightTimeStepPicker } from './code-insight-time-step-picker/CodeInsightTimeStepPicker'
|
||||
|
||||
@ -27,10 +27,9 @@ type State<D> =
|
||||
| { status: StateStatus.Intact }
|
||||
|
||||
export function useLivePreview<D>(input: Input<D>): Output<D> {
|
||||
// Synthetic deps to trigger dry run for fetching live preview data
|
||||
const [lastPreviewVersion, setLastPreviewVersion] = useState(0)
|
||||
const [state, setState] = useState<State<D>>({ status: StateStatus.Intact })
|
||||
|
||||
// Synthetic deps to trigger dry run for fetching live preview data
|
||||
const debouncedInput = useDebounce(input, 500)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export { getSanitizedRepositories } from './repositories'
|
||||
export { getSanitizedSeries } from './series'
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { SearchBasedInsightSeries } from '../../../core'
|
||||
|
||||
export function getSanitizedLine(line: SearchBasedInsightSeries): SearchBasedInsightSeries {
|
||||
return {
|
||||
id: line.id,
|
||||
name: line.name.trim(),
|
||||
stroke: line.stroke,
|
||||
// Query field is a reg exp field for code insight query setting
|
||||
// Native html input element adds escape symbols by itself
|
||||
// to prevent this behavior below we replace double escaping
|
||||
// with just one series of escape characters e.g. - //
|
||||
query: line.query.replace(/\\\\/g, '\\'),
|
||||
}
|
||||
}
|
||||
|
||||
export function getSanitizedSeries(rawSeries: SearchBasedInsightSeries[]): SearchBasedInsightSeries[] {
|
||||
return rawSeries.map(getSanitizedLine)
|
||||
}
|
||||
@ -5,7 +5,7 @@ import { useMergeRefs } from 'use-callback-ref'
|
||||
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { useSearchParameters } from '@sourcegraph/wildcard'
|
||||
|
||||
import { Insight, isBackendInsight } from '../../../core'
|
||||
import { Insight, isBackendInsight, isComputeInsight } from '../../../core'
|
||||
|
||||
import { BackendInsightView } from './backend-insight/BackendInsight'
|
||||
import { BuiltInInsight } from './built-in-insight/BuiltInInsight'
|
||||
@ -46,6 +46,11 @@ export const SmartInsight = forwardRef<HTMLElement, SmartInsightProps>((props, r
|
||||
)
|
||||
}
|
||||
|
||||
if (isComputeInsight(insight)) {
|
||||
// Compute-powered insight card isn't implemented yet
|
||||
return null
|
||||
}
|
||||
|
||||
// Search based extension and lang stats insight are handled by built-in fetchers
|
||||
return (
|
||||
<BuiltInInsight
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
LangStatsInsight,
|
||||
InsightsDashboardOwner,
|
||||
SearchBasedInsight,
|
||||
ComputeInsight,
|
||||
} from '../types'
|
||||
import { InsightContentType } from '../types/insight/common'
|
||||
|
||||
@ -71,14 +72,15 @@ export interface FindInsightByNameInput {
|
||||
}
|
||||
|
||||
export type MinimalSearchBasedInsightData = Omit<SearchBasedInsight, 'id' | 'dashboardReferenceCount' | 'isFrozen'>
|
||||
|
||||
export type MinimalCaptureGroupInsightData = Omit<CaptureGroupInsight, 'id' | 'dashboardReferenceCount' | 'isFrozen'>
|
||||
export type MinimalLangStatsInsightData = Omit<LangStatsInsight, 'id' | 'dashboardReferenceCount' | 'isFrozen'>
|
||||
export type MinimalComputeInsightData = Omit<ComputeInsight, 'id' | 'dashboardReferenceCount' | 'isFrozen'>
|
||||
|
||||
export type CreationInsightInput =
|
||||
| MinimalSearchBasedInsightData
|
||||
| MinimalCaptureGroupInsightData
|
||||
| MinimalLangStatsInsightData
|
||||
| MinimalComputeInsightData
|
||||
|
||||
export interface InsightCreateInput {
|
||||
insight: CreationInsightInput
|
||||
|
||||
@ -15,6 +15,7 @@ import { InsightDashboard, InsightExecutionType, InsightType, isVirtualDashboard
|
||||
import {
|
||||
InsightCreateInput,
|
||||
MinimalCaptureGroupInsightData,
|
||||
MinimalComputeInsightData,
|
||||
MinimalSearchBasedInsightData,
|
||||
} from '../../../code-insights-backend-types'
|
||||
import { createInsightView } from '../../deserialization/create-insight-view'
|
||||
@ -32,6 +33,7 @@ export const createInsight = (apolloClient: ApolloClient<object>, input: Insight
|
||||
|
||||
switch (insight.type) {
|
||||
case InsightType.CaptureGroup:
|
||||
case InsightType.Compute:
|
||||
case InsightType.SearchBased: {
|
||||
return createSearchBasedInsight(apolloClient, insight, dashboard)
|
||||
}
|
||||
@ -55,7 +57,10 @@ export const createInsight = (apolloClient: ApolloClient<object>, input: Insight
|
||||
}
|
||||
}
|
||||
|
||||
type CreationSeriesInsightData = MinimalSearchBasedInsightData | MinimalCaptureGroupInsightData
|
||||
type CreationSeriesInsightData =
|
||||
| MinimalSearchBasedInsightData
|
||||
| MinimalCaptureGroupInsightData
|
||||
| MinimalComputeInsightData
|
||||
|
||||
function createSearchBasedInsight(
|
||||
apolloClient: ApolloClient<object>,
|
||||
@ -68,7 +73,7 @@ function createSearchBasedInsight(
|
||||
// create the insight first and only after update this newly created insight with filter values
|
||||
// This is due to lack of gql API flexibility and should be fixed as soon as BE gql API
|
||||
// supports filters in the create insight mutation.
|
||||
// TODO: Remove this imperative logic as soon as be supports filters
|
||||
// TODO: Remove this imperative logic as soon as BE supports filters
|
||||
if (insight.executionType === InsightExecutionType.Backend && insight.filters) {
|
||||
return from(
|
||||
apolloClient.mutate<FirstStepCreateSearchBasedInsightResult>({
|
||||
@ -134,7 +139,7 @@ function createSearchBasedInsight(
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Apollo cache after insight creation. Add insight to main insights gql query,
|
||||
* Updates Apollo caches after insight creation. Add insight to main insights gql query,
|
||||
* add newly created insight to the cache dashboard that insight was crated from.
|
||||
*/
|
||||
function searchInsightCreationOptimisticUpdate(
|
||||
|
||||
@ -2,12 +2,14 @@ import {
|
||||
LineChartSearchInsightDataSeriesInput,
|
||||
LineChartSearchInsightInput,
|
||||
PieChartSearchInsightInput,
|
||||
TimeIntervalStepUnit,
|
||||
} from '../../../../../../../graphql-operations'
|
||||
import { parseSeriesDisplayOptions } from '../../../../../components/insights-view-grid/components/backend-insight/components/drill-down-filters-panel/drill-down-filters/utils'
|
||||
import { InsightDashboard, InsightType, isVirtualDashboard } from '../../../../types'
|
||||
import {
|
||||
CreationInsightInput,
|
||||
MinimalCaptureGroupInsightData,
|
||||
MinimalComputeInsightData,
|
||||
MinimalLangStatsInsightData,
|
||||
MinimalSearchBasedInsightData,
|
||||
} from '../../../code-insights-backend-types'
|
||||
@ -27,6 +29,8 @@ export function getInsightCreateGqlInput(
|
||||
return getSearchInsightCreateInput(insight, dashboard)
|
||||
case InsightType.CaptureGroup:
|
||||
return getCaptureGroupInsightCreateInput(insight, dashboard)
|
||||
case InsightType.Compute:
|
||||
return getComputeInsightCreateInput(insight, dashboard)
|
||||
case InsightType.LangStats:
|
||||
return getLangStatsInsightCreateInput(insight, dashboard)
|
||||
}
|
||||
@ -115,3 +119,28 @@ export function getLangStatsInsightCreateInput(
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
export function getComputeInsightCreateInput(
|
||||
insight: MinimalComputeInsightData,
|
||||
dashboard: InsightDashboard | null
|
||||
): LineChartSearchInsightInput {
|
||||
const input: LineChartSearchInsightInput = {
|
||||
dataSeries: insight.series.map<LineChartSearchInsightDataSeriesInput>(series => ({
|
||||
query: series.query,
|
||||
options: {
|
||||
label: series.name,
|
||||
lineColor: series.stroke,
|
||||
},
|
||||
repositoryScope: { repositories: insight.repositories },
|
||||
timeScope: { stepInterval: { unit: TimeIntervalStepUnit.WEEK, value: 2 } },
|
||||
groupBy: insight.groupBy,
|
||||
})),
|
||||
options: { title: insight.title },
|
||||
}
|
||||
|
||||
if (dashboard && !isVirtualDashboard(dashboard)) {
|
||||
input.dashboards = [dashboard.id]
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ApolloClient } from '@apollo/client'
|
||||
import { ApolloCache } from '@apollo/client/cache'
|
||||
import { MutationUpdaterFunction } from '@apollo/client/core/types'
|
||||
import { from, Observable } from 'rxjs'
|
||||
import { from, Observable, throwError } from 'rxjs'
|
||||
|
||||
import {
|
||||
UpdateLangStatsInsightResult,
|
||||
@ -51,6 +51,10 @@ export const updateInsight = (
|
||||
)
|
||||
}
|
||||
|
||||
case InsightType.Compute: {
|
||||
return throwError(new Error('update mutation for the compute-powered insight is not implemented yet'))
|
||||
}
|
||||
|
||||
case InsightType.LangStats: {
|
||||
return from(
|
||||
client.mutate<UpdateLangStatsInsightResult, UpdateLangStatsInsightVariables>({
|
||||
|
||||
@ -17,6 +17,7 @@ export enum InsightType {
|
||||
SearchBased = 'SearchBased',
|
||||
LangStats = 'LangStats',
|
||||
CaptureGroup = 'CaptureGroup',
|
||||
Compute = 'Compute',
|
||||
}
|
||||
|
||||
export enum InsightContentType {
|
||||
@ -43,6 +44,8 @@ export interface BaseInsight {
|
||||
dashboards: InsightDashboardReference[]
|
||||
isFrozen: boolean
|
||||
|
||||
// TODO: move these fields out of base insight since they are
|
||||
// specific to the search based and capture group insights only
|
||||
seriesDisplayOptions?: SeriesDisplayOptionsInput
|
||||
appliedSeriesDisplayOptions?: SeriesDisplayOptions
|
||||
defaultSeriesDisplayOptions?: SeriesDisplayOptions
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { InsightExecutionType, InsightType, InsightFilters, InsightDashboardReference } from './common'
|
||||
import { CaptureGroupInsight } from './types/capture-group-insight'
|
||||
import { ComputeInsight } from './types/compute-insight'
|
||||
import { LangStatsInsight } from './types/lang-stat-insight'
|
||||
import { SearchBasedInsight, SearchBasedInsightSeries } from './types/search-insight'
|
||||
|
||||
@ -11,6 +12,7 @@ export type {
|
||||
SearchBasedInsightSeries,
|
||||
LangStatsInsight,
|
||||
CaptureGroupInsight,
|
||||
ComputeInsight,
|
||||
InsightFilters,
|
||||
}
|
||||
|
||||
@ -18,7 +20,7 @@ export type {
|
||||
* Main insight model. Union of all different insights by execution type (backend, runtime)
|
||||
* and insight type (lang-stats, search based, capture group) insights.
|
||||
*/
|
||||
export type Insight = SearchBasedInsight | LangStatsInsight | CaptureGroupInsight
|
||||
export type Insight = SearchBasedInsight | LangStatsInsight | CaptureGroupInsight | ComputeInsight
|
||||
|
||||
/**
|
||||
* Backend insights - insights that have all data series points already in gql API.
|
||||
@ -45,3 +47,7 @@ export function isCaptureGroupInsight(insight: Insight): insight is CaptureGroup
|
||||
export function isLangStatsInsight(insight: Insight): insight is LangStatsInsight {
|
||||
return insight.type === InsightType.LangStats
|
||||
}
|
||||
|
||||
export function isComputeInsight(insight: Insight): insight is ComputeInsight {
|
||||
return insight.type === InsightType.Compute
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { GroupByField } from '@sourcegraph/shared/src/graphql-operations'
|
||||
|
||||
import { BaseInsight, InsightExecutionType, InsightFilters, InsightType } from '../common'
|
||||
|
||||
import { SearchBasedInsightSeries } from './search-insight'
|
||||
|
||||
export interface ComputeInsight extends BaseInsight {
|
||||
type: InsightType.Compute
|
||||
repositories: string[]
|
||||
filters: InsightFilters
|
||||
series: SearchBasedInsightSeries[]
|
||||
groupBy: GroupByField
|
||||
executionType: InsightExecutionType.Backend
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { GroupByField } from '@sourcegraph/shared/src/graphql-operations'
|
||||
|
||||
import { WebStory } from '../../../../../components/WebStory'
|
||||
import { CodeInsightsBackendStoryMock } from '../../../CodeInsightsBackendStoryMock'
|
||||
import { BackendInsightDatum, SeriesChartContent } from '../../../core'
|
||||
@ -73,7 +75,12 @@ const codeInsightsBackend = {
|
||||
export const ComputeLivePreview: Story = () => (
|
||||
<CodeInsightsBackendStoryMock mocks={codeInsightsBackend}>
|
||||
<div className="m-3 px-4 py-5 bg-white">
|
||||
<ComputeLivePreviewComponent disabled={false} repositories="sourcegraph/sourcegraph" series={[]} />
|
||||
<ComputeLivePreviewComponent
|
||||
disabled={false}
|
||||
repositories="sourcegraph/sourcegraph"
|
||||
series={[]}
|
||||
groupBy={GroupByField.AUTHOR}
|
||||
/>
|
||||
</div>
|
||||
</CodeInsightsBackendStoryMock>
|
||||
)
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
|
||||
import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts'
|
||||
import { useDeepMemo, Text } from '@sourcegraph/wildcard'
|
||||
import { groupBy } from 'lodash'
|
||||
|
||||
import { Series } from '../../../../../charts'
|
||||
import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts'
|
||||
import { useDeepMemo } from '@sourcegraph/wildcard'
|
||||
|
||||
import { LegendItem, LegendList, Series } from '../../../../../charts'
|
||||
import { BarChart } from '../../../../../charts/components/bar-chart/BarChart'
|
||||
import { GroupByField } from '../../../../../graphql-operations'
|
||||
import {
|
||||
@ -13,19 +15,23 @@ import {
|
||||
LivePreviewChart,
|
||||
LivePreviewBlurBackdrop,
|
||||
LivePreviewBanner,
|
||||
LivePreviewLegend,
|
||||
getSanitizedRepositories,
|
||||
useLivePreview,
|
||||
StateStatus,
|
||||
COMPUTE_MOCK_CHART,
|
||||
EditableDataSeries,
|
||||
} from '../../../components'
|
||||
import { BackendInsightDatum, CategoricalChartContent, CodeInsightsBackendContext } from '../../../core'
|
||||
import {
|
||||
BackendInsightDatum,
|
||||
CategoricalChartContent,
|
||||
CodeInsightsBackendContext,
|
||||
SeriesPreviewSettings,
|
||||
} from '../../../core'
|
||||
|
||||
interface LanguageUsageDatum {
|
||||
name: string
|
||||
value: number
|
||||
fill: string
|
||||
linkURL: string
|
||||
group?: string
|
||||
}
|
||||
|
||||
@ -33,45 +39,38 @@ interface ComputeLivePreviewProps {
|
||||
disabled: boolean
|
||||
repositories: string
|
||||
className?: string
|
||||
series: {
|
||||
query: string
|
||||
label: string
|
||||
stroke: string
|
||||
groupBy?: GroupByField
|
||||
}[]
|
||||
groupBy: GroupByField
|
||||
series: EditableDataSeries[]
|
||||
}
|
||||
|
||||
export const ComputeLivePreview: React.FunctionComponent<ComputeLivePreviewProps> = props => {
|
||||
// For the purposes of building out this component before the backend is ready
|
||||
// we are using the standard "line series" type data.
|
||||
// TODO after backend is merged, remove update the series value to use that structure
|
||||
const { disabled, repositories, series, className } = props
|
||||
const { getInsightPreviewContent: getLivePreviewContent } = useContext(CodeInsightsBackendContext)
|
||||
|
||||
const sanitizedSeries = series.map(srs => ({
|
||||
query: srs.query,
|
||||
label: srs.label,
|
||||
stroke: srs.stroke,
|
||||
groupBy: srs.groupBy,
|
||||
}))
|
||||
const { disabled, repositories, series, groupBy, className } = props
|
||||
const { getInsightPreviewContent } = useContext(CodeInsightsBackendContext)
|
||||
|
||||
const settings = useDeepMemo({
|
||||
disabled,
|
||||
repositories: getSanitizedRepositories(repositories),
|
||||
series: sanitizedSeries,
|
||||
// For the purposes of building out this component before the backend is ready
|
||||
// we are using the standard "line series" type data.
|
||||
// TODO after backend is merged, remove update the series value to use that structure
|
||||
series: series.map<SeriesPreviewSettings>(srs => ({
|
||||
query: srs.query,
|
||||
label: srs.name,
|
||||
stroke: srs.stroke ?? 'blue',
|
||||
generatedFromCaptureGroup: true,
|
||||
groupBy,
|
||||
})),
|
||||
// TODO: Revisit this hardcoded value. Compute does not use it, but it's still required
|
||||
// for `searchInsightPreview`
|
||||
step: {
|
||||
days: 1,
|
||||
},
|
||||
step: { days: 1 },
|
||||
})
|
||||
|
||||
const getLivePreview = useMemo(
|
||||
() => ({
|
||||
disabled: settings.disabled,
|
||||
fetcher: () => getLivePreviewContent(settings),
|
||||
fetcher: () => getInsightPreviewContent(settings),
|
||||
}),
|
||||
[settings, getLivePreviewContent]
|
||||
[settings, getInsightPreviewContent]
|
||||
)
|
||||
|
||||
const { state, update } = useLivePreview(getLivePreview)
|
||||
@ -117,22 +116,41 @@ export const ComputeLivePreview: React.FunctionComponent<ComputeLivePreviewProps
|
||||
)}
|
||||
|
||||
{state.status === StateStatus.Data && (
|
||||
<LivePreviewLegend series={state.data.series as Series<unknown>[]} />
|
||||
<LegendList className="mt-3">
|
||||
{state.data.series.map(series => (
|
||||
<LegendItem
|
||||
key={series.id}
|
||||
color={getComputeSeriesColor(series)}
|
||||
name={getComputeSeriesName(series)}
|
||||
/>
|
||||
))}
|
||||
</LegendList>
|
||||
)}
|
||||
</LivePreviewCard>
|
||||
|
||||
<Text className="mt-4 pl-2">
|
||||
<strong>Timeframe:</strong> May 20, 2022 - Oct 20, 2022
|
||||
</Text>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
const mapSeriesToCompute = (series: Series<BackendInsightDatum>[]): LanguageUsageDatum[] =>
|
||||
series.map(series => ({
|
||||
group: series.name,
|
||||
name: series.name,
|
||||
value: series.data[0].value ?? 0,
|
||||
fill: series.color ?? 'var(--blue)',
|
||||
linkURL: series.data[0].link ?? '',
|
||||
}))
|
||||
const mapSeriesToCompute = (series: Series<BackendInsightDatum>[]): LanguageUsageDatum[] => {
|
||||
const seriesGroups = groupBy(series, series => series.name)
|
||||
|
||||
// Group series result by seres name and sum up series value with the same name
|
||||
return Object.keys(seriesGroups).map(key =>
|
||||
seriesGroups[key].reduce(
|
||||
(memo, series) => {
|
||||
memo.value += series.data.reduce((sum, datum) => sum + (series.getYValue(datum) ?? 0), 0)
|
||||
|
||||
return memo
|
||||
},
|
||||
{
|
||||
name: getComputeSeriesName(seriesGroups[key][0]),
|
||||
fill: getComputeSeriesColor(seriesGroups[key][0]),
|
||||
value: 0,
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const getComputeSeriesName = (series: Series<any>): string => (series.name ? series.name : 'Other')
|
||||
const getComputeSeriesColor = (series: Series<any>): string =>
|
||||
series.name ? series.color ?? 'var(--blue)' : 'var(--oc-gray-4)'
|
||||
|
||||
@ -70,7 +70,6 @@ export const CreationRoutes: React.FunctionComponent<React.PropsWithChildren<Cre
|
||||
|
||||
<Route
|
||||
path={`${match.url}/group-results`}
|
||||
exact={true}
|
||||
render={() => (
|
||||
<InsightCreationLazyPage
|
||||
mode={InsightCreationPageType.Compute}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import { FC, useContext, useMemo } from 'react'
|
||||
|
||||
import { useHistory } from 'react-router'
|
||||
|
||||
@ -28,9 +28,7 @@ interface InsightCreationPageProps extends TelemetryProps {
|
||||
mode: InsightCreationPageType
|
||||
}
|
||||
|
||||
export const InsightCreationPage: React.FunctionComponent<
|
||||
React.PropsWithChildren<InsightCreationPageProps>
|
||||
> = props => {
|
||||
export const InsightCreationPage: FC<InsightCreationPageProps> = props => {
|
||||
const { mode, telemetryService } = props
|
||||
|
||||
const history = useHistory()
|
||||
|
||||
@ -1,19 +1,30 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import { FunctionComponent, useCallback, useMemo } from 'react'
|
||||
|
||||
import BarChartIcon from 'mdi-react/BarChartIcon'
|
||||
|
||||
import { asError } from '@sourcegraph/common'
|
||||
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { Link, PageHeader, Text, useLocalStorage } from '@sourcegraph/wildcard'
|
||||
import { Link, PageHeader, Text, useLocalStorage, useObservable } from '@sourcegraph/wildcard'
|
||||
|
||||
import { PageTitle } from '../../../../../../components/PageTitle'
|
||||
import { CodeInsightsPage, FormChangeEvent } from '../../../../components'
|
||||
import {
|
||||
CodeInsightCreationMode,
|
||||
CodeInsightsCreationActions,
|
||||
CodeInsightsPage,
|
||||
FORM_ERROR,
|
||||
FormChangeEvent,
|
||||
SubmissionErrors,
|
||||
} from '../../../../components'
|
||||
import { ComputeInsight } from '../../../../core'
|
||||
import { useUiFeatures } from '../../../../hooks'
|
||||
import { CodeInsightTrackType } from '../../../../pings'
|
||||
|
||||
import { ComputeInsightCreationContent } from './components/ComputeInsightCreationContent'
|
||||
import { CreateComputeInsightFormFields } from './types'
|
||||
import { getSanitizedComputeInsight } from './utils/insight-sanitaizer'
|
||||
|
||||
export interface InsightCreateEvent {
|
||||
// TODO: It will be improved in https://github.com/sourcegraph/sourcegraph/issues/37965
|
||||
insight: any
|
||||
insight: ComputeInsight
|
||||
}
|
||||
|
||||
interface ComputeInsightCreationPageProps extends TelemetryProps {
|
||||
@ -23,6 +34,11 @@ interface ComputeInsightCreationPageProps extends TelemetryProps {
|
||||
}
|
||||
|
||||
export const ComputeInsightCreationPage: FunctionComponent<ComputeInsightCreationPageProps> = props => {
|
||||
const { telemetryService, onInsightCreateRequest, onSuccessfulCreation, onCancel } = props
|
||||
|
||||
const { licensed, insight } = useUiFeatures()
|
||||
const creationPermission = useObservable(useMemo(() => insight.getCreationPermissions(), [insight]))
|
||||
|
||||
// We do not use temporal user settings since form values are not so important to
|
||||
// waste users time for waiting response of yet another network request to just
|
||||
// render creation UI form.
|
||||
@ -36,16 +52,42 @@ export const ComputeInsightCreationPage: FunctionComponent<ComputeInsightCreatio
|
||||
setInitialFormValues(event.values)
|
||||
}
|
||||
|
||||
const handleSubmit = (): void => {
|
||||
// TODO: It will be implemented in https://github.com/sourcegraph/sourcegraph/issues/37965
|
||||
}
|
||||
const handleSubmit = useCallback(
|
||||
async (values: CreateComputeInsightFormFields): Promise<SubmissionErrors> => {
|
||||
try {
|
||||
const insight = getSanitizedComputeInsight(values)
|
||||
|
||||
const handleCancel = (): void => {
|
||||
// TODO: It will be implemented in https://github.com/sourcegraph/sourcegraph/issues/37965
|
||||
}
|
||||
await onInsightCreateRequest({ insight })
|
||||
|
||||
// Clear initial values if user successfully created search insight
|
||||
setInitialFormValues(undefined)
|
||||
telemetryService.log('CodeInsightsComputeCreationPageSubmitClick')
|
||||
telemetryService.log(
|
||||
'InsightAddition',
|
||||
{ insightType: CodeInsightTrackType.ComputeInsight },
|
||||
{ insightType: CodeInsightTrackType.ComputeInsight }
|
||||
)
|
||||
|
||||
onSuccessfulCreation()
|
||||
} catch (error) {
|
||||
return { [FORM_ERROR]: asError(error) }
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
[onInsightCreateRequest, onSuccessfulCreation, setInitialFormValues, telemetryService]
|
||||
)
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
// Clear initial values if user successfully created search insight
|
||||
setInitialFormValues(undefined)
|
||||
telemetryService.log('CodeInsightsComputeCreationPageCancelClick')
|
||||
|
||||
onCancel()
|
||||
}, [setInitialFormValues, telemetryService, onCancel])
|
||||
|
||||
return (
|
||||
<CodeInsightsPage className="col-12">
|
||||
<CodeInsightsPage className="col-11">
|
||||
<PageTitle title="Create compute insight - Code Insights" />
|
||||
|
||||
<PageHeader
|
||||
@ -59,13 +101,25 @@ export const ComputeInsightCreationPage: FunctionComponent<ComputeInsightCreatio
|
||||
/>
|
||||
|
||||
<ComputeInsightCreationContent
|
||||
touched={false}
|
||||
initialValue={initialFormValues}
|
||||
data-testid="search-insight-create-page-content"
|
||||
className="pb-5"
|
||||
onChange={handleChange}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
>
|
||||
{form => (
|
||||
<CodeInsightsCreationActions
|
||||
mode={CodeInsightCreationMode.Creation}
|
||||
licensed={licensed}
|
||||
available={creationPermission?.available}
|
||||
submitting={form.submitting}
|
||||
errors={form.submitErrors?.[FORM_ERROR]}
|
||||
clear={form.isFormClearActive}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
</ComputeInsightCreationContent>
|
||||
</CodeInsightsPage>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { FC, HTMLAttributes } from 'react'
|
||||
import { FC, HTMLAttributes, ReactNode } from 'react'
|
||||
|
||||
import { GroupByField } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { Code, Input, Link } from '@sourcegraph/wildcard'
|
||||
|
||||
import {
|
||||
@ -13,18 +14,16 @@ import {
|
||||
getDefaultInputProps,
|
||||
insightRepositoriesAsyncValidator,
|
||||
insightRepositoriesValidator,
|
||||
insightSeriesValidator,
|
||||
insightTitleValidator,
|
||||
RepositoriesField,
|
||||
SubmissionErrors,
|
||||
useField,
|
||||
EditableDataSeries,
|
||||
useForm,
|
||||
} from '../../../../../components'
|
||||
import { useEditableSeries } from '../../../../../components/creation-ui/form-series/use-editable-series'
|
||||
import { useUiFeatures } from '../../../../../hooks'
|
||||
import { ComputeLivePreview } from '../../ComputeLivePreview'
|
||||
import { getSanitizedSeries } from '../../search-insight/utils/insight-sanitizer'
|
||||
import { ComputeInsightMap, CreateComputeInsightFormFields } from '../types'
|
||||
import { CreateComputeInsightFormFields } from '../types'
|
||||
|
||||
import { ComputeInsightMapPicker } from './ComputeInsightMapPicker'
|
||||
|
||||
@ -32,32 +31,35 @@ const INITIAL_INSIGHT_VALUES: CreateComputeInsightFormFields = {
|
||||
series: [createDefaultEditSeries({ edit: true })],
|
||||
title: '',
|
||||
repositories: '',
|
||||
groupBy: ComputeInsightMap.Repositories,
|
||||
groupBy: GroupByField.REPO,
|
||||
dashboardReferenceCount: 0,
|
||||
}
|
||||
|
||||
type NativeContainerProps = Omit<HTMLAttributes<HTMLDivElement>, 'onSubmit' | 'onChange'>
|
||||
type NativeContainerProps = Omit<HTMLAttributes<HTMLDivElement>, 'onSubmit' | 'onChange' | 'children'>
|
||||
|
||||
export interface RenderPropertyInputs {
|
||||
submitting: boolean
|
||||
submitErrors: SubmissionErrors
|
||||
isFormClearActive: boolean
|
||||
}
|
||||
|
||||
interface ComputeInsightCreationContentProps extends NativeContainerProps {
|
||||
/** This component might be used in edit or creation insight case. */
|
||||
mode?: 'creation' | 'edit'
|
||||
touched: boolean
|
||||
children: (input: RenderPropertyInputs) => ReactNode
|
||||
initialValue?: Partial<CreateComputeInsightFormFields>
|
||||
|
||||
onChange: (event: FormChangeEvent<CreateComputeInsightFormFields>) => void
|
||||
onSubmit: (values: CreateComputeInsightFormFields) => SubmissionErrors | Promise<SubmissionErrors> | void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
export const ComputeInsightCreationContent: FC<ComputeInsightCreationContentProps> = props => {
|
||||
const { mode = 'creation', initialValue, onChange, onSubmit, onCancel, ...attributes } = props
|
||||
|
||||
const { touched, initialValue, onChange, onSubmit, children, ...attributes } = props
|
||||
const { licensed } = useUiFeatures()
|
||||
|
||||
const { formAPI, handleSubmit } = useForm<CreateComputeInsightFormFields>({
|
||||
const { formAPI, values, handleSubmit } = useForm<CreateComputeInsightFormFields>({
|
||||
initialValues: { ...INITIAL_INSIGHT_VALUES, ...initialValue },
|
||||
onSubmit,
|
||||
onChange,
|
||||
touched: mode === 'edit',
|
||||
touched,
|
||||
})
|
||||
|
||||
const title = useField({
|
||||
@ -79,6 +81,7 @@ export const ComputeInsightCreationContent: FC<ComputeInsightCreationContentProp
|
||||
const series = useField({
|
||||
name: 'series',
|
||||
formApi: formAPI,
|
||||
validators: { sync: insightSeriesValidator },
|
||||
})
|
||||
|
||||
const groupBy = useField({
|
||||
@ -86,17 +89,32 @@ export const ComputeInsightCreationContent: FC<ComputeInsightCreationContentProp
|
||||
formApi: formAPI,
|
||||
})
|
||||
|
||||
const { series: editSeries } = useEditableSeries(series)
|
||||
const handleFormReset = (): void => {
|
||||
// TODO [VK] Change useForm API in order to implement form.reset method.
|
||||
title.input.onChange('')
|
||||
repositories.input.onChange('')
|
||||
series.input.onChange([createDefaultEditSeries({ edit: true })])
|
||||
|
||||
// Focus first element of the form
|
||||
repositories.input.ref.current?.focus()
|
||||
}
|
||||
|
||||
const hasFilledValue =
|
||||
values.series?.some(line => line.name !== '' || line.query !== '') ||
|
||||
values.repositories !== '' ||
|
||||
values.title !== ''
|
||||
|
||||
// If some fields that needed to run live preview are invalid
|
||||
// we should disable live chart preview
|
||||
const allFieldsForPreviewAreValid =
|
||||
repositories.meta.validState === 'VALID' &&
|
||||
(series.meta.validState === 'VALID' || editSeries.some(series => series.valid))
|
||||
(series.meta.validState === 'VALID' || series.meta.value.some(series => series.valid))
|
||||
|
||||
const validSeries = series.meta.value.filter(series => series.valid)
|
||||
|
||||
return (
|
||||
<CreationUiLayout {...attributes}>
|
||||
<CreationUIForm onSubmit={handleSubmit}>
|
||||
<CreationUIForm noValidate={true} onSubmit={handleSubmit} onReset={handleFormReset}>
|
||||
<FormGroup
|
||||
name="insight repositories"
|
||||
title="Targeted repositories"
|
||||
@ -120,6 +138,7 @@ export const ComputeInsightCreationContent: FC<ComputeInsightCreationContentProp
|
||||
innerRef={series.input.ref}
|
||||
name="data series group"
|
||||
title="Data series"
|
||||
error={series.meta.touched && series.meta.error}
|
||||
subtitle={
|
||||
licensed
|
||||
? 'Add any number of data series to your chart'
|
||||
@ -129,7 +148,8 @@ export const ComputeInsightCreationContent: FC<ComputeInsightCreationContentProp
|
||||
<FormSeries
|
||||
seriesField={series}
|
||||
repositories={repositories.input.value}
|
||||
showValidationErrorsOnMount={false}
|
||||
showValidationErrorsOnMount={formAPI.submitted}
|
||||
showColorPicker={false}
|
||||
queryFieldDescription={
|
||||
<ul className="pl-3">
|
||||
<li>
|
||||
@ -150,7 +170,7 @@ export const ComputeInsightCreationContent: FC<ComputeInsightCreationContentProp
|
||||
<hr className="my-4 w-100" />
|
||||
|
||||
<FormGroup name="map result" title="Map result">
|
||||
<ComputeInsightMapPicker series={series.input.value} {...groupBy.input} />
|
||||
<ComputeInsightMapPicker series={validSeries} {...groupBy.input} />
|
||||
|
||||
<small className="text-muted mt-3">
|
||||
Learn more about <Link to="">grouping results</Link>
|
||||
@ -169,31 +189,23 @@ export const ComputeInsightCreationContent: FC<ComputeInsightCreationContentProp
|
||||
{...getDefaultInputProps(title)}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<hr className="my-4 w-100" />
|
||||
|
||||
{children({
|
||||
submitting: formAPI.submitting,
|
||||
submitErrors: formAPI.submitErrors,
|
||||
isFormClearActive: hasFilledValue,
|
||||
})}
|
||||
</CreationUIForm>
|
||||
|
||||
<CreationUIPreview
|
||||
as={ComputeLivePreview}
|
||||
disabled={!allFieldsForPreviewAreValid}
|
||||
repositories={repositories.meta.value}
|
||||
series={seriesToPreview(editSeries)}
|
||||
series={validSeries}
|
||||
groupBy={groupBy.meta.value}
|
||||
/>
|
||||
</CreationUiLayout>
|
||||
)
|
||||
}
|
||||
|
||||
function seriesToPreview(
|
||||
currentSeries: EditableDataSeries[]
|
||||
): {
|
||||
query: string
|
||||
label: string
|
||||
generatedFromCaptureGroup: boolean
|
||||
stroke: string
|
||||
}[] {
|
||||
const validSeries = currentSeries.filter(series => series.valid)
|
||||
return getSanitizedSeries(validSeries).map(series => ({
|
||||
query: series.query,
|
||||
stroke: series.stroke ? series.stroke : '',
|
||||
label: series.name,
|
||||
generatedFromCaptureGroup: false,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -5,15 +5,15 @@ import { scanSearchQuery } from '@sourcegraph/shared/src/search/query/scanner'
|
||||
import { Filter } from '@sourcegraph/shared/src/search/query/token'
|
||||
import { Button, ButtonGroup, ButtonProps, Tooltip } from '@sourcegraph/wildcard'
|
||||
|
||||
import { GroupByField } from '../../../../../../../graphql-operations'
|
||||
import { SearchBasedInsightSeries } from '../../../../../core'
|
||||
import { ComputeInsightMap } from '../types'
|
||||
|
||||
const TOOLTIP_TEXT = 'Available only for queries with type:commit and type:diff'
|
||||
|
||||
export interface ComputeInsightMapPickerProps {
|
||||
series: SearchBasedInsightSeries[]
|
||||
value: ComputeInsightMap
|
||||
onChange: (nextValue: ComputeInsightMap) => void
|
||||
value: GroupByField
|
||||
onChange: (nextValue: GroupByField) => void
|
||||
}
|
||||
|
||||
export const ComputeInsightMapPicker: FC<ComputeInsightMapPickerProps> = props => {
|
||||
@ -21,7 +21,7 @@ export const ComputeInsightMapPicker: FC<ComputeInsightMapPickerProps> = props =
|
||||
|
||||
const handleOptionClick = (event: MouseEvent<HTMLButtonElement>): void => {
|
||||
const target = event.target as HTMLButtonElement
|
||||
const pickedValue = target.value as ComputeInsightMap
|
||||
const pickedValue = target.value as GroupByField
|
||||
|
||||
onChange(pickedValue)
|
||||
}
|
||||
@ -47,33 +47,25 @@ export const ComputeInsightMapPicker: FC<ComputeInsightMapPickerProps> = props =
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasTypeDiffOrCommit && (value === ComputeInsightMap.Author || value === ComputeInsightMap.Date)) {
|
||||
onChange(ComputeInsightMap.Repositories)
|
||||
if (!hasTypeDiffOrCommit && (value === GroupByField.AUTHOR || value === GroupByField.DATE)) {
|
||||
onChange(GroupByField.REPO)
|
||||
}
|
||||
}, [hasTypeDiffOrCommit, value, onChange])
|
||||
|
||||
return (
|
||||
<ButtonGroup className="mb-3 d-block">
|
||||
<OptionButton
|
||||
active={value === ComputeInsightMap.Repositories}
|
||||
value={ComputeInsightMap.Repositories}
|
||||
onClick={handleOptionClick}
|
||||
>
|
||||
<OptionButton active={value === GroupByField.REPO} value={GroupByField.REPO} onClick={handleOptionClick}>
|
||||
repository
|
||||
</OptionButton>
|
||||
|
||||
<OptionButton
|
||||
active={value === ComputeInsightMap.Path}
|
||||
value={ComputeInsightMap.Path}
|
||||
onClick={handleOptionClick}
|
||||
>
|
||||
<OptionButton active={value === GroupByField.PATH} value={GroupByField.PATH} onClick={handleOptionClick}>
|
||||
path
|
||||
</OptionButton>
|
||||
|
||||
<Tooltip content={!hasTypeDiffOrCommit ? TOOLTIP_TEXT : undefined}>
|
||||
<OptionButton
|
||||
active={value === ComputeInsightMap.Author}
|
||||
value={ComputeInsightMap.Author}
|
||||
active={value === GroupByField.AUTHOR}
|
||||
value={GroupByField.AUTHOR}
|
||||
disabled={!hasTypeDiffOrCommit}
|
||||
onClick={handleOptionClick}
|
||||
>
|
||||
@ -83,8 +75,8 @@ export const ComputeInsightMapPicker: FC<ComputeInsightMapPickerProps> = props =
|
||||
|
||||
<Tooltip content={!hasTypeDiffOrCommit ? TOOLTIP_TEXT : undefined}>
|
||||
<OptionButton
|
||||
active={value === ComputeInsightMap.Date}
|
||||
value={ComputeInsightMap.Date}
|
||||
active={value === GroupByField.DATE}
|
||||
value={GroupByField.DATE}
|
||||
disabled={!hasTypeDiffOrCommit}
|
||||
data-tooltip={!hasTypeDiffOrCommit ? TOOLTIP_TEXT : undefined}
|
||||
onClick={handleOptionClick}
|
||||
@ -97,7 +89,7 @@ export const ComputeInsightMapPicker: FC<ComputeInsightMapPickerProps> = props =
|
||||
}
|
||||
|
||||
interface OptionButtonProps extends ButtonProps {
|
||||
value: ComputeInsightMap
|
||||
value: GroupByField
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
import { EditableDataSeries } from '../../../../components'
|
||||
import { GroupByField } from '@sourcegraph/shared/src/graphql-operations'
|
||||
|
||||
export enum ComputeInsightMap {
|
||||
Repositories = 'repositories',
|
||||
Path = 'path',
|
||||
Author = 'author',
|
||||
Date = 'date',
|
||||
}
|
||||
import { EditableDataSeries } from '../../../../components'
|
||||
|
||||
export interface CreateComputeInsightFormFields {
|
||||
/**
|
||||
@ -28,5 +23,5 @@ export interface CreateComputeInsightFormFields {
|
||||
*/
|
||||
dashboardReferenceCount: number
|
||||
|
||||
groupBy: ComputeInsightMap
|
||||
groupBy: GroupByField
|
||||
}
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import { getSanitizedRepositories, getSanitizedSeries } from '../../../../../components'
|
||||
import { ComputeInsight, InsightExecutionType, InsightType } from '../../../../../core'
|
||||
import { CreateComputeInsightFormFields } from '../types'
|
||||
|
||||
export const getSanitizedComputeInsight = (values: CreateComputeInsightFormFields): ComputeInsight => ({
|
||||
id: 'newly-created-insight',
|
||||
title: values.title,
|
||||
repositories: getSanitizedRepositories(values.repositories),
|
||||
groupBy: values.groupBy,
|
||||
type: InsightType.Compute,
|
||||
executionType: InsightExecutionType.Backend,
|
||||
dashboards: [],
|
||||
series: getSanitizedSeries(values.series),
|
||||
isFrozen: false,
|
||||
dashboardReferenceCount: 0,
|
||||
filters: {
|
||||
excludeRepoRegexp: '',
|
||||
includeRepoRegexp: '',
|
||||
context: '',
|
||||
},
|
||||
})
|
||||
@ -10,10 +10,10 @@ import {
|
||||
SubmissionErrors,
|
||||
createDefaultEditSeries,
|
||||
EditableDataSeries,
|
||||
getSanitizedSeries,
|
||||
} from '../../../../../components'
|
||||
import { LineChartLivePreview, LivePreviewSeries } from '../../LineChartLivePreview'
|
||||
import { CreateInsightFormFields } from '../types'
|
||||
import { getSanitizedSeries } from '../utils/insight-sanitizer'
|
||||
|
||||
import { RenderPropertyInputs, SearchInsightCreationForm } from './SearchInsightCreationForm'
|
||||
import { useInsightCreationForm } from './use-insight-creation-form'
|
||||
|
||||
@ -1,29 +1,7 @@
|
||||
import { getSanitizedRepositories } from '../../../../../components'
|
||||
import {
|
||||
MinimalSearchBasedInsightData,
|
||||
InsightExecutionType,
|
||||
InsightType,
|
||||
SearchBasedInsightSeries,
|
||||
} from '../../../../../core'
|
||||
import { getSanitizedRepositories, getSanitizedSeries } from '../../../../../components'
|
||||
import { MinimalSearchBasedInsightData, InsightExecutionType, InsightType } from '../../../../../core'
|
||||
import { CreateInsightFormFields } from '../types'
|
||||
|
||||
export function getSanitizedLine(line: SearchBasedInsightSeries): SearchBasedInsightSeries {
|
||||
return {
|
||||
id: line.id,
|
||||
name: line.name.trim(),
|
||||
stroke: line.stroke,
|
||||
// Query field is a reg exp field for code insight query setting
|
||||
// Native html input element adds escape symbols by itself
|
||||
// to prevent this behavior below we replace double escaping
|
||||
// with just one series of escape characters e.g. - //
|
||||
query: line.query.replace(/\\\\/g, '\\'),
|
||||
}
|
||||
}
|
||||
|
||||
export function getSanitizedSeries(rawSeries: SearchBasedInsightSeries[]): SearchBasedInsightSeries[] {
|
||||
return rawSeries.map(getSanitizedLine)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function converter from form shape insight to insight as it is
|
||||
* presented in user/org settings.
|
||||
|
||||
@ -2,7 +2,7 @@ import { FunctionComponent } from 'react'
|
||||
|
||||
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
|
||||
import { Insight, isBackendInsight } from '../../../../core'
|
||||
import { Insight, isBackendInsight, isComputeInsight } from '../../../../core'
|
||||
|
||||
import { StandaloneBackendInsight } from './standalone-backend-insight/StandaloneBackendInsight'
|
||||
import { StandaloneRuntimeInsight } from './standalone-runtime-insight/StandaloneRuntimeInsight'
|
||||
@ -19,6 +19,10 @@ export const SmartStandaloneInsight: FunctionComponent<SmartStandaloneInsightPro
|
||||
return <StandaloneBackendInsight insight={insight} telemetryService={telemetryService} className={className} />
|
||||
}
|
||||
|
||||
if (isComputeInsight(insight)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Search based extension and lang stats insight are handled by built-in fetchers
|
||||
return <StandaloneRuntimeInsight insight={insight} telemetryService={telemetryService} className={className} />
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ export enum CodeInsightTrackType {
|
||||
SearchBasedInsight = 'SearchBased',
|
||||
LangStatsInsight = 'LangStats',
|
||||
CaptureGroupInsight = 'CaptureGroup',
|
||||
ComputeInsight = 'ComputeInsight',
|
||||
InProductLandingPageInsight = 'InProductLandingPageInsight',
|
||||
CloudLandingPageInsight = 'CloudLandingPageInsight',
|
||||
}
|
||||
@ -16,5 +17,7 @@ export const getTrackingTypeByInsightType = (insightType: InsightType): CodeInsi
|
||||
return CodeInsightTrackType.SearchBasedInsight
|
||||
case InsightType.LangStats:
|
||||
return CodeInsightTrackType.LangStatsInsight
|
||||
case InsightType.Compute:
|
||||
return CodeInsightTrackType.ComputeInsight
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user