feat(explore): Frontend implementation of dataset creation from infobox (#19855)

* Frontend implementation of create dataset from infobox

* Fixed sl_dataset type

* Fix test

* Fixed sl_dataset type (forgot to save)

* RTL testing

* Adjusted styling/text on infobox and save dataset modal

* Appease lint

* Make infobox invisible and fix tests

* Remove unnecessary placeholder

* Move types to sql lab

* Moved logic into save dataset modal

* Change DatasourceMeta type to Dataset

* Add ExploreDatasource union type to save dataset modal

* Get user info from redux inside save dataset modal

* Addressed comments

* Adjusting to new query type

* Fixed save dataset in explore and union type

* Added testing

* Defined d for queries

* Remove dataset from SaveDatasetModal

* Clarify useSelector parameter

* Fix dndControls union type

* Fix shared-controls union type

* Fix controlPanel union type

* Move ExploreRootState to explore type file

* Remove unnecessary testing playground

* Move datasource type check in DatasourcePanel to a function

* Make all sqllab Query imports reference @superset-ui/core Query type

* Deconstruct query props in ResultSet

* Fix union type in /legacy-plugin-chart-heatmap/src/controlPanel

* Change SaveDatasetModal tests to RTL

* Cleaned datasourceTypeCheck

* Fix infobox styling

* Fix SaveDatasetModal test

* Fix query fixture in sqllab and Query type in SaveDatasetModal test

* Fix Query type and make test query fixture

* Added columns to Query type, separated results property, created QueryResponse union type, and fixed all types affected

* Fixed a couple missed broken types

* Added ExploreDatasource to SqlLab type file

* Removed unneeded Query import from DatasourcePanel

* Address PR comments

* Fix columnChoices

* Fix all incorrect column property checks

* Fix logic on dndGroupByControl

* Dry up savedMetrics type check

* Fixed TIME_COLUMN_OPTION

* Dried savedMetrics type check even further

* Change savedMetricsTypeCheck to defineSavedMetrics

* Change datasourceTypeCheck to isValidDatasourceType

* Fix Query path in groupByControl

* dnd_granularity_sqla now sorts Query types with is_dttm at the top

* Fixed/cleaned query sort

* Add sortedQueryColumns and proper optional chaining to granularity_sqla

* Move testQuery to core-ui, add test coverage for Queries in columnChoices

* Moved DEFAULT_METRICS to core-ui and wrote a test for defineSavedMetrics

* Add license and clean dataset test object

* Change DatasourceType.Dataset to dataset
This commit is contained in:
Lyndsi Kay Williams
2022-06-07 15:03:45 -05:00
committed by GitHub
parent d1c24f81f2
commit ba0c37d3df
40 changed files with 1125 additions and 685 deletions

View File

@@ -92,19 +92,19 @@ function search(value: string, input: HTMLElement) {
}
test('should render', () => {
const { container } = render(setup(props));
const { container } = render(setup(props), { useRedux: true });
expect(container).toBeVisible();
});
test('should display items in controls', () => {
render(setup(props));
render(setup(props), { useRedux: true });
expect(screen.getByText('birth_names')).toBeInTheDocument();
expect(screen.getByText('Metrics')).toBeInTheDocument();
expect(screen.getByText('Columns')).toBeInTheDocument();
});
test('should render the metrics', () => {
render(setup(props));
render(setup(props), { useRedux: true });
const metricsNum = metrics.length;
metrics.forEach(metric =>
expect(screen.getByText(metric.metric_name)).toBeInTheDocument(),
@@ -115,7 +115,7 @@ test('should render the metrics', () => {
});
test('should render the columns', () => {
render(setup(props));
render(setup(props), { useRedux: true });
const columnsNum = columns.length;
columns.forEach(col =>
expect(screen.getByText(col.column_name)).toBeInTheDocument(),
@@ -126,7 +126,7 @@ test('should render the columns', () => {
});
test('should render 0 search results', async () => {
render(setup(props));
render(setup(props), { useRedux: true });
const searchInput = screen.getByPlaceholderText('Search Metrics & Columns');
search('nothing', searchInput);
@@ -134,7 +134,7 @@ test('should render 0 search results', async () => {
});
test('should search and render matching columns', async () => {
render(setup(props));
render(setup(props), { useRedux: true });
const searchInput = screen.getByPlaceholderText('Search Metrics & Columns');
search(columns[0].column_name, searchInput);
@@ -146,7 +146,7 @@ test('should search and render matching columns', async () => {
});
test('should search and render matching metrics', async () => {
render(setup(props));
render(setup(props), { useRedux: true });
const searchInput = screen.getByPlaceholderText('Search Metrics & Columns');
search(metrics[0].metric_name, searchInput);
@@ -174,8 +174,68 @@ test('should render a warning', async () => {
},
},
}),
{ useRedux: true },
);
expect(
await screen.findByRole('img', { name: 'alert-solid' }),
).toBeInTheDocument();
});
test('should render a create dataset infobox', () => {
render(
setup({
...props,
datasource: {
...datasource,
type: DatasourceType.Query,
},
}),
{ useRedux: true },
);
const createButton = screen.getByRole('button', {
name: /create a dataset/i,
});
const infoboxText = screen.getByText(/to edit or add columns and metrics./i);
expect(createButton).toBeVisible();
expect(infoboxText).toBeVisible();
});
test('should render a save dataset modal when "Create a dataset" is clicked', () => {
render(
setup({
...props,
datasource: {
...datasource,
type: DatasourceType.Query,
},
}),
{ useRedux: true },
);
const createButton = screen.getByRole('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', () => {
render(
setup({
...props,
datasource: {
...datasource,
type: DatasourceType.Table,
},
}),
{ useRedux: true },
);
expect(screen.queryByText(/create a dataset/i)).toBe(null);
});

View File

@@ -17,32 +17,33 @@
* under the License.
*/
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { css, styled, t } from '@superset-ui/core';
import { css, styled, t, DatasourceType } from '@superset-ui/core';
import {
ControlConfig,
DatasourceMeta,
Dataset,
ColumnMeta,
} from '@superset-ui/chart-controls';
import { debounce } from 'lodash';
import { matchSorter, rankings } from 'match-sorter';
import Collapse from 'src/components/Collapse';
import Alert from 'src/components/Alert';
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
import { Input } from 'src/components/Input';
import { FAST_DEBOUNCE } from 'src/constants';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import { ExploreActions } from 'src/explore/actions/exploreActions';
import Control from 'src/explore/components/Control';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
import { ExploreDatasource } from 'src/SqlLab/types';
import DatasourcePanelDragOption from './DatasourcePanelDragOption';
import { DndItemType } from '../DndItemType';
import { StyledColumnOption, StyledMetricOption } from '../optionRenderers';
interface DatasourceControl extends ControlConfig {
datasource?: DatasourceMeta;
user: UserWithPermissionsAndRoles;
datasource?: ExploreDatasource;
}
export interface Props {
datasource: DatasourceMeta;
datasource: Dataset;
controls: {
datasource: DatasourceControl;
};
@@ -154,6 +155,16 @@ const SectionHeader = styled.span`
`}
`;
const StyledInfoboxWrapper = styled.div`
${({ theme }) => css`
margin: 0 ${theme.gridUnit * 2.5}px;
span {
text-decoration: underline;
}
`}
`;
const LabelContainer = (props: {
children: React.ReactElement;
className: string;
@@ -192,6 +203,7 @@ export default function DataSourcePanel({
[_columns],
);
const [showSaveDatasetModal, setShowSaveDatasetModal] = useState(false);
const [inputValue, setInputValue] = useState('');
const [lists, setList] = useState({
columns,
@@ -279,6 +291,7 @@ export default function DataSourcePanel({
: lists.metrics.slice(0, DEFAULT_MAX_METRICS_LENGTH),
[lists.metrics, showAllMetrics],
);
const columnSlice = useMemo(
() =>
showAllColumns
@@ -289,6 +302,17 @@ export default function DataSourcePanel({
[lists.columns, showAllColumns],
);
const showInfoboxCheck = () => {
if (sessionStorage.getItem('showInfobox') === 'false') return false;
return true;
};
const isValidDatasourceType =
datasource.type === DatasourceType.Dataset ||
datasource.type === DatasourceType.SlTable ||
datasource.type === DatasourceType.SavedQuery ||
datasource.type === DatasourceType.Query;
const mainBody = useMemo(
() => (
<>
@@ -303,6 +327,29 @@ export default function DataSourcePanel({
placeholder={t('Search Metrics & Columns')}
/>
<div className="field-selections">
{isValidDatasourceType && showInfoboxCheck() && (
<StyledInfoboxWrapper>
<Alert
closable
onClose={() => sessionStorage.setItem('showInfobox', 'false')}
type="info"
message=""
description={
<>
<span
role="button"
tabIndex={0}
onClick={() => setShowSaveDatasetModal(true)}
className="add-dataset-alert-description"
>
{t('Create a dataset')}
</span>
{t(' to edit or add columns and metrics.')}
</>
}
/>
</StyledInfoboxWrapper>
)}
<Collapse
defaultActiveKey={['metrics', 'column']}
expandIconPosition="right"
@@ -399,6 +446,13 @@ export default function DataSourcePanel({
return (
<DatasourceContainer>
<SaveDatasetModal
visible={showSaveDatasetModal}
onHide={() => setShowSaveDatasetModal(false)}
buttonTextOnSave={t('Save')}
buttonTextOnOverwrite={t('Overwrite')}
datasource={datasource}
/>
<Control {...datasourceControl} name="datasource" actions={actions} />
{datasource.id != null && mainBody}
</DatasourceContainer>