diff --git a/superset-frontend/src/explore/components/ExploreActionButtons.tsx b/superset-frontend/src/explore/components/ExploreActionButtons.tsx index 8e2738d3bfe48..5d4ac7b5cc4be 100644 --- a/superset-frontend/src/explore/components/ExploreActionButtons.tsx +++ b/superset-frontend/src/explore/components/ExploreActionButtons.tsx @@ -48,6 +48,7 @@ type ExploreActionButtonsProps = { queriesResponse: {}; slice: { slice_name: string }; addDangerToast: Function; + addSuccessToast: Function; }; const VIZ_TYPES_PIVOTABLE = ['pivot_table', 'pivot_table_v2']; @@ -98,6 +99,7 @@ const ExploreActionButtons = (props: ExploreActionButtonsProps) => { latestQueryFormData, slice, addDangerToast, + addSuccessToast, } = props; const copyTooltipText = t('Copy chart URL to clipboard'); @@ -111,8 +113,10 @@ const ExploreActionButtons = (props: ExploreActionButtonsProps) => { const shortUrl = await getShortUrl(); await copyTextToClipboard(shortUrl); setCopyTooltip(t('Copied to clipboard!')); + addSuccessToast(t('Copied to clipboard!')); } catch (error) { setCopyTooltip(t('Sorry, your browser does not support copying.')); + addDangerToast(t('Sorry, your browser does not support copying.')); } }; diff --git a/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx b/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx index 8dba7fbb40c1b..6fc4bc88a43bd 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx @@ -22,7 +22,7 @@ import { Slice } from 'src/types/Chart'; import { render, screen, waitFor } from 'spec/helpers/testing-library'; import fetchMock from 'fetch-mock'; import userEvent from '@testing-library/user-event'; -import PropertiesModal from '.'; +import PropertiesModal, { PropertiesModalProps } from '.'; const createProps = () => ({ slice: { @@ -68,6 +68,7 @@ const createProps = () => ({ show: true, onHide: jest.fn(), onSave: jest.fn(), + addSuccessToast: jest.fn(), }); fetchMock.get('glob:*/api/v1/chart/318', { @@ -160,10 +161,13 @@ afterAll(() => { fetchMock.resetBehavior(); }); +const renderModal = (props: PropertiesModalProps) => + render(, { useRedux: true }); + test('Should render null when show:false', async () => { const props = createProps(); props.show = false; - render(); + renderModal(props); await waitFor(() => { expect( @@ -174,7 +178,7 @@ test('Should render null when show:false', async () => { test('Should render when show:true', async () => { const props = createProps(); - render(); + renderModal(props); await waitFor(() => { expect( @@ -185,7 +189,7 @@ test('Should render when show:true', async () => { test('Should have modal header', async () => { const props = createProps(); - render(); + renderModal(props); await waitFor(() => { expect(screen.getByText('Edit Chart Properties')).toBeVisible(); @@ -196,7 +200,7 @@ test('Should have modal header', async () => { test('"Close" button should call "onHide"', async () => { const props = createProps(); - render(); + renderModal(props); await waitFor(() => { expect(props.onHide).toBeCalledTimes(0); @@ -212,7 +216,7 @@ test('"Close" button should call "onHide"', async () => { test('Should render all elements inside modal', async () => { const props = createProps(); - render(); + renderModal(props); await waitFor(() => { expect(screen.getAllByRole('textbox')).toHaveLength(5); expect(screen.getByRole('combobox')).toBeInTheDocument(); @@ -240,7 +244,7 @@ test('Should render all elements inside modal', async () => { test('Should have modal footer', async () => { const props = createProps(); - render(); + renderModal(props); await waitFor(() => { expect(screen.getByText('Cancel')).toBeVisible(); @@ -254,7 +258,7 @@ test('Should have modal footer', async () => { test('"Cancel" button should call "onHide"', async () => { const props = createProps(); - render(); + renderModal(props); await waitFor(() => { expect(props.onHide).toBeCalledTimes(0); @@ -270,7 +274,7 @@ test('"Cancel" button should call "onHide"', async () => { test('"Save" button should call only "onSave"', async () => { const props = createProps(); - render(); + renderModal(props); await waitFor(() => { expect(props.onSave).toBeCalledTimes(0); expect(props.onHide).toBeCalledTimes(0); @@ -294,7 +298,7 @@ test('Empty "Certified by" should clear "Certification details"', async () => { certified_by: '', }, }; - render(); + renderModal(noCertifiedByProps); expect( screen.getByRole('textbox', { name: 'Certification details' }), diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index 2b2e9b56ea512..022b74f02907d 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -26,14 +26,16 @@ import rison from 'rison'; import { t, SupersetClient, styled } from '@superset-ui/core'; import Chart, { Slice } from 'src/types/Chart'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; +import withToasts from 'src/components/MessageToasts/withToasts'; -type PropertiesModalProps = { +export type PropertiesModalProps = { slice: Slice; show: boolean; onHide: () => void; onSave: (chart: Chart) => void; permissionsError?: string; existingOwners?: SelectValue; + addSuccessToast: (msg: string) => void; }; const FormItem = Form.Item; @@ -46,11 +48,12 @@ const StyledHelpBlock = styled.span` margin-bottom: 0; `; -export default function PropertiesModal({ +function PropertiesModal({ slice, onHide, onSave, show, + addSuccessToast, }: PropertiesModalProps) { const [submitting, setSubmitting] = useState(false); const [form] = Form.useForm(); @@ -157,6 +160,7 @@ export default function PropertiesModal({ id: slice.slice_id, }; onSave(updatedChart); + addSuccessToast(t('Chart properties updated')); onHide(); } catch (res) { const clientError = await getClientErrorObject(res); @@ -308,3 +312,5 @@ export default function PropertiesModal({ ); } + +export default withToasts(PropertiesModal); diff --git a/superset/views/core.py b/superset/views/core.py index ca7dae3ee51e7..8e115a97ce5de 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -975,11 +975,11 @@ def save_or_overwrite_slice( if action == "saveas" and slice_add_perm: ChartDAO.save(slc) msg = _("Chart [{}] has been saved").format(slc.slice_name) - flash(msg, "info") + flash(msg, "success") elif action == "overwrite" and slice_overwrite_perm: ChartDAO.overwrite(slc) msg = _("Chart [{}] has been overwritten").format(slc.slice_name) - flash(msg, "info") + flash(msg, "success") # Adding slice to a dashboard if requested dash: Optional[Dashboard] = None @@ -1008,7 +1008,7 @@ def save_or_overwrite_slice( _("Chart [{}] was added to dashboard [{}]").format( slc.slice_name, dash.dashboard_title ), - "info", + "success", ) elif new_dashboard_name: # Creating and adding to a new dashboard @@ -1030,7 +1030,7 @@ def save_or_overwrite_slice( _( "Dashboard [{}] just got created and chart [{}] was added " "to it" ).format(dash.dashboard_title, slc.slice_name), - "info", + "success", ) if dash and slc not in dash.slices: