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();