Skip to content
Snippets Groups Projects
Commit 4a162d3c authored by Atte Keinänen's avatar Atte Keinänen
Browse files

Add the new scheduling settings step to db creation

parent f4dcac3f
No related branches found
No related tags found
No related merge requests found
Showing
with 244 additions and 70 deletions
......@@ -44,6 +44,7 @@ export default class DatabaseEditForms extends Component {
...database,
id: this.props.database.id
}, database.details)}
isNewDatabase={!database.id}
submitButtonText={'Save'}
submitting={isSubmitting}>
</DatabaseDetailsForm>
......
......@@ -68,7 +68,6 @@ export default class DatabaseSchedulingForm extends Component {
setIsFullSync = (isFullSync) => {
// TODO: Add event tracking
this.setState(assocIn(this.state, ["unsavedDatabase", "is_full_sync"], isFullSync));
}
......@@ -79,7 +78,7 @@ export default class DatabaseSchedulingForm extends Component {
this.props.save(unsavedDatabase, unsavedDatabase.details);
}
render() {
const { formState: { formError, formSuccess } } = this.props
const { submitButtonText, formState: { formError, formSuccess, isSubmitting } } = this.props
const { unsavedDatabase } = this.state
return (
......@@ -157,8 +156,10 @@ export default class DatabaseSchedulingForm extends Component {
</div>
<div className="Form-actions mt4">
<button className={"Button Button--primary"}>Save changes</button>
<FormMessage formError={formError} formSuccess={formSuccess}></FormMessage>
<button className={"Button Button--primary"} disabled={isSubmitting}>
{isSubmitting ? "Saving..." : submitButtonText }
</button>
<FormMessage formError={formError} formSuccess={formSuccess}/>
</div>
</form>
}
......
......@@ -15,12 +15,14 @@ import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx";
import {
getEditingDatabase,
getFormState
getFormState,
getDatabaseCreationStep
} from "../selectors";
import {
reset,
initializeDatabase,
proceedWithDbCreation,
saveDatabase,
syncDatabaseSchema,
rescanDatabaseFields,
......@@ -30,11 +32,11 @@ import {
} from "../database";
import ConfirmContent from "metabase/components/ConfirmContent";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import CreatedDatabaseModal from "metabase/admin/databases/components/CreatedDatabaseModal";
const mapStateToProps = (state, props) => ({
database: getEditingDatabase(state, props),
formState: getFormState(state, props)
database: getEditingDatabase(state),
databaseCreationStep: getDatabaseCreationStep(state),
formState: getFormState(state)
});
export const Tab = ({ name, setTab, currentTab }) => {
......@@ -69,6 +71,7 @@ export const Tabs = ({ tabs, currentTab, setTab }) =>
const mapDispatchToProps = {
reset,
initializeDatabase,
proceedWithDbCreation,
saveDatabase,
syncDatabaseSchema,
rescanDatabaseFields,
......@@ -83,16 +86,14 @@ export default class DatabaseEditApp extends Component {
constructor(props, context) {
super(props, context);
const showSchedulingAfterDbCreation = "showSchedulingAfterDbCreation" in props.location.query
this.state = {
justCreated: showSchedulingAfterDbCreation,
currentTab: showSchedulingAfterDbCreation ? 'scheduling' : 'connection',
currentTab: 'connection'
};
}
static propTypes = {
database: PropTypes.object,
databaseCreationStep: PropTypes.string,
formState: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
reset: PropTypes.func.isRequired,
......@@ -100,6 +101,7 @@ export default class DatabaseEditApp extends Component {
syncDatabaseSchema: PropTypes.func.isRequired,
rescanDatabaseFields: PropTypes.func.isRequired,
discardSavedFieldValues: PropTypes.func.isRequired,
proceedWithDbCreation: PropTypes.func.isRequired,
deleteDatabase: PropTypes.func.isRequired,
saveDatabase: PropTypes.func.isRequired,
selectEngine: PropTypes.func.isRequired,
......@@ -111,13 +113,24 @@ export default class DatabaseEditApp extends Component {
await this.props.initializeDatabase(this.props.params.databaseId);
}
componentWillReceiveProps(nextProps) {
const addingNewDatabase = !nextProps.database || !nextProps.database.id
if (addingNewDatabase) {
// Update the current creation step (= active tab) if adding a new database
this.setState({ currentTab: nextProps.databaseCreationStep });
}
}
render() {
let { database } = this.props;
const { justCreated, currentTab } = this.state;
let { database, formState } = this.props;
const { currentTab } = this.state;
const editingExistingDatabase = database && database.id != null
const addingNewDatabase = !editingExistingDatabase
const letUserControlScheduling = database && database.details && database.details["let-user-control-scheduling"]
const showTabs = editingExistingDatabase && letUserControlScheduling
return (
<div className="wrapper">
......@@ -128,7 +141,7 @@ export default class DatabaseEditApp extends Component {
<section className="Grid Grid--gutters Grid--2-of-3">
<div className="Grid-cell">
<div className="Form-new bordered rounded shadowed pt0">
{ letUserControlScheduling &&
{ showTabs &&
<Tabs
tabs={['Connection', 'Scheduling']}
currentTab={currentTab}
......@@ -144,16 +157,21 @@ export default class DatabaseEditApp extends Component {
details={database ? database.details : null}
engines={MetabaseSettings.get('engines')}
hiddenFields={{ssl: true}}
formState={this.props.formState}
formState={formState}
selectEngine={this.props.selectEngine}
save={this.props.saveDatabase}
save={ addingNewDatabase
? this.props.proceedWithDbCreation
: this.props.saveDatabase
}
/>
}
{ currentTab === 'scheduling' &&
<DatabaseSchedulingForm
database={database}
formState={this.props.formState}
formState={formState}
// Use saveDatabase both for db creation and updating
save={this.props.saveDatabase}
submitButtonText={ addingNewDatabase ? "Save" : "Save changes" }
/>
}
</div>
......@@ -163,7 +181,7 @@ export default class DatabaseEditApp extends Component {
</div>
{ /* Sidebar Actions */ }
{ database && database.id != null &&
{ editingExistingDatabase &&
<div className="Grid-cell Cell--1of3">
<div className="Actions bordered rounded shadowed">
<div className="Actions-group">
......@@ -228,16 +246,6 @@ export default class DatabaseEditApp extends Component {
</div>
}
</section>
<ModalWithTrigger
ref="createdDatabaseModal"
isInitiallyOpen={justCreated}
>
<CreatedDatabaseModal
databaseId={parseInt(justCreated)}
onDone={() => this.refs.createdDatabaseModal.toggle() }
onClose={() => this.refs.createdDatabaseModal.toggle() }
/>
</ModalWithTrigger>
</div>
);
}
......
......@@ -9,12 +9,30 @@ import MetabaseSettings from "metabase/lib/settings";
import { MetabaseApi } from "metabase/services";
// Default schedules for db sync and deep analysis
export const DEFAULT_SCHEDULES = {
"cache_field_values": {
"schedule_day": null,
"schedule_frame": null,
"schedule_hour": 0,
"schedule_type": "daily"
},
"metadata_sync": {
"schedule_day": null,
"schedule_frame": null,
"schedule_hour": null,
"schedule_type": "hourly"
}
}
export const DB_EDIT_FORM_CONNECTION_TAB = "connection";
export const DB_EDIT_FORM_SCHEDULING_TAB = "scheduling";
export const RESET = "metabase/admin/databases/RESET";
export const SELECT_ENGINE = "metabase/admin/databases/SELECT_ENGINE";
export const FETCH_DATABASES = "metabase/admin/databases/FETCH_DATABASES";
export const INITIALIZE_DATABASE = "metabase/admin/databases/INITIALIZE_DATABASE";
export const ADD_SAMPLE_DATASET = "metabase/admin/databases/ADD_SAMPLE_DATASET";
export const SAVE_DATABASE = "metabase/admin/databases/SAVE_DATABASE";
export const DELETE_DATABASE = "metabase/admin/databases/DELETE_DATABASE";
export const SYNC_DATABASE_SCHEMA = "metabase/admin/databases/SYNC_DATABASE_SCHEMA";
export const RESCAN_DATABASE_FIELDS = "metabase/admin/databases/RESCAN_DATABASE_FIELDS";
......@@ -22,11 +40,15 @@ export const DISCARD_SAVED_FIELD_VALUES = "metabase/admin/databases/DISCARD_SAVE
export const UPDATE_DATABASE = 'metabase/admin/databases/UPDATE_DATABASE'
export const UPDATE_DATABASE_STARTED = 'metabase/admin/databases/UPDATE_DATABASE_STARTED'
export const UPDATE_DATABASE_FAILED = 'metabase/admin/databases/UPDATE_DATABASE_FAILED'
export const SET_DATABASE_CREATION_STEP = 'metabase/admin/databases/SET_DATABASE_CREATION_STEP'
export const CREATE_DATABASE = 'metabase/admin/databases/CREATE_DATABASE'
export const CREATE_DATABASE_STARTED = 'metabase/admin/databases/CREATE_DATABASE_STARTED'
export const VALIDATE_DATABASE_STARTED = 'metabase/admin/databases/VALIDATE_DATABASE_STARTED'
export const VALIDATE_DATABASE_FAILED = 'metabase/admin/databases/VALIDATE_DATABASE_FAILED'
export const CREATE_DATABASE_FAILED = 'metabase/admin/databases/CREATE_DATABASE_FAILED'
export const DELETE_DATABASE_STARTED = 'metabase/admin/databases/DELETE_DATABASE_STARTED'
export const DELETE_DATABASE_FAILED = "metabase/admin/databases/DELETE_DATABASE_FAILED";
export const CLEAR_FORM_STATE = 'metabase/admin/databases/CLEAR_FORM_STATE'
export const reset = createAction(RESET);
......@@ -83,10 +105,37 @@ export const addSampleDataset = createThunkAction(ADD_SAMPLE_DATASET, function()
};
});
export const proceedWithDbCreation = function (database) {
return async function (dispatch, getState) {
if (database.details["let-user-control-scheduling"]) {
try {
dispatch.action(VALIDATE_DATABASE_STARTED);
await MetabaseApi.db_validate(database);
dispatch.action(SET_DATABASE_CREATION_STEP, {
// NOTE Atte Keinänen: DatabaseSchedulingForm needs `editingDatabase` with `schedules` so I decided that
// it makes sense to set the value of editingDatabase as part of SET_DATABASE_CREATION_STEP
database: {
...database,
is_full_sync: true,
schedules: DEFAULT_SCHEDULES
},
step: DB_EDIT_FORM_SCHEDULING_TAB
});
} catch(error) {
dispatch.action(VALIDATE_DATABASE_FAILED, { error });
}
} else {
// Skip the scheduling step if user doesn't need precise control over sync and scan
dispatch(createDatabase(database));
}
}
}
export const createDatabase = function (database) {
return async function (dispatch, getState) {
try {
dispatch.action(CREATE_DATABASE_STARTED, { database })
dispatch.action(CREATE_DATABASE_STARTED, {})
const createdDatabase = await MetabaseApi.db_create(database);
MetabaseAnalytics.trackEvent("Databases", "Create", database.engine);
......@@ -94,17 +143,12 @@ export const createDatabase = function (database) {
// and seeing the db that was just added
await dispatch(fetchDatabases())
if (database.details["let-user-control-scheduling"]) {
// Move to the scheduling settings
dispatch(push(`/admin/databases/${createdDatabase.id}?showSchedulingAfterDbCreation`));
} else {
dispatch(push('/admin/databases?created=' + createdDatabase.id));
dispatch.action(CREATE_DATABASE, { database: createdDatabase })
}
dispatch.action(CREATE_DATABASE)
dispatch(push('/admin/databases?created=' + createdDatabase.id));
} catch (error) {
console.error("error creating a database", error);
MetabaseAnalytics.trackEvent("Databases", "Create Failed", database.engine);
dispatch.action(CREATE_DATABASE_FAILED, { database, error })
dispatch.action(CREATE_DATABASE_FAILED, { error })
}
};
}
......@@ -117,6 +161,7 @@ export const updateDatabase = function(database) {
MetabaseAnalytics.trackEvent("Databases", "Update", database.engine);
dispatch.action(UPDATE_DATABASE, { database: savedDatabase })
setTimeout(() => dispatch.action(CLEAR_FORM_STATE), 3000);
} catch (error) {
MetabaseAnalytics.trackEvent("Databases", "Update Failed", database.engine);
dispatch.action(UPDATE_DATABASE_FAILED, { error });
......@@ -132,20 +177,7 @@ export const saveDatabase = function(database, details) {
const letUserControlScheduling = details["let-user-control-scheduling"];
const overridesIfNoUserControl = letUserControlScheduling ? {} : {
is_full_sync: true,
schedules: {
"cache_field_values": {
"schedule_day": null,
"schedule_frame": null,
"schedule_hour": null,
"schedule_type": "hourly"
},
"metadata_sync": {
"schedule_day": null,
"schedule_frame": null,
"schedule_hour": null,
"schedule_type": "hourly"
}
}
schedules: DEFAULT_SCHEDULES
}
return async function(dispatch, getState) {
......@@ -231,7 +263,8 @@ const editingDatabase = handleActions({
[INITIALIZE_DATABASE]: { next: (state, { payload }) => payload },
[UPDATE_DATABASE]: { next: (state, { payload }) => payload.database || state },
[DELETE_DATABASE]: { next: (state, { payload }) => null },
[SELECT_ENGINE]: { next: (state, { payload }) => ({...state, engine: payload }) }
[SELECT_ENGINE]: { next: (state, { payload }) => ({...state, engine: payload }) },
[SET_DATABASE_CREATION_STEP]: (state, { payload: { database } }) => database
}, null);
const deletes = handleActions({
......@@ -244,22 +277,31 @@ const deletionError = handleActions({
[DELETE_DATABASE_FAILED]: (state, { payload: { error } }) => error,
}, null)
const databaseCreationStep = handleActions({
[RESET]: () => DB_EDIT_FORM_CONNECTION_TAB,
[SET_DATABASE_CREATION_STEP] : (state, { payload: { step } }) => step
}, DB_EDIT_FORM_CONNECTION_TAB)
const DEFAULT_FORM_STATE = { formSuccess: null, formError: null, isSubmitting: false };
const formState = handleActions({
[RESET]: { next: () => DEFAULT_FORM_STATE },
[CREATE_DATABASE_STARTED]: () => ({ isSubmitting: true }),
// not necessarily needed as the page is immediately redirected after db creation
[CREATE_DATABASE]: () => ({ formSuccess: { data: { message: "Successfully created!" } } }),
[VALIDATE_DATABASE_FAILED]: (state, { payload: { error } }) => ({ formError: error }),
[CREATE_DATABASE_FAILED]: (state, { payload: { error } }) => ({ formError: error }),
[UPDATE_DATABASE_STARTED]: () => ({ isSubmitting: true }),
[UPDATE_DATABASE]: () => ({ formSuccess: { data: { message: "Successfully saved!" } } }),
[UPDATE_DATABASE_FAILED]: (state, { payload: { error } }) => ({ formError: error }),
[CLEAR_FORM_STATE]: () => DEFAULT_FORM_STATE
}, DEFAULT_FORM_STATE);
export default combineReducers({
databases,
editingDatabase,
deletionError,
databaseCreationStep,
formState,
deletes
});
......@@ -19,8 +19,9 @@ export const hasSampleDataset = createSelector(
// Database Edit
export const getEditingDatabase = state => state.admin.databases.editingDatabase;
export const getFormState = state => state.admin.databases.formState;
export const getEditingDatabase = state => state.admin.databases.editingDatabase;
export const getFormState = state => state.admin.databases.formState;
export const getDatabaseCreationStep = state => state.admin.databases.databaseCreationStep;
export const getDeletes = state => state.admin.databases.deletes;
export const getDeletionError = state => state.admin.databases.deletionError;
export const getDeletes = state => state.admin.databases.deletes;
export const getDeletionError = state => state.admin.databases.deletionError;
......@@ -7,6 +7,7 @@ import FormLabel from "metabase/components/form/FormLabel.jsx";
import FormMessage from "metabase/components/form/FormMessage.jsx";
import Toggle from "metabase/components/Toggle.jsx";
import { shallowEqual } from "recompose";
// TODO - this should be somewhere more centralized
function isEmpty(str) {
......@@ -47,6 +48,7 @@ export default class DatabaseDetailsForm extends Component {
engines: PropTypes.object.isRequired,
formError: PropTypes.object,
hiddenFields: PropTypes.object,
isNewDatabase: PropTypes.boolean,
submitButtonText: PropTypes.string.isRequired,
submitFn: PropTypes.func.isRequired,
submitting: PropTypes.boolean
......@@ -80,7 +82,7 @@ export default class DatabaseDetailsForm extends Component {
}
componentWillReceiveProps(nextProps) {
if (this.props.details !== nextProps.details) {
if (!shallowEqual(this.props.details, nextProps.details)) {
this.setState({ details: nextProps.details })
}
}
......@@ -262,8 +264,10 @@ export default class DatabaseDetailsForm extends Component {
}
render() {
let { engine, engines, formError, formSuccess, hiddenFields, submitButtonText, submitting } = this.props;
let { valid } = this.state;
let { engine, engines, formError, formSuccess, hiddenFields, submitButtonText, isNewDatabase, submitting } = this.props;
let { valid, details } = this.state;
const willProceedToNextDbCreationStep = isNewDatabase && details["let-user-control-scheduling"];
let fields = [
{
......@@ -291,7 +295,7 @@ export default class DatabaseDetailsForm extends Component {
<div className="Form-actions">
<button className={cx("Button", {"Button--primary": valid})} disabled={!valid || submitting}>
{submitting ? "Saving..." : submitButtonText}
{submitting ? "Saving..." : (willProceedToNextDbCreationStep ? "Next" : submitButtonText)}
</button>
<FormMessage formError={formError} formSuccess={formSuccess}></FormMessage>
</div>
......
......@@ -97,6 +97,7 @@ export const MetabaseApi = {
db_list_with_tables: GET("/api/database?include_tables=true&include_cards=true"),
db_real_list_with_tables: GET("/api/database?include_tables=true&include_cards=false"),
db_create: POST("/api/database"),
db_validate: POST("/api/database/validate"),
db_add_sample_dataset: POST("/api/database/sample_dataset"),
db_get: GET("/api/database/:dbId"),
db_update: PUT("/api/database/:id"),
......
import Button from "metabase/components/Button";
export const click = (enzymeWrapper) => {
if (enzymeWrapper.length === 0) {
throw new Error("The wrapper you provided for `click(wrapper)` is empty.")
}
const nodeType = enzymeWrapper.type();
if (nodeType === Button || nodeType === "button") {
console.trace(
......
......@@ -51,18 +51,59 @@ describe("DatabaseEditApp", () => {
expect.stringContaining("sample-dataset.db;USER=GUEST;PASSWORD=guest")
)
const fullSyncField = editForm.find(FormField).filterWhere((f) => f.props().fieldName === "is_full_sync");
expect(fullSyncField.length).toBe(1);
expect(fullSyncField.find(Toggle).props().value).toBe(true);
const letUserControlSchedulingField =
editForm.find(FormField).filterWhere((f) => f.props().fieldName === "let-user-control-scheduling");
expect(letUserControlSchedulingField.length).toBe(1);
expect(letUserControlSchedulingField.find(Toggle).props().value).toBe(false);
});
it("lets you modify the connection settings", () => {
pending();
// should be pretty straight-forward to do using the selectors of previous test
it("lets you modify the connection settings", async () => {
const store = await createTestStore()
store.pushPath("/admin/databases/1");
const dbEditApp = mount(store.connectContainer(<DatabaseEditApp />));
await store.waitForActions([INITIALIZE_DATABASE])
const editForm = dbEditApp.find(DatabaseEditForms)
const letUserControlSchedulingField =
editForm.find(FormField).filterWhere((f) => f.props().fieldName === "let-user-control-scheduling");
click(letUserControlSchedulingField.find(Toggle))
// Connection and Scheduling tabs shouldn't be visible yet
expect(dbEditApp.find(Tab).length).toBe(0)
clickButton(editForm.find('button[children="Save"]'));
await store.waitForActions([UPDATE_DATABASE])
// Tabs should be now visible as user-controlled scheduling is enabled
expect(dbEditApp.find(Tab).length).toBe(2)
});
afterAll(async () => {
// revert all changes that have been made
// use a direct API call for the sake of simplicity / reliability
const store = await createTestStore()
const database = (await store.dispatch(initializeDatabase(1))).payload
await store.dispatch(saveDatabase(database, {
...database.details,
"let-user-control-scheduling": false
}
))
})
})
describe("Scheduling tab", () => {
beforeAll(async () => {
// Enable the user-controlled scheduling for these tests
const store = await createTestStore()
const database = (await store.dispatch(initializeDatabase(1))).payload
await store.dispatch(saveDatabase(database, {
...database.details,
"let-user-control-scheduling": true
}
))
})
it("shows the initial scheduling settings correctly", async () => {
const store = await createTestStore()
store.pushPath("/admin/databases/1");
......@@ -175,7 +216,7 @@ describe("DatabaseEditApp", () => {
},
{
...database.details,
is_static: false
"let-user-control-scheduling": false
}
))
})
......
......@@ -20,7 +20,7 @@ import {
CREATE_DATABASE,
UPDATE_DATABASE_STARTED,
UPDATE_DATABASE_FAILED,
UPDATE_DATABASE,
UPDATE_DATABASE, VALIDATE_DATABASE_STARTED, SET_DATABASE_CREATION_STEP,
} from "metabase/admin/databases/database"
import DatabaseListApp from "metabase/admin/databases/containers/DatabaseListApp";
......@@ -31,6 +31,9 @@ import { delay } from "metabase/lib/promise"
import { getEditingDatabase } from "metabase/admin/databases/selectors";
import FormMessage, { SERVER_ERROR_MESSAGE } from "metabase/components/form/FormMessage";
import CreatedDatabaseModal from "metabase/admin/databases/components/CreatedDatabaseModal";
import FormField from "metabase/components/form/FormField";
import Toggle from "metabase/components/Toggle";
import DatabaseSchedulingForm, { SyncOption } from "metabase/admin/databases/components/DatabaseSchedulingForm";
describe('dashboard list', () => {
......@@ -96,6 +99,71 @@ describe('dashboard list', () => {
expect(app.find(CreatedDatabaseModal).length).toBe(1);
})
it("should direct you to scheduling settings if you enable the toggle", async () => {
MetabaseApi.db_create = async (db) => { await delay(10); return {...db, id: 10}; };
MetabaseApi.db_validate = async (db) => { await delay(10); return { valid: true }; };
const store = await createTestStore()
store.pushPath("/admin/databases");
const app = mount(store.getAppContainer())
await store.waitForActions([FETCH_DATABASES])
const listAppBeforeAdd = app.find(DatabaseListApp)
const addDbButton = listAppBeforeAdd.find('.Button.Button--primary').first()
click(addDbButton)
const dbDetailsForm = app.find(DatabaseEditApp);
expect(dbDetailsForm.length).toBe(1);
await store.waitForActions([INITIALIZE_DATABASE]);
expect(dbDetailsForm.find('button[children="Save"]').props().disabled).toBe(true)
const updateInputValue = (name, value) =>
setInputValue(dbDetailsForm.find(`input[name="${name}"]`), value);
updateInputValue("name", "Test db name");
updateInputValue("dbname", "test_postgres_db");
updateInputValue("user", "uberadmin");
const letUserControlSchedulingField =
dbDetailsForm.find(FormField).filterWhere((f) => f.props().fieldName === "let-user-control-scheduling");
expect(letUserControlSchedulingField.length).toBe(1);
expect(letUserControlSchedulingField.find(Toggle).props().value).toBe(false);
click(letUserControlSchedulingField.find(Toggle))
const nextStepButton = dbDetailsForm.find('button[children="Next"]')
expect(nextStepButton.props().disabled).toBe(false)
clickButton(nextStepButton)
await store.waitForActions([VALIDATE_DATABASE_STARTED, SET_DATABASE_CREATION_STEP])
// Change the sync period to never in scheduling settings
const schedulingForm = app.find(DatabaseSchedulingForm)
expect(schedulingForm.length).toBe(1);
const syncOptions = schedulingForm.find(SyncOption);
const syncOptionsNever = syncOptions.at(1);
expect(syncOptionsNever.props().selected).toEqual(false);
click(syncOptionsNever)
expect(syncOptionsNever.props().selected).toEqual(true);
const saveButton = dbDetailsForm.find('button[children="Save"]')
expect(saveButton.props().disabled).toBe(false)
clickButton(saveButton)
// Now the submit button should be disabled so that you aren't able to trigger the db creation action twice
await store.waitForActions([CREATE_DATABASE_STARTED])
expect(saveButton.text()).toBe("Saving...");
await store.waitForActions([CREATE_DATABASE]);
expect(store.getPath()).toEqual("/admin/databases?created=10")
expect(app.find(CreatedDatabaseModal).length).toBe(1);
})
it('should show error correctly on failure', async () => {
MetabaseApi.db_create = async () => {
await delay(10);
......
......@@ -379,6 +379,10 @@
{:status 400
:body details-or-error})))
(api/defendpoint POST "/validate"
"Validate that we can connect to a database given a set of details."
[:as {{{:keys [engine] {:keys [host port] :as details} :details} :details, token :token} :body}]
{:valid true})
;;; ------------------------------------------------------------ POST /api/database/sample_dataset ------------------------------------------------------------
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment