Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Dashboards/Queries/Users list pages to React #3381

Merged
merged 40 commits into from
Feb 5, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8e6d6b6
Refine existing implementation of dashboards/queries/users lists and …
kravets-levko Jan 30, 2019
0dff4a5
Migrate common list page controller to React and refactor it's logic
kravets-levko Jan 31, 2019
ec5d46f
Migrate Dashboard list page to React
kravets-levko Jan 31, 2019
77ce09d
Migrate Queries list page to React
kravets-levko Jan 31, 2019
88297f7
Migrate Users list page to React
kravets-levko Jan 31, 2019
efed0e0
Merge branch 'master' into feature/react-list-controllers
kravets-levko Jan 31, 2019
7c82ab4
Merge branch 'master' into feature/react-list-controllers
kravets-levko Feb 2, 2019
02bd99d
Merge branch 'master' into feature/react-list-controllers
kravets-levko Feb 2, 2019
fb8f624
Remove react-timeago dependency
kravets-levko Feb 2, 2019
834f942
Use composition instead of inheritance
kravets-levko Feb 2, 2019
d92df5c
Refine implementation
kravets-levko Feb 2, 2019
a75d63c
Merge sidebar into single component
kravets-levko Feb 3, 2019
683e75a
Refine column definitions
kravets-levko Feb 3, 2019
72c0099
Merge branch 'master' into feature/react-list-controllers
kravets-levko Feb 3, 2019
78ebb21
Use simple controller instead of React context
kravets-levko Feb 3, 2019
1b013d3
Refine implementation
kravets-levko Feb 3, 2019
ccf3deb
Merge branch 'master' into feature/react-list-controllers
kravets-levko Feb 3, 2019
5171317
Restore changes from getredash/redash#2888
kravets-levko Feb 3, 2019
01e999a
Tweak Users list page
kravets-levko Feb 3, 2019
2d3aed6
Ability to render dynamically defined components
kravets-levko Feb 4, 2019
ef7436d
Tweak users list page
kravets-levko Feb 4, 2019
aaaf894
User list page for non-admins
kravets-levko Feb 4, 2019
8a57ded
Fix: ItemsTable ignores isAvailable field
kravets-levko Feb 4, 2019
6412859
Refine implementation
kravets-levko Feb 4, 2019
c419340
Refine implementation
kravets-levko Feb 4, 2019
e40b266
Merge branch 'master' into feature/react-list-controllers
kravets-levko Feb 4, 2019
1785a8c
Implement LiveItemsList as higher order component
kravets-levko Feb 5, 2019
f8445f3
Some fixes
kravets-levko Feb 5, 2019
dcf22cd
Move some definitions to a better place
kravets-levko Feb 5, 2019
e56d2f6
Some fixes
kravets-levko Feb 5, 2019
b0b1873
Refine components
kravets-levko Feb 5, 2019
fcdd864
Refine UsersList page
kravets-levko Feb 5, 2019
706e4bc
Merge branch 'master' into feature/react-list-controllers
kravets-levko Feb 5, 2019
26bff4a
More comments for a god of comments
kravets-levko Feb 5, 2019
b9fb9d0
Merge branch 'master' into feature/react-list-controllers
kravets-levko Feb 5, 2019
3b1055c
Fix wrong tables size on smaller screens
kravets-levko Feb 5, 2019
73bbeab
Merge branch 'master' into feature/react-list-controllers
kravets-levko Feb 5, 2019
64bc61f
Tweak tables
kravets-levko Feb 5, 2019
3fef4ad
Merge branch 'master' into feature/react-list-controllers
arikfr Feb 5, 2019
7e9eea9
Merge branch 'master' into feature/react-list-controllers
kravets-levko Feb 5, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions client/app/assets/less/ant.less
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
@import '~antd/lib/radio/style/index';
@import '~antd/lib/time-picker/style/index';
@import '~antd/lib/pagination/style/index';
@import '~antd/lib/table/style/index';
@import 'inc/ant-variables';

// Remove bold in labels for Ant checkboxes and radio buttons
Expand Down Expand Up @@ -135,3 +136,35 @@
}
}
}

// Table

.@{table-prefix-cls} {
color: inherit;

* {
transition: none !important;
}

&-thead > tr > th {
padding: @table-padding-vertical * 2 @table-padding-horizontal;
}

.@{table-prefix-cls}-column-sorters {
&:before,
&:hover:before {
content: none;
}
}

&-thead > tr > th {
.@{table-prefix-cls}-column-sorter {
&-up,
&-down {
&.on {
color: @table-header-icon-active-color;
}
}
}
}
}
18 changes: 18 additions & 0 deletions client/app/assets/less/inc/ant-variables.less
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,21 @@
@pagination-disabled-bg: fade(@redash-gray, 15%);
@pagination-hover-color: #333;
@pagination-hover-bg: fade(@redash-gray, 25%);


/* --------------------------------------------------------
Table
-----------------------------------------------------------*/

@table-border-radius-base: 0;
@table-header-color: #333;
@table-header-bg: fade(@redash-gray, 3%);
@table-header-icon-color: fade(@text-color, 20%);
@table-header-icon-active-color: @text-color;
@table-header-sort-bg: @table-header-bg;
@table-header-sort-active-bg: @table-header-bg;
@table-header-filter-active-bg: @table-header-bg;
@table-body-sort-bg: transparent;
@table-row-hover-bg: fade(@redash-gray, 5%);
@table-padding-vertical: 7px;
@table-padding-horizontal: 10px;
7 changes: 2 additions & 5 deletions client/app/assets/less/redash/redash-newstyle.less
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,9 @@ body {
box-shadow: inset 3px 0px 0px @brand-primary;
}

.table.table-data {
> tbody > tr > td {
.table-data {
tbody > tr > td {
padding-top: 5px !important;
}

tr:hover {
cursor: pointer;
}

Expand Down
79 changes: 42 additions & 37 deletions client/app/components/FavoritesControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,53 @@ import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
import { $rootScope } from '@/services/ng';

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

const action = item.is_favorite ? item.$unfavorite.bind(item) : item.$favorite.bind(item);
const savedIsFavorite = item.is_favorite;
static defaultProps = {
onChange: () => {},
forceUpdate: '',
};

action().then(() => {
item.is_favorite = !savedIsFavorite;
$rootScope.$broadcast('reloadFavorites');
callback();
});
}
toggleItem(event, item, callback) {
event.preventDefault();
event.stopPropagation();

export function FavoritesControl({ item, onChange }) {
const icon = item.is_favorite ? 'fa fa-star' : 'fa fa-star-o';
const title = item.is_favorite ? 'Remove from favorites' : 'Add to favorites';
return (
<a
href="javascript:void(0)"
title={title}
className="btn-favourite"
onClick={event => toggleItem(event, item, onChange)}
>
<i className={icon} aria-hidden="true" />
</a>
);
}
const action = item.is_favorite ? item.$unfavorite.bind(item) : item.$favorite.bind(item);
const savedIsFavorite = item.is_favorite;

FavoritesControl.propTypes = {
item: PropTypes.shape({
is_favorite: PropTypes.bool.isRequired,
}).isRequired,
onChange: PropTypes.func,
// Force component update when `item` changes.
// Remove this when `react2angular` will finally go to hell
forceUpdate: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
};
action().then(() => {
item.is_favorite = !savedIsFavorite;
this.forceUpdate();
$rootScope.$broadcast('reloadFavorites');
callback();
});
}

FavoritesControl.defaultProps = {
onChange: () => {},
};
render() {
const { item, onChange } = this.props;
const icon = item.is_favorite ? 'fa fa-star' : 'fa fa-star-o';
const title = item.is_favorite ? 'Remove from favorites' : 'Add to favorites';
return (
<a
href="javascript:void(0)"
title={title}
className="btn-favourite"
onClick={event => this.toggleItem(event, item, onChange)}
>
<i className={icon} aria-hidden="true" />
</a>
);
}
}

export default function init(ngModule) {
ngModule.component('favoritesControlImpl', react2angular(FavoritesControl));
Expand Down
7 changes: 5 additions & 2 deletions client/app/components/NoTaggedObjectsFound.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { react2angular } from 'react2angular';
import { BigMessage } from '@/components/BigMessage';
import TagsControl from '@/components/tags-control/TagsControl';

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

NoTaggedObjectsFound.propTypes = {
objectType: PropTypes.string.isRequired,
tags: PropTypes.objectOf(Set).isRequired,
tags: PropTypes.oneOfType([
PropTypes.array,
PropTypes.objectOf(Set),
]).isRequired,
};

export default function init(ngModule) {
Expand Down
3 changes: 2 additions & 1 deletion client/app/components/TagsList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class TagsList extends React.Component {
}
this.forceUpdate();

this.props.onUpdate(this.state.selectedTags);
this.props.onUpdate([...this.state.selectedTags]);
}

render() {
Expand All @@ -63,6 +63,7 @@ export class TagsList extends React.Component {
{map(allTags, tag => (
<a
key={tag.name}
href="javascript:void(0)"
className={classNames('list-group-item', 'max-character', { active: selectedTags.has(tag.name) })}
onClick={event => this.toggleTag(event, tag.name)}
>
Expand Down
2 changes: 1 addition & 1 deletion client/app/components/queries/SchedulePhrase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { RefreshScheduleType, RefreshScheduleDefault } from '../proptypes';

import './ScheduleDialog.css';

class SchedulePhrase extends React.Component {
export class SchedulePhrase extends React.Component {
static propTypes = {
schedule: RefreshScheduleType,
isNew: PropTypes.bool.isRequired,
Expand Down
2 changes: 1 addition & 1 deletion client/app/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function registerVisualizations() {
}

function registerPages() {
const context = require.context('@/pages', true, /^((?![\\/.]test[\\./]).)*\.js$/);
const context = require.context('@/pages', true, /^((?![\\/.]test[\\./]).)*\.jsx?$/);
const routesCollection = registerAll(context);
routesCollection.forEach((routes) => {
ngModule.config(($routeProvider) => {
Expand Down
27 changes: 14 additions & 13 deletions client/app/filters/datetime.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import moment from 'moment';
import { clientConfig } from '@/services/auth';

export default function init(ngModule) {
ngModule.filter('toMilliseconds', () => value => value * 1000.0);

ngModule.filter('dateTime', clientConfig => function dateTime(value) {
if (!value) {
return '';
}
export function formatDateTime(value) {
if (!value) {
return '';
}

const parsed = moment(value);
const parsed = moment(value);
if (!parsed.isValid()) {
return '-';
}

if (!parsed.isValid()) {
return '-';
}
return parsed.format(clientConfig.dateTimeFormat);
}

return parsed.format(clientConfig.dateTimeFormat);
});
export default function init(ngModule) {
ngModule.filter('toMilliseconds', () => value => value * 1000.0);
ngModule.filter('dateTime', () => formatDateTime);
}

init.init = true;
10 changes: 5 additions & 5 deletions client/app/lib/list-ctrl.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { bind } from 'lodash';
import $ from 'jquery';
import { LivePaginator } from '@/lib/pagination';
import { $location } from '@/services/ng';
import { currentUser, clientConfig } from '@/services/auth';

export default class ListCtrl {
constructor($scope, $location, currentUser, clientConfig, defaultOrder = '-created_at') {
constructor($scope, currentPage, resource, defaultOrder = '-created_at') {
this.searchTerm = $location.search().q || '';

this.page = parseInt($location.search().page || 1, 10);
Expand All @@ -20,10 +22,8 @@ export default class ListCtrl {
}
this.defaultOptions = {};

// use $parent because we're using a component as route target instead of controller;
// $parent refers to scope created for the page by router
this.resource = $scope.$parent.$resolve.resource;
this.currentPage = $scope.$parent.$resolve.currentPage;
this.currentPage = currentPage;
this.resource = resource;
this.currentUser = currentUser;

this.showEmptyState = false;
Expand Down
6 changes: 4 additions & 2 deletions client/app/lib/pagination/live-paginator.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isBoolean } from 'lodash';

export default class LivePaginator {
constructor(rowsFetcher, {
page = 1, itemsPerPage = 20, orderByField, orderByReverse = false,
Expand All @@ -6,7 +8,7 @@ export default class LivePaginator {
this.itemsPerPage = itemsPerPage;
this.totalCount = 0;
this.orderByField = orderByField;
this.orderByReverse = orderByReverse;
this.orderByReverse = !!orderByReverse;
kravets-levko marked this conversation as resolved.
Show resolved Hide resolved
this.rowsFetcher = rowsFetcher;
this.fetchPage(page);
}
Expand All @@ -19,7 +21,7 @@ export default class LivePaginator {
if (pageOrder) {
this.orderByField = pageOrder;
}
if (pageOrderReverse) {
if (isBoolean(pageOrderReverse)) {
this.orderByReverse = pageOrderReverse;
}
if (pageSize) {
Expand Down
Loading