Migrate AddToDashboard dialog to React (#4408)

This commit is contained in:
Levko Kravets 2019-12-04 17:50:50 +02:00 committed by GitHub
parent 94bd03dc42
commit ba36b4e671
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 184 additions and 84 deletions

View File

@ -62,7 +62,7 @@ function DesktopNavbar() {
)}
{currentUser.hasPermission('create_dashboard') && (
<Menu.Item key="new-dashboard">
<a onMouseUp={CreateDashboardDialog.showModal}>New Dashboard</a>
<a onMouseUp={() => CreateDashboardDialog.showModal()}>New Dashboard</a>
</Menu.Item>
)}
<Menu.Item key="new-alert">

View File

@ -0,0 +1,110 @@
import { isString } from 'lodash';
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import Modal from 'antd/lib/modal';
import Input from 'antd/lib/input';
import List from 'antd/lib/list';
import Icon from 'antd/lib/icon';
import { wrap as wrapDialog, DialogPropType } from '@/components/DialogWrapper';
import { QueryTagsControl } from '@/components/tags-control/TagsControl';
import { Dashboard } from '@/services/dashboard';
import notification from '@/services/notification';
import useSearchResults from '@/lib/hooks/useSearchResults';
import './add-to-dashboard-dialog.less';
function AddToDashboardDialog({ dialog, visualization }) {
const [searchTerm, setSearchTerm] = useState('');
const [doSearch, dashboards, isLoading] = useSearchResults((term) => {
if (isString(term) && (term !== '')) {
return Dashboard.get({ q: term }).$promise.then(results => results.results);
}
return Promise.resolve([]);
}, { initialResults: [] });
const [selectedDashboard, setSelectedDashboard] = useState(null);
const [saveInProgress, setSaveInProgress] = useState(false);
useEffect(() => { doSearch(searchTerm); }, [doSearch, searchTerm]);
function addWidgetToDashboard() {
// Load dashboard with all widgets
Dashboard.get({ slug: selectedDashboard.slug }).$promise
.then((dashboard) => {
dashboard.addWidget(visualization);
return dashboard;
})
.then((dashboard) => {
dialog.close();
const key = `notification-${Math.random().toString(36).substr(2, 10)}`;
notification.success('Widget added to dashboard', (
<React.Fragment>
<a href={`dashboard/${dashboard.slug}`} onClick={() => notification.close(key)}>{dashboard.name}</a>
<QueryTagsControl isDraft={dashboard.is_draft} tags={dashboard.tags} />
</React.Fragment>
), { key });
})
.catch(() => { notification.error('Widget not added.'); })
.finally(() => { setSaveInProgress(false); });
}
const items = selectedDashboard ? [selectedDashboard] : dashboards;
return (
<Modal
{...dialog.props}
title="Add to Dashboard"
okButtonProps={{ disabled: !selectedDashboard || saveInProgress, loading: saveInProgress }}
cancelButtonProps={{ disabled: saveInProgress }}
onOk={addWidgetToDashboard}
>
<label htmlFor="add-to-dashboard-dialog-dashboard">Choose the dashboard to add this query to:</label>
{!selectedDashboard && (
<Input
id="add-to-dashboard-dialog-dashboard"
className="w-100"
autoComplete="off"
autoFocus
placeholder="Search a dashboard by name"
value={searchTerm}
onChange={event => setSearchTerm(event.target.value)}
suffix={(
<Icon type="close" className={(searchTerm === '') ? 'hidden' : null} onClick={() => setSearchTerm('')} />
)}
/>
)}
{((items.length > 0) || isLoading) && (
<List
className={selectedDashboard ? 'add-to-dashboard-dialog-selection' : 'add-to-dashboard-dialog-search-results'}
bordered
itemLayout="horizontal"
loading={isLoading}
dataSource={items}
renderItem={d => (
<List.Item
key={`dashboard-${d.id}`}
actions={selectedDashboard ? [<Icon type="close" onClick={() => setSelectedDashboard(null)} />] : []}
onClick={selectedDashboard ? null : () => setSelectedDashboard(d)}
>
<div className="add-to-dashboard-dialog-item-content">
{d.name}
<QueryTagsControl isDraft={d.is_draft} tags={d.tags} />
</div>
</List.Item>
)}
/>
)}
</Modal>
);
}
AddToDashboardDialog.propTypes = {
dialog: DialogPropType.isRequired,
visualization: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
export default wrapDialog(AddToDashboardDialog);

View File

@ -0,0 +1,37 @@
@import (reference, less) '~@/assets/less/main.less';
.ant-list {
&.add-to-dashboard-dialog-search-results {
margin-top: 15px;
.ant-list-items {
max-height: 300px;
overflow: auto;
}
.ant-list-item {
padding: 12px;
cursor: pointer;
&:hover, &:active {
@table-row-hover-bg: fade(@redash-gray, 5%);
background-color: @table-row-hover-bg;
}
}
}
&.add-to-dashboard-dialog-selection {
.ant-list-item {
padding: 12px;
.add-to-dashboard-dialog-item-content {
flex: 1 1 auto;
}
.ant-list-item-action li {
margin: 0;
padding: 0;
}
}
}
}

View File

@ -0,0 +1,33 @@
import { useState, useEffect } from 'react';
import { useDebouncedCallback } from 'use-debounce';
export default function useSearchResults(
fetch,
{ initialResults = null, debounceTimeout = 200 } = {},
) {
const [result, setResult] = useState(initialResults);
const [isLoading, setIsLoading] = useState(false);
let currentSearchTerm = null;
let isDestroyed = false;
const [doSearch] = useDebouncedCallback((searchTerm) => {
setIsLoading(true);
currentSearchTerm = searchTerm;
fetch(searchTerm)
.catch(() => null)
.then((data) => {
if ((searchTerm === currentSearchTerm) && !isDestroyed) {
setResult(data);
setIsLoading(false);
}
});
}, debounceTimeout);
useEffect(() => (
// ignore all requests after component destruction
() => { isDestroyed = true; }
), []);
return [doSearch, result, isLoading];
}

View File

@ -1,19 +0,0 @@
<div class="modal-header">
<button type="button" class="close" aria-label="Close" ng-click="$ctrl.close()"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Add to Dashboard</h4>
</div>
<div class="modal-body">
<form name="alertForm" class="form">
<div class="form-group">
<label>Choose the dashboard to add this query to:</label>
<ui-select ng-model="$ctrl.dashboardList" reset-search-input="false" on-select="$ctrl.onDashboardSelected($item)" ng-disabled="$ctrl.saveInProgress">
<ui-select-match placeholder="Search a dashboard by name">{{$select.selected.name}}</ui-select-match>
<ui-select-choices repeat="q in $ctrl.dashboards"
refresh="$ctrl.searchDashboards($select.search, $ctrl.limitToUsersDashboards)"
refresh-delay="0">
<div ng-bind-html="$ctrl.trustAsHtml(q.name | highlight: $select.search)"></div>
</ui-select-choices>
</ui-select>
</div>
</form>
</div>

View File

@ -1,56 +0,0 @@
import template from './add-to-dashboard.html';
import notification from '@/services/notification';
const AddToDashboardForm = {
controller($sce, Dashboard) {
'ngInject';
this.vis = this.resolve.vis;
this.saveInProgress = false;
this.trustAsHtml = html => $sce.trustAsHtml(html);
this.onDashboardSelected = ({ slug }) => {
this.saveInProgress = true;
this.selected_query = this.resolve.query.id;
// Load dashboard with all widgets
Dashboard.get({ slug }).$promise
.then(dashboard => dashboard.addWidget(this.vis))
.then(() => {
this.close();
notification.success('Widget added to dashboard.');
})
.catch(() => {
notification.error('Widget not added.');
})
.finally(() => {
this.saveInProgress = false;
});
};
this.selectedDashboard = null;
this.searchDashboards = (searchTerm) => {
// , limitToUsersDashboards
if (!searchTerm || searchTerm.length < 3) {
return;
}
Dashboard.get(
{
search_term: searchTerm,
},
(results) => {
this.dashboards = results.results;
},
);
};
},
bindings: {
resolve: '<',
close: '&',
dismiss: '&',
vis: '<',
},
template,
};
export default function init(ngModule) {
ngModule.component('addToDashboardDialog', AddToDashboardForm);
}
init.init = true;

View File

@ -8,6 +8,7 @@ import ScheduleDialog from '@/components/queries/ScheduleDialog';
import { newVisualization } from '@/visualizations';
import EditVisualizationDialog from '@/visualizations/EditVisualizationDialog';
import EmbedQueryDialog from '@/components/queries/EmbedQueryDialog';
import AddToDashboardDialog from '@/components/queries/AddToDashboardDialog';
import PermissionsEditorDialog from '@/components/permissions-editor/PermissionsEditorDialog';
import notification from '@/services/notification';
import template from './query.html';
@ -499,14 +500,8 @@ function QueryViewCtrl(
};
$scope.openAddToDashboardForm = (visId) => {
const visualization = getVisualization(visId);
$uibModal.open({
component: 'addToDashboardDialog',
size: 'sm',
resolve: {
query: $scope.query,
vis: visualization,
},
AddToDashboardDialog.showModal({
visualization: getVisualization(visId),
});
};