chore(ci): fix numpy type errors and revert #22610 (#22782)

This commit is contained in:
Ville Brofeldt 2023-01-19 14:53:48 +02:00 committed by GitHub
parent 39c96d0568
commit 577ac81686
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 373 additions and 196 deletions

View File

@ -17,9 +17,9 @@
[metadata]
name = Superset
summary = a data exploration platform
description-file = README.md
description_file = README.md
author = Apache Superset Dev
author-email = dev@superset.apache.org
author_email = dev@superset.apache.org
license = Apache License, Version 2.0
[files]

View File

@ -186,6 +186,27 @@ test('should render a create dataset infobox', async () => {
expect(infoboxText).toBeVisible();
});
test('should render a save dataset modal when "Create a dataset" is clicked', async () => {
const newProps = {
...props,
datasource: {
...datasource,
type: DatasourceType.Query,
},
};
render(<DatasourcePanel {...newProps} />, { useRedux: true, useDnd: true });
const createButton = await screen.findByRole('button', {
name: /create a dataset/i,
});
userEvent.click(createButton);
const saveDatasetModalTitle = screen.getByText(/save or overwrite dataset/i);
expect(saveDatasetModalTitle).toBeVisible();
});
test('should not render a save dataset modal when datasource is not query or dataset', async () => {
const newProps = {
...props,

View File

@ -231,9 +231,7 @@ const ColumnSelectPopover = ({
}, []);
const setDatasetAndClose = () => {
if (setDatasetModal) {
setDatasetModal(true);
}
if (setDatasetModal) setDatasetModal(true);
onClose();
};

View File

@ -337,7 +337,10 @@ export class ChartCreation extends React.PureComponent<
const isButtonDisabled = this.isBtnDisabled();
const datasetHelpText = this.state.canCreateDataset ? (
<span data-test="dataset-write">
<Link to="/dataset/add/" data-test="add-chart-new-dataset">
<Link
to="/tablemodelview/list/#create"
data-test="add-chart-new-dataset"
>
{t('Add a dataset')}
</Link>
{` ${t('or')} `}

View File

@ -21,7 +21,6 @@ import React, { useState, useMemo, useEffect } from 'react';
import rison from 'rison';
import { useSelector } from 'react-redux';
import { useQueryParams, BooleanParam } from 'use-query-params';
import { LocalStorageKeys, setItem } from 'src/utils/localStorageHelpers';
import Loading from 'src/components/Loading';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
@ -158,9 +157,6 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
refreshData();
addSuccessToast(t('Deleted: %s', dbName));
// Delete user-selected db from local storage
setItem(LocalStorageKeys.db, null);
// Close delete modal
setDatabaseCurrentlyDeleting(null);
},

View File

@ -43,14 +43,6 @@ jest.mock('@superset-ui/core', () => ({
isFeatureEnabled: () => true,
}));
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush,
}),
}));
const dbProps = {
show: true,
database_name: 'my database',

View File

@ -31,7 +31,6 @@ import React, {
useReducer,
Reducer,
} from 'react';
import { useHistory } from 'react-router-dom';
import { setItem, LocalStorageKeys } from 'src/utils/localStorageHelpers';
import { UploadChangeParam, UploadFile } from 'antd/lib/upload/interface';
import Tabs from 'src/components/Tabs';
@ -519,7 +518,6 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
t('database'),
addDangerToast,
);
const history = useHistory();
const [tabKey, setTabKey] = useState<string>(DEFAULT_TAB_KEY);
const [availableDbs, getAvailableDbs] = useAvailableDatabases();
@ -1297,7 +1295,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
onClick={() => {
setLoading(true);
fetchAndSetDB();
history.push('/dataset/add/');
window.location.href = '/tablemodelview/list#create';
}}
>
{t('CREATE DATASET')}
@ -1308,7 +1306,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
onClick={() => {
setLoading(true);
fetchAndSetDB();
history.push(`/superset/sqllab/?db=true`);
window.location.href = `/superset/sqllab/?db=true`;
}}
>
{t('QUERY DATA IN SQL LAB')}

View File

@ -20,14 +20,6 @@ import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import AddDataset from 'src/views/CRUD/data/dataset/AddDataset';
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush,
}),
}));
describe('AddDataset', () => {
it('renders a blank state AddDataset', async () => {
render(<AddDataset />, { useRedux: true });

View File

@ -40,7 +40,6 @@ export const exampleDataset: DatasetObject[] = [
id: 1,
database_name: 'test_database',
owners: [1],
backend: 'test_backend',
},
schema: 'test_schema',
dataset_name: 'example_dataset',

View File

@ -17,9 +17,8 @@
* under the License.
*/
import React, { useEffect, useState, useRef } from 'react';
import { SupersetClient, logging, t } from '@superset-ui/core';
import { SupersetClient } from '@superset-ui/core';
import { DatasetObject } from 'src/views/CRUD/data/dataset/AddDataset/types';
import { addDangerToast } from 'src/components/MessageToasts/actions';
import DatasetPanel from './DatasetPanel';
import { ITableColumn, IDatabaseTable, isIDatabaseTable } from './types';
@ -95,17 +94,9 @@ const DatasetPanelWrapper = ({
setColumnList([]);
setHasColumns?.(false);
setHasError(true);
addDangerToast(
t(
'The API response from %s does not match the IDatabaseTable interface.',
path,
),
);
logging.error(
t(
'The API response from %s does not match the IDatabaseTable interface.',
path,
),
// eslint-disable-next-line no-console
console.error(
`The API response from ${path} does not match the IDatabaseTable interface.`,
);
}
} catch (error) {

View File

@ -20,14 +20,6 @@ import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import Footer from 'src/views/CRUD/data/dataset/AddDataset/Footer';
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush,
}),
}));
const mockedProps = {
url: 'realwebsite.com',
};

View File

@ -17,7 +17,6 @@
* under the License.
*/
import React from 'react';
import { useHistory } from 'react-router-dom';
import Button from 'src/components/Button';
import { t } from '@superset-ui/core';
import { useSingleViewResource } from 'src/views/CRUD/hooks';
@ -50,12 +49,12 @@ const LOG_ACTIONS = [
];
function Footer({
url,
datasetObject,
addDangerToast,
hasColumns = false,
datasets,
}: FooterProps) {
const history = useHistory();
const { createResource } = useSingleViewResource<Partial<DatasetObject>>(
'dataset',
t('dataset'),
@ -73,6 +72,11 @@ function Footer({
return LOG_ACTIONS[value];
};
const goToPreviousUrl = () => {
// this is a placeholder url until the final feature gets implemented
// at that point we will be passing in the url of the previous location.
window.location.href = url;
};
const cancelButtonOnClick = () => {
if (!datasetObject) {
@ -81,7 +85,7 @@ function Footer({
const logAction = createLogAction(datasetObject);
logEvent(logAction, datasetObject);
}
history.goBack();
goToPreviousUrl();
};
const tooltipText = t('Select a database table.');
@ -100,13 +104,13 @@ function Footer({
if (typeof response === 'number') {
logEvent(LOG_ACTIONS_DATASET_CREATION_SUCCESS, datasetObject);
// When a dataset is created the response we get is its ID number
history.push(`/chart/add/?dataset=${datasetObject.table_name}`);
goToPreviousUrl();
}
});
}
};
const CREATE_DATASET_TEXT = t('Create Dataset and Create Chart');
const CREATE_DATASET_TEXT = t('Create Dataset');
const disabledCheck =
!datasetObject?.table_name ||
!hasColumns ||

View File

@ -21,7 +21,6 @@ import fetchMock from 'fetch-mock';
import userEvent from '@testing-library/user-event';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import LeftPanel from 'src/views/CRUD/data/dataset/AddDataset/LeftPanel';
import { exampleDataset } from 'src/views/CRUD/data/dataset/AddDataset/DatasetPanel/fixtures';
const databasesEndpoint = 'glob:*/api/v1/database/?q*';
const schemasEndpoint = 'glob:*/api/v1/database/*/schemas*';
@ -182,7 +181,7 @@ test('does not render blank state if there is nothing selected', async () => {
});
test('renders list of options when user clicks on schema', async () => {
render(<LeftPanel setDataset={mockFun} dataset={exampleDataset[0]} />, {
render(<LeftPanel setDataset={mockFun} schema="schema_a" dbId={1} />, {
useRedux: true,
});
@ -190,21 +189,23 @@ test('renders list of options when user clicks on schema', async () => {
const databaseSelect = screen.getByRole('combobox', {
name: 'Select database or type database name',
});
userEvent.click(databaseSelect);
expect(await screen.findByText('test-postgres')).toBeInTheDocument();
userEvent.click(screen.getByText('test-postgres'));
// Schema select will be automatically populated if there is only one schema
// Schema select should be disabled until database is selected
const schemaSelect = screen.getByRole('combobox', {
name: /select schema or type schema name/i,
});
userEvent.click(databaseSelect);
expect(await screen.findByText('test-postgres')).toBeInTheDocument();
expect(schemaSelect).toBeDisabled();
userEvent.click(screen.getByText('test-postgres'));
// Wait for schema field to be enabled
await waitFor(() => {
expect(schemaSelect).toBeEnabled();
});
});
test('searches for a table name', async () => {
render(<LeftPanel setDataset={mockFun} dataset={exampleDataset[0]} />, {
render(<LeftPanel setDataset={mockFun} schema="schema_a" dbId={1} />, {
useRedux: true,
});
@ -244,8 +245,9 @@ test('renders a warning icon when a table name has a pre-existing dataset', asyn
render(
<LeftPanel
setDataset={mockFun}
dataset={exampleDataset[0]}
datasetNames={['Sheet2']}
schema="schema_a"
dbId={1}
datasets={['Sheet2']}
/>,
{
useRedux: true,

View File

@ -16,13 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, {
useEffect,
useState,
SetStateAction,
Dispatch,
useCallback,
} from 'react';
import React, { useEffect, useState, SetStateAction, Dispatch } from 'react';
import {
SupersetClient,
t,
@ -46,16 +40,13 @@ import {
emptyStateComponent,
} from 'src/components/EmptyState';
import { useToasts } from 'src/components/MessageToasts/withToasts';
import { LocalStorageKeys, getItem } from 'src/utils/localStorageHelpers';
import {
DatasetActionType,
DatasetObject,
} from 'src/views/CRUD/data/dataset/AddDataset/types';
import { DatasetActionType } from '../types';
interface LeftPanelProps {
setDataset: Dispatch<SetStateAction<object>>;
dataset?: Partial<DatasetObject> | null;
datasetNames?: (string | null | undefined)[] | undefined;
schema?: string | null | undefined;
dbId?: number;
datasets?: (string | null | undefined)[] | undefined;
}
const SearchIcon = styled(Icons.Search)`
@ -154,8 +145,9 @@ const LeftPanelStyle = styled.div`
export default function LeftPanel({
setDataset,
dataset,
datasetNames,
schema,
dbId,
datasets,
}: LeftPanelProps) {
const theme = useTheme();
@ -168,14 +160,11 @@ export default function LeftPanel({
const { addDangerToast } = useToasts();
const setDatabase = useCallback(
(db: Partial<DatabaseObject>) => {
setDataset({ type: DatasetActionType.selectDatabase, payload: { db } });
setSelectedTable(null);
setResetTables(true);
},
[setDataset],
);
const setDatabase = (db: Partial<DatabaseObject>) => {
setDataset({ type: DatasetActionType.selectDatabase, payload: { db } });
setSelectedTable(null);
setResetTables(true);
};
const setTable = (tableName: string, index: number) => {
setSelectedTable(index);
@ -185,32 +174,28 @@ export default function LeftPanel({
});
};
const getTablesList = useCallback(
(url: string) => {
SupersetClient.get({ url })
.then(({ json }) => {
const options: TableOption[] = json.options.map((table: Table) => {
const option: TableOption = {
value: table.value,
label: <TableOption table={table} />,
text: table.label,
};
const getTablesList = (url: string) => {
SupersetClient.get({ url })
.then(({ json }) => {
const options: TableOption[] = json.options.map((table: Table) => {
const option: TableOption = {
value: table.value,
label: <TableOption table={table} />,
text: table.label,
};
return option;
});
setTableOptions(options);
setLoadTables(false);
setResetTables(false);
setRefresh(false);
})
.catch(error => {
addDangerToast(t('There was an error fetching tables'));
logging.error(t('There was an error fetching tables'), error);
return option;
});
},
[addDangerToast],
);
setTableOptions(options);
setLoadTables(false);
setResetTables(false);
setRefresh(false);
})
.catch(error =>
logging.error('There was an error fetching tables', error),
);
};
const setSchema = (schema: string) => {
if (schema) {
@ -224,28 +209,16 @@ export default function LeftPanel({
setResetTables(true);
};
const encodedSchema = dataset?.schema
? encodeURIComponent(dataset?.schema)
: undefined;
useEffect(() => {
const currentUserSelectedDb = getItem(
LocalStorageKeys.db,
null,
) as DatabaseObject;
if (currentUserSelectedDb) {
setDatabase(currentUserSelectedDb);
}
}, [setDatabase]);
const encodedSchema = schema ? encodeURIComponent(schema) : undefined;
useEffect(() => {
if (loadTables) {
const endpoint = encodeURI(
`/superset/tables/${dataset?.db?.id}/${encodedSchema}/${refresh}/`,
`/superset/tables/${dbId}/${encodedSchema}/${refresh}/`,
);
getTablesList(endpoint);
}
}, [loadTables, dataset?.db?.id, encodedSchema, getTablesList, refresh]);
}, [loadTables]);
useEffect(() => {
if (resetTables) {
@ -289,7 +262,6 @@ export default function LeftPanel({
{SELECT_DATABASE_AND_SCHEMA_TEXT}
</p>
<DatabaseSelector
db={dataset?.db}
handleError={addDangerToast}
onDbChange={setDatabase}
onSchemaChange={setSchema}
@ -297,7 +269,7 @@ export default function LeftPanel({
onEmptyResults={onEmptyResults}
/>
{loadTables && !refresh && Loader(TABLE_LOADING_TEXT)}
{dataset?.schema && !loadTables && !tableOptions.length && !searchVal && (
{schema && !loadTables && !tableOptions.length && !searchVal && (
<div className="emptystate">
<EmptyStateMedium
image="empty-table.svg"
@ -307,7 +279,7 @@ export default function LeftPanel({
</div>
)}
{dataset?.schema && (tableOptions.length > 0 || searchVal.length > 0) && (
{schema && (tableOptions.length > 0 || searchVal.length > 0) && (
<>
<Form>
<p className="table-title">{SELECT_DATABASE_TABLE_TEXT}</p>
@ -351,7 +323,7 @@ export default function LeftPanel({
onClick={() => setTable(option.value, i)}
>
{option.label}
{datasetNames?.includes(option.value) && (
{datasets?.includes(option.value) && (
<Icons.Warning
iconColor={
selectedTable === i

View File

@ -16,17 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, {
useReducer,
Reducer,
useEffect,
useState,
useCallback,
} from 'react';
import { logging, t } from '@superset-ui/core';
import React, { useReducer, Reducer, useEffect, useState } from 'react';
import { logging } from '@superset-ui/core';
import { UseGetDatasetsList } from 'src/views/CRUD/data/hooks';
import rison from 'rison';
import { addDangerToast } from 'src/components/MessageToasts/actions';
import Header from './Header';
import DatasetPanel from './DatasetPanel';
import LeftPanel from './LeftPanel';
@ -92,29 +85,27 @@ export default function AddDataset() {
const queryParams = dataset?.schema
? rison.encode_uri({
filters: [
{ col: 'database', opr: 'rel_o_m', value: dataset?.db?.id },
{ col: 'schema', opr: 'eq', value: encodedSchema },
{ col: 'sql', opr: 'dataset_is_null_or_empty', value: '!t' },
],
})
: undefined;
const getDatasetsList = useCallback(async () => {
const getDatasetsList = async () => {
await UseGetDatasetsList(queryParams)
.then(json => {
setDatasets(json?.result);
})
.catch(error => {
addDangerToast(t('There was an error fetching dataset'));
logging.error(t('There was an error fetching dataset'), error);
});
}, [queryParams]);
.catch(error =>
logging.error('There was an error fetching dataset', error),
);
};
useEffect(() => {
if (dataset?.schema) {
getDatasetsList();
}
}, [dataset?.schema, getDatasetsList]);
}, [dataset?.schema]);
const HeaderComponent = () => (
<Header setDataset={setDataset} title={dataset?.table_name} />
@ -123,8 +114,9 @@ export default function AddDataset() {
const LeftPanelComponent = () => (
<LeftPanel
setDataset={setDataset}
dataset={dataset}
datasetNames={datasetNames}
schema={dataset?.schema}
dbId={dataset?.db?.id}
datasets={datasetNames}
/>
);

View File

@ -16,8 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
import { DatabaseObject } from 'src/components/DatabaseSelector';
export enum DatasetActionType {
selectDatabase,
selectSchema,
@ -26,7 +24,11 @@ export enum DatasetActionType {
}
export interface DatasetObject {
db: DatabaseObject & { owners: [number] };
db: {
id: number;
database_name?: string;
owners?: number[];
};
schema?: string | null;
dataset_name: string;
table_name?: string | null;

View File

@ -0,0 +1,172 @@
/**
* 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, { FunctionComponent, useState, useEffect } from 'react';
import { styled, t } from '@superset-ui/core';
import { useSingleViewResource } from 'src/views/CRUD/hooks';
import Modal from 'src/components/Modal';
import TableSelector from 'src/components/TableSelector';
import withToasts from 'src/components/MessageToasts/withToasts';
import { DatabaseObject } from 'src/components/DatabaseSelector';
import {
getItem,
LocalStorageKeys,
setItem,
} from 'src/utils/localStorageHelpers';
import { isEmpty } from 'lodash';
type DatasetAddObject = {
id: number;
database: number;
schema: string;
table_name: string;
};
interface DatasetModalProps {
addDangerToast: (msg: string) => void;
addSuccessToast: (msg: string) => void;
onDatasetAdd?: (dataset: DatasetAddObject) => void;
onHide: () => void;
show: boolean;
history?: any; // So we can render the modal when not using SPA
}
const TableSelectorContainer = styled.div`
padding-bottom: 340px;
width: 65%;
`;
const DatasetModal: FunctionComponent<DatasetModalProps> = ({
addDangerToast,
onDatasetAdd,
onHide,
show,
history,
}) => {
const [currentDatabase, setCurrentDatabase] = useState<
DatabaseObject | undefined
>();
const [currentSchema, setSchema] = useState<string | undefined>('');
const [currentTableName, setTableName] = useState('');
const [disableSave, setDisableSave] = useState(true);
const {
createResource,
state: { loading },
} = useSingleViewResource<Partial<DatasetAddObject>>(
'dataset',
t('dataset'),
addDangerToast,
);
useEffect(() => {
setDisableSave(currentDatabase === undefined || currentTableName === '');
}, [currentTableName, currentDatabase]);
useEffect(() => {
const currentUserSelectedDb = getItem(
LocalStorageKeys.db,
null,
) as DatabaseObject;
if (currentUserSelectedDb) setCurrentDatabase(currentUserSelectedDb);
}, []);
const onDbChange = (db: DatabaseObject) => {
setCurrentDatabase(db);
};
const onSchemaChange = (schema?: string) => {
setSchema(schema);
};
const onTableChange = (tableName: string) => {
setTableName(tableName);
};
const clearModal = () => {
setSchema('');
setTableName('');
setCurrentDatabase(undefined);
setDisableSave(true);
};
const cleanup = () => {
clearModal();
setItem(LocalStorageKeys.db, null);
};
const hide = () => {
cleanup();
onHide();
};
const onSave = () => {
if (currentDatabase === undefined) {
return;
}
const data = {
database: currentDatabase.id,
...(currentSchema ? { schema: currentSchema } : {}),
table_name: currentTableName,
};
createResource(data).then(response => {
if (!response) {
return;
}
if (onDatasetAdd) {
onDatasetAdd({ id: response.id, ...response });
}
// We need to be able to work with no SPA routes opening the modal
// So useHistory wont be available always thus we check for it
if (!isEmpty(history)) {
history?.push(`/chart/add?dataset=${currentTableName}`);
cleanup();
} else {
window.location.href = `/chart/add?dataset=${currentTableName}`;
cleanup();
onHide();
}
});
};
return (
<Modal
disablePrimaryButton={disableSave}
primaryButtonLoading={loading}
onHandledPrimaryAction={onSave}
onHide={hide}
primaryButtonName={t('Add Dataset and Create Chart')}
show={show}
title={t('Add dataset')}
>
<TableSelectorContainer>
<TableSelector
clearable={false}
formMode
database={currentDatabase}
schema={currentSchema}
tableValue={currentTableName}
onDbChange={onDbChange}
onSchemaChange={onSchemaChange}
onTableSelectChange={onTableChange}
handleError={addDangerToast}
/>
</TableSelectorContainer>
</Modal>
);
};
export default withToasts(DatasetModal);

View File

@ -25,14 +25,6 @@ import DatasetPanel from 'src/views/CRUD/data/dataset/AddDataset/DatasetPanel';
import RightPanel from 'src/views/CRUD/data/dataset/AddDataset/RightPanel';
import Footer from 'src/views/CRUD/data/dataset/AddDataset/Footer';
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush,
}),
}));
describe('DatasetLayout', () => {
it('renders nothing when no components are passed in', () => {
render(<DatasetLayout />);

View File

@ -22,14 +22,16 @@ import React, {
useState,
useMemo,
useCallback,
useEffect,
} from 'react';
import { useHistory } from 'react-router-dom';
import rison from 'rison';
import { useHistory, useLocation } from 'react-router-dom';
import {
createFetchRelated,
createFetchDistinct,
createErrorHandler,
} from 'src/views/CRUD/utils';
import { getItem, LocalStorageKeys } from 'src/utils/localStorageHelpers';
import { ColumnObject } from 'src/views/CRUD/data/dataset/types';
import { useListViewResource } from 'src/views/CRUD/hooks';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
@ -58,6 +60,7 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
import { isUserAdmin } from 'src/dashboard/util/permissionUtils';
import { GenericLink } from 'src/components/GenericLink/GenericLink';
import AddDatasetModal from './AddDatasetModal';
import {
PAGE_SIZE,
@ -136,7 +139,6 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
addSuccessToast,
user,
}) => {
const history = useHistory();
const {
state: {
loading,
@ -150,6 +152,9 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
refreshData,
} = useListViewResource<Dataset>('dataset', t('dataset'), addDangerToast);
const [datasetAddModalOpen, setDatasetAddModalOpen] =
useState<boolean>(false);
const [datasetCurrentlyDeleting, setDatasetCurrentlyDeleting] = useState<
(Dataset & { chart_count: number; dashboard_count: number }) | null
>(null);
@ -186,6 +191,12 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT);
const initialSort = SORT_BY;
useEffect(() => {
const db = getItem(LocalStorageKeys.db, null);
if (!loading && db) {
setDatasetAddModalOpen(true);
}
}, [loading]);
const openDatasetEditModal = useCallback(
({ id }: Dataset) => {
@ -592,6 +603,26 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
});
}
const CREATE_HASH = '#create';
const location = useLocation();
const history = useHistory();
// Sync Dataset Add modal with #create hash
useEffect(() => {
const modalOpen = location.hash === CREATE_HASH && canCreate;
setDatasetAddModalOpen(modalOpen);
}, [canCreate, location.hash]);
// Add #create hash
const openDatasetAddModal = useCallback(() => {
history.replace(`${location.pathname}${location.search}${CREATE_HASH}`);
}, [history, location.pathname, location.search]);
// Remove #create hash
const closeDatasetAddModal = useCallback(() => {
history.replace(`${location.pathname}${location.search}`);
}, [history, location.pathname, location.search]);
if (canCreate) {
buttonArr.push({
name: (
@ -599,9 +630,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
<i className="fa fa-plus" /> {t('Dataset')}{' '}
</>
),
onClick: () => {
history.push('/dataset/add/');
},
onClick: openDatasetAddModal,
buttonStyle: 'primary',
});
@ -698,6 +727,12 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
return (
<>
<SubMenu {...menuData} />
<AddDatasetModal
show={datasetAddModalOpen}
onHide={closeDatasetAddModal}
onDatasetAdd={refreshData}
history={history}
/>
{datasetCurrentlyDeleting && (
<DeleteModal
description={t(

View File

@ -17,8 +17,7 @@
* under the License.
*/
import { useState, useEffect } from 'react';
import { SupersetClient, logging, t } from '@superset-ui/core';
import { addDangerToast } from 'src/components/MessageToasts/actions';
import { SupersetClient, logging } from '@superset-ui/core';
type BaseQueryObject = {
id: number;
@ -81,7 +80,6 @@ export const UseGetDatasetsList = (queryParams: string | undefined) =>
endpoint: `/api/v1/dataset/?q=${queryParams}`,
})
.then(({ json }) => json)
.catch(error => {
addDangerToast(t('There was an error fetching dataset'));
logging.error(t('There was an error fetching dataset'), error);
});
.catch(error =>
logging.error('There was an error fetching dataset', error),
);

View File

@ -30,6 +30,9 @@ jest.mock('react-redux', () => ({
}));
jest.mock('src/views/CRUD/data/database/DatabaseModal', () => () => <span />);
jest.mock('src/views/CRUD/data/dataset/AddDatasetModal.tsx', () => () => (
<span />
));
const dropdownItems = [
{

View File

@ -51,6 +51,7 @@ import {
GlobalMenuDataOptions,
RightMenuProps,
} from './types';
import AddDatasetModal from '../CRUD/data/dataset/AddDatasetModal';
const extensionsRegistry = getExtensionsRegistry();
@ -142,6 +143,7 @@ const RightMenu = ({
HAS_GSHEETS_INSTALLED,
} = useSelector<any, ExtentionConfigs>(state => state.common.conf);
const [showDatabaseModal, setShowDatabaseModal] = useState<boolean>(false);
const [showDatasetModal, setShowDatasetModal] = useState<boolean>(false);
const [engine, setEngine] = useState<string>('');
const canSql = findPermission('can_sqllab', 'Superset', roles);
const canDashboard = findPermission('can_write', 'Dashboard', roles);
@ -177,7 +179,6 @@ const RightMenu = ({
{
label: t('Create dataset'),
name: GlobalMenuDataOptions.DATASET_CREATION,
url: '/dataset/add/',
perm: canDataset && nonExamplesDBConnected,
},
{
@ -285,6 +286,8 @@ const RightMenu = ({
} else if (itemChose.key === GlobalMenuDataOptions.GOOGLE_SHEETS) {
setShowDatabaseModal(true);
setEngine('Google Sheets');
} else if (itemChose.key === GlobalMenuDataOptions.DATASET_CREATION) {
setShowDatasetModal(true);
}
};
@ -293,6 +296,10 @@ const RightMenu = ({
setShowDatabaseModal(false);
};
const handleOnHideDatasetModalModal = () => {
setShowDatasetModal(false);
};
const isDisabled = isAdmin && !allowUploads;
const tooltipText = t(
@ -337,6 +344,7 @@ const RightMenu = ({
);
const handleDatabaseAdd = () => setQuery({ databaseAdded: true });
const handleDatasetAdd = () => setQuery({ datasetAdded: true });
const theme = useTheme();
@ -350,6 +358,13 @@ const RightMenu = ({
onDatabaseAdd={handleDatabaseAdd}
/>
)}
{canDataset && (
<AddDatasetModal
onHide={handleOnHideDatasetModalModal}
show={showDatasetModal}
onDatasetAdd={handleDatasetAdd}
/>
)}
{environmentTag?.text && (
<Label
css={{ borderRadius: `${theme.gridUnit * 125}px` }}

View File

@ -14,6 +14,8 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
import logging
import os
import re
@ -206,7 +208,7 @@ class HiveEngineSpec(PrestoEngineSpec):
with cls.get_engine(database) as engine:
engine.execute(f"DROP TABLE IF EXISTS {str(table)}")
def _get_hive_type(dtype: np.dtype) -> str:
def _get_hive_type(dtype: np.dtype[Any]) -> str:
hive_type_by_dtype = {
np.dtype("bool"): "BOOLEAN",
np.dtype("float64"): "DOUBLE",

View File

@ -14,11 +14,13 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
import json
import logging
from operator import eq, ge, gt, le, lt, ne
from timeit import default_timer
from typing import Optional
from typing import Any, Optional
import numpy as np
import pandas as pd
@ -84,12 +86,12 @@ class AlertCommand(BaseCommand):
except (KeyError, json.JSONDecodeError) as ex:
raise AlertValidatorConfigError() from ex
def _validate_not_null(self, rows: np.recarray) -> None:
def _validate_not_null(self, rows: np.recarray[Any, Any]) -> None:
self._validate_result(rows)
self._result = rows[0][1]
@staticmethod
def _validate_result(rows: np.recarray) -> None:
def _validate_result(rows: np.recarray[Any, Any]) -> None:
# check if query return more than one row
if len(rows) > 1:
raise AlertQueryMultipleRowsError(
@ -108,7 +110,7 @@ class AlertCommand(BaseCommand):
)
)
def _validate_operator(self, rows: np.recarray) -> None:
def _validate_operator(self, rows: np.recarray[Any, Any]) -> None:
self._validate_result(rows)
if rows[0][1] in (0, None, np.nan):
self._result = 0.0

View File

@ -24,6 +24,7 @@ from typing import Any, Dict, List, Optional, Tuple, Type
import numpy as np
import pandas as pd
import pyarrow as pa
from numpy.typing import NDArray
from superset.db_engine_specs import BaseEngineSpec
from superset.superset_typing import DbapiDescription, DbapiResult, ResultSetColumnType
@ -62,16 +63,16 @@ def stringify(obj: Any) -> str:
return json.dumps(obj, default=utils.json_iso_dttm_ser)
def stringify_values(array: np.ndarray) -> np.ndarray:
def stringify_values(array: NDArray[Any]) -> NDArray[Any]:
result = np.copy(array)
with np.nditer(result, flags=["refs_ok"], op_flags=["readwrite"]) as it:
with np.nditer(result, flags=["refs_ok"], op_flags=[["readwrite"]]) as it:
for obj in it:
if pd.isna(obj):
if na_obj := pd.isna(obj):
# pandas <NA> type cannot be converted to string
obj[pd.isna(obj)] = None
obj[na_obj] = None # type: ignore
else:
obj[...] = stringify(obj)
obj[...] = stringify(obj) # type: ignore
return result
@ -106,7 +107,7 @@ class SupersetResultSet:
pa_data: List[pa.Array] = []
deduped_cursor_desc: List[Tuple[Any, ...]] = []
numpy_dtype: List[Tuple[str, ...]] = []
stringified_arr: np.ndarray
stringified_arr: NDArray[Any]
if cursor_description:
# get deduped list of column names
@ -208,7 +209,7 @@ class SupersetResultSet:
return table.to_pandas(integer_object_nulls=True, timestamp_as_object=True)
@staticmethod
def first_nonempty(items: List[Any]) -> Any:
def first_nonempty(items: NDArray[Any]) -> Any:
return next((i for i in items if i), None)
def is_temporal(self, db_type_str: Optional[str]) -> bool:

View File

@ -57,10 +57,10 @@ def boxplot(
"""
def quartile1(series: Series) -> float:
return np.nanpercentile(series, 25, interpolation="midpoint")
return np.nanpercentile(series, 25, interpolation="midpoint") # type: ignore
def quartile3(series: Series) -> float:
return np.nanpercentile(series, 75, interpolation="midpoint")
return np.nanpercentile(series, 75, interpolation="midpoint") # type: ignore
if whisker_type == PostProcessingBoxplotWhiskerType.TUKEY:
@ -99,8 +99,8 @@ def boxplot(
return np.nanpercentile(series, low)
else:
whisker_high = np.max
whisker_low = np.min
whisker_high = np.max # type: ignore
whisker_low = np.min # type: ignore
def outliers(series: Series) -> Set[float]:
above = series[series > whisker_high(series)]
@ -126,7 +126,7 @@ def boxplot(
# nanpercentile needs numeric values, otherwise the isnan function
# that's used in the underlying function will fail
for column in metrics:
if df.dtypes[column] == np.object:
if df.dtypes[column] == np.object_:
df[column] = to_numeric(df[column], errors="coerce")
return aggregate(df, groupby=groupby, aggregates=aggregates)

View File

@ -85,7 +85,7 @@ def flatten(
_columns = []
for series in df.columns.to_flat_index():
_cells = []
for cell in series if is_sequence(series) else [series]:
for cell in series if is_sequence(series) else [series]: # type: ignore
if pd.notnull(cell):
# every cell should be converted to string and escape comma
_cells.append(escape_separator(str(cell)))

View File

@ -24,7 +24,7 @@ from pandas import DataFrame, NamedAgg
from superset.exceptions import InvalidPostProcessingError
NUMPY_FUNCTIONS = {
NUMPY_FUNCTIONS: Dict[str, Callable[..., Any]] = {
"average": np.average,
"argmin": np.argmin,
"argmax": np.argmax,

View File

@ -228,7 +228,10 @@ class DatasetEditor(BaseSupersetView):
@has_access
@permission_name("read")
def root(self) -> FlaskResponse:
return super().render_app_template()
dev = request.args.get("testing")
if dev is not None:
return super().render_app_template()
return redirect("/")
@expose("/<pk>", methods=["GET"])
@has_access