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

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

* fix styling for restyled error
This commit is contained in:
Tsuneo Yoshioka
2025-09-16 16:24:03 +09:00
committed by GitHub
parent 59951eda3d
commit b8ebf49436
8 changed files with 72 additions and 30 deletions

View File

@@ -98,7 +98,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;
@@ -121,7 +121,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;

View File

@@ -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,10 +177,10 @@ 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(
@@ -181,7 +188,7 @@ routes.register(
routeWithUserSession({
path: "/dashboards",
title: "Dashboards",
render: pageProps => <DashboardListPage {...pageProps} currentPage="all" />,
render: (pageProps) => <DashboardListPage {...pageProps} currentPage="all" />,
})
);
routes.register(
@@ -189,7 +196,7 @@ routes.register(
routeWithUserSession({
path: "/dashboards/favorites",
title: "Favorite Dashboards",
render: pageProps => <DashboardListPage {...pageProps} currentPage="favorites" />,
render: (pageProps) => <DashboardListPage {...pageProps} currentPage="favorites" orderByField="starred_at" />,
})
);
routes.register(
@@ -197,6 +204,6 @@ routes.register(
routeWithUserSession({
path: "/dashboards/my",
title: "My Dashboards",
render: pageProps => <DashboardListPage {...pageProps} currentPage="my" />,
render: (pageProps) => <DashboardListPage {...pageProps} currentPage="my" />,
})
);

View File

@@ -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">

View File

@@ -115,12 +115,19 @@ function QueriesList({ controller }) {
};
}, [updateSearch]);
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">
@@ -207,7 +214,7 @@ const QueriesListPage = itemsList(
return (item) => new Query(item);
},
}),
() => new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
({ ...props }) => new UrlStateStorage({ orderByField: props.orderByField ?? "created_at", orderByReverse: true })
);
routes.register(
@@ -223,7 +230,7 @@ routes.register(
routeWithUserSession({
path: "/queries/favorites",
title: "Favorite Queries",
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="favorites" />,
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="favorites" orderByField="starred_at" />,
})
);
routes.register(

View File

@@ -26,6 +26,8 @@ order_map = {
"-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)

View File

@@ -44,6 +44,8 @@ order_map = {
"-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)

View File

@@ -1145,15 +1145,19 @@ class Dashboard(ChangeTrackingMixin, TimestampMixin, BelongsToOrgMixin, db.Model
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):

View File

@@ -82,9 +82,19 @@ class QuerySerializer(Serializer):
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 @@ class DashboardSerializer(Serializer):
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