Skip to content

Commit

Permalink
[Feature] Migrate CreateDashboardDialog to React (#3826)
Browse files Browse the repository at this point in the history
  • Loading branch information
kravets-levko authored May 27, 2019
1 parent 5dff5b9 commit 9480d89
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 87 deletions.
1 change: 1 addition & 0 deletions client/app/assets/less/ant.less
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion client/app/components/DynamicComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <RealComponent {...props}>{children}</RealComponent>;
}
Expand Down
10 changes: 2 additions & 8 deletions client/app/components/app-header/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 });
Expand Down
88 changes: 88 additions & 0 deletions client/app/components/dashboards/CreateDashboardDialog.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Modal
{...dialog.props}
{...(isCreateDashboardEnabled ? {} : { footer: null })}
title="New Dashboard"
okText="Save"
cancelText="Close"
okButtonProps={{
disabled: !isValid || saveInProgress,
loading: saveInProgress,
'data-test': 'DashboardSaveButton',
}}
cancelButtonProps={{
disabled: saveInProgress,
}}
onOk={save}
closable={!saveInProgress}
maskClosable={!saveInProgress}
wrapProps={{
'data-test': 'CreateDashboardDialog',
}}
>
<DynamicComponent name="CreateDashboardDialogExtra" disabled={!isCreateDashboardEnabled}>
<Input
ref={inputRef}
defaultValue={name}
onChange={handleNameChange}
onPressEnter={save}
placeholder="Dashboard Name"
disabled={saveInProgress}
/>
</DynamicComponent>
</Modal>
);
}

CreateDashboardDialog.propTypes = {
dialog: DialogPropType.isRequired,
};

export default wrapDialog(CreateDashboardDialog);
19 changes: 0 additions & 19 deletions client/app/components/dashboards/edit-dashboard-dialog.html

This file was deleted.

47 changes: 0 additions & 47 deletions client/app/components/dashboards/edit-dashboard-dialog.js

This file was deleted.

13 changes: 2 additions & 11 deletions client/app/components/empty-state/EmptyState.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -131,7 +122,7 @@ export function EmptyState({
<Step
show={isAvailable.dashboard}
completed={isCompleted.dashboard}
onClick={createDashboard}
onClick={() => CreateDashboardDialog.showModal()}

This comment has been minimized.

Copy link
@jezdez

jezdez Aug 5, 2019

Member

I think this is a CSP regression: #4039

This comment has been minimized.

Copy link
@kravets-levko

kravets-levko Aug 5, 2019

Author Collaborator

This cannot cause CSP issues:

  • onClick attaches event listener using element.addEventListener() and does not change href attribute (no any javascript: protocol which may be blocked by CSP);
  • we have similar code all around the app and it works fine.

This comment has been minimized.

Copy link
@jezdez

jezdez Aug 6, 2019

Member

Dang, but where does the CSP issue come from here? Were you able to reproduce #4039?

urlText="Create"
text="your first Dashboard"
/>
Expand Down
2 changes: 2 additions & 0 deletions client/app/pages/data-sources/DataSourcesList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -105,6 +106,7 @@ class DataSourcesList extends React.Component {
<i className="fa fa-plus m-r-5" />
New Data Source
</Button>
<DynamicComponent name="DataSourcesListExtra" />
</div>
{this.state.loading ? <LoadingState className="" /> : this.renderDataSources()}
</div>
Expand Down
2 changes: 1 addition & 1 deletion client/cypress/integration/dashboard/dashboard_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 9480d89

Please sign in to comment.