diff --git a/superset-frontend/src/dashboard/types.ts b/superset-frontend/src/dashboard/types.ts index ae602aa7fd..51b2b320e7 100644 --- a/superset-frontend/src/dashboard/types.ts +++ b/superset-frontend/src/dashboard/types.ts @@ -57,12 +57,15 @@ export type DashboardLayoutState = { present: DashboardLayout }; export type DashboardState = { preselectNativeFilters?: JsonObject; editMode: boolean; + isPublished: boolean; directPathToChild: string[]; activeTabs: ActiveTabs; fullSizeChartId: number | null; isRefreshing: boolean; + hasUnsavedChanges: boolean; }; export type DashboardInfo = { + id: number; common: { flash_messages: string[]; conf: JsonObject; diff --git a/superset-frontend/src/explore/components/Control.tsx b/superset-frontend/src/explore/components/Control.tsx index 04c1267974..ecb050d773 100644 --- a/superset-frontend/src/explore/components/Control.tsx +++ b/superset-frontend/src/explore/components/Control.tsx @@ -18,6 +18,7 @@ */ import React, { ReactNode, useCallback, useState } from 'react'; import { ControlType } from '@superset-ui/chart-controls'; +import { ControlComponentProps as BaseControlComponentProps } from '@superset-ui/chart-controls/lib/shared-controls/components/types'; import { JsonValue, QueryFormData } from '@superset-ui/core'; import ErrorBoundary from 'src/components/ErrorBoundary'; import { ExploreActions } from 'src/explore/actions/exploreActions'; @@ -43,6 +44,13 @@ export type ControlProps = { renderTrigger?: boolean; }; +/** + * + */ +export type ControlComponentProps< + ValueType extends JsonValue = JsonValue +> = Omit & BaseControlComponentProps; + export default function Control(props: ControlProps) { const { actions: { setControlValue }, diff --git a/superset-frontend/src/explore/components/ControlHeader.jsx b/superset-frontend/src/explore/components/ControlHeader.jsx index 9a45917f6c..b78733135d 100644 --- a/superset-frontend/src/explore/components/ControlHeader.jsx +++ b/superset-frontend/src/explore/components/ControlHeader.jsx @@ -26,8 +26,8 @@ import Icons from 'src/components/Icons'; const propTypes = { name: PropTypes.string, - label: PropTypes.string, - description: PropTypes.string, + label: PropTypes.node, + description: PropTypes.node, validationErrors: PropTypes.array, renderTrigger: PropTypes.bool, rightNode: PropTypes.node, diff --git a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx index 3448433476..1af0186c39 100644 --- a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx +++ b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx @@ -45,7 +45,7 @@ const datasource = { datasource_name: 'table1', description: 'desc', }; -const props = { +const props: DatasourcePanelProps = { datasource, controls: { datasource: { @@ -57,12 +57,7 @@ const props = { }, }, actions: { - setControlValue: () => ({ - type: 'type', - controlName: 'control', - value: 'val', - validationErrors: [], - }), + setControlValue: jest.fn(), }, }; diff --git a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx index 209458335c..f31ec11020 100644 --- a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx +++ b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx @@ -16,13 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { ControlConfig, DatasourceMeta } from '@superset-ui/chart-controls'; import { debounce } from 'lodash'; import { matchSorter, rankings } from 'match-sorter'; @@ -193,60 +187,61 @@ export default function DataSourcePanel({ const DEFAULT_MAX_COLUMNS_LENGTH = 50; const DEFAULT_MAX_METRICS_LENGTH = 50; - const search = useCallback( - debounce((value: string) => { - if (value === '') { - setList({ columns, metrics }); - return; - } - setList({ - columns: matchSorter(columns, value, { - keys: [ - { - key: 'verbose_name', - threshold: rankings.CONTAINS, - }, - { - key: 'column_name', - threshold: rankings.CONTAINS, - }, - { - key: item => - [item.description, item.expression].map( - x => x?.replace(/[_\n\s]+/g, ' ') || '', - ), - threshold: rankings.CONTAINS, - maxRanking: rankings.CONTAINS, - }, - ], - keepDiacritics: true, - }), - metrics: matchSorter(metrics, value, { - keys: [ - { - key: 'verbose_name', - threshold: rankings.CONTAINS, - }, - { - key: 'metric_name', - threshold: rankings.CONTAINS, - }, - { - key: item => - [item.description, item.expression].map( - x => x?.replace(/[_\n\s]+/g, ' ') || '', - ), - threshold: rankings.CONTAINS, - maxRanking: rankings.CONTAINS, - }, - ], - keepDiacritics: true, - baseSort: (a, b) => - Number(b.item.is_certified) - Number(a.item.is_certified) || - String(a.rankedValue).localeCompare(b.rankedValue), - }), - }); - }, FAST_DEBOUNCE), + const search = useMemo( + () => + debounce((value: string) => { + if (value === '') { + setList({ columns, metrics }); + return; + } + setList({ + columns: matchSorter(columns, value, { + keys: [ + { + key: 'verbose_name', + threshold: rankings.CONTAINS, + }, + { + key: 'column_name', + threshold: rankings.CONTAINS, + }, + { + key: item => + [item.description, item.expression].map( + x => x?.replace(/[_\n\s]+/g, ' ') || '', + ), + threshold: rankings.CONTAINS, + maxRanking: rankings.CONTAINS, + }, + ], + keepDiacritics: true, + }), + metrics: matchSorter(metrics, value, { + keys: [ + { + key: 'verbose_name', + threshold: rankings.CONTAINS, + }, + { + key: 'metric_name', + threshold: rankings.CONTAINS, + }, + { + key: item => + [item.description, item.expression].map( + x => x?.replace(/[_\n\s]+/g, ' ') || '', + ), + threshold: rankings.CONTAINS, + maxRanking: rankings.CONTAINS, + }, + ], + keepDiacritics: true, + baseSort: (a, b) => + Number(b.item.is_certified) - Number(a.item.is_certified) || + String(a.rankedValue).localeCompare(b.rankedValue), + }), + }); + }, FAST_DEBOUNCE), [columns, metrics], ); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx index 93d00a7059..37240d57ca 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx @@ -18,13 +18,17 @@ */ import React from 'react'; import { render, screen } from 'spec/helpers/testing-library'; -import { LabelProps } from 'src/explore/components/controls/DndColumnSelectControl/types'; -import { DndColumnSelect } from 'src/explore/components/controls/DndColumnSelectControl/DndColumnSelect'; +import { + DndColumnSelect, + DndColumnSelectProps, +} from 'src/explore/components/controls/DndColumnSelectControl/DndColumnSelect'; -const defaultProps: LabelProps = { +const defaultProps: DndColumnSelectProps = { + type: 'DndColumnSelect', name: 'Filter', onChange: jest.fn(), options: { string: { column_name: 'Column A' } }, + actions: { setControlValue: jest.fn() }, }; test('renders with default props', () => { diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx index 69cc6442e2..d3e447ca99 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx @@ -20,7 +20,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import { FeatureFlag, isFeatureEnabled, tn } from '@superset-ui/core'; import { ColumnMeta } from '@superset-ui/chart-controls'; import { isEmpty } from 'lodash'; -import { LabelProps } from 'src/explore/components/controls/DndColumnSelectControl/types'; import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel'; import OptionWrapper from 'src/explore/components/controls/DndColumnSelectControl/OptionWrapper'; import { OptionSelector } from 'src/explore/components/controls/DndColumnSelectControl/utils'; @@ -28,8 +27,13 @@ import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/t import { DndItemType } from 'src/explore/components/DndItemType'; import { useComponentDidUpdate } from 'src/common/hooks/useComponentDidUpdate'; import ColumnSelectPopoverTrigger from './ColumnSelectPopoverTrigger'; +import { DndControlProps } from './types'; -export const DndColumnSelect = (props: LabelProps) => { +export type DndColumnSelectProps = DndControlProps & { + options: Record; +}; + +export function DndColumnSelect(props: DndColumnSelectProps) { const { value, options, @@ -68,6 +72,7 @@ export const DndColumnSelect = (props: LabelProps) => { ) { onChange(optionSelectorValues); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(value), JSON.stringify(optionSelector.getValues())]); // useComponentDidUpdate to avoid running this for the first render, to avoid @@ -203,7 +208,7 @@ export const DndColumnSelect = (props: LabelProps) => { return (
- + {
); -}; +} diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx index bddb80a9aa..40962c7c60 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx @@ -23,17 +23,24 @@ import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetr import AdhocFilter, { EXPRESSION_TYPES, } from 'src/explore/components/controls/FilterControl/AdhocFilter'; -import { DndFilterSelect } from 'src/explore/components/controls/DndColumnSelectControl/DndFilterSelect'; +import { + DndFilterSelect, + DndFilterSelectProps, +} from 'src/explore/components/controls/DndColumnSelectControl/DndFilterSelect'; +import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants'; +import { DEFAULT_FORM_DATA } from '@superset-ui/plugin-chart-echarts/lib/Timeseries/types'; -const defaultProps = { +const defaultProps: DndFilterSelectProps = { + type: 'DndFilterSelect', name: 'Filter', value: [], columns: [], - datasource: {}, - formData: {}, + datasource: PLACEHOLDER_DATASOURCE, + formData: null, savedMetrics: [], + selectedMetrics: [], onChange: jest.fn(), - options: { string: { column_name: 'Column' } }, + actions: { setControlValue: jest.fn() }, }; test('renders with default props', () => { @@ -53,9 +60,15 @@ test('renders with value', () => { }); test('renders options with saved metric', () => { - render(, { - useDnd: true, - }); + render( + , + { + useDnd: true, + }, + ); expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument(); }); @@ -84,8 +97,14 @@ test('renders options with adhoc metric', () => { expression: 'AVG(birth_names.num)', metric_name: 'avg__num', }); - render(, { - useDnd: true, - }); + render( + , + { + useDnd: true, + }, + ); expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument(); }); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx index a9e327bfd6..f9a142b665 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx @@ -22,6 +22,8 @@ import { isFeatureEnabled, logging, Metric, + QueryFormData, + QueryFormMetric, SupersetClient, t, } from '@superset-ui/core'; @@ -30,11 +32,8 @@ import { OPERATOR_ENUM_TO_OPERATOR_TYPE, Operators, } from 'src/explore/constants'; -import { OptionSortType } from 'src/explore/types'; -import { - DndFilterSelectProps, - OptionValueType, -} from 'src/explore/components/controls/DndColumnSelectControl/types'; +import { Datasource, OptionSortType } from 'src/explore/types'; +import { OptionValueType } from 'src/explore/components/controls/DndColumnSelectControl/types'; import AdhocFilterPopoverTrigger from 'src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger'; import OptionWrapper from 'src/explore/components/controls/DndColumnSelectControl/OptionWrapper'; import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel'; @@ -48,6 +47,7 @@ import { DndItemValue, } from 'src/explore/components/DatasourcePanel/types'; import { DndItemType } from 'src/explore/components/DndItemType'; +import { ControlComponentProps } from 'src/explore/components/Control'; const DND_ACCEPTED_TYPES = [ DndItemType.Column, @@ -59,8 +59,16 @@ const DND_ACCEPTED_TYPES = [ const isDictionaryForAdhocFilter = (value: OptionValueType) => !(value instanceof AdhocFilter) && value?.expressionType; +export interface DndFilterSelectProps + extends ControlComponentProps { + columns: ColumnMeta[]; + savedMetrics: Metric[]; + selectedMetrics: QueryFormMetric[]; + datasource: Datasource; +} + export const DndFilterSelect = (props: DndFilterSelectProps) => { - const { datasource, onChange } = props; + const { datasource, onChange = () => {}, name: controlName } = props; const propsValues = Array.from(props.value ?? []); const [values, setValues] = useState( @@ -74,7 +82,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => { const optionsForSelect = ( columns: ColumnMeta[], - formData: Record, + formData: QueryFormData | null | undefined, ) => { const options: OptionSortType[] = [ ...columns, @@ -369,7 +377,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => { setDroppedItem(item.value); togglePopover(true); }, - [togglePopover], + [controlName, togglePopover], ); const ghostButtonText = isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) @@ -378,7 +386,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => { return ( <> - + { const columnNames = new Set( [...(columns || []), ...(savedMetrics || [])] @@ -104,6 +108,12 @@ const columnsContainAllMetrics = ( ); }; +export type DndMetricSelectProps = DndControlProps & { + savedMetrics: savedMetricType[]; + columns: ColumnMeta[]; + datasourceType?: DatasourceType; +}; + export const DndMetricSelect = (props: any) => { const { onChange, multi, columns, savedMetrics } = props; @@ -130,7 +140,7 @@ export const DndMetricSelect = (props: any) => { [multi, onChange], ); - const [value, setValue] = useState<(AdhocMetric | Metric | string)[]>( + const [value, setValue] = useState( coerceAdhocMetrics(props.value), ); const [droppedItem, setDroppedItem] = useState( @@ -176,7 +186,9 @@ export const DndMetricSelect = (props: any) => { const onNewMetric = useCallback( (newMetric: Metric) => { - const newValue = props.multi ? [...value, newMetric] : [newMetric]; + const newValue = props.multi + ? [...value, newMetric.metric_name] + : [newMetric.metric_name]; setValue(newValue); handleChange(newValue); }, @@ -191,7 +203,7 @@ export const DndMetricSelect = (props: any) => { const newValue = value.map(value => { if ( // compare saved metrics - value === (oldMetric as Metric).metric_name || + ('metric_name' in oldMetric && value === oldMetric.metric_name) || // compare adhoc metrics typeof (value as AdhocMetric).optionName !== 'undefined' ? (value as AdhocMetric).optionName === @@ -254,7 +266,7 @@ export const DndMetricSelect = (props: any) => { ); const valueRenderer = useCallback( - (option: Metric | AdhocMetric | string, index: number) => ( + (option: ValueType, index: number) => ( { return (
- + false, valuesRenderer: () => , - onChange: jest.fn(), - options: { string: { column_name: 'Column' } }, }; test('renders with default props', async () => { diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx index d3bf95d090..019d9d3b99 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { ReactNode } from 'react'; import { useDrop } from 'react-dnd'; import { t, useTheme } from '@superset-ui/core'; import ControlHeader from 'src/explore/components/ControlHeader'; @@ -25,18 +25,35 @@ import { DndLabelsContainer, HeaderContainer, } from 'src/explore/components/controls/OptionControls'; -import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/types'; +import { + DatasourcePanelDndItem, + DndItemValue, +} from 'src/explore/components/DatasourcePanel/types'; import Icons from 'src/components/Icons'; -import { DndColumnSelectProps } from './types'; +import { DndItemType } from '../../DndItemType'; -export default function DndSelectLabel({ +export type DndSelectLabelProps = { + name: string; + accept: DndItemType | DndItemType[]; + ghostButtonText?: string; + onDrop: (item: DatasourcePanelDndItem) => void; + canDrop: (item: DatasourcePanelDndItem) => boolean; + canDropValue?: (value: DndItemValue) => boolean; + onDropValue?: (value: DndItemValue) => void; + valuesRenderer: () => ReactNode; + displayGhostButton?: boolean; + onClickGhostButton?: () => void; +}; + +export default function DndSelectLabel({ displayGhostButton = true, + accept, ...props -}: DndColumnSelectProps) { +}: DndSelectLabelProps) { const theme = useTheme(); const [{ isOver, canDrop }, datasourcePanelDrop] = useDrop({ - accept: props.accept, + accept, drop: (item: DatasourcePanelDndItem) => { props.onDrop(item); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts index b77e9d2633..9156d48e82 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts @@ -17,13 +17,9 @@ * under the License. */ import { ReactNode } from 'react'; -import { Metric } from '@superset-ui/core'; +import { JsonValue } from '@superset-ui/core'; +import { ControlComponentProps } from 'src/explore/components/Control'; import { ColumnMeta } from '@superset-ui/chart-controls'; -import { - DatasourcePanelDndItem, - DndItemValue, -} from '../../DatasourcePanel/types'; -import { DndItemType } from '../../DndItemType'; export interface OptionProps { children?: ReactNode; @@ -42,40 +38,16 @@ export interface OptionItemInterface { dragIndex: number; } -export interface LabelProps { - name: string; - value?: T; - onChange: (value?: T) => void; - options: { string: ColumnMeta }; +/** + * Shared control props for all DnD control. + */ +export type DndControlProps< + ValueType extends JsonValue +> = ControlComponentProps & { multi?: boolean; canDelete?: boolean; ghostButtonText?: string; - label?: string; -} - -export interface DndColumnSelectProps< - T = string[] | string, - O = string[] | string -> extends LabelProps { - onDrop: (item: DatasourcePanelDndItem) => void; - canDrop: (item: DatasourcePanelDndItem) => boolean; - canDropValue?: (value: DndItemValue) => boolean; - onDropValue?: (value: DndItemValue) => void; - valuesRenderer: () => ReactNode; - accept: DndItemType | DndItemType[]; - ghostButtonText?: string; - displayGhostButton?: boolean; - onClickGhostButton?: () => void; -} + onChange: (value: ValueType | ValueType[] | null | undefined) => void; +}; export type OptionValueType = Record; -export interface DndFilterSelectProps { - name: string; - value: OptionValueType[]; - columns: ColumnMeta[]; - datasource: Record; - formData: Record; - savedMetrics: Metric[]; - onChange: (filters: OptionValueType[]) => void; - options: { string: ColumnMeta }; -} diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/utils/optionSelector.ts b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/utils/optionSelector.ts index 532aa33fc6..6000d57039 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/utils/optionSelector.ts +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/utils/optionSelector.ts @@ -22,25 +22,25 @@ import { ensureIsArray } from '@superset-ui/core'; export class OptionSelector { values: ColumnMeta[]; - options: { string: ColumnMeta }; + options: Record; multi: boolean; constructor( - options: { string: ColumnMeta }, + options: Record, multi: boolean, - initialValues?: string[] | string, + initialValues?: string[] | string | null, ) { this.options = options; this.multi = multi; this.values = ensureIsArray(initialValues) .map(value => { - if (value in options) { + if (value && value in options) { return options[value]; } return null; }) - .filter(Boolean); + .filter(Boolean) as ColumnMeta[]; } add(value: string) { diff --git a/superset-frontend/src/explore/types.ts b/superset-frontend/src/explore/types.ts index 32ad4ebe70..fe6436ab86 100644 --- a/superset-frontend/src/explore/types.ts +++ b/superset-frontend/src/explore/types.ts @@ -22,7 +22,8 @@ import { AnnotationData, AdhocMetric, } from '@superset-ui/core'; -import { ColumnMeta } from '@superset-ui/chart-controls'; +import { ColumnMeta, DatasourceMeta } from '@superset-ui/chart-controls'; +import { DatabaseObject } from 'src/views/CRUD/types'; export { Slice, Chart } from 'src/types/Chart'; @@ -54,3 +55,10 @@ export interface ChartState { export type OptionSortType = Partial< ColumnMeta & AdhocMetric & { saved_metric_name: string } >; + +export type Datasource = DatasourceMeta & { + database?: DatabaseObject; + datasource?: string; + schema?: string; + is_sqllab_view?: boolean; +}; diff --git a/superset-frontend/src/types/Database.ts b/superset-frontend/src/types/Database.ts new file mode 100644 index 0000000000..02c9347215 --- /dev/null +++ b/superset-frontend/src/types/Database.ts @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default interface Database { + id: number; + allow_run_async: boolean; + database_name: string; + encrypted_extra: string; + extra: string; + impersonate_user: boolean; + server_cert: string; + sqlalchemy_uri: string; +} diff --git a/superset-frontend/src/views/CRUD/types.ts b/superset-frontend/src/views/CRUD/types.ts index d08ca46a69..35cfc55161 100644 --- a/superset-frontend/src/views/CRUD/types.ts +++ b/superset-frontend/src/views/CRUD/types.ts @@ -17,6 +17,7 @@ * under the License. */ import { User } from 'src/types/bootstrapTypes'; +import Database from 'src/types/Database'; import Owner from 'src/types/Owner'; export type FavoriteStatus = { @@ -137,12 +138,5 @@ export type ImportResourceName = | 'dataset' | 'saved_query'; -export type DatabaseObject = { - allow_run_async?: boolean; - database_name?: string; - encrypted_extra?: string; - extra?: string; - impersonate_user?: boolean; - server_cert?: string; - sqlalchemy_uri: string; -}; +export type DatabaseObject = Partial & + Pick;