diff --git a/superset-frontend/src/components/Form/LabeledErrorBoundInput.stories.tsx b/superset-frontend/src/components/Form/LabeledErrorBoundInput.stories.tsx index 6061848d85016..d1c81b9af3a6e 100644 --- a/superset-frontend/src/components/Form/LabeledErrorBoundInput.stories.tsx +++ b/superset-frontend/src/components/Form/LabeledErrorBoundInput.stories.tsx @@ -32,6 +32,7 @@ export const InteractiveLabeledErrorBoundInput = ({ placeholder, type, id, + tooltipText, }: LabeledErrorBoundInputProps) => { const [currentValue, setCurrentValue] = useState(value); @@ -58,6 +59,8 @@ export const InteractiveLabeledErrorBoundInput = ({ placeholder={placeholder} type={type} required + hasTooltip + tooltipText={tooltipText} /> ); }; @@ -66,6 +69,7 @@ InteractiveLabeledErrorBoundInput.args = { name: 'Username', placeholder: 'Example placeholder text...', id: 1, + tooltipText: 'This is a tooltip', }; InteractiveLabeledErrorBoundInput.argTypes = { diff --git a/superset-frontend/src/components/Form/LabeledErrorBoundInput.test.jsx b/superset-frontend/src/components/Form/LabeledErrorBoundInput.test.jsx index 15f956fd5773a..86b6324daed50 100644 --- a/superset-frontend/src/components/Form/LabeledErrorBoundInput.test.jsx +++ b/superset-frontend/src/components/Form/LabeledErrorBoundInput.test.jsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, fireEvent, screen } from 'spec/helpers/testing-library'; import LabeledErrorBoundInput from 'src/components/Form/LabeledErrorBoundInput'; const defaultProps = { @@ -27,6 +27,8 @@ const defaultProps = { validationMethods: () => {}, errorMessage: '', helpText: 'This is a line of example help text', + hasTooltip: false, + tooltipText: 'This is a tooltip', value: '', placeholder: 'Example placeholder text...', type: 'textbox', @@ -58,4 +60,19 @@ describe('LabeledErrorBoundInput', () => { expect(textboxInput).toBeVisible(); expect(errorText).toBeVisible(); }); + it('renders a LabledErrorBoundInput with a InfoTooltip', async () => { + defaultProps.hasTooltip = true; + render(); + + const label = screen.getByText(/username/i); + const textboxInput = screen.getByRole('textbox'); + const tooltipIcon = screen.getByTestId('info-solid-small'); + + fireEvent.mouseOver(tooltipIcon); + + expect(tooltipIcon).toBeVisible(); + expect(label).toBeVisible(); + expect(textboxInput).toBeVisible(); + expect(await screen.findByText('This is a tooltip')).toBeInTheDocument(); + }); }); diff --git a/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx b/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx index f71df9d18ebae..d1a0bafafbc3c 100644 --- a/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx +++ b/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx @@ -19,6 +19,7 @@ import React from 'react'; import { Input } from 'antd'; import { styled, css, SupersetTheme } from '@superset-ui/core'; +import InfoTooltip from 'src/components/InfoTooltip'; import FormItem from './FormItem'; import FormLabel from './FormLabel'; @@ -30,6 +31,8 @@ export interface LabeledErrorBoundInputProps { errorMessage: string | null; helpText?: string; required?: boolean; + hasTooltip?: boolean; + tooltipText?: string | null; id?: string; classname?: string; [x: string]: any; @@ -61,6 +64,7 @@ const alertIconStyles = (theme: SupersetTheme, hasError: boolean) => css` } }`} `; + const StyledFormGroup = styled('div')` input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { @@ -73,20 +77,36 @@ const StyledFormGroup = styled('div')` } `; +const infoTooltip = (theme: SupersetTheme) => css` + svg { + vertical-align: bottom; + margin-bottom: ${theme.gridUnit * 0.25}px; + } +`; + const LabeledErrorBoundInput = ({ label, validationMethods, errorMessage, helpText, required = false, + hasTooltip = false, + tooltipText, id, className, ...props }: LabeledErrorBoundInputProps) => ( - + infoTooltip(theme)} + > {label} + {hasTooltip && ( + + )} alertIconStyles(theme, !!errorMessage)} validateTrigger={Object.keys(validationMethods)} diff --git a/superset-frontend/src/components/IconButton/index.tsx b/superset-frontend/src/components/IconButton/index.tsx index 47413641b7609..d36015e295354 100644 --- a/superset-frontend/src/components/IconButton/index.tsx +++ b/superset-frontend/src/components/IconButton/index.tsx @@ -133,6 +133,7 @@ const IconButton = styled( background-color: ${({ theme }) => theme.colors.grayscale.light5}; color: ${({ theme }) => theme.colors.grayscale.dark2}; border: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + box-shadow: 4px 4px 20px ${({ theme }) => theme.colors.grayscale.light2}; } `; diff --git a/superset-frontend/src/components/InfoTooltip/index.tsx b/superset-frontend/src/components/InfoTooltip/index.tsx index b3558593973f3..245c95b026094 100644 --- a/superset-frontend/src/components/InfoTooltip/index.tsx +++ b/superset-frontend/src/components/InfoTooltip/index.tsx @@ -42,11 +42,11 @@ export interface InfoTooltipProps { trigger?: string | Array; overlayStyle?: any; bgColor?: string; + viewBox?: string; } const StyledTooltip = styled(Tooltip)` cursor: pointer; - path:first-of-type { fill: #999999; } @@ -65,6 +65,7 @@ export default function InfoTooltip({ trigger = 'hover', overlayStyle = defaultOverlayStyle, bgColor = defaultColor, + viewBox = '0 -2 24 24', }: InfoTooltipProps) { return ( - + ); } diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm.tsx index 6bc74275bdcc0..5b9c57a463876 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm.tsx @@ -51,6 +51,8 @@ export const FormFieldOrder = [ interface FieldPropTypes { required: boolean; + hasTooltip?: boolean; + tooltipText?: (valuse: any) => string; onParametersChange: (value: any) => string; onParametersUploadFileChange: (value: any) => string; changeMethods: { onParametersChange: (value: any) => string } & { @@ -94,20 +96,35 @@ const CredentialsInfo = ({ changeMethods, isEditMode, db }: FieldPropTypes) => { )} {uploadOption === CredentialInfoOptions.copyPaste || isEditMode ? ( -
+
Service Account