diff --git a/client/app/assets/less/ant.less b/client/app/assets/less/ant.less
index 245f11d9ce..ce2c21c9bd 100644
--- a/client/app/assets/less/ant.less
+++ b/client/app/assets/less/ant.less
@@ -33,6 +33,7 @@
@import "~antd/lib/spin/style/index";
@import "~antd/lib/tabs/style/index";
@import "~antd/lib/notification/style/index";
+@import "~antd/lib/progress/style/index";
@import 'inc/ant-variables';
// Increase z-indexes to avoid conflicts with some other libraries (e.g. Plotly)
diff --git a/client/app/components/DynamicComponent.jsx b/client/app/components/DynamicComponent.jsx
index 645cb2b94b..16587b6856 100644
--- a/client/app/components/DynamicComponent.jsx
+++ b/client/app/components/DynamicComponent.jsx
@@ -43,7 +43,7 @@ export default class DynamicComponent extends React.Component {
const { name, children, ...props } = this.props;
const RealComponent = componentsRegistry.get(name);
if (!RealComponent) {
- return null;
+ return children;
}
return {children};
}
diff --git a/client/app/components/app-header/index.js b/client/app/components/app-header/index.js
index 3b34e3168e..d29fdca658 100644
--- a/client/app/components/app-header/index.js
+++ b/client/app/components/app-header/index.js
@@ -1,4 +1,5 @@
import debug from 'debug';
+import CreateDashboardDialog from '@/components/dashboards/CreateDashboardDialog';
import logoUrl from '@/assets/images/redash_icon_small.png';
import frontendVersion from '@/version.json';
@@ -35,14 +36,7 @@ function controller($rootScope, $location, $route, $uibModal, Auth, currentUser,
$rootScope.$on('reloadFavorites', this.reload);
- this.newDashboard = () => {
- $uibModal.open({
- component: 'editDashboardDialog',
- resolve: {
- dashboard: () => ({ name: null, layout: null }),
- },
- });
- };
+ this.newDashboard = () => CreateDashboardDialog.showModal();
this.searchQueries = () => {
$location.path('/queries').search({ q: this.searchTerm });
diff --git a/client/app/components/dashboards/CreateDashboardDialog.jsx b/client/app/components/dashboards/CreateDashboardDialog.jsx
new file mode 100644
index 0000000000..204d96fae1
--- /dev/null
+++ b/client/app/components/dashboards/CreateDashboardDialog.jsx
@@ -0,0 +1,88 @@
+import { trim } from 'lodash';
+import React, { useRef, useState, useEffect } from 'react';
+import Modal from 'antd/lib/modal';
+import Input from 'antd/lib/input';
+import DynamicComponent from '@/components/DynamicComponent';
+import { wrap as wrapDialog, DialogPropType } from '@/components/DialogWrapper';
+import { $location, $http } from '@/services/ng';
+import recordEvent from '@/services/recordEvent';
+import { policy } from '@/services/policy';
+
+function CreateDashboardDialog({ dialog }) {
+ const [name, setName] = useState('');
+ const [isValid, setIsValid] = useState(false);
+ const [saveInProgress, setSaveInProgress] = useState(false);
+ const inputRef = useRef();
+ const isCreateDashboardEnabled = policy.isCreateDashboardEnabled();
+
+ // ANGULAR_REMOVE_ME Replace all this with `autoFocus` attribute (it does not work
+ // if dialog is opened from Angular code, but works fine if open dialog from React code)
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ if (inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, 100);
+ return () => clearTimeout(timer);
+ }, []);
+
+ function handleNameChange(event) {
+ const value = trim(event.target.value);
+ setName(value);
+ setIsValid(value !== '');
+ }
+
+ function save() {
+ if (name !== '') {
+ setSaveInProgress(true);
+
+ $http.post('api/dashboards', { name })
+ .then(({ data }) => {
+ dialog.close();
+ $location.path(`/dashboard/${data.slug}`).search('edit').replace();
+ });
+ recordEvent('create', 'dashboard');
+ }
+ }
+
+ return (
+
+
+
+
+
+ );
+}
+
+CreateDashboardDialog.propTypes = {
+ dialog: DialogPropType.isRequired,
+};
+
+export default wrapDialog(CreateDashboardDialog);
diff --git a/client/app/components/dashboards/edit-dashboard-dialog.html b/client/app/components/dashboards/edit-dashboard-dialog.html
deleted file mode 100644
index 684e0885a8..0000000000
--- a/client/app/components/dashboards/edit-dashboard-dialog.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
\ No newline at end of file
diff --git a/client/app/components/dashboards/edit-dashboard-dialog.js b/client/app/components/dashboards/edit-dashboard-dialog.js
deleted file mode 100644
index a1c765dec7..0000000000
--- a/client/app/components/dashboards/edit-dashboard-dialog.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { isEmpty } from 'lodash';
-import { policy } from '@/services/policy';
-import template from './edit-dashboard-dialog.html';
-
-const EditDashboardDialog = {
- bindings: {
- resolve: '<',
- close: '&',
- dismiss: '&',
- },
- template,
- controller($location, $http, Events) {
- 'ngInject';
-
- this.dashboard = this.resolve.dashboard;
- this.policy = policy;
-
- this.isFormValid = () => !isEmpty(this.dashboard.name);
-
- this.saveDashboard = () => {
- if (!this.isFormValid()) {
- return;
- }
-
- this.saveInProgress = true;
-
- $http
- .post('api/dashboards', {
- name: this.dashboard.name,
- })
- .success((response) => {
- this.close();
- $location
- .path(`/dashboard/${response.slug}`)
- .search('edit')
- .replace();
- });
- Events.record('create', 'dashboard');
- };
- },
-};
-
-export default function init(ngModule) {
- ngModule.component('editDashboardDialog', EditDashboardDialog);
-}
-
-init.init = true;
diff --git a/client/app/components/empty-state/EmptyState.jsx b/client/app/components/empty-state/EmptyState.jsx
index 7fcaeea800..b53424d1aa 100644
--- a/client/app/components/empty-state/EmptyState.jsx
+++ b/client/app/components/empty-state/EmptyState.jsx
@@ -3,20 +3,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
import classNames from 'classnames';
-import { $uibModal } from '@/services/ng';
+import CreateDashboardDialog from '@/components/dashboards/CreateDashboardDialog';
import { currentUser } from '@/services/auth';
import organizationStatus from '@/services/organizationStatus';
import './empty-state.less';
-function createDashboard() {
- $uibModal.open({
- component: 'editDashboardDialog',
- resolve: {
- dashboard: () => ({ name: null, layout: null }),
- },
- });
-}
-
function Step({ show, completed, text, url, urlText, onClick }) {
if (!show) {
return null;
@@ -131,7 +122,7 @@ export function EmptyState({
CreateDashboardDialog.showModal()}
urlText="Create"
text="your first Dashboard"
/>
diff --git a/client/app/pages/data-sources/DataSourcesList.jsx b/client/app/pages/data-sources/DataSourcesList.jsx
index 9d532b1e6a..3666ebd77f 100644
--- a/client/app/pages/data-sources/DataSourcesList.jsx
+++ b/client/app/pages/data-sources/DataSourcesList.jsx
@@ -11,6 +11,7 @@ import { routesToAngularRoutes } from '@/lib/utils';
import CardsList from '@/components/cards-list/CardsList';
import LoadingState from '@/components/items-list/components/LoadingState';
import CreateSourceDialog from '@/components/CreateSourceDialog';
+import DynamicComponent from '@/components/DynamicComponent';
import helper from '@/components/dynamic-form/dynamicFormHelper';
import recordEvent from '@/services/recordEvent';
@@ -105,6 +106,7 @@ class DataSourcesList extends React.Component {
New Data Source
+
{this.state.loading ? : this.renderDataSources()}
diff --git a/client/cypress/integration/dashboard/dashboard_spec.js b/client/cypress/integration/dashboard/dashboard_spec.js
index 86f03a3c19..1060244921 100644
--- a/client/cypress/integration/dashboard/dashboard_spec.js
+++ b/client/cypress/integration/dashboard/dashboard_spec.js
@@ -68,7 +68,7 @@ describe('Dashboard', () => {
cy.server();
cy.route('POST', 'api/dashboards').as('NewDashboard');
- cy.getByTestId('EditDashboardDialog').within(() => {
+ cy.getByTestId('CreateDashboardDialog').within(() => {
cy.getByTestId('DashboardSaveButton').should('be.disabled');
cy.get('input').type('Foo Bar');
cy.getByTestId('DashboardSaveButton').click();