Skip to content

Commit

Permalink
feat: add empty states to sqlab editor and select (#19598)
Browse files Browse the repository at this point in the history
* feat: add empty states to sqlab editor and select

* add suggestions and test

* update type

* lint fix and add suggestions

* fix typo

* run lint

* remove unused code

* fix test

* remove redux for propagation and other suggestions

* add t

* lint

* fix text and remove code

* ts and fix t in p

* fix spelling

* remove unused prop

* add fn to prop change state

* remove unused code

* remove unused types

* update code and test

* fix lint

* fix ts

* update ts

* add type export and fix test

* Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* remove handlerror and unused code

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
  • Loading branch information
pkdotson and michael-s-molina committed Apr 15, 2022
1 parent 154f1ea commit 06ec88e
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 17 deletions.
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 @@ -180,6 +182,7 @@ class SqlEditor extends React.PureComponent {
),
showCreateAsModal: false,
createAs: '',
showEmptyState: false,
};
this.sqlEditorRef = React.createRef();
this.northPaneRef = React.createRef();
Expand All @@ -189,6 +192,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 @@ -228,7 +232,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 @@ -369,6 +377,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 @@ -760,10 +772,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
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,28 @@ 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 = await screen.findByText('empty');
expect(emptystate).toBeInTheDocument();
expect(screen.queryByText('test-mysql')).not.toBeInTheDocument();
});

test('Should schema select display options', async () => {
const props = createProps();
render(<DatabaseSelector {...props} />, { useRedux: true });
Expand Down
16 changes: 11 additions & 5 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,7 +186,7 @@ export default function DatabaseSelector({
getDbList(result);
}
if (result.length === 0) {
handleError(t("It seems you don't have access to any database"));
if (onEmptyResults) onEmptyResults(search);
}
const options = result.map((row: DatabaseObject) => ({
label: (
Expand All @@ -197,13 +202,14 @@ export default function DatabaseSelector({
allow_multi_schema_metadata_fetch:
row.allow_multi_schema_metadata_fetch,
}));

return {
data: options,
totalCount: options.length,
};
});
},
[formMode, getDbList, handleError, sqlLabMode],
[formMode, getDbList, sqlLabMode],
);

useEffect(() => {
Expand Down Expand Up @@ -272,6 +278,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 +296,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

0 comments on commit 06ec88e

Please sign in to comment.