mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 16:51:55 +00:00
Code Insights: Reuse series picker in compute insight creation UI page (#38183)
* Improve type exports * Refactor form series component (preparation for reusing it in compute insight page) * Update repositories prop comment * Put validators in a separate file * Fix autofocus problem for series form fields * Move insight series picker to the shared components directory * Fix imports for the search-based and capture group insight creation UI pages * Move constants to the top level exports * Reuse form series component on the compute-powered insight page * Simplify useField generics types * Fix visual bug with query input editor * Fix problems with bad imports and outdated mocks
This commit is contained in:
parent
97ba145d8e
commit
a79eba4791
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
import { Page } from '../../../../components/Page'
|
||||
import { useUiFeatures } from '../../hooks/use-ui-features'
|
||||
import { useUiFeatures } from '../../hooks'
|
||||
|
||||
import { CodeInsightsLimitAccessBanner } from './limit-access-banner/CodeInsightsLimitAccessBanner'
|
||||
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
import { FC, ReactNode } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { Button } from '@sourcegraph/wildcard'
|
||||
|
||||
import { useUiFeatures } from '../../../hooks'
|
||||
import { LimitedAccessLabel, useFieldAPI } from '../../index'
|
||||
|
||||
import { FormSeriesInput } from './components/form-series-input/FormSeriesInput'
|
||||
import { SeriesCard } from './components/series-card/SeriesCard'
|
||||
import { EditableDataSeries } from './types'
|
||||
import { useEditableSeries } from './use-editable-series'
|
||||
|
||||
import styles from './FormSeries.module.scss'
|
||||
|
||||
export interface FormSeriesProps {
|
||||
seriesField: useFieldAPI<EditableDataSeries[]>
|
||||
repositories: string
|
||||
showValidationErrorsOnMount: boolean
|
||||
|
||||
/**
|
||||
* This field is only needed for specifying a special compute-specific
|
||||
* query field description when this component is used on the compute-powered insight.
|
||||
* This prop should be removed when we will have a better form series management
|
||||
* solution, see https://github.com/sourcegraph/sourcegraph/issues/38236
|
||||
*/
|
||||
queryFieldDescription?: ReactNode
|
||||
}
|
||||
|
||||
export const FormSeries: FC<FormSeriesProps> = props => {
|
||||
const { seriesField, showValidationErrorsOnMount, repositories, queryFieldDescription } = props
|
||||
|
||||
const { licensed } = useUiFeatures()
|
||||
const { series, changeSeries, editRequest, editCommit, cancelEdit, deleteSeries } = useEditableSeries(seriesField)
|
||||
|
||||
return (
|
||||
<ul data-testid="form-series" className="list-unstyled d-flex flex-column">
|
||||
{series.map((line, index) =>
|
||||
line.edit ? (
|
||||
<FormSeriesInput
|
||||
key={line.id}
|
||||
series={line}
|
||||
showValidationErrorsOnMount={showValidationErrorsOnMount}
|
||||
index={index + 1}
|
||||
cancel={series.length > 1}
|
||||
autofocus={line.autofocus}
|
||||
repositories={repositories}
|
||||
queryFieldDescription={queryFieldDescription}
|
||||
className={classNames('p-3', styles.formSeriesItem)}
|
||||
onSubmit={editCommit}
|
||||
onCancel={() => cancelEdit(line.id)}
|
||||
onChange={(seriesValues, valid) => changeSeries(seriesValues, valid, index)}
|
||||
/>
|
||||
) : (
|
||||
line && (
|
||||
<SeriesCard
|
||||
key={line.id}
|
||||
disabled={index >= 10}
|
||||
onEdit={() => editRequest(line.id)}
|
||||
onRemove={() => deleteSeries(line.id)}
|
||||
className={styles.formSeriesItem}
|
||||
{...line}
|
||||
/>
|
||||
)
|
||||
)
|
||||
)}
|
||||
|
||||
{!licensed && (
|
||||
<LimitedAccessLabel message="Unlock Code Insights for unlimited data series" className="mx-auto my-3" />
|
||||
)}
|
||||
|
||||
<Button
|
||||
data-testid="add-series-button"
|
||||
type="button"
|
||||
onClick={() => editRequest()}
|
||||
variant="link"
|
||||
disabled={!licensed ? series.length >= 10 : false}
|
||||
className={classNames(styles.formSeriesItem, styles.formSeriesAddButton, 'p-3')}
|
||||
>
|
||||
+ Add another data series
|
||||
</Button>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
@ -6,7 +6,7 @@ import { noop } from 'rxjs'
|
||||
|
||||
import { Label } from '@sourcegraph/wildcard'
|
||||
|
||||
import { DATA_SERIES_COLORS } from '../../constants'
|
||||
import { DATA_SERIES_COLORS } from '../../../../../constants'
|
||||
|
||||
import styles from './FormColorInput.module.scss'
|
||||
|
||||
@ -1,54 +1,42 @@
|
||||
import React from 'react'
|
||||
import { FC, ReactNode } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { noop } from 'rxjs'
|
||||
|
||||
import { Button, Card, Input, Code } from '@sourcegraph/wildcard'
|
||||
|
||||
import { getDefaultInputProps } from '../../../../../../components/form/getDefaultInputProps'
|
||||
import { useField } from '../../../../../../components/form/hooks/useField'
|
||||
import { useForm, ValidationResult } from '../../../../../../components/form/hooks/useForm'
|
||||
import { InsightQueryInput } from '../../../../../../components/form/query-input/InsightQueryInput'
|
||||
import { createRequiredValidator } from '../../../../../../components/form/validators'
|
||||
import { searchQueryValidator } from '../../../capture-group/utils/search-query-validator'
|
||||
import { DEFAULT_DATA_SERIES_COLOR } from '../../constants'
|
||||
import { DEFAULT_DATA_SERIES_COLOR } from '../../../../../constants'
|
||||
import { getDefaultInputProps, useField, InsightQueryInput, useForm } from '../../../../form'
|
||||
import { EditableDataSeries } from '../../types'
|
||||
import { FormColorInput } from '../form-color-input/FormColorInput'
|
||||
|
||||
import { getQueryPatternTypeFilter } from './get-pattern-type-filter'
|
||||
|
||||
const requiredNameField = createRequiredValidator('Name is a required field for data series.')
|
||||
const validQuery = (value: string | undefined, validity: ValidityState | null | undefined): ValidationResult => {
|
||||
const result = createRequiredValidator('Query is a required field for data series.')(value, validity)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
const { isNotContext, isNotRepo } = searchQueryValidator(value || '', true)
|
||||
|
||||
if (!isNotContext) {
|
||||
return 'The `context:` filter is not supported; instead, run over all repositories and use the `context:` on the filter panel after creation'
|
||||
}
|
||||
|
||||
if (!isNotRepo) {
|
||||
return 'Do not include a `repo:` filter; add targeted repositories above, or filter repos on the filter panel after creation'
|
||||
}
|
||||
}
|
||||
import { requiredNameField, validQuery } from './validators'
|
||||
|
||||
interface FormSeriesInputProps {
|
||||
series: EditableDataSeries
|
||||
|
||||
/** Series index. */
|
||||
index: number
|
||||
|
||||
/**
|
||||
* Show all validation error of all fields within the form.
|
||||
*/
|
||||
/** Show all validation error of all fields within the form. */
|
||||
showValidationErrorsOnMount?: boolean
|
||||
|
||||
series: EditableDataSeries
|
||||
|
||||
/** Code Insight repositories field string value - repo1, repo2, ... */
|
||||
/**
|
||||
* Code Insight repositories field string value - repo1, repo2, ...
|
||||
* This prop is used in order to generate a proper link for the query preview button.
|
||||
*/
|
||||
repositories: string
|
||||
|
||||
/** Enable autofocus behavior of first input of form. */
|
||||
/**
|
||||
* This field is only needed for specifying a special compute-specific
|
||||
* query field description when this component is used on the compute-powered insight.
|
||||
* This prop should be removed when we will have a better form series management
|
||||
* solution, see https://github.com/sourcegraph/sourcegraph/issues/38236
|
||||
*/
|
||||
queryFieldDescription?: ReactNode
|
||||
|
||||
/** Enable autofocus behavior of the first input element of series form. */
|
||||
autofocus?: boolean
|
||||
|
||||
/** Enable cancel button. */
|
||||
@ -67,7 +55,7 @@ interface FormSeriesInputProps {
|
||||
onChange?: (formValues: EditableDataSeries, valid: boolean) => void
|
||||
}
|
||||
|
||||
export const FormSeriesInput: React.FunctionComponent<React.PropsWithChildren<FormSeriesInputProps>> = props => {
|
||||
export const FormSeriesInput: FC<FormSeriesInputProps> = props => {
|
||||
const {
|
||||
index,
|
||||
series,
|
||||
@ -76,6 +64,7 @@ export const FormSeriesInput: React.FunctionComponent<React.PropsWithChildren<Fo
|
||||
cancel = false,
|
||||
autofocus = true,
|
||||
repositories,
|
||||
queryFieldDescription,
|
||||
onCancel = noop,
|
||||
onSubmit = noop,
|
||||
onChange = noop,
|
||||
@ -147,7 +136,14 @@ export const FormSeriesInput: React.FunctionComponent<React.PropsWithChildren<Fo
|
||||
repositories={repositories}
|
||||
patternType={getQueryPatternTypeFilter(queryField.input.value)}
|
||||
placeholder="Example: patternType:regexp const\s\w+:\s(React\.)?FunctionComponent"
|
||||
message={<QueryFieldDescription />}
|
||||
message={
|
||||
queryFieldDescription ?? (
|
||||
<span>
|
||||
Do not include the <Code>context:</Code> or <Code>repo:</Code> filter; if needed,{' '}
|
||||
<Code>repo:</Code> will be added automatically.
|
||||
</span>
|
||||
)
|
||||
}
|
||||
className="mt-4"
|
||||
{...getDefaultInputProps(queryField)}
|
||||
/>
|
||||
@ -179,10 +175,3 @@ export const FormSeriesInput: React.FunctionComponent<React.PropsWithChildren<Fo
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const QueryFieldDescription: React.FunctionComponent<React.PropsWithChildren<unknown>> = () => (
|
||||
<span>
|
||||
Do not include the <Code>context:</Code> or <Code>repo:</Code> filter; if needed, <Code>repo:</Code> will be
|
||||
added automatically.
|
||||
</span>
|
||||
)
|
||||
@ -0,0 +1,22 @@
|
||||
import { searchQueryValidator } from '../../../../../pages/insights/creation/capture-group/utils/search-query-validator'
|
||||
import { createRequiredValidator, ValidationResult } from '../../../../form'
|
||||
|
||||
export const requiredNameField = createRequiredValidator('Name is a required field for data series.')
|
||||
|
||||
export const validQuery = (value: string | undefined, validity: ValidityState | null | undefined): ValidationResult => {
|
||||
const result = createRequiredValidator('Query is a required field for data series.')(value, validity)
|
||||
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
|
||||
const { isNotContext, isNotRepo } = searchQueryValidator(value || '', true)
|
||||
|
||||
if (!isNotContext) {
|
||||
return 'The `context:` filter is not supported; instead, run over all repositories and use the `context:` on the filter panel after creation'
|
||||
}
|
||||
|
||||
if (!isNotRepo) {
|
||||
return 'Do not include a `repo:` filter; add targeted repositories above, or filter repos on the filter panel after creation'
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ import classNames from 'classnames'
|
||||
|
||||
import { Button, Card } from '@sourcegraph/wildcard'
|
||||
|
||||
import { DEFAULT_DATA_SERIES_COLOR } from '../../../../constants'
|
||||
import { DEFAULT_DATA_SERIES_COLOR } from '../../../../../constants'
|
||||
|
||||
import styles from './SeriesCard.module.scss'
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export { FormSeries } from './FormSeries'
|
||||
export { createDefaultEditSeries } from './use-editable-series'
|
||||
export type { EditableDataSeries } from './types'
|
||||
@ -0,0 +1,7 @@
|
||||
import { SearchBasedInsightSeries } from '../../../core'
|
||||
|
||||
export interface EditableDataSeries extends SearchBasedInsightSeries {
|
||||
valid: boolean
|
||||
edit: boolean
|
||||
autofocus: boolean
|
||||
}
|
||||
@ -2,61 +2,63 @@ import { useState } from 'react'
|
||||
|
||||
import * as uuid from 'uuid'
|
||||
|
||||
import { useFieldAPI } from '../../../../../../../components/form/hooks/useField'
|
||||
import { DEFAULT_DATA_SERIES_COLOR } from '../../../constants'
|
||||
import { CreateInsightFormFields, EditableDataSeries } from '../../../types'
|
||||
import { useFieldAPI } from '../../form'
|
||||
|
||||
import { remove, replace } from './helpers'
|
||||
|
||||
const EDIT_SERIES_PREFIX = 'runtime-series'
|
||||
import { EditableDataSeries } from './types'
|
||||
|
||||
export const createDefaultEditSeries = (series?: Partial<EditableDataSeries>): EditableDataSeries => ({
|
||||
id: `${EDIT_SERIES_PREFIX}.${uuid.v4()}`,
|
||||
...defaultEditSeries,
|
||||
id: `runtime-series.${uuid.v4()}`,
|
||||
...DEFAULT_EDITABLE_SERIES,
|
||||
...series,
|
||||
})
|
||||
|
||||
const defaultEditSeries = {
|
||||
const DEFAULT_EDITABLE_SERIES = {
|
||||
valid: false,
|
||||
edit: false,
|
||||
autofocus: false,
|
||||
name: '',
|
||||
query: '',
|
||||
stroke: DEFAULT_DATA_SERIES_COLOR,
|
||||
}
|
||||
|
||||
export interface UseEditableSeriesProps {
|
||||
series: useFieldAPI<CreateInsightFormFields['series']>
|
||||
}
|
||||
|
||||
export interface UseEditableSeriesAPI {
|
||||
/**
|
||||
* Edit series array used below for rendering series edit form.
|
||||
* In case of some element has undefined value we're showing
|
||||
* series card with data instead of form.
|
||||
* */
|
||||
editSeries: CreateInsightFormFields['series']
|
||||
* A list of editable data series. Basically, this is just a
|
||||
* sorted/filtered list of original data series but with
|
||||
* additional logic around create/updated/delete actions for
|
||||
* series list.
|
||||
*/
|
||||
series: EditableDataSeries[]
|
||||
|
||||
/**
|
||||
* Handler to listen latest values of particular sereis form.
|
||||
* */
|
||||
listen: (liveSeries: EditableDataSeries, valid: boolean, index: number) => void
|
||||
/** Call whenever the user changes any fields (title, query, color) of series */
|
||||
changeSeries: (liveSeries: EditableDataSeries, valid: boolean, index: number) => void
|
||||
|
||||
/**
|
||||
* Handlers for CRUD operations over series.
|
||||
* */
|
||||
/** Call whenever the user clicks the edit series button in series preview card. */
|
||||
editRequest: (seriesId?: string) => void
|
||||
|
||||
/** Call whenever the user clicks the save series button */
|
||||
editCommit: (editedSeries: EditableDataSeries) => void
|
||||
|
||||
/**
|
||||
* Call whenever the user cancel series editing by clicking cancel button
|
||||
* in series form.
|
||||
*/
|
||||
cancelEdit: (seriesId: string) => void
|
||||
|
||||
/**
|
||||
* Call whenever the user tries to delete series by clicking delete button
|
||||
* in series preview card.
|
||||
*/
|
||||
deleteSeries: (series: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of CRUD operation over insight series. Used in form to manage
|
||||
* edit, delete, add, and cancel series forms.
|
||||
* Basically this is just a stateful selector function over series that simplifies work
|
||||
* with editable series and its special UX actions like delete series through preview card,
|
||||
* edit series through form, create new series through add more series button.
|
||||
*/
|
||||
export function useEditableSeries(props: UseEditableSeriesProps): UseEditableSeriesAPI {
|
||||
const { series } = props
|
||||
|
||||
export function useEditableSeries(series: useFieldAPI<EditableDataSeries[]>): UseEditableSeriesAPI {
|
||||
const [seriesBeforeEdit, setSeriesBeforeEdit] = useState<Record<string, EditableDataSeries>>({})
|
||||
|
||||
const handleSeriesLiveChange = (liveSeries: EditableDataSeries, valid: boolean): void => {
|
||||
@ -79,7 +81,7 @@ export function useEditableSeries(props: UseEditableSeriesProps): UseEditableSer
|
||||
const index = newEditSeries.findIndex(series => series.id === seriesId)
|
||||
|
||||
if (index !== -1) {
|
||||
newEditSeries[index] = { ...seriesValue[index], edit: true }
|
||||
newEditSeries[index] = { ...seriesValue[index], edit: true, autofocus: true }
|
||||
|
||||
const newSeriesID = newEditSeries[index].id
|
||||
|
||||
@ -92,7 +94,7 @@ export function useEditableSeries(props: UseEditableSeriesProps): UseEditableSer
|
||||
})
|
||||
}
|
||||
} else {
|
||||
newEditSeries.push(createDefaultEditSeries({ edit: true }))
|
||||
newEditSeries.push(createDefaultEditSeries({ edit: true, autofocus: true }))
|
||||
}
|
||||
|
||||
series.meta.setState(state => ({ ...state, value: newEditSeries }))
|
||||
@ -163,11 +165,21 @@ export function useEditableSeries(props: UseEditableSeriesProps): UseEditableSer
|
||||
}
|
||||
|
||||
return {
|
||||
editSeries: series.input.value,
|
||||
listen: handleSeriesLiveChange,
|
||||
series: series.input.value,
|
||||
changeSeries: handleSeriesLiveChange,
|
||||
editRequest: handleEditSeriesRequest,
|
||||
editCommit: handleEditSeriesCommit,
|
||||
cancelEdit: handleEditSeriesCancel,
|
||||
deleteSeries: handleRemoveSeries,
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper replace element in array by index and return new array. */
|
||||
function replace<Element>(list: Element[], index: number, newElement: Element): Element[] {
|
||||
return [...list.slice(0, index), newElement, ...list.slice(index + 1)]
|
||||
}
|
||||
|
||||
/** Helper remove element from array by index. */
|
||||
function remove<Element>(list: Element[], index: number): Element[] {
|
||||
return [...list.slice(0, index), ...list.slice(index + 1)]
|
||||
}
|
||||
@ -5,3 +5,5 @@ export { getSanitizedRepositories } from './sanitizers/repositories'
|
||||
|
||||
export { CodeInsightDashboardsVisibility } from './CodeInsightDashboardsVisibility'
|
||||
export { CodeInsightTimeStepPicker } from './code-insight-time-step-picker/CodeInsightTimeStepPicker'
|
||||
export { FormSeries, createDefaultEditSeries } from './form-series'
|
||||
export type { EditableDataSeries } from './form-series'
|
||||
@ -25,7 +25,7 @@ export interface Validators<FieldValue> {
|
||||
/**
|
||||
* Subset of native input props that useField can set to the native input element.
|
||||
*/
|
||||
interface InputProps<Value> extends Omit<InputHTMLAttributes<HTMLInputElement>, 'name' | 'value' | 'onChange'> {
|
||||
export interface InputProps<Value> extends Omit<InputHTMLAttributes<HTMLInputElement>, 'name' | 'value' | 'onChange'> {
|
||||
onChange?: (value: Value) => void
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ export type UseFieldProps<FormValues, Key, Value> = {
|
||||
*
|
||||
* Should be used with useForm hook to connect field and form component's states.
|
||||
*/
|
||||
export function useField<FormValues, Key extends keyof FormAPI<FormValues>['initialValues']>(
|
||||
export function useField<FormValues, Key extends keyof FormValues>(
|
||||
props: UseFieldProps<FormValues, Key, FormValues[Key]>
|
||||
): useFieldAPI<FormValues[Key]> {
|
||||
const { formApi, name, validators, onChange = noop, ...inputProps } = props
|
||||
@ -130,7 +130,7 @@ export function useField<FormValues, Key extends keyof FormAPI<FormValues>['init
|
||||
}
|
||||
|
||||
if (async) {
|
||||
// Due the call of start async validation in useLayoutEffect we have to
|
||||
// Due to the call of start async validation in useLayoutEffect we have to
|
||||
// schedule the async validation event in the next tick to be able run
|
||||
// observable pipeline validation since useAsyncValidation hook use
|
||||
// useObservable hook internally which calls '.subscribe' in useEffect.
|
||||
@ -189,7 +189,7 @@ export function useField<FormValues, Key extends keyof FormAPI<FormValues>['init
|
||||
|
||||
type FieldStateTransformer<Value> = (previousState: FieldState<Value>) => FieldState<Value>
|
||||
|
||||
function useFormFieldState<FormValues, Key extends keyof FormAPI<FormValues>['initialValues']>(
|
||||
function useFormFieldState<FormValues, Key extends keyof FormValues>(
|
||||
name: Key,
|
||||
formAPI: FormAPI<FormValues>
|
||||
): [FieldState<FormValues[Key]>, Dispatch<FieldStateTransformer<FormValues[Key]>>] {
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
import { debounce, DebouncedFunc, isFunction } from 'lodash'
|
||||
import { noop } from 'rxjs'
|
||||
|
||||
import { useDistinctValue } from '../../../hooks/use-distinct-value'
|
||||
import { useDistinctValue } from '../../../hooks'
|
||||
|
||||
// Special key for the submit error store.
|
||||
export const FORM_ERROR = 'useForm/submissionErrors'
|
||||
@ -106,7 +106,7 @@ export interface FormAPI<FormValues> {
|
||||
/**
|
||||
* Mark to understand was there an attempt by user to submit the form?
|
||||
* Used in useField hook to trigger appearance of error message if
|
||||
* user tried submit the form.
|
||||
* user tried to submit the form.
|
||||
*/
|
||||
submitted: boolean
|
||||
|
||||
|
||||
@ -11,8 +11,10 @@ export { FormGroup } from './form-group/FormGroup'
|
||||
|
||||
// form hooks
|
||||
export { useForm, FORM_ERROR } from './hooks/useForm'
|
||||
export type { Form, SubmissionErrors, FormChangeEvent } from './hooks/useForm'
|
||||
export type { Form, ValidationResult, SubmissionErrors, FormChangeEvent } from './hooks/useForm'
|
||||
|
||||
export { useField } from './hooks/useField'
|
||||
export type { useFieldAPI } from './hooks/useField'
|
||||
|
||||
export { useCheckboxes } from './hooks/useCheckboxes'
|
||||
export { useAsyncInsightTitleValidator } from './hooks/use-async-insight-title-validator'
|
||||
|
||||
@ -5,6 +5,11 @@
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
// Spread standard input paddings in order to fix visually problem
|
||||
// with codemirror editor on the code insight creation UI pages.
|
||||
// See https://github.com/sourcegraph/sourcegraph/issues/37785
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ export const InsightQueryInput = forwardRef<HTMLInputElement, InsightQueryInputP
|
||||
{children}
|
||||
</Monaco.Root>
|
||||
) : (
|
||||
<Monaco.Field {...props} ref={reference} className={props.className} />
|
||||
<Monaco.Field {...props} ref={reference} className={classNames(styles.inputWrapper, props.className)} />
|
||||
)}
|
||||
|
||||
<Monaco.PreviewLink query={previewQuery} patternType={patternType} className={styles.previewButton} />
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import escapeRegExp from 'lodash/escapeRegExp'
|
||||
|
||||
import { getSanitizedRepositories } from '../../../creation-ui-kit'
|
||||
import { getSanitizedRepositories } from '../../../creation-ui'
|
||||
|
||||
export const generateRepoFiltersQuery = (repositoriesString: string): string => {
|
||||
const repositories = getSanitizedRepositories(repositoriesString)
|
||||
|
||||
@ -4,7 +4,7 @@ import { Combobox, ComboboxInput, ComboboxPopover } from '@reach/combobox'
|
||||
|
||||
import { FlexTextArea } from '@sourcegraph/wildcard'
|
||||
|
||||
import { getSanitizedRepositories } from '../../creation-ui-kit'
|
||||
import { getSanitizedRepositories } from '../../creation-ui'
|
||||
|
||||
import { SuggestionsPanel } from './components/suggestion-panel/SuggestionPanel'
|
||||
import { useRepoSuggestions } from './hooks/use-repo-suggestions'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getSanitizedRepositories } from '../../../creation-ui-kit'
|
||||
import { getSanitizedRepositories } from '../../../creation-ui'
|
||||
|
||||
interface SuggestionsSearchTermInput {
|
||||
value: string
|
||||
|
||||
@ -2,9 +2,9 @@ import { Validator } from './hooks/useField'
|
||||
import { ValidationResult } from './hooks/useForm'
|
||||
|
||||
/**
|
||||
* Validator for required form field which returns error massage
|
||||
* Validator for required form field which returns error message
|
||||
* as a sign of invalid state.
|
||||
* */
|
||||
*/
|
||||
export const createRequiredValidator = <Value>(errorMessage: string): Validator<Value> => (value, validity) => {
|
||||
if (validity?.valueMissing) {
|
||||
return errorMessage
|
||||
@ -20,6 +20,6 @@ export const createRequiredValidator = <Value>(errorMessage: string): Validator<
|
||||
|
||||
/**
|
||||
* Composes a few validators together and show first error for form field.
|
||||
* */
|
||||
*/
|
||||
export const composeValidators = <Value>(...validators: Validator<Value>[]): Validator<Value> => (value, validity) =>
|
||||
validators.reduce<ValidationResult>((error, validator) => error || validator(value, validity), undefined)
|
||||
|
||||
@ -3,7 +3,7 @@ export { CodeInsightsIcon } from '../../../insights/Icons'
|
||||
export * from './insights-view-grid'
|
||||
export * from './views'
|
||||
export * from './trancated-text/TruncatedText'
|
||||
export * from './creation-ui-kit'
|
||||
export * from './creation-ui'
|
||||
export * from './form'
|
||||
export * from './limited-access-label/LimitedAccessLabel'
|
||||
export * from './code-insights-page/CodeInsightsPage'
|
||||
|
||||
@ -9,8 +9,7 @@ import { Button, Menu, MenuButton, MenuItem, MenuList, H2 } from '@sourcegraph/w
|
||||
import { getLineColor, LegendItem, LegendList, ParentSize, Series } from '../../../../../charts'
|
||||
import { WebStory } from '../../../../../components/WebStory'
|
||||
import { useSeriesToggle } from '../../../../../insights/utils/use-series-toggle'
|
||||
import { SeriesChart } from '../chart'
|
||||
import { SeriesBasedChartTypes } from '../types'
|
||||
import { SeriesBasedChartTypes, SeriesChart } from '../chart'
|
||||
|
||||
import * as Card from './InsightCard'
|
||||
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { WebStory } from '../../../../../../components/WebStory'
|
||||
import { CategoricalBasedChartTypes } from '../../types'
|
||||
|
||||
import { CategoricalChart } from './CategoricalChart'
|
||||
import { CategoricalBasedChartTypes, CategoricalChart } from './CategoricalChart'
|
||||
|
||||
const StoryConfig: Meta = {
|
||||
title: 'web/insights/views/CategoricalChart',
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import React, { SVGProps } from 'react'
|
||||
|
||||
import { CategoricalLikeChart, PieChart } from '../../../../../../charts'
|
||||
import { CategoricalBasedChartTypes } from '../../types'
|
||||
import { LockedChart } from '../locked/LockedChart'
|
||||
|
||||
export enum CategoricalBasedChartTypes {
|
||||
Pie,
|
||||
Bar,
|
||||
}
|
||||
|
||||
export interface CategoricalChartProps<Datum>
|
||||
extends CategoricalLikeChart<Datum>,
|
||||
Omit<SVGProps<SVGSVGElement>, 'type'> {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export { SeriesChart } from './series/SeriesChart'
|
||||
export { CategoricalChart } from './categorical/CategoricalChart'
|
||||
export { SeriesChart, SeriesBasedChartTypes } from './series/SeriesChart'
|
||||
export { CategoricalChart, CategoricalBasedChartTypes } from './categorical/CategoricalChart'
|
||||
|
||||
export type { SeriesChartProps } from './series/SeriesChart'
|
||||
export type { CategoricalChartProps } from './categorical/CategoricalChart'
|
||||
|
||||
@ -3,9 +3,8 @@ import { Meta, Story } from '@storybook/react'
|
||||
import { Series } from '../../../../../../charts'
|
||||
import { WebStory } from '../../../../../../components/WebStory'
|
||||
import { useSeriesToggle } from '../../../../../../insights/utils/use-series-toggle'
|
||||
import { SeriesBasedChartTypes } from '../../types'
|
||||
|
||||
import { SeriesChart } from './SeriesChart'
|
||||
import { SeriesBasedChartTypes, SeriesChart } from './SeriesChart'
|
||||
|
||||
const StoryConfig: Meta = {
|
||||
title: 'web/insights/views/SeriesChart',
|
||||
|
||||
@ -4,9 +4,12 @@ import { LineChart, SeriesLikeChart } from '../../../../../../charts'
|
||||
import { LineChartProps } from '../../../../../../charts/components/line-chart/LineChart'
|
||||
import { SeriesWithData } from '../../../../../../charts/components/line-chart/utils'
|
||||
import { UseSeriesToggleReturn } from '../../../../../../insights/utils/use-series-toggle'
|
||||
import { SeriesBasedChartTypes } from '../../types'
|
||||
import { LockedChart } from '../locked/LockedChart'
|
||||
|
||||
export enum SeriesBasedChartTypes {
|
||||
Line,
|
||||
}
|
||||
|
||||
export interface SeriesChartProps<D> extends SeriesLikeChart<D>, Omit<SVGProps<SVGSVGElement>, 'type'> {
|
||||
type: SeriesBasedChartTypes
|
||||
width: number
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
export * from './card/InsightCard'
|
||||
|
||||
export { SeriesChart, CategoricalChart } from './chart'
|
||||
export { CategoricalBasedChartTypes, SeriesBasedChartTypes } from './types'
|
||||
export { SeriesChart, CategoricalChart, CategoricalBasedChartTypes, SeriesBasedChartTypes } from './chart'
|
||||
|
||||
export type { SeriesChartProps, CategoricalChartProps } from './chart'
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
export enum SeriesBasedChartTypes {
|
||||
Line,
|
||||
}
|
||||
|
||||
export enum CategoricalBasedChartTypes {
|
||||
Pie,
|
||||
Bar,
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { InsightDataNode } from '../../../../../../../graphql-operations'
|
||||
import { DATA_SERIES_COLORS } from '../../../../../pages/insights/creation/search-insight'
|
||||
import { DATA_SERIES_COLORS } from '../../../../../constants'
|
||||
import { BackendInsight } from '../../../../types'
|
||||
import { BackendInsightData } from '../../../code-insights-backend-types'
|
||||
import { createLineChartContent } from '../../../utils/create-line-chart-content'
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export { useLazyParallelRequest } from './use-parallel-requests/use-parallel-request'
|
||||
export type { LazyQueryState, LazyQueryResult } from './use-parallel-requests/use-parallel-request'
|
||||
|
||||
export { useApi } from './use-api'
|
||||
export { useCopyURLHandler } from './use-copy-url-handler'
|
||||
export { useDeleteInsight } from './use-delete-insight'
|
||||
|
||||
@ -24,6 +24,13 @@ import { CodeInsightsBackendContext, SeriesChartContent } from '../../../core'
|
||||
import { getSanitizedCaptureQuery } from './capture-group/utils/capture-group-insight-sanitizer'
|
||||
import { InsightStep } from './search-insight'
|
||||
|
||||
export interface LivePreviewSeries {
|
||||
query: string
|
||||
label: string
|
||||
generatedFromCaptureGroup: boolean
|
||||
stroke: string
|
||||
}
|
||||
|
||||
interface LineChartLivePreviewProps {
|
||||
disabled: boolean
|
||||
repositories: string
|
||||
@ -31,12 +38,7 @@ interface LineChartLivePreviewProps {
|
||||
step: InsightStep
|
||||
isAllReposMode: boolean
|
||||
className?: string
|
||||
series: {
|
||||
query: string
|
||||
label: string
|
||||
generatedFromCaptureGroup: boolean
|
||||
stroke: string
|
||||
}[]
|
||||
series: LivePreviewSeries[]
|
||||
}
|
||||
|
||||
export const LineChartLivePreview: React.FunctionComponent<
|
||||
|
||||
@ -2,7 +2,7 @@ import { FilterType, resolveFilter } from '@sourcegraph/shared/src/search/query/
|
||||
import { scanSearchQuery } from '@sourcegraph/shared/src/search/query/scanner'
|
||||
import { Filter } from '@sourcegraph/shared/src/search/query/token'
|
||||
|
||||
import { getSanitizedRepositories } from '../../../../../components/creation-ui-kit'
|
||||
import { getSanitizedRepositories } from '../../../../../components'
|
||||
import { InsightExecutionType, InsightType, MinimalCaptureGroupInsightData } from '../../../../../core'
|
||||
import { CaptureGroupFormFields } from '../types'
|
||||
|
||||
|
||||
@ -6,8 +6,7 @@ import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryServi
|
||||
import { Link, PageHeader, Text, useLocalStorage } from '@sourcegraph/wildcard'
|
||||
|
||||
import { PageTitle } from '../../../../../../components/PageTitle'
|
||||
import { CodeInsightsPage } from '../../../../components/code-insights-page/CodeInsightsPage'
|
||||
import { FormChangeEvent } from '../../../../components/form/hooks/useForm'
|
||||
import { CodeInsightsPage, FormChangeEvent } from '../../../../components'
|
||||
|
||||
import { ComputeInsightCreationContent } from './components/ComputeInsightCreationContent'
|
||||
import { CreateComputeInsightFormFields } from './components/types'
|
||||
@ -42,7 +41,7 @@ export const ComputeInsightCreationPage: FunctionComponent<ComputeInsightCreatio
|
||||
}
|
||||
|
||||
return (
|
||||
<CodeInsightsPage className="col-10">
|
||||
<CodeInsightsPage className="col-12">
|
||||
<PageTitle title="Create compute insight - Code Insights" />
|
||||
|
||||
<PageHeader
|
||||
|
||||
@ -1,13 +1,34 @@
|
||||
import { FunctionComponent, HTMLAttributes } from 'react'
|
||||
|
||||
import { CreationUiLayout, CreationUIForm, CreationUIPreview } from '../../../../../components'
|
||||
import { FormChangeEvent, SubmissionErrors } from '../../../../../components/form/hooks/useForm'
|
||||
import { Code } from '@sourcegraph/wildcard'
|
||||
|
||||
import {
|
||||
CreationUiLayout,
|
||||
CreationUIForm,
|
||||
CreationUIPreview,
|
||||
FormChangeEvent,
|
||||
SubmissionErrors,
|
||||
FormSeries,
|
||||
FormGroup,
|
||||
useForm,
|
||||
createDefaultEditSeries,
|
||||
useField,
|
||||
} from '../../../../../components'
|
||||
import { useUiFeatures } from '../../../../../hooks'
|
||||
|
||||
import { CreateComputeInsightFormFields } from './types'
|
||||
|
||||
const INITIAL_INSIGHT_VALUES: CreateComputeInsightFormFields = {
|
||||
series: [createDefaultEditSeries({ edit: true })],
|
||||
title: '',
|
||||
repositories: '',
|
||||
dashboardReferenceCount: 0,
|
||||
}
|
||||
|
||||
type NativeContainerProps = Omit<HTMLAttributes<HTMLDivElement>, 'onSubmit' | 'onChange'>
|
||||
|
||||
interface ComputeInsightCreationContentProps extends NativeContainerProps {
|
||||
mode?: 'creation' | 'edit'
|
||||
initialValue?: Partial<CreateComputeInsightFormFields>
|
||||
|
||||
onChange: (event: FormChangeEvent<CreateComputeInsightFormFields>) => void
|
||||
@ -16,11 +37,55 @@ interface ComputeInsightCreationContentProps extends NativeContainerProps {
|
||||
}
|
||||
|
||||
export const ComputeInsightCreationContent: FunctionComponent<ComputeInsightCreationContentProps> = props => {
|
||||
const { initialValue, onChange, onSubmit, onCancel, ...attributes } = props
|
||||
const { mode = 'creation', initialValue, onChange, onSubmit, onCancel, ...attributes } = props
|
||||
const { licensed } = useUiFeatures()
|
||||
|
||||
const form = useForm<CreateComputeInsightFormFields>({
|
||||
initialValues: { ...INITIAL_INSIGHT_VALUES, ...initialValue },
|
||||
onSubmit,
|
||||
onChange,
|
||||
touched: mode === 'edit',
|
||||
})
|
||||
|
||||
const series = useField({
|
||||
name: 'series',
|
||||
formApi: form.formAPI,
|
||||
})
|
||||
|
||||
return (
|
||||
<CreationUiLayout {...attributes}>
|
||||
<CreationUIForm>Hello World</CreationUIForm>
|
||||
<CreationUIForm>
|
||||
<FormGroup
|
||||
name="data series group"
|
||||
title="Data series"
|
||||
subtitle={
|
||||
licensed
|
||||
? 'Add any number of data series to your chart'
|
||||
: 'Add up to 10 data series to your chart'
|
||||
}
|
||||
innerRef={series.input.ref}
|
||||
>
|
||||
<FormSeries
|
||||
seriesField={series}
|
||||
repositories=""
|
||||
showValidationErrorsOnMount={false}
|
||||
queryFieldDescription={
|
||||
<ul className="pl-3">
|
||||
<li>
|
||||
Do not include the <Code weight="bold">repo:</Code> filter as it will be added
|
||||
automatically, if needed{' '}
|
||||
</li>
|
||||
<li>
|
||||
You can use <Code weight="bold">before:</Code> and <Code weight="bold">after:</Code>{' '}
|
||||
operators for <Code weight="bold">type:diff</Code> and{' '}
|
||||
<Code weight="bold">type:commit</Code> to define the timeframe (example query:{' '}
|
||||
<Code>type:diff author:nick before:"last thursday" SearchTerm</Code>)
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</CreationUIForm>
|
||||
|
||||
<CreationUIPreview>This is live preview</CreationUIPreview>
|
||||
</CreationUiLayout>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { EditableDataSeries } from '../../search-insight'
|
||||
import { EditableDataSeries } from '../../../../../components'
|
||||
|
||||
export interface CreateComputeInsightFormFields {
|
||||
/**
|
||||
|
||||
@ -6,7 +6,7 @@ import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/teleme
|
||||
|
||||
import { WebStory } from '../../../../../../components/WebStory'
|
||||
import { CodeInsightsBackendStoryMock } from '../../../../CodeInsightsBackendStoryMock'
|
||||
import { SERIES_MOCK_CHART } from '../../../../components/creation-ui-kit'
|
||||
import { SERIES_MOCK_CHART } from '../../../../components'
|
||||
|
||||
import { SearchInsightCreationPage as SearchInsightCreationPageComponent } from './SearchInsightCreationPage'
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import React, { FormEventHandler, RefObject, useMemo } from 'react'
|
||||
import { FC, FormEventHandler, RefObject, useMemo } from 'react'
|
||||
|
||||
import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts'
|
||||
import { Button, Checkbox, Input, Link, useObservable } from '@sourcegraph/wildcard'
|
||||
|
||||
import { LoaderButton } from '../../../../../../../components/LoaderButton'
|
||||
import {
|
||||
FormSeries,
|
||||
LimitedAccessLabel,
|
||||
CodeInsightDashboardsVisibility,
|
||||
CodeInsightTimeStepPicker,
|
||||
@ -17,9 +18,7 @@ import {
|
||||
} from '../../../../../components'
|
||||
import { Insight } from '../../../../../core'
|
||||
import { useUiFeatures } from '../../../../../hooks'
|
||||
import { CreateInsightFormFields, EditableDataSeries } from '../types'
|
||||
|
||||
import { FormSeries } from './form-series/FormSeries'
|
||||
import { CreateInsightFormFields } from '../types'
|
||||
|
||||
interface CreationSearchInsightFormProps {
|
||||
/** This component might be used in edit or creation insight case. */
|
||||
@ -44,22 +43,6 @@ interface CreationSearchInsightFormProps {
|
||||
insight?: Insight
|
||||
|
||||
onCancel: () => void
|
||||
|
||||
/**
|
||||
* Handler to listen latest value form particular series edit form
|
||||
* Used to get information for live preview chart.
|
||||
*/
|
||||
onSeriesLiveChange: (liveSeries: EditableDataSeries, isValid: boolean, index: number) => void
|
||||
|
||||
/**
|
||||
* Handlers for CRUD operation over series. Add, delete, update and cancel
|
||||
* series edit form.
|
||||
*/
|
||||
onEditSeriesRequest: (seriesId?: string) => void
|
||||
onEditSeriesCommit: (editedSeries: EditableDataSeries) => void
|
||||
onEditSeriesCancel: (seriesId: string) => void
|
||||
onSeriesRemove: (seriesId: string) => void
|
||||
|
||||
onFormReset: () => void
|
||||
}
|
||||
|
||||
@ -67,9 +50,7 @@ interface CreationSearchInsightFormProps {
|
||||
* Displays creation code insight form (title, visibility, series, etc.)
|
||||
* UI layer only, all controlled data should be managed by consumer of this component.
|
||||
*/
|
||||
export const SearchInsightCreationForm: React.FunctionComponent<
|
||||
React.PropsWithChildren<CreationSearchInsightFormProps>
|
||||
> = props => {
|
||||
export const SearchInsightCreationForm: FC<CreationSearchInsightFormProps> = props => {
|
||||
const {
|
||||
mode,
|
||||
innerRef,
|
||||
@ -88,11 +69,6 @@ export const SearchInsightCreationForm: React.FunctionComponent<
|
||||
dashboardReferenceCount,
|
||||
insight,
|
||||
onCancel,
|
||||
onSeriesLiveChange,
|
||||
onEditSeriesRequest,
|
||||
onEditSeriesCommit,
|
||||
onEditSeriesCancel,
|
||||
onSeriesRemove,
|
||||
onFormReset,
|
||||
} = props
|
||||
|
||||
@ -164,14 +140,9 @@ export const SearchInsightCreationForm: React.FunctionComponent<
|
||||
innerRef={series.input.ref}
|
||||
>
|
||||
<FormSeries
|
||||
series={series.input.value}
|
||||
seriesField={series}
|
||||
repositories={repositories.input.value}
|
||||
showValidationErrorsOnMount={submitted}
|
||||
onLiveChange={onSeriesLiveChange}
|
||||
onEditSeriesRequest={onEditSeriesRequest}
|
||||
onEditSeriesCommit={onEditSeriesCommit}
|
||||
onEditSeriesCancel={onEditSeriesCancel}
|
||||
onSeriesRemove={onSeriesRemove}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
|
||||
@ -1,128 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { Button } from '@sourcegraph/wildcard'
|
||||
|
||||
import { LimitedAccessLabel } from '../../../../../../components/limited-access-label/LimitedAccessLabel'
|
||||
import { useUiFeatures } from '../../../../../../hooks/use-ui-features'
|
||||
import { EditableDataSeries } from '../../types'
|
||||
import { FormSeriesInput } from '../form-series-input/FormSeriesInput'
|
||||
|
||||
import { SeriesCard } from './components/series-card/SeriesCard'
|
||||
|
||||
import styles from './FormSeries.module.scss'
|
||||
|
||||
export interface FormSeriesProps {
|
||||
/**
|
||||
* Show all validation error for all forms and fields within the series forms.
|
||||
*/
|
||||
showValidationErrorsOnMount: boolean
|
||||
|
||||
/**
|
||||
* Controlled value (series - chart lines) for series input component.
|
||||
*/
|
||||
series?: EditableDataSeries[]
|
||||
|
||||
/**
|
||||
* Code Insight repositories field string value - repo1, repo2, ...
|
||||
*/
|
||||
repositories: string
|
||||
|
||||
/**
|
||||
* Live change series handler while user typing in active series form.
|
||||
* Used by consumers to get latest values from series inputs and pass
|
||||
* them tp live preview chart.
|
||||
*/
|
||||
onLiveChange: (liveSeries: EditableDataSeries, isValid: boolean, index: number) => void
|
||||
|
||||
/**
|
||||
* Handler that runs every time user clicked edit on particular
|
||||
* series card.
|
||||
*/
|
||||
onEditSeriesRequest: (seriesId?: string) => void
|
||||
|
||||
/**
|
||||
* Handler that runs every time use clicked commit (done) in
|
||||
* series edit form.
|
||||
*/
|
||||
onEditSeriesCommit: (editedSeries: EditableDataSeries) => void
|
||||
|
||||
/**
|
||||
* Handler that runs every time use canceled (click cancel) in
|
||||
* series edit form.
|
||||
*/
|
||||
onEditSeriesCancel: (seriesId: string) => void
|
||||
|
||||
/**
|
||||
* Handler that runs every time use removed (click remove) in
|
||||
* series card.
|
||||
*/
|
||||
onSeriesRemove: (seriesId: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders form series (sub-form) for series (chart lines) creation code insight form.
|
||||
*/
|
||||
export const FormSeries: React.FunctionComponent<React.PropsWithChildren<FormSeriesProps>> = props => {
|
||||
const {
|
||||
series = [],
|
||||
showValidationErrorsOnMount,
|
||||
repositories,
|
||||
onEditSeriesRequest,
|
||||
onEditSeriesCommit,
|
||||
onEditSeriesCancel,
|
||||
onSeriesRemove,
|
||||
onLiveChange,
|
||||
} = props
|
||||
|
||||
const { licensed } = useUiFeatures()
|
||||
|
||||
return (
|
||||
<ul data-testid="form-series" className="list-unstyled d-flex flex-column">
|
||||
{series.map((line, index) =>
|
||||
line.edit ? (
|
||||
<FormSeriesInput
|
||||
key={line.id}
|
||||
series={line}
|
||||
showValidationErrorsOnMount={showValidationErrorsOnMount}
|
||||
index={index + 1}
|
||||
cancel={series.length > 1}
|
||||
autofocus={series.length > 1}
|
||||
repositories={repositories}
|
||||
onSubmit={onEditSeriesCommit}
|
||||
onCancel={() => onEditSeriesCancel(line.id)}
|
||||
className={classNames('p-3', styles.formSeriesItem)}
|
||||
onChange={(seriesValues, valid) => onLiveChange({ ...line, ...seriesValues }, valid, index)}
|
||||
/>
|
||||
) : (
|
||||
line && (
|
||||
<SeriesCard
|
||||
key={line.id}
|
||||
disabled={index >= 10}
|
||||
onEdit={() => onEditSeriesRequest(line.id)}
|
||||
onRemove={() => onSeriesRemove(line.id)}
|
||||
className={styles.formSeriesItem}
|
||||
{...line}
|
||||
/>
|
||||
)
|
||||
)
|
||||
)}
|
||||
|
||||
{!licensed && (
|
||||
<LimitedAccessLabel message="Unlock Code Insights for unlimited data series" className="mx-auto my-3" />
|
||||
)}
|
||||
|
||||
<Button
|
||||
data-testid="add-series-button"
|
||||
type="button"
|
||||
onClick={() => onEditSeriesRequest()}
|
||||
variant="link"
|
||||
disabled={!licensed ? series.length >= 10 : false}
|
||||
className={classNames(styles.formSeriesItem, styles.formSeriesAddButton, 'p-3')}
|
||||
>
|
||||
+ Add another data series
|
||||
</Button>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
@ -8,15 +8,16 @@ import {
|
||||
CreationUIPreview,
|
||||
FormChangeEvent,
|
||||
SubmissionErrors,
|
||||
createDefaultEditSeries,
|
||||
EditableDataSeries,
|
||||
} from '../../../../../../components'
|
||||
import { Insight } from '../../../../../../core'
|
||||
import { LineChartLivePreview } from '../../../LineChartLivePreview'
|
||||
import { CreateInsightFormFields, EditableDataSeries } from '../../types'
|
||||
import { LineChartLivePreview, LivePreviewSeries } from '../../../LineChartLivePreview'
|
||||
import { CreateInsightFormFields } from '../../types'
|
||||
import { getSanitizedSeries } from '../../utils/insight-sanitizer'
|
||||
import { SearchInsightCreationForm } from '../SearchInsightCreationForm'
|
||||
|
||||
import { useEditableSeries, createDefaultEditSeries } from './hooks/use-editable-series'
|
||||
import { useInsightCreationForm } from './hooks/use-insight-creation-form/use-insight-creation-form'
|
||||
import { useInsightCreationForm } from './hooks/use-insight-creation-form'
|
||||
|
||||
export interface SearchInsightCreationContentProps {
|
||||
/** This component might be used in edit or creation insight case. */
|
||||
@ -61,8 +62,6 @@ export const SearchInsightCreationContent: FC<SearchInsightCreationContentProps>
|
||||
onSubmit,
|
||||
})
|
||||
|
||||
const { editSeries, listen, editRequest, editCommit, cancelEdit, deleteSeries } = useEditableSeries({ series })
|
||||
|
||||
const handleFormReset = (): void => {
|
||||
// TODO [VK] Change useForm API in order to implement form.reset method.
|
||||
title.input.onChange('')
|
||||
@ -78,7 +77,7 @@ export const SearchInsightCreationContent: FC<SearchInsightCreationContentProps>
|
||||
// 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.input.value.some(series => series.valid)) &&
|
||||
stepValue.meta.validState === 'VALID' &&
|
||||
// For the "all repositories" mode we are not able to show the live preview chart
|
||||
!allReposMode.input.value
|
||||
@ -107,12 +106,7 @@ export const SearchInsightCreationContent: FC<SearchInsightCreationContentProps>
|
||||
isFormClearActive={hasFilledValue}
|
||||
dashboardReferenceCount={initialValue?.dashboardReferenceCount}
|
||||
insight={insight}
|
||||
onSeriesLiveChange={listen}
|
||||
onCancel={onCancel}
|
||||
onEditSeriesRequest={editRequest}
|
||||
onEditSeriesCancel={cancelEdit}
|
||||
onEditSeriesCommit={editCommit}
|
||||
onSeriesRemove={deleteSeries}
|
||||
onFormReset={handleFormReset}
|
||||
/>
|
||||
|
||||
@ -121,7 +115,7 @@ export const SearchInsightCreationContent: FC<SearchInsightCreationContentProps>
|
||||
disabled={!allFieldsForPreviewAreValid}
|
||||
repositories={repositories.meta.value}
|
||||
isAllReposMode={allReposMode.input.value}
|
||||
series={seriesToPreview(editSeries)}
|
||||
series={seriesToPreview(series.input.value)}
|
||||
step={step.meta.value}
|
||||
stepValue={stepValue.meta.value}
|
||||
/>
|
||||
@ -129,7 +123,7 @@ export const SearchInsightCreationContent: FC<SearchInsightCreationContentProps>
|
||||
)
|
||||
}
|
||||
|
||||
function seriesToPreview(currentSeries: EditableDataSeries[]): any {
|
||||
function seriesToPreview(currentSeries: EditableDataSeries[]): LivePreviewSeries[] {
|
||||
const validSeries = currentSeries.filter(series => series.valid)
|
||||
return getSanitizedSeries(validSeries).map(series => ({
|
||||
query: series.query,
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Helper replace element in array by index and return new array.
|
||||
* */
|
||||
export function replace<Element>(list: Element[], index: number, newElement: Element): Element[] {
|
||||
return [...list.slice(0, index), newElement, ...list.slice(index + 1)]
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper remove element from array by index
|
||||
* */
|
||||
export function remove<Element>(list: Element[], index: number): Element[] {
|
||||
return [...list.slice(0, index), ...list.slice(index + 1)]
|
||||
}
|
||||
@ -1,15 +1,22 @@
|
||||
import { useAsyncInsightTitleValidator } from '../../../../../../../../components/form/hooks/use-async-insight-title-validator'
|
||||
import { useField, useFieldAPI } from '../../../../../../../../components/form/hooks/useField'
|
||||
import { Form, FormChangeEvent, SubmissionErrors, useForm } from '../../../../../../../../components/form/hooks/useForm'
|
||||
import { createRequiredValidator } from '../../../../../../../../components/form/validators'
|
||||
import { CreateInsightFormFields, EditableDataSeries, InsightStep } from '../../../../types'
|
||||
import { INITIAL_INSIGHT_VALUES } from '../../initial-insight-values'
|
||||
import {
|
||||
useAsyncInsightTitleValidator,
|
||||
useField,
|
||||
useFieldAPI,
|
||||
Form,
|
||||
FormChangeEvent,
|
||||
SubmissionErrors,
|
||||
useForm,
|
||||
createRequiredValidator,
|
||||
EditableDataSeries,
|
||||
} from '../../../../../../../components'
|
||||
import { CreateInsightFormFields, InsightStep } from '../../../types'
|
||||
import { INITIAL_INSIGHT_VALUES } from '../initial-insight-values'
|
||||
import {
|
||||
repositoriesExistValidator,
|
||||
repositoriesFieldValidator,
|
||||
requiredStepValueField,
|
||||
seriesRequired,
|
||||
} from '../../validators'
|
||||
} from '../validators'
|
||||
|
||||
const titleRequiredValidator = createRequiredValidator('Title is a required field.')
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { createDefaultEditSeries } from '../../../../../../components'
|
||||
import { CreateInsightFormFields } from '../../types'
|
||||
|
||||
import { createDefaultEditSeries } from './hooks/use-editable-series'
|
||||
|
||||
export const INITIAL_INSIGHT_VALUES: CreateInsightFormFields = {
|
||||
// If user opens the creation form to create insight
|
||||
// we want to show the series form as soon as possible
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import { renderError } from '@sourcegraph/branded/src/components/alerts'
|
||||
import { dedupeWhitespace } from '@sourcegraph/common'
|
||||
|
||||
import { getSanitizedRepositories } from '../../../../../../components/creation-ui-kit'
|
||||
import { getSanitizedRepositories, createRequiredValidator, EditableDataSeries } from '../../../../../../components'
|
||||
import { Validator } from '../../../../../../components/form/hooks/useField'
|
||||
import { AsyncValidator } from '../../../../../../components/form/hooks/utils/use-async-validation'
|
||||
import { createRequiredValidator } from '../../../../../../components/form/validators'
|
||||
import { fetchRepositories } from '../../../../../../core/backend/gql-backend/methods/get-built-in-insight-data/utils/fetch-repositories'
|
||||
import { EditableDataSeries } from '../../types'
|
||||
|
||||
export const repositoriesFieldValidator: Validator<string> = value => {
|
||||
if (value !== undefined && dedupeWhitespace(value).trim() === '') {
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
export { DATA_SERIES_COLORS, DEFAULT_DATA_SERIES_COLOR } from './constants'
|
||||
|
||||
export { SearchInsightCreationPage } from './SearchInsightCreationPage'
|
||||
export { encodeSearchInsightUrl } from './utils/search-insight-url-parsers/search-insight-url-parsers'
|
||||
export { getQueryPatternTypeFilter } from './components/form-series-input/get-pattern-type-filter'
|
||||
export { getQueryPatternTypeFilter } from '../../../../components/creation-ui/form-series/components/form-series-input/get-pattern-type-filter'
|
||||
|
||||
export type { SearchInsightURLValues } from './utils/search-insight-url-parsers/search-insight-url-parsers'
|
||||
export type { CreateInsightFormFields, EditableDataSeries, InsightStep } from './types'
|
||||
export type { CreateInsightFormFields, InsightStep } from './types'
|
||||
|
||||
@ -1,36 +1,21 @@
|
||||
import { SearchBasedInsightSeries } from '../../../../core'
|
||||
import { EditableDataSeries } from '../../../../components/creation-ui/form-series/types'
|
||||
|
||||
export type InsightStep = 'hours' | 'days' | 'weeks' | 'months' | 'years'
|
||||
|
||||
export interface EditableDataSeries extends SearchBasedInsightSeries {
|
||||
valid: boolean
|
||||
edit: boolean
|
||||
}
|
||||
|
||||
export interface CreateInsightFormFields {
|
||||
/**
|
||||
* Code Insight series setting (name of line, line query, color)
|
||||
*/
|
||||
/** Code Insight series setting (name of line, line query, color) */
|
||||
series: EditableDataSeries[]
|
||||
|
||||
/**
|
||||
* Title of code insight
|
||||
*/
|
||||
/** Title of code insight */
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Repositories which to be used to get the info for code insights
|
||||
*/
|
||||
/** Repositories which to be used to get the info for code insights */
|
||||
repositories: string
|
||||
|
||||
/**
|
||||
* Setting for set chart step - how often do we collect data.
|
||||
*/
|
||||
/** Setting for set chart step - how often do we collect data. */
|
||||
step: InsightStep
|
||||
|
||||
/**
|
||||
* Value for insight step setting
|
||||
*/
|
||||
/** Value for insight step setting */
|
||||
stepValue: string
|
||||
|
||||
/**
|
||||
@ -39,8 +24,6 @@ export interface CreateInsightFormFields {
|
||||
*/
|
||||
allRepos: boolean
|
||||
|
||||
/**
|
||||
* The total number of dashboards on which this insight is referenced.
|
||||
*/
|
||||
/** The total number of dashboards on which this insight is referenced. */
|
||||
dashboardReferenceCount: number
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { getSanitizedRepositories } from '../../../../../components/creation-ui-kit'
|
||||
import { getSanitizedRepositories } from '../../../../../components'
|
||||
import {
|
||||
MinimalSearchBasedInsightData,
|
||||
InsightExecutionType,
|
||||
InsightType,
|
||||
SearchBasedInsightSeries,
|
||||
} from '../../../../../core'
|
||||
import { CreateInsightFormFields, EditableDataSeries } from '../types'
|
||||
import { CreateInsightFormFields } from '../types'
|
||||
|
||||
export function getSanitizedLine(line: EditableDataSeries): SearchBasedInsightSeries {
|
||||
export function getSanitizedLine(line: SearchBasedInsightSeries): SearchBasedInsightSeries {
|
||||
return {
|
||||
id: line.id,
|
||||
name: line.name.trim(),
|
||||
@ -20,7 +20,7 @@ export function getSanitizedLine(line: EditableDataSeries): SearchBasedInsightSe
|
||||
}
|
||||
}
|
||||
|
||||
export function getSanitizedSeries(rawSeries: EditableDataSeries[]): SearchBasedInsightSeries[] {
|
||||
export function getSanitizedSeries(rawSeries: SearchBasedInsightSeries[]): SearchBasedInsightSeries[] {
|
||||
return rawSeries.map(getSanitizedLine)
|
||||
}
|
||||
|
||||
|
||||
@ -27,8 +27,8 @@ describe('decodeSearchInsightUrl', () => {
|
||||
title: 'Insight title',
|
||||
allRepos: true,
|
||||
series: [
|
||||
{ id: 1, edit: false, valid: true, name: 'series 1', query: 'test1', stroke: 'red' },
|
||||
{ id: 2, edit: false, valid: true, name: 'series 2', query: 'test2', stroke: 'blue' },
|
||||
{ id: 1, edit: false, valid: true, autofocus: false, name: 'series 1', query: 'test1', stroke: 'red' },
|
||||
{ id: 2, edit: false, valid: true, autofocus: false, name: 'series 2', query: 'test2', stroke: 'blue' },
|
||||
],
|
||||
})
|
||||
})
|
||||
@ -51,8 +51,24 @@ describe('encodeSearchInsightUrl', () => {
|
||||
title: 'Insight title',
|
||||
allRepos: true,
|
||||
series: [
|
||||
{ id: '1', edit: false, valid: true, name: 'series 1', query: 'test1', stroke: 'red' },
|
||||
{ id: '2', edit: false, valid: true, name: 'series 2', query: 'test2', stroke: 'blue' },
|
||||
{
|
||||
id: '1',
|
||||
edit: false,
|
||||
valid: true,
|
||||
autofocus: false,
|
||||
name: 'series 1',
|
||||
query: 'test1',
|
||||
stroke: 'red',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
edit: false,
|
||||
valid: true,
|
||||
autofocus: false,
|
||||
name: 'series 2',
|
||||
query: 'test2',
|
||||
stroke: 'blue',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createDefaultEditSeries } from '../../../../../../components'
|
||||
import { SearchBasedInsightSeries } from '../../../../../../core'
|
||||
import { createDefaultEditSeries } from '../../components/search-insight-creation-content/hooks/use-editable-series'
|
||||
import { CreateInsightFormFields } from '../../types'
|
||||
|
||||
export function decodeSearchInsightUrl(queryParameters: string): Partial<CreateInsightFormFields> | null {
|
||||
|
||||
@ -6,8 +6,8 @@ import { stringHuman } from '@sourcegraph/shared/src/search/query/printer'
|
||||
import { scanSearchQuery } from '@sourcegraph/shared/src/search/query/scanner'
|
||||
import { isFilterType, isRepoFilter } from '@sourcegraph/shared/src/search/query/validate'
|
||||
|
||||
import { CodeInsightsBackendContext } from '../../../../../../core/backend/code-insights-backend-context'
|
||||
import { createDefaultEditSeries } from '../../components/search-insight-creation-content/hooks/use-editable-series'
|
||||
import { createDefaultEditSeries } from '../../../../../../components'
|
||||
import { CodeInsightsBackendContext } from '../../../../../../core'
|
||||
import { INITIAL_INSIGHT_VALUES } from '../../components/search-insight-creation-content/initial-insight-values'
|
||||
import { CreateInsightFormFields } from '../../types'
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import { SubmissionErrors } from '../../../../components/form/hooks/useForm'
|
||||
import { SubmissionErrors } from '../../../../components'
|
||||
import { MinimalCaptureGroupInsightData, CaptureGroupInsight } from '../../../../core'
|
||||
import { CaptureGroupFormFields } from '../../creation/capture-group'
|
||||
import { CaptureGroupCreationContent } from '../../creation/capture-group/components/CaptureGroupCreationContent'
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import { SubmissionErrors } from '../../../../components/form/hooks/useForm'
|
||||
import { SubmissionErrors, createDefaultEditSeries } from '../../../../components'
|
||||
import { MinimalSearchBasedInsightData, SearchBasedInsight } from '../../../../core'
|
||||
import { CreateInsightFormFields, InsightStep } from '../../creation/search-insight'
|
||||
import { createDefaultEditSeries } from '../../creation/search-insight/components/search-insight-creation-content/hooks/use-editable-series'
|
||||
import { SearchInsightCreationContent } from '../../creation/search-insight/components/search-insight-creation-content/SearchInsightCreationContent'
|
||||
import { getSanitizedSearchInsight } from '../../creation/search-insight/utils/insight-sanitizer'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DATA_SERIES_COLORS } from '../../../../insights/creation/search-insight'
|
||||
import { DATA_SERIES_COLORS } from '../../../../../constants'
|
||||
|
||||
import { CaptureGroupExampleContent, SearchInsightExampleContent } from './types'
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { SettingsExperimentalFeatures } from '@sourcegraph/shared/src/schema/settings.schema'
|
||||
|
||||
import { DATA_SERIES_COLORS } from '../../../../../constants'
|
||||
import { InsightType } from '../../../../../core'
|
||||
import { CaptureInsightUrlValues } from '../../../../insights/creation/capture-group'
|
||||
import { DATA_SERIES_COLORS, SearchInsightURLValues } from '../../../../insights/creation/search-insight'
|
||||
import { SearchInsightURLValues } from '../../../../insights/creation/search-insight'
|
||||
|
||||
export interface TemplateSection {
|
||||
title: string
|
||||
|
||||
@ -7,11 +7,13 @@ import { noop } from 'rxjs'
|
||||
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { Button, Card, Link, useObservable, useDebounce, Icon, Input, H2, H3, Text } from '@sourcegraph/wildcard'
|
||||
|
||||
import { getDefaultInputProps } from '../../../../../components/form/getDefaultInputProps'
|
||||
import { useField } from '../../../../../components/form/hooks/useField'
|
||||
import { useForm } from '../../../../../components/form/hooks/useForm'
|
||||
import { InsightQueryInput } from '../../../../../components/form/query-input/InsightQueryInput'
|
||||
import { RepositoriesField } from '../../../../../components/form/repositories-field/RepositoriesField'
|
||||
import {
|
||||
getDefaultInputProps,
|
||||
useField,
|
||||
useForm,
|
||||
InsightQueryInput,
|
||||
RepositoriesField,
|
||||
} from '../../../../../components'
|
||||
import { CodeInsightsBackendContext } from '../../../../../core'
|
||||
import { getQueryPatternTypeFilter } from '../../../../insights/creation/search-insight'
|
||||
import {
|
||||
|
||||
@ -5,8 +5,9 @@ import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryServi
|
||||
import { useDeepMemo } from '@sourcegraph/wildcard'
|
||||
|
||||
import { useSeriesToggle } from '../../../../../../../insights/utils/use-series-toggle'
|
||||
import { SeriesBasedChartTypes, SeriesChart } from '../../../../../components'
|
||||
import {
|
||||
SeriesBasedChartTypes,
|
||||
SeriesChart,
|
||||
getSanitizedRepositories,
|
||||
useLivePreview,
|
||||
StateStatus,
|
||||
@ -18,16 +19,14 @@ import {
|
||||
LivePreviewBanner,
|
||||
LivePreviewLegend,
|
||||
SERIES_MOCK_CHART,
|
||||
} from '../../../../../components/creation-ui-kit'
|
||||
import { CodeInsightsBackendContext, SeriesChartContent } from '../../../../../core'
|
||||
} from '../../../../../components'
|
||||
import { DATA_SERIES_COLORS } from '../../../../../constants'
|
||||
import { CodeInsightsBackendContext, SearchBasedInsightSeries, SeriesChartContent } from '../../../../../core'
|
||||
import { CodeInsightTrackType, useCodeInsightViewPings } from '../../../../../pings'
|
||||
import { DATA_SERIES_COLORS, EditableDataSeries } from '../../../../insights/creation/search-insight'
|
||||
|
||||
const createExampleDataSeries = (query: string): EditableDataSeries[] => [
|
||||
const createExampleDataSeries = (query: string): SearchBasedInsightSeries[] => [
|
||||
{
|
||||
query,
|
||||
valid: true,
|
||||
edit: false,
|
||||
id: '1',
|
||||
name: 'TODOs',
|
||||
stroke: DATA_SERIES_COLORS.ORANGE,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user