chore(frontend): migrate SqlLab and explore JS/JSX files to TypeScript (#36760)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Evan Rusackas
2026-01-06 10:52:58 -08:00
committed by GitHub
parent aaa174f820
commit 9aff89c1b4
69 changed files with 3272 additions and 1482 deletions

View File

@@ -19,6 +19,7 @@
import {
render,
screen,
selectOption,
userEvent,
waitFor,
} from 'spec/helpers/testing-library';
@@ -210,39 +211,23 @@ test('fetches chart on mount if value present', async () => {
});
test('keeps apply disabled when missing required fields', async () => {
// With EVENT type and Table source, the component requires selecting a chart
// and filling in required fields. Without completing these, Apply should be disabled.
await waitForRender({
annotationType: ANNOTATION_TYPES_METADATA.EVENT.value,
sourceType: 'Table',
});
userEvent.click(
screen.getByRole('combobox', { name: 'Annotation layer value' }),
);
expect(await screen.findByText('Chart A')).toBeInTheDocument();
userEvent.click(screen.getByText('Chart A'));
// Apply button should be disabled initially since required fields are not filled
expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled();
// Select Chart A from the annotation layer value dropdown
await selectOption('Chart A', 'Annotation layer value');
// Wait for the chart data to load
await screen.findByText(/title column/i);
userEvent.click(
screen.getByRole('combobox', { name: 'Annotation layer title column' }),
);
expect(await screen.findByText(/none/i)).toBeInTheDocument();
userEvent.click(screen.getByText('None'));
userEvent.click(screen.getByText('Style'));
// The checkbox for automatic color is in the Style tab
userEvent.click(screen.getByText('Use automatic color'));
userEvent.click(
screen.getByRole('combobox', { name: 'Annotation layer stroke' }),
);
expect(await screen.findByText('Dashed')).toBeInTheDocument();
userEvent.click(screen.getByText('Dashed'));
userEvent.click(screen.getByText('Opacity'));
userEvent.click(
screen.getByRole('combobox', { name: 'Annotation layer opacity' }),
);
expect(await screen.findByText(/0.5/i)).toBeInTheDocument();
userEvent.click(screen.getByText('0.5'));
const checkboxes = screen.getAllByRole('checkbox');
checkboxes.forEach(checkbox => userEvent.click(checkbox));
// Apply should still be disabled because name is not filled
expect(screen.getByRole('button', { name: 'Apply' })).toBeDisabled();
});

View File

@@ -16,9 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { PureComponent } from 'react';
import React, { PureComponent } from 'react';
import rison from 'rison';
import PropTypes from 'prop-types';
import {
Button,
AsyncSelect,
@@ -34,8 +33,13 @@ import {
isValidExpression,
getColumnLabel,
VizType,
type QueryFormColumn,
} from '@superset-ui/core';
import { styled, withTheme } from '@apache-superset/core/ui';
import {
styled,
withTheme,
type SupersetTheme,
} from '@apache-superset/core/ui';
import SelectControl from 'src/explore/components/controls/SelectControl';
import TextControl from 'src/explore/components/controls/TextControl';
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
@@ -50,60 +54,81 @@ import {
ANNOTATION_SOURCE_TYPES_METADATA,
} from './AnnotationTypes';
interface SelectOption {
value: string | number;
label: string;
viz_type?: string;
[key: string]: unknown;
}
interface SliceData {
data: {
groupby?: string[];
all_columns?: string[];
include_time?: boolean;
[key: string]: unknown;
};
}
interface AnnotationOverrides {
time_range?: string | null;
time_grain_sqla?: string | null;
granularity?: string | null;
time_shift?: string;
[key: string]: unknown;
}
interface AnnotationLayerProps {
name?: string;
annotationType?: string;
sourceType?: string;
color?: string;
opacity?: string;
style?: string;
width?: number;
showMarkers?: boolean;
hideLine?: boolean;
value?: string | number | SelectOption;
overrides?: AnnotationOverrides;
show?: boolean;
showLabel?: boolean;
titleColumn?: string;
descriptionColumns?: string[];
timeColumn?: string;
intervalEndColumn?: string;
vizType?: string;
error?: string;
colorScheme?: string;
theme: SupersetTheme;
addAnnotationLayer?: (annotation: Record<string, unknown>) => void;
removeAnnotationLayer?: () => void;
close?: () => void;
}
interface AnnotationLayerState {
name: string;
annotationType: string;
sourceType: string | null;
value: string | number | SelectOption | null;
overrides: AnnotationOverrides;
show: boolean;
showLabel: boolean;
titleColumn: string;
descriptionColumns: string[];
timeColumn: string;
intervalEndColumn: string;
color: string;
opacity: string;
style: string;
width: number;
showMarkers: boolean;
hideLine: boolean;
isNew: boolean;
slice: SliceData | null;
}
const AUTOMATIC_COLOR = '';
const propTypes = {
name: PropTypes.string,
annotationType: PropTypes.string,
sourceType: PropTypes.string,
color: PropTypes.string,
opacity: PropTypes.string,
style: PropTypes.string,
width: PropTypes.number,
showMarkers: PropTypes.bool,
hideLine: PropTypes.bool,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
overrides: PropTypes.object,
show: PropTypes.bool,
showLabel: PropTypes.bool,
titleColumn: PropTypes.string,
descriptionColumns: PropTypes.arrayOf(PropTypes.string),
timeColumn: PropTypes.string,
intervalEndColumn: PropTypes.string,
vizType: PropTypes.string,
error: PropTypes.string,
colorScheme: PropTypes.string,
addAnnotationLayer: PropTypes.func,
removeAnnotationLayer: PropTypes.func,
close: PropTypes.func,
};
const defaultProps = {
name: '',
annotationType: DEFAULT_ANNOTATION_TYPE,
sourceType: '',
color: AUTOMATIC_COLOR,
opacity: '',
style: 'solid',
width: 1,
showMarkers: false,
hideLine: false,
overrides: {},
colorScheme: 'd3Category10',
show: true,
showLabel: false,
titleColumn: '',
descriptionColumns: [],
timeColumn: '',
intervalEndColumn: '',
addAnnotationLayer: () => {},
removeAnnotationLayer: () => {},
close: () => {},
};
const NotFoundContentWrapper = styled.div`
&& > div:first-child {
padding-left: 0;
@@ -134,8 +159,34 @@ const NotFoundContent = () => (
</NotFoundContentWrapper>
);
class AnnotationLayer extends PureComponent {
constructor(props) {
class AnnotationLayer extends PureComponent<
AnnotationLayerProps,
AnnotationLayerState
> {
static defaultProps = {
name: '',
annotationType: DEFAULT_ANNOTATION_TYPE,
sourceType: '',
color: AUTOMATIC_COLOR,
opacity: '',
style: 'solid',
width: 1,
showMarkers: false,
hideLine: false,
overrides: {},
colorScheme: 'd3Category10',
show: true,
showLabel: false,
titleColumn: '',
descriptionColumns: [],
timeColumn: '',
intervalEndColumn: '',
addAnnotationLayer: () => {},
removeAnnotationLayer: () => {},
close: () => {},
};
constructor(props: AnnotationLayerProps) {
super(props);
const {
name,
@@ -159,42 +210,46 @@ class AnnotationLayer extends PureComponent {
} = props;
// Only allow override whole time_range
if ('since' in overrides || 'until' in overrides) {
overrides.time_range = null;
delete overrides.since;
delete overrides.until;
const processedOverrides: AnnotationOverrides = overrides
? { ...overrides }
: {};
if ('since' in processedOverrides || 'until' in processedOverrides) {
processedOverrides.time_range = null;
delete processedOverrides.since;
delete processedOverrides.until;
}
// Check if annotationType is supported by this chart
const metadata = getChartMetadataRegistry().get(vizType);
const metadata = vizType ? getChartMetadataRegistry().get(vizType) : null;
const supportedAnnotationTypes = metadata?.supportedAnnotationTypes || [];
const resolvedAnnotationType = annotationType || DEFAULT_ANNOTATION_TYPE;
const validAnnotationType = supportedAnnotationTypes.includes(
annotationType,
resolvedAnnotationType,
)
? annotationType
? resolvedAnnotationType
: supportedAnnotationTypes[0];
this.state = {
// base
name,
annotationType: validAnnotationType,
sourceType,
value,
overrides,
show,
showLabel,
name: name || '',
annotationType: validAnnotationType || DEFAULT_ANNOTATION_TYPE,
sourceType: sourceType || null,
value: value || null,
overrides: processedOverrides,
show: show ?? true,
showLabel: showLabel ?? false,
// slice
titleColumn,
descriptionColumns,
timeColumn,
intervalEndColumn,
titleColumn: titleColumn || '',
descriptionColumns: descriptionColumns || [],
timeColumn: timeColumn || '',
intervalEndColumn: intervalEndColumn || '',
// display
color: color || AUTOMATIC_COLOR,
opacity,
style,
width,
showMarkers,
hideLine,
opacity: opacity || '',
style: style || 'solid',
width: width ?? 1,
showMarkers: showMarkers ?? false,
hideLine: hideLine ?? false,
// refData
isNew: !name,
slice: null,
@@ -229,57 +284,71 @@ class AnnotationLayer extends PureComponent {
/* The value prop is the id of the chart/native. This function will set
value in state to an object with the id as value.value to be used by
AsyncSelect */
this.fetchAppliedAnnotation(value);
if (value !== null && typeof value !== 'object') {
this.fetchAppliedAnnotation(value);
}
}
}
componentDidUpdate(prevProps, prevState) {
componentDidUpdate(
_prevProps: AnnotationLayerProps,
prevState: AnnotationLayerState,
): void {
if (this.shouldFetchSliceData(prevState)) {
const { value } = this.state;
this.fetchSliceData(value.value);
if (value && typeof value === 'object' && 'value' in value) {
this.fetchSliceData(value.value);
}
}
}
getSupportedSourceTypes(annotationType) {
getSupportedSourceTypes(annotationType: string): SelectOption[] {
// Get vis types that can be source.
const sources = getChartMetadataRegistry()
.entries()
.filter(({ value: chartMetadata }) =>
chartMetadata.canBeAnnotationType(annotationType),
chartMetadata?.canBeAnnotationType(annotationType),
)
.map(({ key, value: chartMetadata }) => ({
value: key === VizType.Line ? 'line' : key,
label: chartMetadata.name,
label: chartMetadata?.name || key,
}));
// Prepend native source if applicable
if (ANNOTATION_TYPES_METADATA[annotationType]?.supportNativeSource) {
const annotationMeta =
ANNOTATION_TYPES_METADATA[
annotationType as keyof typeof ANNOTATION_TYPES_METADATA
];
if (annotationMeta && 'supportNativeSource' in annotationMeta) {
sources.unshift(ANNOTATION_SOURCE_TYPES_METADATA.NATIVE);
}
return sources;
}
shouldFetchAppliedAnnotation() {
shouldFetchAppliedAnnotation(): boolean {
const { value, sourceType } = this.state;
return value && requiresQuery(sourceType);
return !!value && requiresQuery(sourceType ?? undefined);
}
shouldFetchSliceData(prevState) {
shouldFetchSliceData(prevState: AnnotationLayerState): boolean {
const { value, sourceType } = this.state;
const isChart =
sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE &&
requiresQuery(sourceType);
requiresQuery(sourceType ?? undefined);
const valueIsNew = value && prevState.value !== value;
return valueIsNew && isChart;
return !!valueIsNew && isChart;
}
isValidFormulaAnnotation(expression, annotationType) {
isValidFormulaAnnotation(
expression: string | number | SelectOption | null,
annotationType: string,
): boolean {
if (annotationType === ANNOTATION_TYPES.FORMULA) {
return isValidExpression(expression);
return isValidExpression(expression as string);
}
return true;
}
isValidForm() {
isValidForm(): boolean {
const {
name,
annotationType,
@@ -302,11 +371,13 @@ class AnnotationLayer extends PureComponent {
errors.push(validateNonEmpty(intervalEndColumn));
}
}
errors.push(!this.isValidFormulaAnnotation(value, annotationType));
if (!this.isValidFormulaAnnotation(value, annotationType)) {
errors.push(t('Invalid formula expression'));
}
return !errors.filter(x => x).length;
}
handleAnnotationType(annotationType) {
handleAnnotationType(annotationType: string): void {
this.setState({
annotationType,
sourceType: null,
@@ -315,7 +386,7 @@ class AnnotationLayer extends PureComponent {
});
}
handleAnnotationSourceType(sourceType) {
handleAnnotationSourceType(sourceType: string): void {
const { sourceType: prevSourceType } = this.state;
if (prevSourceType !== sourceType) {
@@ -327,24 +398,28 @@ class AnnotationLayer extends PureComponent {
}
}
handleSelectValue(selectedValueObject) {
handleSelectValue(selectedValueObject: SelectOption): void {
this.setState({
value: selectedValueObject,
descriptionColumns: [],
intervalEndColumn: null,
timeColumn: null,
titleColumn: null,
intervalEndColumn: '',
timeColumn: '',
titleColumn: '',
overrides: { time_range: null },
});
}
handleTextValue(inputValue) {
handleTextValue(inputValue: string): void {
this.setState({
value: inputValue,
});
}
fetchNativeAnnotations = async (search, page, pageSize) => {
fetchNativeAnnotations = async (
search: string,
page: number,
pageSize: number,
): Promise<{ data: SelectOption[]; totalCount: number }> => {
const queryParams = rison.encode({
filters: [
{
@@ -364,7 +439,7 @@ class AnnotationLayer extends PureComponent {
const { result, count } = json;
const layersArray = result.map(layer => ({
const layersArray = result.map((layer: { id: number; name: string }) => ({
value: layer.id,
label: layer.name,
}));
@@ -375,7 +450,11 @@ class AnnotationLayer extends PureComponent {
};
};
fetchCharts = async (search, page, pageSize) => {
fetchCharts = async (
search: string,
page: number,
pageSize: number,
): Promise<{ data: SelectOption[]; totalCount: number }> => {
const { annotationType } = this.state;
const queryParams = rison.encode({
@@ -401,11 +480,11 @@ class AnnotationLayer extends PureComponent {
const registry = getChartMetadataRegistry();
const chartsArray = result
.filter(chart => {
.filter((chart: { id: number; slice_name: string; viz_type: string }) => {
const metadata = registry.get(chart.viz_type);
return metadata && metadata.canBeAnnotationType(annotationType);
})
.map(chart => ({
.map((chart: { id: number; slice_name: string; viz_type: string }) => ({
value: chart.id,
label: chart.slice_name,
viz_type: chart.viz_type,
@@ -417,7 +496,11 @@ class AnnotationLayer extends PureComponent {
};
};
fetchOptions = (search, page, pageSize) => {
fetchOptions = (
search: string,
page: number,
pageSize: number,
): Promise<{ data: SelectOption[]; totalCount: number }> => {
const { sourceType } = this.state;
if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
@@ -426,7 +509,7 @@ class AnnotationLayer extends PureComponent {
return this.fetchCharts(search, page, pageSize);
};
fetchSliceData = id => {
fetchSliceData = (id: string | number): void => {
const queryParams = rison.encode({
columns: ['query_context'],
});
@@ -439,7 +522,9 @@ class AnnotationLayer extends PureComponent {
const dataObject = {
data: {
...formData,
groupby: formData.groupby?.map(column => getColumnLabel(column)),
groupby: formData.groupby?.map((column: QueryFormColumn) =>
getColumnLabel(column),
),
},
};
this.setState({
@@ -448,7 +533,7 @@ class AnnotationLayer extends PureComponent {
});
};
fetchAppliedChart(id) {
fetchAppliedChart(id: string | number): void {
const { annotationType } = this.state;
const registry = getChartMetadataRegistry();
const queryParams = rison.encode({
@@ -474,7 +559,9 @@ class AnnotationLayer extends PureComponent {
slice: {
data: {
...formData,
groupby: formData.groupby?.map(column => getColumnLabel(column)),
groupby: formData.groupby?.map((column: QueryFormColumn) =>
getColumnLabel(column),
),
},
},
});
@@ -482,7 +569,7 @@ class AnnotationLayer extends PureComponent {
});
}
fetchAppliedNativeAnnotation(id) {
fetchAppliedNativeAnnotation(id: string | number): void {
SupersetClient.get({
endpoint: `/api/v1/annotation_layer/${id}`,
}).then(({ json }) => {
@@ -497,7 +584,7 @@ class AnnotationLayer extends PureComponent {
});
}
fetchAppliedAnnotation(id) {
fetchAppliedAnnotation(id: string | number): void {
const { sourceType } = this.state;
if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
@@ -506,12 +593,12 @@ class AnnotationLayer extends PureComponent {
return this.fetchAppliedChart(id);
}
deleteAnnotation() {
this.props.removeAnnotationLayer();
this.props.close();
deleteAnnotation(): void {
this.props.removeAnnotationLayer?.();
this.props.close?.();
}
applyAnnotation() {
applyAnnotation(): void {
const { value, sourceType } = this.state;
if (this.isValidForm()) {
const annotationFields = [
@@ -532,32 +619,42 @@ class AnnotationLayer extends PureComponent {
'timeColumn',
'intervalEndColumn',
];
const newAnnotation = {};
const newAnnotation: Record<string, unknown> = {};
annotationFields.forEach(field => {
if (this.state[field] !== null) {
newAnnotation[field] = this.state[field];
const stateValue = this.state[field as keyof AnnotationLayerState];
if (stateValue !== null) {
newAnnotation[field] = stateValue;
}
});
// Prepare newAnnotation.value for use in runAnnotationQuery()
const applicableValue = requiresQuery(sourceType) ? value.value : value;
const applicableValue =
requiresQuery(sourceType ?? undefined) &&
value &&
typeof value === 'object'
? (value as SelectOption).value
: value;
newAnnotation.value = applicableValue;
if (newAnnotation.color === AUTOMATIC_COLOR) {
newAnnotation.color = null;
}
this.props.addAnnotationLayer(newAnnotation);
this.props.addAnnotationLayer?.(newAnnotation);
this.setState({ isNew: false });
}
}
submitAnnotation() {
submitAnnotation(): void {
this.applyAnnotation();
this.props.close();
this.props.close?.();
}
renderChartHeader(label, description, value) {
renderChartHeader(
label: string,
description: string,
value: string | number | SelectOption | null,
): React.ReactNode {
return (
<ControlHeader
hovered
@@ -568,11 +665,11 @@ class AnnotationLayer extends PureComponent {
);
}
renderValueConfiguration() {
renderValueConfiguration(): React.ReactNode {
const { annotationType, sourceType, value } = this.state;
let label = '';
let description = '';
if (requiresQuery(sourceType)) {
if (requiresQuery(sourceType ?? undefined)) {
if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
label = t('Annotation layer');
description = t('Select the Annotation Layer you would like to use.');
@@ -592,7 +689,7 @@ class AnnotationLayer extends PureComponent {
in milliseconds since epoch. mathjs is used to evaluate the formulas.
Example: '2x+5'`);
}
if (requiresQuery(sourceType)) {
if (requiresQuery(sourceType ?? undefined)) {
return (
<AsyncSelect
/* key to force re-render on sourceType change */
@@ -608,6 +705,8 @@ class AnnotationLayer extends PureComponent {
);
}
if (annotationType === ANNOTATION_TYPES.FORMULA) {
// Extract primitive value for TextControl (formula is always a string)
const textValue = typeof value === 'object' ? null : value;
return (
<TextControl
name="annotation-layer-value"
@@ -616,7 +715,7 @@ class AnnotationLayer extends PureComponent {
description={description}
label={label}
placeholder=""
value={value}
value={textValue}
onChange={this.handleTextValue}
validationErrors={
!this.isValidFormulaAnnotation(value, annotationType)
@@ -629,7 +728,7 @@ class AnnotationLayer extends PureComponent {
return '';
}
renderSliceConfiguration() {
renderSliceConfiguration(): React.ReactNode {
const {
annotationType,
sourceType,
@@ -679,7 +778,9 @@ class AnnotationLayer extends PureComponent {
clearable={false}
options={timeColumnOptions}
value={timeColumn}
onChange={v => this.setState({ timeColumn: v })}
onChange={(
v: string | number | (string | number)[] | null | undefined,
) => this.setState({ timeColumn: String(v ?? '') })}
/>
)}
{annotationType === ANNOTATION_TYPES.INTERVAL && (
@@ -694,7 +795,14 @@ class AnnotationLayer extends PureComponent {
validationErrors={!intervalEndColumn ? ['Mandatory'] : []}
options={columns}
value={intervalEndColumn}
onChange={value => this.setState({ intervalEndColumn: value })}
onChange={(
value:
| string
| number
| (string | number)[]
| null
| undefined,
) => this.setState({ intervalEndColumn: String(value ?? '') })}
/>
)}
<SelectControl
@@ -705,7 +813,9 @@ class AnnotationLayer extends PureComponent {
description={t('Pick a title for you annotation.')}
options={[{ value: '', label: t('None') }].concat(columns)}
value={titleColumn}
onChange={value => this.setState({ titleColumn: value })}
onChange={(
value: string | number | (string | number)[] | null | undefined,
) => this.setState({ titleColumn: String(value ?? '') })}
/>
{annotationType !== ANNOTATION_TYPES.TIME_SERIES && (
<SelectControl
@@ -719,7 +829,17 @@ class AnnotationLayer extends PureComponent {
multi
options={columns}
value={descriptionColumns}
onChange={value => this.setState({ descriptionColumns: value })}
onChange={(
value:
| string
| number
| (string | number)[]
| null
| undefined,
) => {
const cols = Array.isArray(value) ? value.map(String) : [];
this.setState({ descriptionColumns: cols });
}}
/>
)}
<div style={{ marginTop: '1rem' }}>
@@ -784,7 +904,7 @@ class AnnotationLayer extends PureComponent {
return '';
}
renderDisplayConfiguration() {
renderDisplayConfiguration(): React.ReactNode {
const {
color,
opacity,
@@ -794,9 +914,10 @@ class AnnotationLayer extends PureComponent {
hideLine,
annotationType,
} = this.state;
const colorScheme = getCategoricalSchemeRegistry()
.get(this.props.colorScheme)
.colors.concat();
const colorScheme =
getCategoricalSchemeRegistry()
.get(this.props.colorScheme)
?.colors.concat() ?? [];
if (
color &&
color !== AUTOMATIC_COLOR &&
@@ -823,7 +944,9 @@ class AnnotationLayer extends PureComponent {
]}
value={style}
clearable={false}
onChange={v => this.setState({ style: v })}
onChange={(
v: string | number | (string | number)[] | null | undefined,
) => this.setState({ style: String(v ?? 'solid') })}
/>
<SelectControl
ariaLabel={t('Annotation layer opacity')}
@@ -837,7 +960,9 @@ class AnnotationLayer extends PureComponent {
{ value: 'opacityHigh', label: '0.8' },
]}
value={opacity}
onChange={value => this.setState({ opacity: value })}
onChange={(
value: string | number | (string | number)[] | null | undefined,
) => this.setState({ opacity: String(value ?? '') })}
/>
<div
style={{
@@ -905,14 +1030,19 @@ class AnnotationLayer extends PureComponent {
);
}
render() {
render(): React.ReactNode {
const { isNew, name, annotationType, sourceType, show, showLabel } =
this.state;
const isValid = this.isValidForm();
const metadata = getChartMetadataRegistry().get(this.props.vizType);
const metadata = this.props.vizType
? getChartMetadataRegistry().get(this.props.vizType)
: null;
const supportedAnnotationTypes = metadata
? metadata.supportedAnnotationTypes.map(
type => ANNOTATION_TYPES_METADATA[type],
type =>
ANNOTATION_TYPES_METADATA[
type as keyof typeof ANNOTATION_TYPES_METADATA
],
)
: [];
const supportedSourceTypes = this.getSupportedSourceTypes(annotationType);
@@ -989,7 +1119,7 @@ class AnnotationLayer extends PureComponent {
<Button
buttonSize="small"
buttonStyle="secondary"
onClick={() => this.props.close()}
onClick={() => this.props.close?.()}
>
{t('Cancel')}
</Button>
@@ -1026,7 +1156,4 @@ class AnnotationLayer extends PureComponent {
}
}
AnnotationLayer.propTypes = propTypes;
AnnotationLayer.defaultProps = defaultProps;
export default withTheme(AnnotationLayer);

View File

@@ -18,12 +18,25 @@
*/
import { t } from '@superset-ui/core';
function extractTypes(metadata) {
return Object.keys(metadata).reduce((prev, key) => {
const result = prev;
result[key] = key;
return result;
}, {});
interface Annotation {
sourceType?: string;
timeColumn?: string;
intervalEndColumn?: string;
titleColumn?: string;
descriptionColumns?: string[];
}
function extractTypes<T extends Record<string, { value: string }>>(
metadata: T,
): Record<keyof T, string> {
return Object.keys(metadata).reduce(
(prev, key) => {
const result = prev;
result[key as keyof T] = key;
return result;
},
{} as Record<keyof T, string>,
);
}
export const ANNOTATION_TYPES_METADATA = {
@@ -62,7 +75,9 @@ export const ANNOTATION_SOURCE_TYPES = extractTypes(
ANNOTATION_SOURCE_TYPES_METADATA,
);
export function requiresQuery(annotationSourceType) {
export function requiresQuery(
annotationSourceType: string | undefined,
): boolean {
return !!annotationSourceType;
}
@@ -71,11 +86,16 @@ const NATIVE_COLUMN_NAMES = {
intervalEndColumn: 'end_dttm',
titleColumn: 'short_descr',
descriptionColumns: ['long_descr'],
};
} as const;
export function applyNativeColumns(annotation) {
export function applyNativeColumns(annotation: Annotation): Annotation {
if (annotation.sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
return { ...annotation, ...NATIVE_COLUMN_NAMES };
return {
...annotation,
...NATIVE_COLUMN_NAMES,
// Spread to convert readonly array to mutable
descriptionColumns: [...NATIVE_COLUMN_NAMES.descriptionColumns],
};
}
return annotation;
}