mirror of
https://github.com/apache/superset.git
synced 2026-05-10 02:15:50 +00:00
chore(frontend): migrate SqlLab and explore JS/JSX files to TypeScript (#36760)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,656 @@
|
||||
/* eslint-disable camelcase */
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
DatasourceType,
|
||||
SupersetClient,
|
||||
t,
|
||||
Datasource,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
css,
|
||||
styled,
|
||||
withTheme,
|
||||
type SupersetTheme,
|
||||
} from '@apache-superset/core/ui';
|
||||
import { getTemporalColumns } from '@superset-ui/chart-controls';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
import {
|
||||
Dropdown,
|
||||
Tooltip,
|
||||
Button,
|
||||
ModalTrigger,
|
||||
} from '@superset-ui/core/components';
|
||||
import {
|
||||
ChangeDatasourceModal,
|
||||
DatasourceModal,
|
||||
ErrorAlert,
|
||||
} from 'src/components';
|
||||
import { Menu } from '@superset-ui/core/components/Menu';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import WarningIconWithTooltip from '@superset-ui/core/components/WarningIconWithTooltip';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { getDatasourceAsSaveableDataset } from 'src/utils/datasourceUtils';
|
||||
import {
|
||||
userHasPermission,
|
||||
isUserAdmin,
|
||||
} from 'src/dashboard/util/permissionUtils';
|
||||
import { ErrorMessageWithStackTrace } from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||
import ViewQueryModalFooter from 'src/explore/components/controls/ViewQueryModalFooter';
|
||||
import ViewQuery from 'src/explore/components/controls/ViewQuery';
|
||||
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// Extended Datasource interface with all properties used in this component
|
||||
interface ExtendedDatasource extends Datasource {
|
||||
sql?: string;
|
||||
select_star?: string;
|
||||
owners?: Array<{
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
value?: number;
|
||||
}>;
|
||||
extra?: string;
|
||||
health_check_message?: string;
|
||||
database?: {
|
||||
id: number;
|
||||
database_name: string;
|
||||
backend?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface User {
|
||||
userId?: number;
|
||||
username?: string;
|
||||
roles?: Record<string, unknown[]>;
|
||||
}
|
||||
|
||||
interface DatasourceControlActions {
|
||||
changeDatasource: (datasource: ExtendedDatasource) => void;
|
||||
setControlValue: (name: string, value: unknown) => void;
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
granularity_sqla?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface DatasourceControlProps {
|
||||
actions: DatasourceControlActions;
|
||||
onChange?: () => void;
|
||||
value?: string | null;
|
||||
datasource: ExtendedDatasource;
|
||||
form_data?: FormData;
|
||||
isEditable?: boolean;
|
||||
onDatasourceSave?: ((datasource: ExtendedDatasource) => void) | null;
|
||||
theme: SupersetTheme;
|
||||
user: User;
|
||||
// ControlHeader-related props
|
||||
hovered?: boolean;
|
||||
type?: string;
|
||||
label?: string;
|
||||
default?: unknown;
|
||||
description?: string | null;
|
||||
validationErrors?: string[];
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface DatasourceControlState {
|
||||
showEditDatasourceModal: boolean;
|
||||
showChangeDatasourceModal: boolean;
|
||||
showSaveDatasetModal: boolean;
|
||||
showDatasource?: boolean;
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
datasource: PropTypes.object.isRequired,
|
||||
form_data: PropTypes.object.isRequired,
|
||||
isEditable: PropTypes.bool,
|
||||
onDatasourceSave: PropTypes.func,
|
||||
user: PropTypes.object.isRequired,
|
||||
// ControlHeader-related props
|
||||
hovered: PropTypes.bool,
|
||||
type: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
default: PropTypes.any,
|
||||
description: PropTypes.string,
|
||||
validationErrors: PropTypes.array,
|
||||
name: PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onChange: () => {},
|
||||
onDatasourceSave: null,
|
||||
value: null,
|
||||
isEditable: true,
|
||||
};
|
||||
|
||||
const getDatasetType = (datasource: ExtendedDatasource): string => {
|
||||
if (datasource.type === 'query') {
|
||||
return 'query';
|
||||
}
|
||||
if (datasource.type === 'table' && datasource.sql) {
|
||||
return 'virtual_dataset';
|
||||
}
|
||||
return 'physical_dataset';
|
||||
};
|
||||
|
||||
const Styles = styled.div`
|
||||
.data-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colorSplit};
|
||||
padding: ${({ theme }) => 4 * theme.sizeUnit}px;
|
||||
padding-right: ${({ theme }) => 2 * theme.sizeUnit}px;
|
||||
}
|
||||
.error-alert {
|
||||
margin: ${({ theme }) => 2 * theme.sizeUnit}px;
|
||||
min-height: 150px;
|
||||
}
|
||||
.ant-dropdown-trigger {
|
||||
margin-left: ${({ theme }) => 2 * theme.sizeUnit}px;
|
||||
}
|
||||
.btn-group .open .dropdown-toggle {
|
||||
box-shadow: none;
|
||||
&.button-default {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
i.angle {
|
||||
color: ${({ theme }) => theme.colorPrimary};
|
||||
}
|
||||
svg.datasource-modal-trigger {
|
||||
color: ${({ theme }) => theme.colorPrimary};
|
||||
cursor: pointer;
|
||||
}
|
||||
.title-select {
|
||||
flex: 1 1 100%;
|
||||
display: inline-block;
|
||||
padding: ${({ theme }) => theme.sizeUnit * 2}px 0px;
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.datasource-svg {
|
||||
margin-right: ${({ theme }) => 2 * theme.sizeUnit}px;
|
||||
flex: none;
|
||||
}
|
||||
span[aria-label='dataset-physical'] {
|
||||
color: ${({ theme }) => theme.colorIcon};
|
||||
}
|
||||
span[aria-label='more'] {
|
||||
color: ${({ theme }) => theme.colorPrimary};
|
||||
}
|
||||
`;
|
||||
|
||||
const CHANGE_DATASET = 'change_dataset';
|
||||
const VIEW_IN_SQL_LAB = 'view_in_sql_lab';
|
||||
const EDIT_DATASET = 'edit_dataset';
|
||||
const QUERY_PREVIEW = 'query_preview';
|
||||
const SAVE_AS_DATASET = 'save_as_dataset';
|
||||
|
||||
// If the string is longer than this value's number characters we add
|
||||
// a tooltip for user can see the full name by hovering over the visually truncated string in UI
|
||||
const VISIBLE_TITLE_LENGTH = 25;
|
||||
|
||||
// Assign icon for each DatasourceType. If no icon assignment is found in the lookup, no icon will render
|
||||
export const datasourceIconLookup: Record<string, React.ReactNode> = {
|
||||
query: <Icons.ConsoleSqlOutlined className="datasource-svg" />,
|
||||
physical_dataset: <Icons.TableOutlined className="datasource-svg" />,
|
||||
virtual_dataset: <Icons.ConsoleSqlOutlined className="datasource-svg" />,
|
||||
};
|
||||
|
||||
// Render title for datasource with tooltip only if text is longer than VISIBLE_TITLE_LENGTH
|
||||
export const renderDatasourceTitle = (
|
||||
displayString: string | undefined,
|
||||
tooltip: string,
|
||||
) =>
|
||||
displayString?.length && displayString.length > VISIBLE_TITLE_LENGTH ? (
|
||||
// Add a tooltip only for long names that will be visually truncated
|
||||
<Tooltip title={tooltip}>
|
||||
<span className="title-select">{displayString}</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span title={tooltip} className="title-select">
|
||||
{displayString}
|
||||
</span>
|
||||
);
|
||||
|
||||
// Different data source types use different attributes for the display title
|
||||
export const getDatasourceTitle = (
|
||||
datasource: ExtendedDatasource | null | undefined,
|
||||
): string => {
|
||||
if (datasource?.type === 'query') return datasource?.sql || '';
|
||||
return datasource?.name || '';
|
||||
};
|
||||
|
||||
const preventRouterLinkWhileMetaClicked = (evt: React.MouseEvent) => {
|
||||
if (evt.metaKey) {
|
||||
evt.preventDefault();
|
||||
} else {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
class DatasourceControl extends PureComponent<
|
||||
DatasourceControlProps,
|
||||
DatasourceControlState
|
||||
> {
|
||||
static propTypes = propTypes;
|
||||
|
||||
static defaultProps = defaultProps;
|
||||
|
||||
constructor(props: DatasourceControlProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showEditDatasourceModal: false,
|
||||
showChangeDatasourceModal: false,
|
||||
showSaveDatasetModal: false,
|
||||
};
|
||||
}
|
||||
|
||||
onDatasourceSave = (datasource: Datasource) => {
|
||||
// Cast to ExtendedDatasource for the component's internal use
|
||||
this.props.actions.changeDatasource(datasource as ExtendedDatasource);
|
||||
// Cast datasource for getTemporalColumns which expects Dataset | QueryResponse
|
||||
const { temporalColumns, defaultTemporalColumn } = getTemporalColumns(
|
||||
datasource as Parameters<typeof getTemporalColumns>[0],
|
||||
);
|
||||
const { columns } = datasource;
|
||||
// the current granularity_sqla might not be a temporal column anymore
|
||||
const timeCol = this.props.form_data?.granularity_sqla;
|
||||
const isGranularitySqlaTemporal = columns.find(
|
||||
({ column_name }) => column_name === timeCol,
|
||||
)?.is_dttm;
|
||||
// the current main_dttm_col might not be a temporal column anymore
|
||||
const isDefaultTemporal = columns.find(
|
||||
({ column_name }) => column_name === defaultTemporalColumn,
|
||||
)?.is_dttm;
|
||||
|
||||
// if the current granularity_sqla is empty or it is not a temporal column anymore
|
||||
// let's update the control value
|
||||
if (datasource.type === 'table' && !isGranularitySqlaTemporal) {
|
||||
const temporalColumn = isDefaultTemporal
|
||||
? defaultTemporalColumn
|
||||
: temporalColumns?.[0];
|
||||
this.props.actions.setControlValue(
|
||||
'granularity_sqla',
|
||||
temporalColumn || null,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.onDatasourceSave) {
|
||||
this.props.onDatasourceSave(datasource);
|
||||
}
|
||||
};
|
||||
|
||||
toggleShowDatasource = () => {
|
||||
this.setState(({ showDatasource }) => ({
|
||||
showDatasource: !showDatasource,
|
||||
}));
|
||||
};
|
||||
|
||||
toggleChangeDatasourceModal = () => {
|
||||
this.setState(({ showChangeDatasourceModal }) => ({
|
||||
showChangeDatasourceModal: !showChangeDatasourceModal,
|
||||
}));
|
||||
};
|
||||
|
||||
toggleEditDatasourceModal = () => {
|
||||
this.setState(({ showEditDatasourceModal }) => ({
|
||||
showEditDatasourceModal: !showEditDatasourceModal,
|
||||
}));
|
||||
};
|
||||
|
||||
toggleSaveDatasetModal = () => {
|
||||
this.setState(({ showSaveDatasetModal }) => ({
|
||||
showSaveDatasetModal: !showSaveDatasetModal,
|
||||
}));
|
||||
};
|
||||
|
||||
handleMenuItemClick = ({ key }: { key: string }) => {
|
||||
switch (key) {
|
||||
case CHANGE_DATASET:
|
||||
this.toggleChangeDatasourceModal();
|
||||
break;
|
||||
|
||||
case EDIT_DATASET:
|
||||
this.toggleEditDatasourceModal();
|
||||
break;
|
||||
|
||||
case VIEW_IN_SQL_LAB:
|
||||
{
|
||||
const { datasource } = this.props;
|
||||
const payload = {
|
||||
datasourceKey: `${datasource.id}__${datasource.type}`,
|
||||
sql: datasource.sql,
|
||||
};
|
||||
SupersetClient.postForm('/sqllab/', {
|
||||
form_data: safeStringify(payload),
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case SAVE_AS_DATASET:
|
||||
this.toggleSaveDatasetModal();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
showChangeDatasourceModal,
|
||||
showEditDatasourceModal,
|
||||
showSaveDatasetModal,
|
||||
} = this.state;
|
||||
const { datasource, onChange, theme } = this.props;
|
||||
let extra;
|
||||
if (datasource?.extra) {
|
||||
if (typeof datasource.extra === 'string') {
|
||||
try {
|
||||
extra = JSON.parse(datasource.extra);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
} else {
|
||||
extra = datasource.extra; // eslint-disable-line prefer-destructuring
|
||||
}
|
||||
}
|
||||
const isMissingDatasource = !datasource?.id || Boolean(extra?.error);
|
||||
let isMissingParams = false;
|
||||
if (isMissingDatasource) {
|
||||
const datasourceId = getUrlParam(URL_PARAMS.datasourceId);
|
||||
const sliceId = getUrlParam(URL_PARAMS.sliceId);
|
||||
|
||||
if (!datasourceId && !sliceId) {
|
||||
isMissingParams = true;
|
||||
}
|
||||
}
|
||||
|
||||
const { user } = this.props;
|
||||
const allowEdit =
|
||||
datasource.owners?.map(o => o.id || o.value).includes(user.userId) ||
|
||||
isUserAdmin(user);
|
||||
|
||||
const canAccessSqlLab = userHasPermission(user, 'SQL Lab', 'menu_access');
|
||||
|
||||
const editText = t('Edit dataset');
|
||||
const requestedQuery = {
|
||||
datasourceKey: `${datasource.id}__${datasource.type}`,
|
||||
sql: datasource.sql,
|
||||
};
|
||||
const defaultDatasourceMenuItems = [];
|
||||
if (this.props.isEditable && !isMissingDatasource) {
|
||||
defaultDatasourceMenuItems.push({
|
||||
key: EDIT_DATASET,
|
||||
label: !allowEdit ? (
|
||||
<Tooltip
|
||||
title={t(
|
||||
'You must be a dataset owner in order to edit. Please reach out to a dataset owner to request modifications or edit access.',
|
||||
)}
|
||||
>
|
||||
{editText}
|
||||
</Tooltip>
|
||||
) : (
|
||||
editText
|
||||
),
|
||||
disabled: !allowEdit,
|
||||
'data-test': 'edit-dataset',
|
||||
});
|
||||
}
|
||||
|
||||
defaultDatasourceMenuItems.push({
|
||||
key: CHANGE_DATASET,
|
||||
label: t('Swap dataset'),
|
||||
});
|
||||
|
||||
if (!isMissingDatasource && canAccessSqlLab) {
|
||||
defaultDatasourceMenuItems.push({
|
||||
key: VIEW_IN_SQL_LAB,
|
||||
label: (
|
||||
<Link
|
||||
to={{
|
||||
pathname: '/sqllab',
|
||||
state: { requestedQuery },
|
||||
}}
|
||||
onClick={preventRouterLinkWhileMetaClicked}
|
||||
>
|
||||
{t('View in SQL Lab')}
|
||||
</Link>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const defaultDatasourceMenu = (
|
||||
<Menu
|
||||
onClick={this.handleMenuItemClick}
|
||||
items={defaultDatasourceMenuItems}
|
||||
/>
|
||||
);
|
||||
|
||||
const queryDatasourceMenuItems = [
|
||||
{
|
||||
key: QUERY_PREVIEW,
|
||||
label: (
|
||||
<ModalTrigger
|
||||
triggerNode={
|
||||
<div data-test="view-query-menu-item">{t('Query preview')}</div>
|
||||
}
|
||||
modalTitle={t('Query preview')}
|
||||
modalBody={
|
||||
<ViewQuery
|
||||
sql={datasource?.sql || datasource?.select_star || ''}
|
||||
datasource={`${datasource.id}__${datasource.type}`}
|
||||
/>
|
||||
}
|
||||
modalFooter={
|
||||
<ViewQueryModalFooter
|
||||
changeDatasource={this.toggleSaveDatasetModal}
|
||||
datasource={{
|
||||
id: String(datasource.id),
|
||||
sql: datasource.sql || '',
|
||||
type: datasource.type,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
draggable={false}
|
||||
resizable={false}
|
||||
responsive
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (canAccessSqlLab) {
|
||||
queryDatasourceMenuItems.push({
|
||||
key: VIEW_IN_SQL_LAB,
|
||||
label: (
|
||||
<Link
|
||||
to={{
|
||||
pathname: '/sqllab',
|
||||
state: { requestedQuery },
|
||||
}}
|
||||
onClick={preventRouterLinkWhileMetaClicked}
|
||||
>
|
||||
{t('View in SQL Lab')}
|
||||
</Link>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
queryDatasourceMenuItems.push({
|
||||
key: SAVE_AS_DATASET,
|
||||
label: <span>{t('Save as dataset')}</span>,
|
||||
});
|
||||
|
||||
const queryDatasourceMenu = (
|
||||
<Menu
|
||||
onClick={this.handleMenuItemClick}
|
||||
items={queryDatasourceMenuItems}
|
||||
/>
|
||||
);
|
||||
|
||||
const { health_check_message: healthCheckMessage } = datasource;
|
||||
|
||||
const titleText =
|
||||
isMissingDatasource && !datasource.name
|
||||
? t('Missing dataset')
|
||||
: getDatasourceTitle(datasource);
|
||||
|
||||
const tooltip = titleText;
|
||||
|
||||
return (
|
||||
<Styles data-test="datasource-control" className="DatasourceControl">
|
||||
<div className="data-container">
|
||||
{datasourceIconLookup[getDatasetType(datasource)]}
|
||||
{renderDatasourceTitle(titleText, tooltip)}
|
||||
{healthCheckMessage && (
|
||||
<Tooltip title={healthCheckMessage}>
|
||||
<Icons.WarningOutlined
|
||||
css={css`
|
||||
margin-left: ${theme.sizeUnit * 2}px;
|
||||
`}
|
||||
iconColor={theme.colorWarning}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{extra?.warning_markdown && (
|
||||
<WarningIconWithTooltip warningMarkdown={extra.warning_markdown} />
|
||||
)}
|
||||
<Dropdown
|
||||
popupRender={() =>
|
||||
datasource.type === DatasourceType.Query
|
||||
? queryDatasourceMenu
|
||||
: defaultDatasourceMenu
|
||||
}
|
||||
trigger={['click']}
|
||||
data-test="datasource-menu"
|
||||
>
|
||||
<Icons.MoreOutlined
|
||||
iconSize="xl"
|
||||
iconColor={theme.colorPrimary}
|
||||
className="datasource-modal-trigger"
|
||||
data-test="datasource-menu-trigger"
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{/* missing dataset */}
|
||||
{isMissingDatasource && isMissingParams && (
|
||||
<div className="error-alert">
|
||||
<ErrorAlert
|
||||
type="warning"
|
||||
message={t('Missing URL parameters')}
|
||||
description={t(
|
||||
'The URL is missing the dataset_id or slice_id parameters.',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isMissingDatasource && !isMissingParams && (
|
||||
<div className="error-alert">
|
||||
{extra?.error ? (
|
||||
<ErrorMessageWithStackTrace
|
||||
title={extra.error.statusText || extra.error.message}
|
||||
subtitle={
|
||||
extra.error.statusText ? extra.error.message : undefined
|
||||
}
|
||||
error={extra.error}
|
||||
source="explore"
|
||||
/>
|
||||
) : (
|
||||
<ErrorAlert
|
||||
type="warning"
|
||||
message={t('Missing dataset')}
|
||||
descriptionPre={false}
|
||||
descriptionDetailsCollapsed={false}
|
||||
descriptionDetails={
|
||||
<>
|
||||
<p>
|
||||
{t(
|
||||
'The dataset linked to this chart may have been deleted.',
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
onClick={() =>
|
||||
this.handleMenuItemClick({ key: CHANGE_DATASET })
|
||||
}
|
||||
>
|
||||
{t('Swap dataset')}
|
||||
</Button>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showEditDatasourceModal && (
|
||||
<DatasourceModal
|
||||
datasource={datasource}
|
||||
show={showEditDatasourceModal}
|
||||
onDatasourceSave={this.onDatasourceSave}
|
||||
onHide={this.toggleEditDatasourceModal}
|
||||
/>
|
||||
)}
|
||||
{showChangeDatasourceModal && (
|
||||
<ChangeDatasourceModal
|
||||
onDatasourceSave={this.onDatasourceSave}
|
||||
onHide={this.toggleChangeDatasourceModal}
|
||||
show={showChangeDatasourceModal}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
{showSaveDatasetModal && (
|
||||
<SaveDatasetModal
|
||||
visible={showSaveDatasetModal}
|
||||
onHide={this.toggleSaveDatasetModal}
|
||||
buttonTextOnSave={t('Save')}
|
||||
buttonTextOnOverwrite={t('Overwrite')}
|
||||
modalDescription={t(
|
||||
'Save this query as a virtual dataset to continue exploring',
|
||||
)}
|
||||
datasource={getDatasourceAsSaveableDataset(datasource)}
|
||||
openWindow={false}
|
||||
formData={this.props.form_data}
|
||||
/>
|
||||
)}
|
||||
</Styles>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// withTheme injects the theme prop, so we need to cast the component type
|
||||
export default withTheme(
|
||||
DatasourceControl as React.ComponentType<
|
||||
Omit<DatasourceControlProps, 'theme'>
|
||||
>,
|
||||
);
|
||||
Reference in New Issue
Block a user