Skip to content

Commit ac68fe1

Browse files
kravets-levkoarikfr
authored andcommitted
Migrate Dashboards/Queries/Users list pages to React (#3381)
* Refine existing implementation of dashboards/queries/users lists and a common base controller * Migrate common list page controller to React and refactor it's logic * Migrate Dashboard list page to React * Migrate Queries list page to React * Migrate Users list page to React * Remove react-timeago dependency * Use composition instead of inheritance * Refine implementation * Merge sidebar into single component * Refine column definitions * Use simple controller instead of React context * Refine implementation * Restore changes from #2888 * Tweak Users list page * Ability to render dynamically defined components * Tweak users list page * User list page for non-admins * Fix: ItemsTable ignores isAvailable field * Refine implementation * Refine implementation * Implement LiveItemsList as higher order component * Some fixes * Move some definitions to a better place * Some fixes * Refine components * Refine UsersList page * More comments for a god of comments * Fix wrong tables size on smaller screens * Tweak tables
1 parent 1385593 commit ac68fe1

34 files changed

+1527
-798
lines changed

client/app/assets/less/ant.less

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,35 @@
140140
}
141141
}
142142
}
143+
144+
// Table
145+
146+
.@{table-prefix-cls} {
147+
color: inherit;
148+
149+
* {
150+
transition: none !important;
151+
}
152+
153+
&-thead > tr > th {
154+
padding: @table-padding-vertical * 2 @table-padding-horizontal;
155+
}
156+
157+
.@{table-prefix-cls}-column-sorters {
158+
&:before,
159+
&:hover:before {
160+
content: none;
161+
}
162+
}
163+
164+
&-thead > tr > th {
165+
.@{table-prefix-cls}-column-sorter {
166+
&-up,
167+
&-down {
168+
&.on {
169+
color: @table-header-icon-active-color;
170+
}
171+
}
172+
}
173+
}
174+
}

client/app/assets/less/inc/ant-variables.less

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,21 @@
5050
@pagination-disabled-bg: fade(@redash-gray, 15%);
5151
@pagination-hover-color: #333;
5252
@pagination-hover-bg: fade(@redash-gray, 25%);
53+
54+
55+
/* --------------------------------------------------------
56+
Table
57+
-----------------------------------------------------------*/
58+
59+
@table-border-radius-base: 0;
60+
@table-header-color: #333;
61+
@table-header-bg: fade(@redash-gray, 3%);
62+
@table-header-icon-color: fade(@text-color, 20%);
63+
@table-header-icon-active-color: @text-color;
64+
@table-header-sort-bg: @table-header-bg;
65+
@table-header-sort-active-bg: @table-header-bg;
66+
@table-header-filter-active-bg: @table-header-bg;
67+
@table-body-sort-bg: transparent;
68+
@table-row-hover-bg: fade(@redash-gray, 5%);
69+
@table-padding-vertical: 7px;
70+
@table-padding-horizontal: 10px;

client/app/assets/less/redash/redash-newstyle.less

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,9 @@ body {
172172
box-shadow: inset 3px 0px 0px @brand-primary;
173173
}
174174

175-
.table.table-data {
176-
> tbody > tr > td {
175+
.table-data {
176+
tbody > tr > td {
177177
padding-top: 5px !important;
178-
}
179-
180-
tr:hover {
181178
cursor: pointer;
182179
}
183180

client/app/components/BigMessage.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import React from 'react';
22
import PropTypes from 'prop-types';
33
import { react2angular } from 'react2angular';
44

5-
export function BigMessage({ message, icon, children }) {
5+
export function BigMessage({ message, icon, children, className }) {
66
return (
7-
<div className="tiled bg-white p-15 text-center">
7+
<div className={'p-15 text-center ' + className}>
88
<h3 className="m-t-0 m-b-0">
99
<i className={'fa ' + icon} />
1010
</h3>
@@ -19,11 +19,13 @@ BigMessage.propTypes = {
1919
message: PropTypes.string,
2020
icon: PropTypes.string.isRequired,
2121
children: PropTypes.node,
22+
className: PropTypes.string,
2223
};
2324

2425
BigMessage.defaultProps = {
2526
message: '',
2627
children: null,
28+
className: 'tiled bg-white',
2729
};
2830

2931
export default function init(ngModule) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { isFunction, isString } from 'lodash';
2+
import React from 'react';
3+
import PropTypes from 'prop-types';
4+
5+
const componentsRegistry = new Map();
6+
const activeInstances = new Set();
7+
8+
export function registerComponent(name, component) {
9+
if (isString(name) && (name !== '')) {
10+
componentsRegistry[name] = isFunction(component) ? component : null;
11+
// Refresh active DynamicComponent instances which use this component
12+
activeInstances.forEach((dynamicComponent) => {
13+
if (dynamicComponent.props.name === name) {
14+
dynamicComponent.forceUpdate();
15+
}
16+
});
17+
}
18+
}
19+
20+
export function unregisterComponent(name) {
21+
registerComponent(name, null);
22+
}
23+
24+
export default class DynamicComponent extends React.Component {
25+
static propTypes = {
26+
name: PropTypes.string.isRequired,
27+
children: PropTypes.node,
28+
};
29+
30+
static defaultProps = {
31+
children: null,
32+
};
33+
34+
componentDidMount() {
35+
activeInstances.add(this);
36+
}
37+
38+
componentWillUnmount() {
39+
activeInstances.delete(this);
40+
}
41+
42+
render() {
43+
const { name, children, ...props } = this.props;
44+
const RealComponent = componentsRegistry.get(name);
45+
if (!RealComponent) {
46+
return null;
47+
}
48+
return <RealComponent {...props}>{children}</RealComponent>;
49+
}
50+
}

client/app/components/FavoritesControl.jsx

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,53 @@ import PropTypes from 'prop-types';
33
import { react2angular } from 'react2angular';
44
import { $rootScope } from '@/services/ng';
55

6-
function toggleItem(event, item, callback) {
7-
event.preventDefault();
8-
event.stopPropagation();
6+
export class FavoritesControl extends React.Component {
7+
static propTypes = {
8+
item: PropTypes.shape({
9+
is_favorite: PropTypes.bool.isRequired,
10+
}).isRequired,
11+
onChange: PropTypes.func,
12+
// Force component update when `item` changes.
13+
// Remove this when `react2angular` will finally go to hell
14+
forceUpdate: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
15+
};
916

10-
const action = item.is_favorite ? item.$unfavorite.bind(item) : item.$favorite.bind(item);
11-
const savedIsFavorite = item.is_favorite;
17+
static defaultProps = {
18+
onChange: () => {},
19+
forceUpdate: '',
20+
};
1221

13-
action().then(() => {
14-
item.is_favorite = !savedIsFavorite;
15-
$rootScope.$broadcast('reloadFavorites');
16-
callback();
17-
});
18-
}
22+
toggleItem(event, item, callback) {
23+
event.preventDefault();
24+
event.stopPropagation();
1925

20-
export function FavoritesControl({ item, onChange }) {
21-
const icon = item.is_favorite ? 'fa fa-star' : 'fa fa-star-o';
22-
const title = item.is_favorite ? 'Remove from favorites' : 'Add to favorites';
23-
return (
24-
<a
25-
href="javascript:void(0)"
26-
title={title}
27-
className="btn-favourite"
28-
onClick={event => toggleItem(event, item, onChange)}
29-
>
30-
<i className={icon} aria-hidden="true" />
31-
</a>
32-
);
33-
}
26+
const action = item.is_favorite ? item.$unfavorite.bind(item) : item.$favorite.bind(item);
27+
const savedIsFavorite = item.is_favorite;
3428

35-
FavoritesControl.propTypes = {
36-
item: PropTypes.shape({
37-
is_favorite: PropTypes.bool.isRequired,
38-
}).isRequired,
39-
onChange: PropTypes.func,
40-
// Force component update when `item` changes.
41-
// Remove this when `react2angular` will finally go to hell
42-
forceUpdate: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
43-
};
29+
action().then(() => {
30+
item.is_favorite = !savedIsFavorite;
31+
this.forceUpdate();
32+
$rootScope.$broadcast('reloadFavorites');
33+
callback();
34+
});
35+
}
4436

45-
FavoritesControl.defaultProps = {
46-
onChange: () => {},
47-
};
37+
render() {
38+
const { item, onChange } = this.props;
39+
const icon = item.is_favorite ? 'fa fa-star' : 'fa fa-star-o';
40+
const title = item.is_favorite ? 'Remove from favorites' : 'Add to favorites';
41+
return (
42+
<a
43+
href="javascript:void(0)"
44+
title={title}
45+
className="btn-favourite"
46+
onClick={event => this.toggleItem(event, item, onChange)}
47+
>
48+
<i className={icon} aria-hidden="true" />
49+
</a>
50+
);
51+
}
52+
}
4853

4954
export default function init(ngModule) {
5055
ngModule.component('favoritesControlImpl', react2angular(FavoritesControl));

client/app/components/NoTaggedObjectsFound.jsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { react2angular } from 'react2angular';
44
import { BigMessage } from '@/components/BigMessage';
55
import TagsControl from '@/components/tags-control/TagsControl';
66

7-
function NoTaggedObjectsFound({ objectType, tags }) {
7+
export function NoTaggedObjectsFound({ objectType, tags }) {
88
return (
99
<BigMessage icon="fa-tags">
1010
No {objectType} found tagged with&nbsp;<TagsControl className="inline-tags-control" tags={Array.from(tags)} />.
@@ -14,7 +14,10 @@ function NoTaggedObjectsFound({ objectType, tags }) {
1414

1515
NoTaggedObjectsFound.propTypes = {
1616
objectType: PropTypes.string.isRequired,
17-
tags: PropTypes.objectOf(Set).isRequired,
17+
tags: PropTypes.oneOfType([
18+
PropTypes.array,
19+
PropTypes.objectOf(Set),
20+
]).isRequired,
1821
};
1922

2023
export default function init(ngModule) {

client/app/components/TagsList.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class TagsList extends React.Component {
5252
}
5353
this.forceUpdate();
5454

55-
this.props.onUpdate(this.state.selectedTags);
55+
this.props.onUpdate([...this.state.selectedTags]);
5656
}
5757

5858
render() {
@@ -63,6 +63,7 @@ export class TagsList extends React.Component {
6363
{map(allTags, tag => (
6464
<a
6565
key={tag.name}
66+
href="javascript:void(0)"
6667
className={classNames('list-group-item', 'max-character', { active: selectedTags.has(tag.name) })}
6768
onClick={event => this.toggleTag(event, tag.name)}
6869
>

0 commit comments

Comments
 (0)