Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -446,4 +446,82 @@ describe('ResultSet', () => {
}),
).toBe(null);
});

test('should allow download as CSV when user has permission to export data', async () => {
const { queryByTestId } = setup(
mockedProps,
mockStore({
...initialState,
user: {
...user,
roles: {
sql_lab: [['can_export_csv', 'SQLLab']],
},
},
sqlLab: {
...initialState.sqlLab,
queries: {
[queries[0].id]: queries[0],
},
},
}),
);
expect(queryByTestId('export-csv-button')).toBeInTheDocument();
});

test('should not allow download as CSV when user does not have permission to export data', async () => {
const { queryByTestId } = setup(
mockedProps,
mockStore({
...initialState,
user,
sqlLab: {
...initialState.sqlLab,
queries: {
[queries[0].id]: queries[0],
},
},
}),
);
expect(queryByTestId('export-csv-button')).not.toBeInTheDocument();
});

test('should allow copy to clipboard when user has permission to export data', async () => {
const { queryByTestId } = setup(
mockedProps,
mockStore({
...initialState,
user: {
...user,
roles: {
sql_lab: [['can_export_csv', 'SQLLab']],
},
},
sqlLab: {
...initialState.sqlLab,
queries: {
[queries[0].id]: queries[0],
},
},
}),
);
expect(queryByTestId('copy-to-clipboard-button')).toBeInTheDocument();
});

test('should not allow copy to clipboard when user does not have permission to export data', async () => {
const { queryByTestId } = setup(
mockedProps,
mockStore({
...initialState,
user,
sqlLab: {
...initialState.sqlLab,
queries: {
[queries[0].id]: queries[0],
},
},
}),
);
expect(queryByTestId('copy-to-clipboard-button')).not.toBeInTheDocument();
});
});
41 changes: 27 additions & 14 deletions superset-frontend/src/SqlLab/components/ResultSet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import {
LOG_ACTIONS_SQLLAB_DOWNLOAD_CSV,
} from 'src/logger/LogUtils';
import Icons from 'src/components/Icons';
import { findPermission } from 'src/utils/findPermission';
import ExploreCtasResultsButton from '../ExploreCtasResultsButton';
import ExploreResultsButton from '../ExploreResultsButton';
import HighlightedSql from '../HighlightedSql';
Expand Down Expand Up @@ -309,6 +310,12 @@ const ResultSet = ({
schema: query?.schema,
};

const canExportData = findPermission(
'can_export_csv',
'SQLLab',
user?.roles,
);

return (
<ResultSetControls>
<SaveDatasetModal
Expand All @@ -328,29 +335,35 @@ const ResultSet = ({
onClick={createExploreResultsOnClick}
/>
)}
{csv && (
{csv && canExportData && (
<Button
buttonSize="small"
href={getExportCsvUrl(query.id)}
data-test="export-csv-button"
onClick={() => logAction(LOG_ACTIONS_SQLLAB_DOWNLOAD_CSV, {})}
>
<i className="fa fa-file-text-o" /> {t('Download to CSV')}
</Button>
)}

<CopyToClipboard
text={prepareCopyToClipboardTabularData(data, columns)}
wrapped={false}
copyNode={
<Button buttonSize="small">
<i className="fa fa-clipboard" /> {t('Copy to Clipboard')}
</Button>
}
hideTooltip
onCopyEnd={() =>
logAction(LOG_ACTIONS_SQLLAB_COPY_RESULT_TO_CLIPBOARD, {})
}
/>
{canExportData && (
<CopyToClipboard
text={prepareCopyToClipboardTabularData(data, columns)}
wrapped={false}
copyNode={
<Button
buttonSize="small"
data-test="copy-to-clipboard-button"
>
<i className="fa fa-clipboard" /> {t('Copy to Clipboard')}
</Button>
}
hideTooltip
onCopyEnd={() =>
logAction(LOG_ACTIONS_SQLLAB_COPY_RESULT_TO_CLIPBOARD, {})
}
/>
)}
</ResultSetButtons>
{search && (
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ const ChartContextMenu = (
const canDatasourceSamples = useSelector((state: RootState) =>
findPermission('can_samples', 'Datasource', state.user?.roles),
);
const canDownload = useSelector((state: RootState) =>
findPermission('can_csv', 'Superset', state.user?.roles),
);
const canDrill = useSelector((state: RootState) =>
findPermission('can_drill', 'Dashboard', state.user?.roles),
);
Expand Down Expand Up @@ -256,6 +259,7 @@ const ChartContextMenu = (
formData={formData}
contextMenuY={clientY}
submenuIndex={submenuIndex}
canDownload={canDownload}
open={openKeys.includes('drill-by-submenu')}
key="drill-by-submenu"
{...(additionalConfig?.drillBy || {})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const renderMenu = ({
<DrillByMenuItems
formData={formData ?? defaultFormData}
drillByConfig={drillByConfig}
canDownload
open
{...rest}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export interface DrillByMenuItemsProps {
onClick?: (event: MouseEvent) => void;
openNewModal?: boolean;
excludedColumns?: Column[];
canDownload: boolean;
open: boolean;
}

Expand Down Expand Up @@ -105,6 +106,7 @@ export const DrillByMenuItems = ({
onClick = () => {},
excludedColumns,
openNewModal = true,
canDownload,
open,
...rest
}: DrillByMenuItemsProps) => {
Expand Down Expand Up @@ -344,6 +346,7 @@ export const DrillByMenuItems = ({
formData={formData}
onHideModal={closeModal}
dataset={{ ...dataset!, verbose_map: verboseMap }}
canDownload={canDownload}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const renderModal = async (
onHideModal={() => setShowModal(false)}
dataset={dataset}
drillByConfig={{ groupbyFieldName: 'groupby', filters: [] }}
canDownload
{...modalProps}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export interface DrillByModalProps {
drillByConfig: Required<ContextMenuFilters>['drillBy'];
formData: BaseFormData & { [key: string]: any };
onHideModal: () => void;
canDownload: boolean;
}

type DrillByConfigs = (ContextMenuFilters['drillBy'] & { column?: Column })[];
Expand All @@ -161,6 +162,7 @@ export default function DrillByModal({
drillByConfig,
formData,
onHideModal,
canDownload,
}: DrillByModalProps) {
const dispatch = useDispatch();
const theme = useTheme();
Expand Down Expand Up @@ -200,6 +202,7 @@ export default function DrillByModal({
const resultsTable = useResultsTableView(
chartDataResult,
formData.datasource,
canDownload,
);

const [currentFormData, setCurrentFormData] = useState(formData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const MOCK_CHART_DATA_RESULT = [

test('Displays results table for 1 query', () => {
const { result } = renderHook(() =>
useResultsTableView(MOCK_CHART_DATA_RESULT.slice(0, 1), '1__table'),
useResultsTableView(MOCK_CHART_DATA_RESULT.slice(0, 1), '1__table', true),
);
render(result.current, { useRedux: true });
expect(screen.queryByRole('tablist')).not.toBeInTheDocument();
Expand All @@ -76,7 +76,7 @@ test('Displays results table for 1 query', () => {

test('Displays results for 2 queries', async () => {
const { result } = renderHook(() =>
useResultsTableView(MOCK_CHART_DATA_RESULT, '1__table'),
useResultsTableView(MOCK_CHART_DATA_RESULT, '1__table', true),
);
render(result.current, { useRedux: true });
const getActiveTabElement = () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const PaginationContainer = styled.div`
export const useResultsTableView = (
chartDataResult: QueryData[] | undefined,
datasourceId: string,
canDownload: boolean,
) => {
if (!isDefined(chartDataResult)) {
return <div />;
Expand All @@ -48,6 +49,7 @@ export const useResultsTableView = (
dataSize={DATA_SIZE}
datasourceId={datasourceId}
isVisible
canDownload={canDownload}
/>
</PaginationContainer>
);
Expand All @@ -65,6 +67,7 @@ export const useResultsTableView = (
dataSize={DATA_SIZE}
datasourceId={datasourceId}
isVisible
canDownload={canDownload}
/>
</PaginationContainer>
</Tabs.TabPane>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,7 @@ const SliceHeaderControls = (props: SliceHeaderControlsPropsWithRouter) => {
dataSize={20}
isRequest
isVisible
canDownload={!!props.supersetCanCSV}
/>
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const DataTablesPane = ({
ownState,
errorMessage,
actions,
canDownload,
}: DataTablesPaneProps) => {
const theme = useTheme();
const [activeTabKey, setActiveTabKey] = useState<string>(ResultTypes.Results);
Expand Down Expand Up @@ -198,6 +199,7 @@ export const DataTablesPane = ({
isRequest: isRequest.results,
actions,
isVisible: ResultTypes.Results === activeTabKey,
canDownload,
}).map((pane, idx) => {
if (idx === 0) {
return (
Expand Down Expand Up @@ -235,6 +237,7 @@ export const DataTablesPane = ({
isRequest={isRequest.samples}
actions={actions}
isVisible={ResultTypes.Samples === activeTabKey}
canDownload={canDownload}
/>
</Tabs.TabPane>
</Tabs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const TableControls = ({
columnTypes,
rowcount,
isLoading,
canDownload,
}: TableControlsProps) => {
const originalTimeColumns = getTimeColumns(datasourceId);
const formattedTimeColumns = zip<string, GenericDataType>(
Expand Down Expand Up @@ -76,7 +77,9 @@ export const TableControls = ({
`}
>
<RowCountLabel rowcount={rowcount} loading={isLoading} />
<CopyToClipboardButton data={formattedData} columns={columnNames} />
{canDownload && (
<CopyToClipboardButton data={formattedData} columns={columnNames} />
)}
</div>
</TableControlsWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const ResultsPaneOnDashboard = ({
actions,
isVisible,
dataSize = 50,
canDownload,
}: ResultsPaneProps) => {
const resultsPanes = useResultsPane({
errorMessage,
Expand All @@ -63,6 +64,7 @@ export const ResultsPaneOnDashboard = ({
actions,
dataSize,
isVisible,
canDownload,
});

if (resultsPanes.length === 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const SamplesPane = ({
actions,
dataSize = 50,
isVisible,
canDownload,
}: SamplesPaneProps) => {
const [filterText, setFilterText] = useState('');
const [data, setData] = useState<Record<string, any>[][]>([]);
Expand Down Expand Up @@ -114,6 +115,7 @@ export const SamplesPane = ({
datasourceId={datasourceId}
onInputChange={input => setFilterText(input)}
isLoading={isLoading}
canDownload={canDownload}
/>
<Error>{responseError}</Error>
</>
Expand All @@ -135,6 +137,7 @@ export const SamplesPane = ({
datasourceId={datasourceId}
onInputChange={input => setFilterText(input)}
isLoading={isLoading}
canDownload={canDownload}
/>
<TableView
columns={columns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const SingleQueryResultPane = ({
datasourceId,
dataSize = 50,
isVisible,
canDownload,
}: SingleQueryResultPaneProp) => {
const [filterText, setFilterText] = useState('');

Expand All @@ -60,6 +61,7 @@ export const SingleQueryResultPane = ({
datasourceId={datasourceId}
onInputChange={input => setFilterText(input)}
isLoading={false}
canDownload={canDownload}
/>
<TableView
columns={columns}
Expand Down
Loading