diff --git a/superset-frontend/src/logger/LogUtils.ts b/superset-frontend/src/logger/LogUtils.ts
index b3e834bc935..9da6f36f49f 100644
--- a/superset-frontend/src/logger/LogUtils.ts
+++ b/superset-frontend/src/logger/LogUtils.ts
@@ -35,6 +35,15 @@ export const LOG_ACTIONS_EXPLORE_DASHBOARD_CHART = 'explore_dashboard_chart';
export const LOG_ACTIONS_EXPORT_CSV_DASHBOARD_CHART =
'export_csv_dashboard_chart';
export const LOG_ACTIONS_CHANGE_DASHBOARD_FILTER = 'change_dashboard_filter';
+export const LOG_ACTIONS_DATASET_CREATION_EMPTY_CANCELLATION =
+ 'dataset_creation_empty_cancellation';
+export const LOG_ACTIONS_DATASET_CREATION_DATABASE_CANCELLATION =
+ 'dataset_creation_database_cancellation';
+export const LOG_ACTIONS_DATASET_CREATION_SCHEMA_CANCELLATION =
+ 'dataset_creation_schema_cancellation';
+export const LOG_ACTIONS_DATASET_CREATION_TABLE_CANCELLATION =
+ 'dataset_creation_table_cancellation';
+export const LOG_ACTIONS_DATASET_CREATION_SUCCESS = 'dataset_creation_success';
// Log event types --------------------------------------------------------------
export const LOG_EVENT_TYPE_TIMING = new Set([
@@ -56,6 +65,14 @@ export const LOG_EVENT_TYPE_USER = new Set([
LOG_ACTIONS_MOUNT_EXPLORER,
]);
+export const LOG_EVENT_DATASET_TYPE_DATASET_CREATION = [
+ LOG_ACTIONS_DATASET_CREATION_EMPTY_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_DATABASE_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_SCHEMA_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_TABLE_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_SUCCESS,
+];
+
export const Logger = {
timeOriginOffset: 0,
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/AddDataset.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/AddDataset.test.tsx
index 53ece888246..f20c5ae5d1e 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/AddDataset.test.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/AddDataset.test.tsx
@@ -35,7 +35,7 @@ describe('AddDataset', () => {
// Left panel
expect(blankeStateImgs[0]).toBeVisible();
// Footer
- expect(screen.getByText(/footer/i)).toBeVisible();
+ expect(screen.getByText(/Cancel/i)).toBeVisible();
expect(blankeStateImgs.length).toBe(1);
});
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Footer/Footer.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Footer/Footer.test.tsx
index bbff68c88da..44724ad5978 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Footer/Footer.test.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Footer/Footer.test.tsx
@@ -20,10 +20,47 @@ import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import Footer from 'src/views/CRUD/data/dataset/AddDataset/Footer';
-describe('Footer', () => {
- it('renders a blank state Footer', () => {
- render();
+const mockedProps = {
+ url: 'realwebsite.com',
+};
- expect(screen.getByText(/footer/i)).toBeVisible();
+const mockPropsWithDataset = {
+ url: 'realwebsite.com',
+ datasetObject: {
+ database: {
+ id: '1',
+ database_name: 'examples',
+ },
+ owners: [1, 2, 3],
+ schema: 'public',
+ dataset_name: 'Untitled',
+ table_name: 'real_info',
+ },
+};
+
+describe('Footer', () => {
+ it('renders a Footer with a cancel button and a disabled create button', () => {
+ render(, { useRedux: true });
+
+ const saveButton = screen.getByRole('button', {
+ name: /Cancel/i,
+ });
+
+ const createButton = screen.getByRole('button', {
+ name: /Create/i,
+ });
+
+ expect(saveButton).toBeVisible();
+ expect(createButton).toBeDisabled();
+ });
+
+ it('renders a Create Dataset button when a table is selected', () => {
+ render(, { useRedux: true });
+
+ const createButton = screen.getByRole('button', {
+ name: /Create/i,
+ });
+
+ expect(createButton).toBeEnabled();
});
});
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Footer/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Footer/index.tsx
index 07c35741eef..2148f114cde 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Footer/index.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Footer/index.tsx
@@ -17,7 +17,104 @@
* under the License.
*/
import React from 'react';
+import Button from 'src/components/Button';
+import { t } from '@superset-ui/core';
+import { useSingleViewResource } from 'src/views/CRUD/hooks';
+import { logEvent } from 'src/logger/actions';
+import withToasts from 'src/components/MessageToasts/withToasts';
+import {
+ LOG_ACTIONS_DATASET_CREATION_EMPTY_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_DATABASE_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_SCHEMA_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_TABLE_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_SUCCESS,
+} from 'src/logger/LogUtils';
+import { DatasetObject } from '../types';
-export default function Footer() {
- return
Footer
;
+interface FooterProps {
+ url: string;
+ addDangerToast: () => void;
+ datasetObject?: Partial | null;
+ onDatasetAdd?: (dataset: DatasetObject) => void;
}
+
+const INPUT_FIELDS = ['db', 'schema', 'table_name'];
+const LOG_ACTIONS = [
+ LOG_ACTIONS_DATASET_CREATION_EMPTY_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_DATABASE_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_SCHEMA_CANCELLATION,
+ LOG_ACTIONS_DATASET_CREATION_TABLE_CANCELLATION,
+];
+
+function Footer({ url, datasetObject, addDangerToast }: FooterProps) {
+ const { createResource } = useSingleViewResource>(
+ 'dataset',
+ t('dataset'),
+ addDangerToast,
+ );
+
+ const createLogAction = (dataset: Partial) => {
+ let totalCount = 0;
+ const value = Object.keys(dataset).reduce((total, key) => {
+ if (INPUT_FIELDS.includes(key) && dataset[key]) {
+ totalCount += 1;
+ }
+ return totalCount;
+ }, 0);
+
+ 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) {
+ logEvent(LOG_ACTIONS_DATASET_CREATION_EMPTY_CANCELLATION, {});
+ } else {
+ const logAction = createLogAction(datasetObject);
+ logEvent(logAction, datasetObject);
+ }
+ goToPreviousUrl();
+ };
+
+ const tooltipText = t('Select a database table.');
+
+ const onSave = () => {
+ if (datasetObject) {
+ const data = {
+ database: datasetObject.db?.id,
+ schema: datasetObject.schema,
+ table_name: datasetObject.table_name,
+ };
+ createResource(data).then(response => {
+ if (!response) {
+ return;
+ }
+ if (typeof response === 'number') {
+ logEvent(LOG_ACTIONS_DATASET_CREATION_SUCCESS, datasetObject);
+ // When a dataset is created the response we get is its ID number
+ goToPreviousUrl();
+ }
+ });
+ }
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+export default withToasts(Footer);
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx
index 1b40e42299a..bcc7a4a0dbb 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx
@@ -31,11 +31,13 @@ import { TableOption } from 'src/components/TableSelector';
import RefreshLabel from 'src/components/RefreshLabel';
import { Table } from 'src/hooks/apiResources';
import Loading from 'src/components/Loading';
-import DatabaseSelector from 'src/components/DatabaseSelector';
+import DatabaseSelector, {
+ DatabaseObject,
+} from 'src/components/DatabaseSelector';
import { debounce } from 'lodash';
import { EmptyStateMedium } from 'src/components/EmptyState';
import { useToasts } from 'src/components/MessageToasts/withToasts';
-import { DatasetActionType, DatasetObject } from '../types';
+import { DatasetActionType } from '../types';
interface LeftPanelProps {
setDataset: Dispatch>;
@@ -60,7 +62,7 @@ const LeftPanelStyle = styled.div`
}
.refresh {
position: absolute;
- top: ${theme.gridUnit * 43.25}px;
+ top: ${theme.gridUnit * 37.25}px;
left: ${theme.gridUnit * 16.75}px;
span[role="button"]{
font-size: ${theme.gridUnit * 4.25}px;
@@ -80,17 +82,28 @@ const LeftPanelStyle = styled.div`
overflow: auto;
position: absolute;
bottom: 0;
- top: ${theme.gridUnit * 97.5}px;
+ top: ${theme.gridUnit * 92.25}px;
left: ${theme.gridUnit * 3.25}px;
right: 0;
.options {
+ cursor: pointer;
padding: ${theme.gridUnit * 1.75}px;
border-radius: ${theme.borderRadius}px;
+ :hover {
+ background-color: ${theme.colors.grayscale.light4}
+ }
+ }
+ .options-highlighted {
+ cursor: pointer;
+ padding: ${theme.gridUnit * 1.75}px;
+ border-radius: ${theme.borderRadius}px;
+ background-color: ${theme.colors.primary.dark1};
+ color: ${theme.colors.grayscale.light5};
}
}
form > span[aria-label="refresh"] {
position: absolute;
- top: ${theme.gridUnit * 73}px;
+ top: ${theme.gridUnit * 67.5}px;
left: ${theme.gridUnit * 42.75}px;
font-size: ${theme.gridUnit * 4.25}px;
}
@@ -108,10 +121,9 @@ const LeftPanelStyle = styled.div`
margin-bottom: 10px;
}
p {
- color: ${theme.colors.grayscale.light1}
+ color: ${theme.colors.grayscale.light1};
}
}
- }
`}
`;
@@ -125,14 +137,24 @@ export default function LeftPanel({
const [loadTables, setLoadTables] = useState(false);
const [searchVal, setSearchVal] = useState('');
const [refresh, setRefresh] = useState(false);
+ const [selectedTable, setSelectedTable] = useState(null);
const { addDangerToast } = useToasts();
- const setDatabase = (db: Partial) => {
- setDataset({ type: DatasetActionType.selectDatabase, payload: db });
+ const setDatabase = (db: Partial) => {
+ setDataset({ type: DatasetActionType.selectDatabase, payload: { db } });
+ setSelectedTable(null);
setResetTables(true);
};
+ const setTable = (tableName: string, index: number) => {
+ setSelectedTable(index);
+ setDataset({
+ type: DatasetActionType.selectTable,
+ payload: { name: 'table_name', value: tableName },
+ });
+ };
+
const getTablesList = (url: string) => {
SupersetClient.get({ url })
.then(({ json }) => {
@@ -164,6 +186,7 @@ export default function LeftPanel({
});
setLoadTables(true);
}
+ setSelectedTable(null);
setResetTables(true);
};
@@ -212,7 +235,6 @@ export default function LeftPanel({
onSchemaChange={setSchema}
/>
{loadTables && !refresh && Loader('Table loading')}
-
{schema && !loadTables && !tableOptions.length && !searchVal && (
)}
{!refresh &&
- tableOptions.map((o, i) => (
-
- {o.label}
+ tableOptions.map((option, i) => (
+
setTable(option.value, i)}
+ >
+ {option.label}
))}
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx
index 79c6e5f4c3a..5500d5ded22 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx
@@ -53,7 +53,7 @@ export function datasetReducer(
case DatasetActionType.selectTable:
return {
...trimmedState,
- ...action.payload,
+ [action.payload.name]: action.payload.value,
};
case DatasetActionType.changeDataset:
return {
@@ -78,16 +78,22 @@ export default function AddDataset() {
);
+ const prevUrl =
+ '/tablemodelview/list/?pageIndex=0&sortColumn=changed_on_delta_humanized&sortOrder=desc';
+
+ const FooterComponent = () => (
+
+ );
return (
);
}
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx
index b6f04469b8f..2451ea9028b 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx
@@ -24,9 +24,11 @@ export enum DatasetActionType {
}
export interface DatasetObject {
- id: number;
- database_name?: string;
- owners?: number[];
+ db: {
+ id: number;
+ database_name?: string;
+ owners?: number[];
+ };
schema?: string | null;
dataset_name: string;
table_name?: string | null;
@@ -43,10 +45,13 @@ export type Schema = {
export type DSReducerActionType =
| {
- type: DatasetActionType.selectDatabase | DatasetActionType.selectTable;
+ type: DatasetActionType.selectDatabase;
payload: Partial
;
}
| {
- type: DatasetActionType.changeDataset | DatasetActionType.selectSchema;
+ type:
+ | DatasetActionType.changeDataset
+ | DatasetActionType.selectSchema
+ | DatasetActionType.selectTable;
payload: DatasetReducerPayloadType;
};
diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx
index cf57c34360c..42dc3a994b4 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx
@@ -79,8 +79,8 @@ describe('DatasetLayout', () => {
});
it('renders a Footer when passed in', () => {
- render();
+ render(} />, { useRedux: true });
- expect(screen.getByText(/footer/i)).toBeVisible();
+ expect(screen.getByText(/Cancel/i)).toBeVisible();
});
});
diff --git a/superset-frontend/src/views/CRUD/data/dataset/styles.ts b/superset-frontend/src/views/CRUD/data/dataset/styles.ts
index 757837f221c..48cecc3c5c5 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/styles.ts
+++ b/superset-frontend/src/views/CRUD/data/dataset/styles.ts
@@ -95,6 +95,11 @@ export const StyledLayoutFooter = styled.div`
border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
color: ${({ theme }) => theme.colors.info.base};
+ border-top: ${({ theme }) => theme.gridUnit / 4}px solid
+ ${({ theme }) => theme.colors.grayscale.light2};
+ padding: ${({ theme }) => theme.gridUnit * 4}px;
+ display: flex;
+ justify-content: flex-end;
`;
export const HeaderComponentStyles = styled.div`