From b940e2f2d2cd46b9e9ae955402472f4334e563f5 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Wed, 22 Feb 2023 16:31:49 -0800 Subject: [PATCH 1/8] feat: database extension registry --- .../src/ui-overrides/ExtensionsRegistry.ts | 25 +++- .../databases/DatabaseModal/ExtraOptions.tsx | 52 ++++++- .../databases/DatabaseModal/index.tsx | 130 ++++++++++++++++++ 3 files changed, 204 insertions(+), 3 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts index cd67d37474728..81201e4968bde 100644 --- a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts +++ b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts @@ -29,7 +29,7 @@ type ReturningDisplayable

= (props: P) => string | React.ReactElement; /** * This type defines all available extensions of Superset's default UI. - * Namespace the keys here to follow the form of 'some_domain.functonality.item'. + * Namespace the keys here to follow the form of 'some_domain.functionality.item'. * Take care to name your keys well, as the name describes what this extension point's role is in Superset. * * When defining a new option here, take care to keep any parameters to functions (or components) minimal. @@ -69,6 +69,28 @@ type DatabaseDeleteRelatedExtensionProps = { databaseId: number; }; +/** + * Interface for extensions to database connections + */ +interface DatabaseConnectionExtension { + /** + * Display title text for the extension show when creating a database connection + */ + title: string; + /** + * url or dataURI (recommended) of a logo to use in place of a title. title is fallback display if no logo is provided + */ + logo?: string; + /** + * Descriptive text displayed under the logo or title to provide user with more context about the configuration section + */ + description: string; + /** + * React component to render for display in the database connection configuration + */ + component: React.ComponentType; +} + export type Extensions = Partial<{ 'alertsreports.header.icon': React.ComponentType; 'embedded.documentation.configuration_details': React.ComponentType; @@ -83,6 +105,7 @@ export type Extensions = Partial<{ 'welcome.banner': React.ComponentType; 'welcome.main.replacement': React.ComponentType; 'ssh_tunnel.form.switch': React.ComponentType; + 'databaseconnection.extensions': DatabaseConnectionExtension[]; 'database.delete.related': React.ComponentType; }>; diff --git a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx index 30436abbc4fac..c978f0754f353 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import React, { ChangeEvent, EventHandler } from 'react'; +import React, { ChangeEvent, EventHandler, FunctionComponent } from 'react'; import cx from 'classnames'; -import { t, SupersetTheme } from '@superset-ui/core'; +import { t, SupersetTheme, getExtensionsRegistry } from '@superset-ui/core'; import InfoTooltip from 'src/components/InfoTooltip'; import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox'; import Collapse from 'src/components/Collapse'; @@ -31,6 +31,13 @@ import { } from './styles'; import { DatabaseObject, ExtraJson } from '../types'; +const extensionsRegistry = getExtensionsRegistry(); +export interface IExtensionProps { + db?: DatabaseObject | null; + registerPostProcess: Function; + unregisterPostProcess: Function; +} + const ExtraOptions = ({ db, onInputChange, @@ -38,6 +45,8 @@ const ExtraOptions = ({ onEditorChange, onExtraInputChange, onExtraEditorChange, + registerPostProcess, + unregisterPostProcess, }: { db: DatabaseObject | null; onInputChange: EventHandler>; @@ -45,6 +54,8 @@ const ExtraOptions = ({ onEditorChange: Function; onExtraInputChange: EventHandler>; onExtraEditorChange: Function; + registerPostProcess: Function; + unregisterPostProcess: Function; }) => { const expandableModalIsOpen = !!db?.expose_in_sqllab; const createAsOpen = !!(db?.allow_ctas || db?.allow_cvas); @@ -61,6 +72,16 @@ const ExtraOptions = ({ return value; }); + const extensionProps: IExtensionProps = { + db, + registerPostProcess, + unregisterPostProcess, + }; + + const dbConfigExtensions = extensionsRegistry.get( + 'databaseconnection.extensions', + ); + return ( )} + {dbConfigExtensions?.map?.(extension => { + const Extension = + extension.component as FunctionComponent; + return ( + + {extension.logo ? ( + dbt Cloud + ) : ( +

{extension?.title}

+ )} +

{extension?.description}

+ + } + key={extension?.title} + > + + + + + ); + })} diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.tsx index 4a80612369557..35a085524e07e 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx @@ -518,6 +518,42 @@ export function dbReducer( const DEFAULT_TAB_KEY = '1'; +/** + * Enum to differentiate create and update post processing + */ +export enum EDbPostProcessType { + /** + * Indicates a post process for a newly created DB connection + */ + CREATE = 'create', + /** + * Indicates a post process for existing DB connection that has been updated + */ + UPDATE = 'update', +} +/** + * Interface representing a DatabaseModal save / update post process + */ +interface IDbPostProcess { + /** + * Unique id of the post process used as a key for registration / lookup. Using a static key for each post process + * is important so that component render cycles do not re-register the same post processes + */ + id: string; + /** + * Human readable name for the post processes to assist with logging / debugging + */ + name: string; + /** + * The function that will get invoked after the DatabaseModal has saved / updated the database connection + */ + callback: Function; + /** + * Type of post processing to perform to distinguish as create vs update existing DB Connection + */ + type: EDbPostProcessType; +} + const DatabaseModal: FunctionComponent = ({ addDangerToast, addSuccessToast, @@ -584,6 +620,93 @@ const DatabaseModal: FunctionComponent = ({ const [useSSHTunneling, setUseSSHTunneling] = useState(false); + const createPostProcessingCallbacks = useRef({}); + const updatePostProcessingCallbacks = useRef({}); + + /** + * Invokes all registered callback functions from config items in ExtraOptions AFTER the + * save occurs for the DatabaseConnection create / edit + * @param db The database connection data + * @param type Differentiates between a create and edit to filter post processing to the correct callbacks + */ + const runPostProcesses = ( + db: Partial, + type: EDbPostProcessType, + ) => { + let processCallbacks: { [x: string]: IDbPostProcess } = {}; + // Differentiate between post processing registered for create vs update + switch (type) { + case EDbPostProcessType.CREATE: + processCallbacks = createPostProcessingCallbacks.current; + break; + case EDbPostProcessType.UPDATE: + processCallbacks = updatePostProcessingCallbacks.current; + break; + default: + break; + } + + Object.keys(processCallbacks).forEach(key => { + const postProcess = processCallbacks[key]; + if (typeof postProcess?.callback === 'function') { + try { + postProcess?.callback?.(db); + } catch (error) { + // eslint-disable-next-line no-console + console.error( + `Database connection post process callback function failed for ${postProcess?.name}`, + ); + // eslint-disable-next-line no-console + console.error(error); + } + } + }); + }; + + /** + * Allows components in the ExtraOptions panel to register post processing callback functions + * @param process IDbPostProcess object + */ + const registerPostProcess = (process: IDbPostProcess) => { + switch (process?.type) { + case EDbPostProcessType.CREATE: + createPostProcessingCallbacks.current[process.id] = process; + break; + case EDbPostProcessType.UPDATE: + updatePostProcessingCallbacks.current[process.id] = process; + break; + default: + // eslint-disable-next-line no-console + console.error( + `The database connection post process object has an invalid type and was not registered`, + process, + ); + break; + } + }; + + /** + * Allows components in the ExtraOptions panel to unregister post processing callback functions + * @param process IDbPostProcess object + */ + const unregisterPostProcess = (process: IDbPostProcess) => { + switch (process?.type) { + case EDbPostProcessType.CREATE: + delete createPostProcessingCallbacks.current[process.id]; + break; + case EDbPostProcessType.UPDATE: + delete updatePostProcessingCallbacks.current[process.id]; + break; + default: + // eslint-disable-next-line no-console + console.error( + `The database connection post process object has an invalid type and was not unregistered`, + process, + ); + break; + } + }; + const conf = useCommonConf(); const dbImages = getDatabaseImages(); const connectionAlert = getConnectionAlert(); @@ -715,6 +838,7 @@ const DatabaseModal: FunctionComponent = ({ }; const onSave = async () => { + runPostProcesses({ uuid: db?.uuid }, EDbPostProcessType.CREATE); // Clone DB object const dbToUpdate = { ...(db || {}) }; @@ -803,6 +927,7 @@ const DatabaseModal: FunctionComponent = ({ ); if (result) { if (onDatabaseAdd) onDatabaseAdd(); + runPostProcesses(db, EDbPostProcessType.UPDATE); if (!editNewDb) { onClose(); addSuccessToast(t('Database settings updated')); @@ -817,6 +942,7 @@ const DatabaseModal: FunctionComponent = ({ if (dbId) { setHasConnectedDb(true); if (onDatabaseAdd) onDatabaseAdd(); + runPostProcesses(db, EDbPostProcessType.CREATE); if (useTabLayout) { // tab layout only has one step // so it should close immediately on save @@ -1596,6 +1722,8 @@ const DatabaseModal: FunctionComponent = ({ if (!editNewDb) { return ( onChange(ActionType.inputChange, { @@ -1807,6 +1935,8 @@ const DatabaseModal: FunctionComponent = ({ {t('Advanced')}} key="2"> onChange(ActionType.inputChange, { From a8b59fafa691ff7a5f7a7e0311222691e5ec887e Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Tue, 21 Mar 2023 17:11:00 -0700 Subject: [PATCH 2/8] Pass component as icon --- .../src/ui-overrides/ExtensionsRegistry.ts | 2 +- .../databases/DatabaseModal/ExtraOptions.tsx | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts index 81201e4968bde..54f40e4805d9c 100644 --- a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts +++ b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts @@ -80,7 +80,7 @@ interface DatabaseConnectionExtension { /** * url or dataURI (recommended) of a logo to use in place of a title. title is fallback display if no logo is provided */ - logo?: string; + logo?: React.ComponentType; /** * Descriptive text displayed under the logo or title to provide user with more context about the configuration section */ diff --git a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx index c978f0754f353..1761371e59e2c 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx @@ -461,19 +461,19 @@ const ExtraOptions = ({ {dbConfigExtensions?.map?.(extension => { const Extension = extension.component as FunctionComponent; + let header; + if (extension.logo) { + const ExtensionLogo = extension.logo as FunctionComponent; + header = ; + } else { + header =

{extension?.title}

; + } + return ( - {extension.logo ? ( - dbt Cloud - ) : ( -

{extension?.title}

- )} + {header}

{extension?.description}

} From 42004cb9315152ab982b30fc68442925e80655e6 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Tue, 28 Mar 2023 16:58:59 -0700 Subject: [PATCH 3/8] Pass component as description --- .../superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts | 6 +++++- .../src/features/databases/DatabaseModal/ExtraOptions.tsx | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts index 54f40e4805d9c..32f36a04e8ee3 100644 --- a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts +++ b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts @@ -84,11 +84,15 @@ interface DatabaseConnectionExtension { /** * Descriptive text displayed under the logo or title to provide user with more context about the configuration section */ - description: string; + description: React.ComponentType; /** * React component to render for display in the database connection configuration */ component: React.ComponentType; + /** + * Is the database extension enabled? + */ + enabled: () => boolean; } export type Extensions = Partial<{ diff --git a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx index 1761371e59e2c..c3f6f027c3e6d 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx @@ -468,16 +468,20 @@ const ExtraOptions = ({ } else { header =

{extension?.title}

; } + const ExtensionDescription = extension.description as FunctionComponent; return ( {header} -

{extension?.description}

+

+ +

} key={extension?.title} + collapsible={extension.enabled() ? 'header' : 'disabled'} > From c2e24722d6215d2beaaccf6e836893e1e40b719f Mon Sep 17 00:00:00 2001 From: Elizabeth Thompson Date: Wed, 29 Mar 2023 15:17:05 -0700 Subject: [PATCH 4/8] limit db extra extension to one element --- .../src/ui-overrides/ExtensionsRegistry.ts | 15 +- .../components/controls/SelectControl.jsx | 15 +- .../databases/DatabaseModal/ExtraOptions.tsx | 90 +++++------ .../databases/DatabaseModal/index.tsx | 153 +++--------------- .../databases/DatabaseModal/styles.ts | 1 + 5 files changed, 87 insertions(+), 187 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts index 32f36a04e8ee3..d47bf802cad4d 100644 --- a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts +++ b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts @@ -72,7 +72,7 @@ type DatabaseDeleteRelatedExtensionProps = { /** * Interface for extensions to database connections */ -interface DatabaseConnectionExtension { +export interface DatabaseConnectionExtension { /** * Display title text for the extension show when creating a database connection */ @@ -93,6 +93,17 @@ interface DatabaseConnectionExtension { * Is the database extension enabled? */ enabled: () => boolean; + + /** + * Callback for onsave + */ + // TODO: we need to move the db types to superset-ui/core in order to import them correctly + onSave: (componentState: any, db: any) => void; + + /** + * Used for parent to store data + */ + onEdit?: (componentState: any) => void; } export type Extensions = Partial<{ @@ -109,7 +120,7 @@ export type Extensions = Partial<{ 'welcome.banner': React.ComponentType; 'welcome.main.replacement': React.ComponentType; 'ssh_tunnel.form.switch': React.ComponentType; - 'databaseconnection.extensions': DatabaseConnectionExtension[]; + 'databaseconnection.extraOption': DatabaseConnectionExtension; 'database.delete.related': React.ComponentType; }>; diff --git a/superset-frontend/src/explore/components/controls/SelectControl.jsx b/superset-frontend/src/explore/components/controls/SelectControl.jsx index d23e66927773c..166382a15ca78 100644 --- a/superset-frontend/src/explore/components/controls/SelectControl.jsx +++ b/superset-frontend/src/explore/components/controls/SelectControl.jsx @@ -31,11 +31,14 @@ const propTypes = { disabled: PropTypes.bool, freeForm: PropTypes.bool, isLoading: PropTypes.bool, + mode: PropTypes.string, multi: PropTypes.bool, isMulti: PropTypes.bool, name: PropTypes.string.isRequired, onChange: PropTypes.func, onFocus: PropTypes.func, + onSelect: PropTypes.func, + onDeselect: PropTypes.func, value: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, @@ -174,12 +177,14 @@ export default class SelectControl extends React.PureComponent { label, multi, name, - placeholder, + notFoundContent, onFocus, + onSelect, + onDeselect, + placeholder, showHeader, - value, tokenSeparators, - notFoundContent, + value, // ControlHeader props description, renderTrigger, @@ -236,10 +241,12 @@ export default class SelectControl extends React.PureComponent { : true, header: showHeader && , loading: isLoading, - mode: isMulti || multi ? 'multiple' : 'single', + mode: this.props.mode || (isMulti || multi ? 'multiple' : 'single'), name: `select-${name}`, onChange: this.onChange, onFocus, + onSelect, + onDeselect, options: this.state.options, placeholder, sortComparator: this.props.sortComparator, diff --git a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx index c3f6f027c3e6d..dad8b337f958d 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx @@ -16,9 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import React, { ChangeEvent, EventHandler, FunctionComponent } from 'react'; +import React, { ChangeEvent, EventHandler } from 'react'; import cx from 'classnames'; -import { t, SupersetTheme, getExtensionsRegistry } from '@superset-ui/core'; +import { + t, + SupersetTheme, + DatabaseConnectionExtension, +} from '@superset-ui/core'; import InfoTooltip from 'src/components/InfoTooltip'; import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox'; import Collapse from 'src/components/Collapse'; @@ -31,13 +35,6 @@ import { } from './styles'; import { DatabaseObject, ExtraJson } from '../types'; -const extensionsRegistry = getExtensionsRegistry(); -export interface IExtensionProps { - db?: DatabaseObject | null; - registerPostProcess: Function; - unregisterPostProcess: Function; -} - const ExtraOptions = ({ db, onInputChange, @@ -45,8 +42,7 @@ const ExtraOptions = ({ onEditorChange, onExtraInputChange, onExtraEditorChange, - registerPostProcess, - unregisterPostProcess, + extraExtension, }: { db: DatabaseObject | null; onInputChange: EventHandler>; @@ -54,8 +50,7 @@ const ExtraOptions = ({ onEditorChange: Function; onExtraInputChange: EventHandler>; onExtraEditorChange: Function; - registerPostProcess: Function; - unregisterPostProcess: Function; + extraExtension: DatabaseConnectionExtension | undefined; }) => { const expandableModalIsOpen = !!db?.expose_in_sqllab; const createAsOpen = !!(db?.allow_ctas || db?.allow_cvas); @@ -72,15 +67,9 @@ const ExtraOptions = ({ return value; }); - const extensionProps: IExtensionProps = { - db, - registerPostProcess, - unregisterPostProcess, - }; - - const dbConfigExtensions = extensionsRegistry.get( - 'databaseconnection.extensions', - ); + const ExtraExtensionComponent = extraExtension?.component; + const ExtraExtensionLogo = extraExtension?.logo; + const ExtensionDescription = extraExtension?.description; return ( )}
- {dbConfigExtensions?.map?.(extension => { - const Extension = - extension.component as FunctionComponent; - let header; - if (extension.logo) { - const ExtensionLogo = extension.logo as FunctionComponent; - header = ; - } else { - header =

{extension?.title}

; - } - const ExtensionDescription = extension.description as FunctionComponent; - - return ( - - {header} -

- -

- - } - key={extension?.title} - collapsible={extension.enabled() ? 'header' : 'disabled'} - > - - - -
- ); - })} + {extraExtension && ExtraExtensionComponent && ExtensionDescription && ( + + {ExtraExtensionLogo && } + ({ + fontSize: theme.typography.sizes.l, + fontWeight: theme.typography.weights.bold, + })} + > + {extraExtension?.title} + +

+ +

+ + } + key={extraExtension?.title} + collapsible={extraExtension.enabled?.() ? 'header' : 'disabled'} + > + + + +
+ )} diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.tsx index 35a085524e07e..143411852d030 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx @@ -518,42 +518,6 @@ export function dbReducer( const DEFAULT_TAB_KEY = '1'; -/** - * Enum to differentiate create and update post processing - */ -export enum EDbPostProcessType { - /** - * Indicates a post process for a newly created DB connection - */ - CREATE = 'create', - /** - * Indicates a post process for existing DB connection that has been updated - */ - UPDATE = 'update', -} -/** - * Interface representing a DatabaseModal save / update post process - */ -interface IDbPostProcess { - /** - * Unique id of the post process used as a key for registration / lookup. Using a static key for each post process - * is important so that component render cycles do not re-register the same post processes - */ - id: string; - /** - * Human readable name for the post processes to assist with logging / debugging - */ - name: string; - /** - * The function that will get invoked after the DatabaseModal has saved / updated the database connection - */ - callback: Function; - /** - * Type of post processing to perform to distinguish as create vs update existing DB Connection - */ - type: EDbPostProcessType; -} - const DatabaseModal: FunctionComponent = ({ addDangerToast, addSuccessToast, @@ -614,98 +578,30 @@ const DatabaseModal: FunctionComponent = ({ sshTunnelPrivateKeyPasswordFields, setSSHTunnelPrivateKeyPasswordFields, ] = useState([]); + const [extraExtensionComponentState, setExtraExtensionComponentState] = + useState({}); const SSHTunnelSwitchComponent = extensionsRegistry.get('ssh_tunnel.form.switch') ?? SSHTunnelSwitch; const [useSSHTunneling, setUseSSHTunneling] = useState(false); - const createPostProcessingCallbacks = useRef({}); - const updatePostProcessingCallbacks = useRef({}); - - /** - * Invokes all registered callback functions from config items in ExtraOptions AFTER the - * save occurs for the DatabaseConnection create / edit - * @param db The database connection data - * @param type Differentiates between a create and edit to filter post processing to the correct callbacks - */ - const runPostProcesses = ( - db: Partial, - type: EDbPostProcessType, - ) => { - let processCallbacks: { [x: string]: IDbPostProcess } = {}; - // Differentiate between post processing registered for create vs update - switch (type) { - case EDbPostProcessType.CREATE: - processCallbacks = createPostProcessingCallbacks.current; - break; - case EDbPostProcessType.UPDATE: - processCallbacks = updatePostProcessingCallbacks.current; - break; - default: - break; - } - - Object.keys(processCallbacks).forEach(key => { - const postProcess = processCallbacks[key]; - if (typeof postProcess?.callback === 'function') { - try { - postProcess?.callback?.(db); - } catch (error) { - // eslint-disable-next-line no-console - console.error( - `Database connection post process callback function failed for ${postProcess?.name}`, - ); - // eslint-disable-next-line no-console - console.error(error); - } - } - }); - }; - - /** - * Allows components in the ExtraOptions panel to register post processing callback functions - * @param process IDbPostProcess object - */ - const registerPostProcess = (process: IDbPostProcess) => { - switch (process?.type) { - case EDbPostProcessType.CREATE: - createPostProcessingCallbacks.current[process.id] = process; - break; - case EDbPostProcessType.UPDATE: - updatePostProcessingCallbacks.current[process.id] = process; - break; - default: - // eslint-disable-next-line no-console - console.error( - `The database connection post process object has an invalid type and was not registered`, - process, - ); - break; - } - }; + let dbConfigExtraExtension = extensionsRegistry.get( + 'databaseconnection.extraOption', + ); - /** - * Allows components in the ExtraOptions panel to unregister post processing callback functions - * @param process IDbPostProcess object - */ - const unregisterPostProcess = (process: IDbPostProcess) => { - switch (process?.type) { - case EDbPostProcessType.CREATE: - delete createPostProcessingCallbacks.current[process.id]; - break; - case EDbPostProcessType.UPDATE: - delete updatePostProcessingCallbacks.current[process.id]; - break; - default: - // eslint-disable-next-line no-console - console.error( - `The database connection post process object has an invalid type and was not unregistered`, - process, - ); - break; - } - }; + if (dbConfigExtraExtension) { + // add method for db modal to store data + dbConfigExtraExtension = { + ...dbConfigExtraExtension, + onEdit: componentState => { + setExtraExtensionComponentState({ + ...extraExtensionComponentState, + ...componentState, + }); + }, + }; + } const conf = useCommonConf(); const dbImages = getDatabaseImages(); @@ -838,7 +734,8 @@ const DatabaseModal: FunctionComponent = ({ }; const onSave = async () => { - runPostProcesses({ uuid: db?.uuid }, EDbPostProcessType.CREATE); + // TODO: Elizabeth - add some error handling or promise/callback here + dbConfigExtraExtension?.onSave(extraExtensionComponentState, db); // Clone DB object const dbToUpdate = { ...(db || {}) }; @@ -927,7 +824,8 @@ const DatabaseModal: FunctionComponent = ({ ); if (result) { if (onDatabaseAdd) onDatabaseAdd(); - runPostProcesses(db, EDbPostProcessType.UPDATE); + // TODO: Elizabeth- check to see if we need these extra calls. + dbConfigExtraExtension?.onSave(extraExtensionComponentState, db); if (!editNewDb) { onClose(); addSuccessToast(t('Database settings updated')); @@ -942,7 +840,8 @@ const DatabaseModal: FunctionComponent = ({ if (dbId) { setHasConnectedDb(true); if (onDatabaseAdd) onDatabaseAdd(); - runPostProcesses(db, EDbPostProcessType.CREATE); + // TODO: Elizabeth- check to see if we need these extra calls. + dbConfigExtraExtension?.onSave(extraExtensionComponentState, db); if (useTabLayout) { // tab layout only has one step // so it should close immediately on save @@ -1722,8 +1621,7 @@ const DatabaseModal: FunctionComponent = ({ if (!editNewDb) { return ( onChange(ActionType.inputChange, { @@ -1935,8 +1833,7 @@ const DatabaseModal: FunctionComponent = ({ {t('Advanced')}} key="2"> onChange(ActionType.inputChange, { diff --git a/superset-frontend/src/features/databases/DatabaseModal/styles.ts b/superset-frontend/src/features/databases/DatabaseModal/styles.ts index ed30e7885b92f..fdb6eb943f212 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/styles.ts +++ b/superset-frontend/src/features/databases/DatabaseModal/styles.ts @@ -573,6 +573,7 @@ export const StyledStickyHeader = styled.div` top: 0; z-index: ${({ theme }) => theme.zIndex.max}; background: ${({ theme }) => theme.colors.grayscale.light5}; + height: ${({ theme }) => theme.gridUnit * 16}px; `; export const StyledCatalogTable = styled.div` From 084a1c3c5c194592a10f6b8397b1e7e5b0875b70 Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Mon, 8 May 2023 15:28:35 -0700 Subject: [PATCH 5/8] add callbacks for onsave in the database modal --- .../src/ui-overrides/ExtensionsRegistry.ts | 2 +- .../databases/DatabaseModal/index.tsx | 41 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts index d47bf802cad4d..4a139b5da9ce0 100644 --- a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts +++ b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts @@ -98,7 +98,7 @@ export interface DatabaseConnectionExtension { * Callback for onsave */ // TODO: we need to move the db types to superset-ui/core in order to import them correctly - onSave: (componentState: any, db: any) => void; + onSave: (componentState: any, db: any) => any; /** * Used for parent to store data diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.tsx index 143411852d030..b4d6004a19f85 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx @@ -735,7 +735,19 @@ const DatabaseModal: FunctionComponent = ({ const onSave = async () => { // TODO: Elizabeth - add some error handling or promise/callback here - dbConfigExtraExtension?.onSave(extraExtensionComponentState, db); + let dbConfigExtraExtensionOnSaveError; + dbConfigExtraExtension + ?.onSave(extraExtensionComponentState, db) + .then(({ error }: { error: any }) => { + if (error) { + dbConfigExtraExtensionOnSaveError = error; + addDangerToast(error); + } + }); + if (dbConfigExtraExtensionOnSaveError) { + setLoading(false); + return; + } // Clone DB object const dbToUpdate = { ...(db || {}) }; @@ -825,7 +837,18 @@ const DatabaseModal: FunctionComponent = ({ if (result) { if (onDatabaseAdd) onDatabaseAdd(); // TODO: Elizabeth- check to see if we need these extra calls. - dbConfigExtraExtension?.onSave(extraExtensionComponentState, db); + dbConfigExtraExtension + ?.onSave(extraExtensionComponentState, db) + .then(({ error }: { error: any }) => { + if (error) { + dbConfigExtraExtensionOnSaveError = error; + addDangerToast(error); + } + }); + if (dbConfigExtraExtensionOnSaveError) { + setLoading(false); + return; + } if (!editNewDb) { onClose(); addSuccessToast(t('Database settings updated')); @@ -841,7 +864,19 @@ const DatabaseModal: FunctionComponent = ({ setHasConnectedDb(true); if (onDatabaseAdd) onDatabaseAdd(); // TODO: Elizabeth- check to see if we need these extra calls. - dbConfigExtraExtension?.onSave(extraExtensionComponentState, db); + dbConfigExtraExtension + ?.onSave(extraExtensionComponentState, db) + .then(({ error }: { error: any }) => { + if (error) { + dbConfigExtraExtensionOnSaveError = error; + addDangerToast(error); + } + }); + if (dbConfigExtraExtensionOnSaveError) { + setLoading(false); + return; + } + if (useTabLayout) { // tab layout only has one step // so it should close immediately on save From 5a9710d786ac8a0a1bbd6886f2eb4cd643367acb Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Mon, 8 May 2023 15:55:39 -0700 Subject: [PATCH 6/8] remove comment --- .../src/features/databases/DatabaseModal/index.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.tsx index b4d6004a19f85..2820a921e6e9c 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx @@ -734,7 +734,6 @@ const DatabaseModal: FunctionComponent = ({ }; const onSave = async () => { - // TODO: Elizabeth - add some error handling or promise/callback here let dbConfigExtraExtensionOnSaveError; dbConfigExtraExtension ?.onSave(extraExtensionComponentState, db) @@ -836,7 +835,6 @@ const DatabaseModal: FunctionComponent = ({ ); if (result) { if (onDatabaseAdd) onDatabaseAdd(); - // TODO: Elizabeth- check to see if we need these extra calls. dbConfigExtraExtension ?.onSave(extraExtensionComponentState, db) .then(({ error }: { error: any }) => { @@ -863,7 +861,6 @@ const DatabaseModal: FunctionComponent = ({ if (dbId) { setHasConnectedDb(true); if (onDatabaseAdd) onDatabaseAdd(); - // TODO: Elizabeth- check to see if we need these extra calls. dbConfigExtraExtension ?.onSave(extraExtensionComponentState, db) .then(({ error }: { error: any }) => { From 843fe63ae02105333f931bc3ce647bf7612a5e10 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Wed, 17 May 2023 16:37:22 -0700 Subject: [PATCH 7/8] Extensions for modals --- .../src/ui-overrides/ExtensionsRegistry.ts | 6 +++- .../src/pages/DatabaseList/index.tsx | 4 +-- .../src/pages/DatasetList/index.tsx | 36 +++++++++++++++---- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts index 4a139b5da9ce0..70f53ab941a1d 100644 --- a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts +++ b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts @@ -66,7 +66,10 @@ type RightMenuItemIconProps = { menuChild: MenuObjectChildProps; }; type DatabaseDeleteRelatedExtensionProps = { - databaseId: number; + database: object; +}; +type DatasetDeleteRelatedExtensionProps = { + dataset: object; }; /** @@ -122,6 +125,7 @@ export type Extensions = Partial<{ 'ssh_tunnel.form.switch': React.ComponentType; 'databaseconnection.extraOption': DatabaseConnectionExtension; 'database.delete.related': React.ComponentType; + 'dataset.delete.related': React.ComponentType; }>; /** diff --git a/superset-frontend/src/pages/DatabaseList/index.tsx b/superset-frontend/src/pages/DatabaseList/index.tsx index 44a4ac3ca002d..9365c021e27ae 100644 --- a/superset-frontend/src/pages/DatabaseList/index.tsx +++ b/superset-frontend/src/pages/DatabaseList/index.tsx @@ -534,9 +534,9 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { databaseCurrentlyDeleting.sqllab_tab_count, )}

- {DatabaseDeleteRelatedExtension && currentDatabase?.id && ( + {DatabaseDeleteRelatedExtension && ( )} diff --git a/superset-frontend/src/pages/DatasetList/index.tsx b/superset-frontend/src/pages/DatasetList/index.tsx index 28b9a522be5ed..fa006ad42502f 100644 --- a/superset-frontend/src/pages/DatasetList/index.tsx +++ b/superset-frontend/src/pages/DatasetList/index.tsx @@ -16,7 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { FeatureFlag, styled, SupersetClient, t } from '@superset-ui/core'; +import { + FeatureFlag, + getExtensionsRegistry, + styled, + SupersetClient, + t, +} from '@superset-ui/core'; import React, { FunctionComponent, useState, @@ -64,6 +70,11 @@ import { } from 'src/features/datasets/constants'; import DuplicateDatasetModal from 'src/features/datasets/DuplicateDatasetModal'; +const extensionsRegistry = getExtensionsRegistry(); +const DatasetDeleteRelatedExtension = extensionsRegistry.get( + 'dataset.delete.related', +); + const FlexRowContainer = styled.div` align-items: center; display: flex; @@ -707,12 +718,23 @@ const DatasetList: FunctionComponent = ({ {datasetCurrentlyDeleting && ( +

+ {t( + 'The dataset %s is linked to %s charts that appear on %s dashboards. Are you sure you want to continue? Deleting the dataset will break those objects.', + datasetCurrentlyDeleting.table_name, + datasetCurrentlyDeleting.chart_count, + datasetCurrentlyDeleting.dashboard_count, + )} +

+ {DatasetDeleteRelatedExtension && ( + + )} + + } onConfirm={() => { if (datasetCurrentlyDeleting) { handleDatasetDelete(datasetCurrentlyDeleting); From c89b3cb8330dfc60ba502645b2547f1567258768 Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Wed, 17 May 2023 17:13:17 -0700 Subject: [PATCH 8/8] fix(dbt cloud): modal header overlap in db connection --- .../src/features/databases/DatabaseModal/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset-frontend/src/features/databases/DatabaseModal/styles.ts b/superset-frontend/src/features/databases/DatabaseModal/styles.ts index fdb6eb943f212..e38b4d96eccf4 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/styles.ts +++ b/superset-frontend/src/features/databases/DatabaseModal/styles.ts @@ -573,7 +573,7 @@ export const StyledStickyHeader = styled.div` top: 0; z-index: ${({ theme }) => theme.zIndex.max}; background: ${({ theme }) => theme.colors.grayscale.light5}; - height: ${({ theme }) => theme.gridUnit * 16}px; + height: auto; `; export const StyledCatalogTable = styled.div`