feat: Makes "Add to dashboard" in Save chart modal paginated (#23634)

This commit is contained in:
Michael S. Molina
2023-04-18 16:31:09 -03:00
committed by GitHub
parent 0c0d2b38a6
commit d6b6d9eae6
6 changed files with 175 additions and 269 deletions

View File

@@ -19,17 +19,18 @@
/* eslint camelcase: 0 */
import React from 'react';
import { Dispatch } from 'redux';
import { SelectValue } from 'antd/lib/select';
import { isFeatureEnabled } from 'src/featureFlags';
import rison from 'rison';
import { connect } from 'react-redux';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import {
css,
DatasourceType,
ensureIsArray,
FeatureFlag,
isDefined,
styled,
SupersetClient,
t,
} from '@superset-ui/core';
import { Input } from 'src/components/Input';
@@ -38,11 +39,10 @@ import Alert from 'src/components/Alert';
import Modal from 'src/components/Modal';
import { Radio } from 'src/components/Radio';
import Button from 'src/components/Button';
import { Select } from 'src/components';
import { AsyncSelect } from 'src/components';
import Loading from 'src/components/Loading';
import { setSaveChartModalVisibility } from 'src/explore/actions/saveModalActions';
import { SaveActionType } from 'src/explore/types';
import { isFeatureEnabled } from 'src/featureFlags';
// Session storage key for recent dashboard
const SK_DASHBOARD_ID = 'save_chart_recent_dashboard';
@@ -52,7 +52,6 @@ interface SaveModalProps extends RouteComponentProps {
actions: Record<string, any>;
form_data?: Record<string, any>;
userId: number;
dashboards: Array<any>;
alert?: string;
sliceName?: string;
slice?: Record<string, any>;
@@ -63,15 +62,14 @@ interface SaveModalProps extends RouteComponentProps {
}
type SaveModalState = {
saveToDashboardId: number | string | null;
newSliceName?: string;
newDashboardName?: string;
datasetName: string;
alert: string | null;
action: SaveActionType;
isLoading: boolean;
saveStatus?: string | null;
vizType?: string;
dashboard?: { label: string; value: string | number };
};
export const StyledModal = styled(Modal)`
@@ -89,15 +87,15 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
constructor(props: SaveModalProps) {
super(props);
this.state = {
saveToDashboardId: null,
newSliceName: props.sliceName,
datasetName: props.datasource?.name,
alert: null,
action: this.canOverwriteSlice() ? 'overwrite' : 'saveas',
isLoading: false,
vizType: props.form_data?.viz_type,
dashboard: undefined,
};
this.onDashboardSelectChange = this.onDashboardSelectChange.bind(this);
this.onDashboardChange = this.onDashboardChange.bind(this);
this.onSliceNameChange = this.onSliceNameChange.bind(this);
this.changeAction = this.changeAction.bind(this);
this.saveOrOverwrite = this.saveOrOverwrite.bind(this);
@@ -107,7 +105,8 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
}
isNewDashboard(): boolean {
return !!(!this.state.saveToDashboardId && this.state.newDashboardName);
const { dashboard } = this.state;
return typeof dashboard?.value === 'string';
}
canOverwriteSlice(): boolean {
@@ -117,30 +116,26 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
);
}
componentDidMount() {
this.props.actions.fetchDashboards(this.props.userId).then(() => {
if (ensureIsArray(this.props.dashboards).length === 0) {
return;
}
const dashboardIds = this.props.dashboards?.map(
dashboard => dashboard.value,
);
async componentDidMount() {
let { dashboardId } = this.props;
if (!dashboardId) {
const lastDashboard = sessionStorage.getItem(SK_DASHBOARD_ID);
let recentDashboard = lastDashboard && parseInt(lastDashboard, 10);
if (this.props.dashboardId) {
recentDashboard = this.props.dashboardId;
dashboardId = lastDashboard && parseInt(lastDashboard, 10);
}
if (dashboardId) {
try {
const result = await this.loadDashboard(dashboardId);
if (result) {
this.setState({
dashboard: { label: result.dashboard_title, value: result.id },
});
}
} catch (error) {
this.props.actions.addDangerToast(
t('An error occurred while loading dashboard information.'),
);
}
if (
recentDashboard !== null &&
dashboardIds.indexOf(recentDashboard) !== -1
) {
this.setState({
saveToDashboardId: recentDashboard,
});
}
});
}
}
handleDatasetNameChange = (e: React.FormEvent<HTMLInputElement>) => {
@@ -152,11 +147,8 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
this.setState({ newSliceName: event.target.value });
}
onDashboardSelectChange(selected: SelectValue) {
const newDashboardName = selected ? String(selected) : undefined;
const saveToDashboardId =
selected && typeof selected === 'number' ? selected : null;
this.setState({ saveToDashboardId, newDashboardName });
onDashboardChange(dashboard: { label: string; value: string | number }) {
this.setState({ dashboard });
}
changeAction(action: SaveActionType) {
@@ -206,19 +198,22 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
delete formData.url_params;
let dashboard: DashboardGetResponse | null = null;
if (this.state.newDashboardName || this.state.saveToDashboardId) {
let saveToDashboardId = this.state.saveToDashboardId || null;
if (!this.state.saveToDashboardId) {
if (this.state.dashboard) {
let validId = this.state.dashboard.value;
if (this.isNewDashboard()) {
const response = await this.props.actions.createDashboard(
this.state.newDashboardName,
this.state.dashboard.label,
);
saveToDashboardId = response.id;
validId = response.id;
}
try {
dashboard = await this.loadDashboard(validId as number);
} catch (error) {
this.props.actions.saveSliceFailed();
return;
}
const response = await this.props.actions.getDashboard(
saveToDashboardId,
);
dashboard = response.result;
if (isDefined(dashboard) && isDefined(dashboard?.id)) {
sliceDashboards = sliceDashboards.includes(dashboard.id)
? sliceDashboards
@@ -240,7 +235,7 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
dashboard
? {
title: dashboard.dashboard_title,
new: !this.state.saveToDashboardId,
new: this.isNewDashboard(),
}
: null,
);
@@ -251,7 +246,7 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
dashboard
? {
title: dashboard.dashboard_title,
new: !this.state.saveToDashboardId,
new: this.isNewDashboard(),
}
: null,
);
@@ -284,95 +279,132 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
}
}
renderSaveChartModal = () => {
const dashboardSelectValue =
this.state.saveToDashboardId || this.state.newDashboardName;
return (
<Form data-test="save-modal-body" layout="vertical">
{(this.state.alert || this.props.alert) && (
<Alert
type="warning"
message={this.state.alert || this.props.alert}
onClose={this.removeAlert}
/>
)}
<FormItem data-test="radio-group">
<Radio
id="overwrite-radio"
disabled={!this.canOverwriteSlice()}
checked={this.state.action === 'overwrite'}
onChange={() => this.changeAction('overwrite')}
data-test="save-overwrite-radio"
>
{t('Save (Overwrite)')}
</Radio>
<Radio
id="saveas-radio"
data-test="saveas-radio"
checked={this.state.action === 'saveas'}
onChange={() => this.changeAction('saveas')}
>
{t('Save as...')}
</Radio>
</FormItem>
<hr />
<FormItem label={t('Chart name')} required>
<Input
name="new_slice_name"
type="text"
placeholder="Name"
value={this.state.newSliceName}
onChange={this.onSliceNameChange}
data-test="new-chart-name"
/>
</FormItem>
{this.props.datasource?.type === 'query' && (
<FormItem label={t('Dataset Name')} required>
<InfoTooltipWithTrigger
tooltip={t('A reusable dataset will be saved with your chart.')}
placement="right"
/>
<Input
name="dataset_name"
type="text"
placeholder="Dataset Name"
value={this.state.datasetName}
onChange={this.handleDatasetNameChange}
data-test="new-dataset-name"
/>
</FormItem>
)}
{!(
isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) &&
this.state.vizType === 'filter_box'
) && (
<FormItem
label={t('Add to dashboard')}
data-test="save-chart-modal-select-dashboard-form"
>
<Select
allowClear
allowNewOptions
ariaLabel={t('Select a dashboard')}
options={this.props.dashboards}
onChange={this.onDashboardSelectChange}
value={dashboardSelectValue || undefined}
placeholder={
<div>
<b>{t('Select')}</b>
{t(' a dashboard OR ')}
<b>{t('create')}</b>
{t(' a new one')}
</div>
}
/>
</FormItem>
)}
</Form>
);
loadDashboard = async (id: number) => {
const response = await SupersetClient.get({
endpoint: `/api/v1/dashboard/${id}`,
});
return response.json.result;
};
loadDashboards = async (search: string, page: number, pageSize: number) => {
const queryParams = rison.encode({
columns: ['id', 'dashboard_title'],
filters: [
{
col: 'dashboard_title',
opr: 'ct',
value: search,
},
{
col: 'owners',
opr: 'rel_m_m',
value: this.props.userId,
},
],
page,
page_size: pageSize,
order_column: 'dashboard_title',
});
const { json } = await SupersetClient.get({
endpoint: `/api/v1/dashboard/?q=${queryParams}`,
});
const { result, count } = json;
return {
data: result.map(
(dashboard: { id: number; dashboard_title: string }) => ({
value: dashboard.id,
label: dashboard.dashboard_title,
}),
),
totalCount: count,
};
};
renderSaveChartModal = () => (
<Form data-test="save-modal-body" layout="vertical">
{(this.state.alert || this.props.alert) && (
<Alert
type="warning"
message={this.state.alert || this.props.alert}
onClose={this.removeAlert}
/>
)}
<FormItem data-test="radio-group">
<Radio
id="overwrite-radio"
disabled={!this.canOverwriteSlice()}
checked={this.state.action === 'overwrite'}
onChange={() => this.changeAction('overwrite')}
data-test="save-overwrite-radio"
>
{t('Save (Overwrite)')}
</Radio>
<Radio
id="saveas-radio"
data-test="saveas-radio"
checked={this.state.action === 'saveas'}
onChange={() => this.changeAction('saveas')}
>
{t('Save as...')}
</Radio>
</FormItem>
<hr />
<FormItem label={t('Chart name')} required>
<Input
name="new_slice_name"
type="text"
placeholder="Name"
value={this.state.newSliceName}
onChange={this.onSliceNameChange}
data-test="new-chart-name"
/>
</FormItem>
{this.props.datasource?.type === 'query' && (
<FormItem label={t('Dataset Name')} required>
<InfoTooltipWithTrigger
tooltip={t('A reusable dataset will be saved with your chart.')}
placement="right"
/>
<Input
name="dataset_name"
type="text"
placeholder="Dataset Name"
value={this.state.datasetName}
onChange={this.handleDatasetNameChange}
data-test="new-dataset-name"
/>
</FormItem>
)}
{!(
isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) &&
this.state.vizType === 'filter_box'
) && (
<FormItem
label={t('Add to dashboard')}
data-test="save-chart-modal-select-dashboard-form"
>
<AsyncSelect
allowClear
allowNewOptions
ariaLabel={t('Select a dashboard')}
options={this.loadDashboards}
onChange={this.onDashboardChange}
value={this.state.dashboard}
placeholder={
<div>
<b>{t('Select')}</b>
{t(' a dashboard OR ')}
<b>{t('create')}</b>
{t(' a new one')}
</div>
}
/>
</FormItem>
)}
</Form>
);
renderFooter = () => (
<div data-test="save-modal-footer">
<Button id="btn_cancel" buttonSize="small" onClick={this.onHide}>
@@ -383,7 +415,7 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
buttonSize="small"
disabled={
!this.state.newSliceName ||
(!this.state.saveToDashboardId && !this.state.newDashboardName) ||
!this.state.dashboard ||
(this.props.datasource?.type !== DatasourceType.Table &&
!this.state.datasetName) ||
(isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) &&