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

Make favorite queries/dashboard order by starred at(favorited at) #7351

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions client/app/components/items-list/ItemsList.tsx
Original file line number Diff line number Diff line change
@@ -93,7 +93,7 @@ export interface ItemsListWrappedComponentProps<I, P = any> {
export function wrap<I, P = any>(
WrappedComponent: React.ComponentType<ItemsListWrappedComponentProps<I>>,
createItemsSource: () => ItemsSource,
createStateStorage: () => StateStorage
createStateStorage: ( { ...props }) => StateStorage
) {
class ItemsListWrapper extends React.Component<ItemsListWrapperProps, ItemsListWrapperState<I, P>> {
private _itemsSource: ItemsSource;
@@ -116,7 +116,7 @@ export function wrap<I, P = any>(
constructor(props: ItemsListWrapperProps) {
super(props);

const stateStorage = createStateStorage();
const stateStorage = createStateStorage({ ...props });
const itemsSource = createItemsSource();
this._itemsSource = itemsSource;

23 changes: 15 additions & 8 deletions client/app/pages/dashboards/DashboardList.jsx
Original file line number Diff line number Diff line change
@@ -81,12 +81,19 @@ function DashboardListExtraActions(props) {
}

function DashboardList({ controller }) {
let usedListColumns = listColumns;
if (controller.params.currentPage === "favorites") {
usedListColumns = [
...usedListColumns,
Columns.dateTime.sortable({ title: "Starred At", field: "starred_at", width: "1%" }),
];
}
const {
areExtraActionsAvailable,
listColumns: tableColumns,
Component: ExtraActionsComponent,
selectedItems,
} = useItemsListExtraActions(controller, listColumns, DashboardListExtraActions);
} = useItemsListExtraActions(controller, usedListColumns, DashboardListExtraActions);

return (
<div className="page-dashboard-list">
@@ -139,9 +146,9 @@ function DashboardList({ controller }) {
showPageSizeSelect
totalCount={controller.totalItemsCount}
pageSize={controller.itemsPerPage}
onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}
onPageSizeChange={(itemsPerPage) => controller.updatePagination({ itemsPerPage })}
page={controller.page}
onChange={page => controller.updatePagination({ page })}
onChange={(page) => controller.updatePagination({ page })}
/>
</div>
</React.Fragment>
@@ -170,33 +177,33 @@ const DashboardListPage = itemsList(
}[currentPage];
},
getItemProcessor() {
return item => new Dashboard(item);
return (item) => new Dashboard(item);
},
}),
() => new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
({ ...props }) => new UrlStateStorage({ orderByField: props.orderByField ?? "created_at", orderByReverse: true })
);

routes.register(
"Dashboards.List",
routeWithUserSession({
path: "/dashboards",
title: "Dashboards",
render: pageProps => <DashboardListPage {...pageProps} currentPage="all" />,
render: (pageProps) => <DashboardListPage {...pageProps} currentPage="all" />,
})
);
routes.register(
"Dashboards.Favorites",
routeWithUserSession({
path: "/dashboards/favorites",
title: "Favorite Dashboards",
render: pageProps => <DashboardListPage {...pageProps} currentPage="favorites" />,
render: (pageProps) => <DashboardListPage {...pageProps} currentPage="favorites" orderByField="starred_at" />,
})
);
routes.register(
"Dashboards.My",
routeWithUserSession({
path: "/dashboards/my",
title: "My Dashboards",
render: pageProps => <DashboardListPage {...pageProps} currentPage="my" />,
render: (pageProps) => <DashboardListPage {...pageProps} currentPage="my" />,
})
);
8 changes: 4 additions & 4 deletions client/app/pages/home/components/FavoritesList.jsx
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ export function FavoriteList({ title, resource, itemUrl, emptyState }) {
useEffect(() => {
setLoading(true);
resource
.favorites()
.favorites({ order: "-starred_at" })
.then(({ results }) => setItems(results))
.finally(() => setLoading(false));
}, [resource]);
@@ -28,7 +28,7 @@ export function FavoriteList({ title, resource, itemUrl, emptyState }) {
</div>
{!isEmpty(items) && (
<div role="list" className="list-group">
{items.map(item => (
{items.map((item) => (
<Link key={itemUrl(item)} role="listitem" className="list-group-item" href={itemUrl(item)}>
<span className="btn-favorite m-r-5">
<i className="fa fa-star" aria-hidden="true" />
@@ -61,7 +61,7 @@ export function DashboardAndQueryFavoritesList() {
<FavoriteList
title="Favorite Dashboards"
resource={Dashboard}
itemUrl={dashboard => dashboard.url}
itemUrl={(dashboard) => dashboard.url}
emptyState={
<p>
<span className="btn-favorite m-r-5">
@@ -76,7 +76,7 @@ export function DashboardAndQueryFavoritesList() {
<FavoriteList
title="Favorite Queries"
resource={Query}
itemUrl={query => `queries/${query.id}`}
itemUrl={(query) => `queries/${query.id}`}
emptyState={
<p>
<span className="btn-favorite m-r-5">
25 changes: 16 additions & 9 deletions client/app/pages/queries-list/QueriesList.jsx
Original file line number Diff line number Diff line change
@@ -108,12 +108,19 @@ function QueriesList({ controller }) {
};
}, []);

let usedListColumns = listColumns;
if (controller.params.currentPage === "favorites") {
usedListColumns = [
...usedListColumns,
Columns.dateTime.sortable({ title: "Starred At", field: "starred_at", width: "1%" }),
];
}
const {
areExtraActionsAvailable,
listColumns: tableColumns,
Component: ExtraActionsComponent,
selectedItems,
} = useItemsListExtraActions(controller, listColumns, QueriesListExtraActions);
} = useItemsListExtraActions(controller, usedListColumns, QueriesListExtraActions);

return (
<div className="page-queries-list">
@@ -165,9 +172,9 @@ function QueriesList({ controller }) {
showPageSizeSelect
totalCount={controller.totalItemsCount}
pageSize={controller.itemsPerPage}
onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}
onPageSizeChange={(itemsPerPage) => controller.updatePagination({ itemsPerPage })}
page={controller.page}
onChange={page => controller.updatePagination({ page })}
onChange={(page) => controller.updatePagination({ page })}
/>
</div>
</React.Fragment>
@@ -196,41 +203,41 @@ const QueriesListPage = itemsList(
}[currentPage];
},
getItemProcessor() {
return item => new Query(item);
return (item) => new Query(item);
},
}),
() => new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
({ ...props }) => new UrlStateStorage({ orderByField: props.orderByField ?? "created_at", orderByReverse: true })
);

routes.register(
"Queries.List",
routeWithUserSession({
path: "/queries",
title: "Queries",
render: pageProps => <QueriesListPage {...pageProps} currentPage="all" />,
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="all" />,
})
);
routes.register(
"Queries.Favorites",
routeWithUserSession({
path: "/queries/favorites",
title: "Favorite Queries",
render: pageProps => <QueriesListPage {...pageProps} currentPage="favorites" />,
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="favorites" orderByField="starred_at" />,
})
);
routes.register(
"Queries.Archived",
routeWithUserSession({
path: "/queries/archive",
title: "Archived Queries",
render: pageProps => <QueriesListPage {...pageProps} currentPage="archive" />,
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="archive" />,
})
);
routes.register(
"Queries.My",
routeWithUserSession({
path: "/queries/my",
title: "My Queries",
render: pageProps => <QueriesListPage {...pageProps} currentPage="my" />,
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="my" />,
})
);
2 changes: 2 additions & 0 deletions redash/handlers/dashboards.py
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@
"-name": "-lowercase_name",
"created_at": "created_at",
"-created_at": "-created_at",
"starred_at": "favorites-created_at",
"-starred_at": "-favorites-created_at",
}

order_results = partial(_order_results, default_order="-created_at", allowed_orders=order_map)
2 changes: 2 additions & 0 deletions redash/handlers/queries.py
Original file line number Diff line number Diff line change
@@ -44,6 +44,8 @@
"-executed_at": "-query_results-retrieved_at",
"created_by": "users-name",
"-created_by": "-users-name",
"starred_at": "favorites-created_at",
"-starred_at": "-favorites-created_at",
}

order_results = partial(_order_results, default_order="-created_at", allowed_orders=order_map)
20 changes: 12 additions & 8 deletions redash/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1145,15 +1145,19 @@ def all_tags(cls, org, user):
def favorites(cls, user, base_query=None):
if base_query is None:
base_query = cls.all(user.org, user.group_ids, user.id)
return base_query.join(
(
Favorite,
and_(
Favorite.object_type == "Dashboard",
Favorite.object_id == Dashboard.id,
),
return (
base_query.distinct(cls.lowercase_name, Dashboard.created_at, Dashboard.slug, Favorite.created_at)
.join(
(
Favorite,
and_(
Favorite.object_type == "Dashboard",
Favorite.object_id == Dashboard.id,
),
)
)
).filter(Favorite.user_id == user.id)
.filter(Favorite.user_id == user.id)
)

@classmethod
def by_user(cls, user):
30 changes: 25 additions & 5 deletions redash/serializers/__init__.py
Original file line number Diff line number Diff line change
@@ -82,9 +82,19 @@ def serialize(self):
else:
result = [serialize_query(query, **self.options) for query in self.object_or_list]
if self.options.get("with_favorite_state", True):
favorite_ids = models.Favorite.are_favorites(current_user.id, self.object_or_list)
queries = list(self.object_or_list)
favorites = models.Favorite.query.filter(
models.Favorite.object_id.in_([o.id for o in queries]),
models.Favorite.object_type == "Query",
models.Favorite.user_id == current_user.id,
)
favorites_dict = {fav.object_id: fav for fav in favorites}

for query in result:
query["is_favorite"] = query["id"] in favorite_ids
favorite = favorites_dict.get(query["id"])
query["is_favorite"] = favorite is not None
if favorite:
query["starred_at"] = favorite.created_at

return result

@@ -263,9 +273,19 @@ def serialize(self):
else:
result = [serialize_dashboard(obj, **self.options) for obj in self.object_or_list]
if self.options.get("with_favorite_state", True):
favorite_ids = models.Favorite.are_favorites(current_user.id, self.object_or_list)
for obj in result:
obj["is_favorite"] = obj["id"] in favorite_ids
dashboards = list(self.object_or_list)
favorites = models.Favorite.query.filter(
models.Favorite.object_id.in_([o.id for o in dashboards]),
models.Favorite.object_type == "Dashboard",
models.Favorite.user_id == current_user.id,
)
favorites_dict = {fav.object_id: fav for fav in favorites}

for query in result:
favorite = favorites_dict.get(query["id"])
query["is_favorite"] = favorite is not None
if favorite:
query["starred_at"] = favorite.created_at

return result