Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add empty states to sqlab editor and select #19598

Merged
merged 31 commits into from
Apr 15, 2022
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7dadc58
feat: add empty states to sqlab editor and select
pkdotson Apr 7, 2022
d9984ea
add suggestions and test
pkdotson Apr 11, 2022
aa5e8bc
update type
pkdotson Apr 11, 2022
5d04b36
Merge branch 'master' of https://github.com/preset-io/superset into f…
pkdotson Apr 11, 2022
cd4abf9
lint fix and add suggestions
pkdotson Apr 11, 2022
e417d79
fix typo
pkdotson Apr 11, 2022
d088f14
run lint
pkdotson Apr 11, 2022
68d5827
remove unused code
pkdotson Apr 11, 2022
bae0137
fix test
pkdotson Apr 12, 2022
bdcda07
remove redux for propagation and other suggestions
pkdotson Apr 13, 2022
efcdf4b
add t
pkdotson Apr 13, 2022
7f15b18
Merge branch 'master' of https://github.com/preset-io/superset into f…
pkdotson Apr 13, 2022
61ee6c6
lint
pkdotson Apr 13, 2022
7255d35
fix text and remove code
pkdotson Apr 13, 2022
19283db
ts and fix t in p
pkdotson Apr 13, 2022
dbed5fe
fix spelling
pkdotson Apr 13, 2022
f1eba19
remove unused prop
pkdotson Apr 13, 2022
7bca734
add fn to prop change state
pkdotson Apr 14, 2022
a586d38
remove unused code
pkdotson Apr 14, 2022
a5c5bf2
remove unused types
pkdotson Apr 14, 2022
c705ce6
update code and test
pkdotson Apr 15, 2022
94d3485
fix lint
pkdotson Apr 15, 2022
d222fea
fix ts
pkdotson Apr 15, 2022
66e9c46
update ts
pkdotson Apr 15, 2022
8bd7cf6
add type export and fix test
pkdotson Apr 15, 2022
9adc5ea
Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index…
pkdotson Apr 15, 2022
2a4995c
Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index…
pkdotson Apr 15, 2022
8217c2c
Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index…
pkdotson Apr 15, 2022
925e107
Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index…
pkdotson Apr 15, 2022
bf36826
remove handlerror and unused code
pkdotson Apr 15, 2022
b628c7c
Merge branch 'feat-blackstate-sql1' of https://github.com/preset-io/s…
pkdotson Apr 15, 2022
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 @@ -38,6 +38,7 @@ import {
queryEditorSetSelectedText,
queryEditorSetSchemaOptions,
} from 'src/SqlLab/actions/sqlLab';
import { EmptyStateBig } from 'src/components/EmptyState';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import { initialState, queries, table } from 'src/SqlLab/fixtures';

Expand All @@ -57,7 +58,19 @@ describe('SqlEditor', () => {
queryEditorSetSchemaOptions,
addDangerToast: jest.fn(),
},
database: {},
database: {
allow_ctas: false,
allow_cvas: false,
allow_dml: false,
allow_file_upload: false,
allow_multi_schema_metadata_fetch: false,
allow_run_async: false,
backend: 'postgresql',
database_name: 'examples',
expose_in_sqllab: true,
force_ctas_schema: null,
id: 1,
},
queryEditorId: initialState.sqlLab.queryEditors[0].id,
latestQuery: queries[0],
tables: [table],
Expand All @@ -80,6 +93,12 @@ describe('SqlEditor', () => {
},
);

it('does not render SqlEditor if no db selected', () => {
const database = {};
const updatedProps = { ...mockedProps, database };
const wrapper = buildWrapper(updatedProps);
expect(wrapper.find(EmptyStateBig)).toExist();
});
it('render a SqlEditorLeftBar', async () => {
const wrapper = buildWrapper();
await waitForComponentToPaint(wrapper);
Expand Down
25 changes: 24 additions & 1 deletion superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ import {
setItem,
} from 'src/utils/localStorageHelpers';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import { EmptyStateBig } from 'src/components/EmptyState';
import { isEmpty } from 'lodash';
import TemplateParamsEditor from '../TemplateParamsEditor';
import ConnectedSouthPane from '../SouthPane/state';
import SaveQuery from '../SaveQuery';
Expand Down Expand Up @@ -179,6 +181,7 @@ class SqlEditor extends React.PureComponent {
),
showCreateAsModal: false,
createAs: '',
showEmptyState: false,
};
this.sqlEditorRef = React.createRef();
this.northPaneRef = React.createRef();
Expand All @@ -188,6 +191,7 @@ class SqlEditor extends React.PureComponent {
this.onResizeEnd = this.onResizeEnd.bind(this);
this.canValidateQuery = this.canValidateQuery.bind(this);
this.runQuery = this.runQuery.bind(this);
this.setEmptyState = this.setEmptyState.bind(this);
this.stopQuery = this.stopQuery.bind(this);
this.saveQuery = this.saveQuery.bind(this);
this.onSqlChanged = this.onSqlChanged.bind(this);
Expand Down Expand Up @@ -227,7 +231,11 @@ class SqlEditor extends React.PureComponent {
// We need to measure the height of the sql editor post render to figure the height of
// the south pane so it gets rendered properly
// eslint-disable-next-line react/no-did-mount-set-state
const db = this.props.database;
this.setState({ height: this.getSqlEditorHeight() });
if (!db || isEmpty(db)) {
this.setEmptyState(true);
}

window.addEventListener('resize', this.handleWindowResize);
window.addEventListener('beforeunload', this.onBeforeUnload);
Expand Down Expand Up @@ -362,6 +370,10 @@ class SqlEditor extends React.PureComponent {
return base;
}

setEmptyState(bool) {
this.setState({ showEmptyState: bool });
}

setQueryEditorSql(sql) {
this.props.queryEditorSetSql(this.props.queryEditor, sql);
}
Expand Down Expand Up @@ -753,10 +765,21 @@ class SqlEditor extends React.PureComponent {
queryEditor={this.props.queryEditor}
tables={this.props.tables}
actions={this.props.actions}
setEmptyState={this.setEmptyState}
/>
</div>
</CSSTransition>
{this.queryPane()}
{this.state.showEmptyState ? (
<EmptyStateBig
image="vector.svg"
title={t('Select a database to write a query')}
description={t(
'Choose one of the available databases from the panel on the left.',
)}
/>
) : (
this.queryPane()
)}
<StyledModal
visible={this.state.showCreateAsModal}
title={t(createViewModalTitle)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useEffect, useRef, useCallback, useMemo } from 'react';
import React, {
useEffect,
useRef,
useCallback,
useMemo,
useState,
Dispatch,
SetStateAction,
} from 'react';
import Button from 'src/components/Button';
import { t, styled, css, SupersetTheme } from '@superset-ui/core';
import Collapse from 'src/components/Collapse';
Expand All @@ -25,6 +33,7 @@ import { TableSelectorMultiple } from 'src/components/TableSelector';
import { IconTooltip } from 'src/components/IconTooltip';
import { QueryEditor } from 'src/SqlLab/types';
import { DatabaseObject } from 'src/components/DatabaseSelector';
import { EmptyStateSmall } from 'src/components/EmptyState';
import TableElement, { Table, TableElementProps } from '../TableElement';

interface ExtendedTable extends Table {
Expand Down Expand Up @@ -54,6 +63,8 @@ interface SqlEditorLeftBarProps {
tables?: ExtendedTable[];
actions: actionsTypes & TableElementProps['actions'];
database: DatabaseObject;
setEmptyState: Dispatch<SetStateAction<boolean>>;
showDisabled: boolean;
}

const StyledScrollbarContainer = styled.div`
Expand Down Expand Up @@ -88,15 +99,23 @@ export default function SqlEditorLeftBar({
queryEditor,
tables = [],
height = 500,
setEmptyState,
}: SqlEditorLeftBarProps) {
// Ref needed to avoid infinite rerenders on handlers
// that require and modify the queryEditor
const queryEditorRef = useRef<QueryEditor>(queryEditor);
const [emptyResultsWithSearch, setEmptyResultsWithSearch] = useState(false);

useEffect(() => {
queryEditorRef.current = queryEditor;
}, [queryEditor]);

const onEmptyResults = (searchText?: string) => {
setEmptyResultsWithSearch(!!searchText);
};

const onDbChange = ({ id: dbId }: { id: number }) => {
setEmptyState(false);
actions.queryEditorSetDb(queryEditor, dbId);
actions.queryEditorSetFunctionNames(queryEditor, dbId);
};
Expand Down Expand Up @@ -164,6 +183,22 @@ export default function SqlEditorLeftBar({
const shouldShowReset = window.location.search === '?reset=1';
const tableMetaDataHeight = height - 130; // 130 is the height of the selects above

const emptyStateComponent = (
<EmptyStateSmall
image="empty.svg"
title={
emptyResultsWithSearch
? t('No databases match your search')
: t('There are no databases available')
}
description={
<p>
{t('Manage your databases')}{' '}
<a href="/databaseview/list">{t('here')}</a>
</p>
}
/>
);
const handleSchemaChange = useCallback(
(schema: string) => {
if (queryEditorRef.current) {
Expand All @@ -185,6 +220,8 @@ export default function SqlEditorLeftBar({
return (
<div className="SqlEditorLeftBar">
<TableSelectorMultiple
onEmptyResults={onEmptyResults}
emptyState={emptyStateComponent}
database={database}
getDbList={actions.setDatabases}
handleError={actions.addDangerToast}
Expand Down
1 change: 1 addition & 0 deletions superset-frontend/src/SqlLab/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export type RootState = {
activeSouthPaneTab: string | number; // default is string; action.newQuery.id is number
alerts: any[];
databases: Record<string, any>;
dbConnect: boolean;
offline: boolean;
queries: Query[];
queryEditors: QueryEditor[];
Expand Down
21 changes: 21 additions & 0 deletions superset-frontend/src/assets/images/vector.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import React from 'react';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import { SupersetClient } from '@superset-ui/core';
import userEvent from '@testing-library/user-event';
import DatabaseSelector from '.';
import DatabaseSelector, { DatabaseSelectorProps } from '.';
import { EmptyStateSmall } from '../EmptyState';

const SupersetClientGet = jest.spyOn(SupersetClient, 'get');

const createProps = () => ({
const createProps = (): DatabaseSelectorProps => ({
db: {
id: 1,
database_name: 'test',
Expand All @@ -38,12 +39,10 @@ const createProps = () => ({
schema: undefined,
sqlLabMode: true,
getDbList: jest.fn(),
getTableList: jest.fn(),
handleError: jest.fn(),
onDbChange: jest.fn(),
onSchemaChange: jest.fn(),
onSchemasLoad: jest.fn(),
onUpdate: jest.fn(),
});

beforeEach(() => {
Expand Down Expand Up @@ -191,25 +190,21 @@ test('Refresh should work', async () => {
await waitFor(() => {
expect(SupersetClientGet).toBeCalledTimes(2);
expect(props.getDbList).toBeCalledTimes(0);
expect(props.getTableList).toBeCalledTimes(0);
expect(props.handleError).toBeCalledTimes(0);
expect(props.onDbChange).toBeCalledTimes(0);
expect(props.onSchemaChange).toBeCalledTimes(0);
expect(props.onSchemasLoad).toBeCalledTimes(0);
expect(props.onUpdate).toBeCalledTimes(0);
});

userEvent.click(screen.getByRole('button', { name: 'refresh' }));

await waitFor(() => {
expect(SupersetClientGet).toBeCalledTimes(3);
expect(props.getDbList).toBeCalledTimes(1);
expect(props.getTableList).toBeCalledTimes(0);
expect(props.handleError).toBeCalledTimes(0);
expect(props.onDbChange).toBeCalledTimes(0);
expect(props.onSchemaChange).toBeCalledTimes(0);
expect(props.onSchemasLoad).toBeCalledTimes(2);
expect(props.onUpdate).toBeCalledTimes(0);
});
});

Expand All @@ -224,6 +219,29 @@ test('Should database select display options', async () => {
expect(await screen.findByText('test-mysql')).toBeInTheDocument();
});

test('should show empty state if there are no options', async () => {
SupersetClientGet.mockImplementation(
async () => ({ json: { result: [] } } as any),
);
const props = createProps();
render(
<DatabaseSelector
{...props}
db={undefined}
emptyState={<EmptyStateSmall title="empty" image="" />}
/>,
{ useRedux: true },
);
const select = screen.getByRole('combobox', {
name: 'Select database or type database name',
});
userEvent.click(select);
// const emptystate = getByAltText('empty');
pkdotson marked this conversation as resolved.
Show resolved Hide resolved
const emptystate = await screen.findByText('empty');
expect(emptystate).toBeInTheDocument();
expect(screen.queryByText('test-mysql')).not.toBeInTheDocument();
pkdotson marked this conversation as resolved.
Show resolved Hide resolved
});

test('Should schema select display options', async () => {
const props = createProps();
render(<DatabaseSelector {...props} />, { useRedux: true });
Expand Down
13 changes: 10 additions & 3 deletions superset-frontend/src/components/DatabaseSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,15 @@ export type DatabaseObject = {

type SchemaValue = { label: string; value: string };

interface DatabaseSelectorProps {
export interface DatabaseSelectorProps {
db?: DatabaseObject;
emptyState?: ReactNode;
formMode?: boolean;
getDbList?: (arg0: any) => {};
handleError: (msg: string) => void;
isDatabaseSelectEnabled?: boolean;
onDbChange?: (db: DatabaseObject) => void;
onEmptyResults?: (searchText?: string) => void;
onSchemaChange?: (schema?: string) => void;
onSchemasLoad?: (schemas: Array<object>) => void;
readOnly?: boolean;
Expand All @@ -118,10 +120,12 @@ const SelectLabel = ({
export default function DatabaseSelector({
db,
formMode = false,
emptyState,
getDbList,
handleError,
isDatabaseSelectEnabled = true,
onDbChange,
onEmptyResults,
onSchemaChange,
onSchemasLoad,
readOnly = false,
Expand All @@ -146,6 +150,7 @@ export default function DatabaseSelector({
);
const [refresh, setRefresh] = useState(0);
const { addSuccessToast } = useToasts();

const loadDatabases = useMemo(
() =>
async (
Expand Down Expand Up @@ -181,6 +186,7 @@ export default function DatabaseSelector({
getDbList(result);
}
if (result.length === 0) {
if (onEmptyResults) onEmptyResults(search);
handleError(t("It seems you don't have access to any database"));
}
const options = result.map((row: DatabaseObject) => ({
Expand All @@ -197,6 +203,7 @@ export default function DatabaseSelector({
allow_multi_schema_metadata_fetch:
row.allow_multi_schema_metadata_fetch,
}));

return {
data: options,
totalCount: options.length,
Expand Down Expand Up @@ -272,6 +279,7 @@ export default function DatabaseSelector({
data-test="select-database"
header={<FormLabel>{t('Database')}</FormLabel>}
lazyLoading={false}
notFoundContent={emptyState}
onChange={changeDataBase}
value={currentDb}
placeholder={t('Select database or type database name')}
Expand All @@ -289,11 +297,10 @@ export default function DatabaseSelector({
tooltipContent={t('Force refresh schema list')}
/>
);

return renderSelectRow(
<Select
ariaLabel={t('Select schema or type schema name')}
disabled={readOnly}
disabled={!currentDb || readOnly}
header={<FormLabel>{t('Schema')}</FormLabel>}
labelInValue
lazyLoading={false}
Expand Down
Loading