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

Featured and All Benchmarks #577

Merged
merged 5 commits into from
Jan 1, 2023
Merged
Show file tree
Hide file tree
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
55 changes: 48 additions & 7 deletions backend/src/impl/db_utils/benchmark_db_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from explainaboard_web.impl.db_utils.dataset_db_utils import DatasetDBUtils
from explainaboard_web.impl.db_utils.db_utils import DBUtils
from explainaboard_web.impl.db_utils.system_db_utils import SystemDBUtils
from explainaboard_web.impl.db_utils.user_db_utils import UserDBUtils
from explainaboard_web.impl.internal_models.system_model import SystemModel
from explainaboard_web.impl.utils import abort_with_error_message
from explainaboard_web.models import (
Expand Down Expand Up @@ -50,7 +51,9 @@ def _convert_id_to_db(doc: dict) -> None:
doc.pop("id")

@staticmethod
def find_configs(parent: str, page: int = 0, page_size: int = 0):
def find_configs(
parent: str, page: int = 0, page_size: int = 0
) -> list[BenchmarkConfig]:
permissions_list = [{"is_private": False}]
user = get_user()
if user:
Expand All @@ -61,21 +64,32 @@ def find_configs(parent: str, page: int = 0, page_size: int = 0):
cursor, _ = DBUtils.find(
DBUtils.BENCHMARK_METADATA, filt=filt, limit=page * page_size
)
configs = []

config_dicts = []
for config_dict in list(cursor):
BenchmarkDBUtils._convert_id_from_db(config_dict)
parent_id = config_dict.get("parent")
if parent_id:
parent_config = BenchmarkDBUtils.find_config_by_id(parent_id)
# do not insert preferred username here as every single config will
# issue a find instruction in DB, which creates a lot of overhead
parent_config = BenchmarkDBUtils.find_config_by_id(
parent_id, include_preferred_username=False
)
parent_dict = parent_config.to_dict()
BenchmarkDBUtils._update_with_not_none_values(parent_dict, config_dict)
config_dict = parent_dict

configs.append(BenchmarkConfig.from_dict(config_dict))
return configs
config_dicts.append(config_dict)

# insert preferred usernames in batch to reduce overhead in DB
UserDBUtils.insert_preferred_usernames(config_dicts)

return [BenchmarkConfig.from_dict(config_dict) for config_dict in config_dicts]

@staticmethod
def find_config_by_id(benchmark_id: str) -> BenchmarkConfig:
def find_config_by_id(
benchmark_id: str, include_preferred_username: bool = True
) -> BenchmarkConfig:
config_dict = DBUtils.find_one_by_id(DBUtils.BENCHMARK_METADATA, benchmark_id)
if not config_dict:
abort_with_error_message(404, f"benchmark id: {benchmark_id} not found")
Expand All @@ -88,8 +102,32 @@ def find_config_by_id(benchmark_id: str) -> BenchmarkConfig:
BenchmarkDBUtils._update_with_not_none_values(parent_dict, config_dict)
config_dict = parent_dict

if include_preferred_username:
UserDBUtils.insert_preferred_username(config_dict)
else:
config_dict["preferred_username"] = ""

return BenchmarkConfig.from_dict(config_dict)

@staticmethod
def find_configs_featured() -> list[BenchmarkConfig]:
cursor, _ = DBUtils.find(DBUtils.BENCHMARK_FEATURED_LIST, limit=1)
cursor_list = list(cursor)
if len(cursor_list) < 1:
abort_with_error_message(500, "featured list not found")

config_dicts = []
for benchmark_id in cursor_list[0]["ids"]:
config_dict = BenchmarkDBUtils.find_config_by_id(
benchmark_id, include_preferred_username=False
).to_dict()
config_dicts.append(config_dict)

# insert preferred usernames in batch to reduce overhead in DB
UserDBUtils.insert_preferred_usernames(config_dicts)

return [BenchmarkConfig.from_dict(config_dict) for config_dict in config_dicts]

@staticmethod
def create_benchmark(props: BenchmarkCreateProps) -> BenchmarkConfig:
props_dict = props.to_dict()
Expand All @@ -103,10 +141,13 @@ def create_benchmark(props: BenchmarkCreateProps) -> BenchmarkConfig:
"last_modified"
] = datetime.datetime.utcnow()

config = BenchmarkConfig.from_dict(props_dict)
BenchmarkDBUtils._convert_id_to_db(props_dict)
DBUtils.insert_one(DBUtils.BENCHMARK_METADATA, props_dict)

BenchmarkDBUtils._convert_id_from_db(props_dict)
UserDBUtils.insert_preferred_username(props_dict)
config = BenchmarkConfig.from_dict(props_dict)

return config

@staticmethod
Expand Down
3 changes: 3 additions & 0 deletions backend/src/impl/db_utils/db_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class DBUtils:
BENCHMARK_METADATA = DBCollection(
db_name="metadata", collection_name="benchmark_metadata"
)
BENCHMARK_FEATURED_LIST = DBCollection(
db_name="metadata", collection_name="benchmark_featured_list"
)

@staticmethod
def _convert_id(_id: str | ObjectId):
Expand Down
13 changes: 13 additions & 0 deletions backend/src/impl/db_utils/user_db_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@


class UserDBUtils:
@staticmethod
def insert_preferred_username(doc: dict) -> None:
user = UserDBUtils.find_users([doc["creator"]])[0]
doc["preferred_username"] = user.preferred_username

@staticmethod
def insert_preferred_usernames(docs: list[dict]) -> None:
user_ids = {doc["creator"] for doc in docs}
users = UserDBUtils.find_users(list(user_ids))
id_to_preferred_username = {user.id: user.preferred_username for user in users}
for doc in docs:
doc["preferred_username"] = id_to_preferred_username[doc["creator"]]

@staticmethod
def create_user(user: User) -> User:
doc = user.to_dict()
Expand Down
9 changes: 7 additions & 2 deletions backend/src/impl/default_controllers_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,13 @@ def metric_descriptions_get() -> dict[str, str]:
""" /benchmarks """


def benchmark_configs_get(parent: str | None) -> list[BenchmarkConfig]:
return BenchmarkDBUtils.find_configs(parent)
def benchmark_configs_get(
parent: str | None, featured: bool | None
) -> list[BenchmarkConfig]:
if featured:
return BenchmarkDBUtils.find_configs_featured()
else:
return BenchmarkDBUtils.find_configs(parent)


def benchmark_get_by_id(benchmark_id: str, by_creator: bool) -> Benchmark:
Expand Down
46 changes: 36 additions & 10 deletions frontend/src/components/BenchmarkCards/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import "./index.css";
import {
Card,
Expand All @@ -10,6 +9,7 @@ import {
Space,
Popconfirm,
Button,
Radio,
message,
} from "antd";
import { BenchmarkConfig } from "../../clients/openapi";
Expand All @@ -19,15 +19,22 @@ import { BenchmarkSubmitDrawer } from "../BenchmarkSubmitDrawer";
import { backendClient, parseBackendError } from "../../clients";
import { DeleteOutlined } from "@ant-design/icons";
import { useUser } from "../useUser";
import { FilterUpdate } from "../../pages/BenchmarkPage/BenchmarkFilter";

interface Props {
items: Array<BenchmarkConfig>;
subtitle: string;
isAtRootPage: boolean;
showFeatured: boolean;
onFilterChange: (value: FilterUpdate) => void;
}

export function BenchmarkCards({ items, subtitle }: Props) {
export function BenchmarkCards({
items,
isAtRootPage,
showFeatured,
onFilterChange,
}: Props) {
const { userInfo } = useUser();
const history = useHistory();
const [submitDrawerVisible, setSubmitDrawerVisible] = useState(false);

async function deleteBenchmark(benchmarkID: string) {
Expand All @@ -46,17 +53,35 @@ export function BenchmarkCards({ items, subtitle }: Props) {
setSubmitDrawerVisible(true);
}

const featuredVsAllOptions = [
{ label: "Featured", value: true, disabled: !isAtRootPage },
{ label: "All", value: false, disabled: !isAtRootPage },
];

const featuredVsAllBenchmarksToggle = (
<Radio.Group
options={featuredVsAllOptions}
onChange={({ target: { value } }) => {
onFilterChange({ showFeatured: value });
}}
value={showFeatured}
optionType="button"
buttonStyle="solid"
/>
);

return (
<div className="page">
<Helmet>
<title>ExplainaBoard - Benchmarks</title>
</Helmet>
<PageHeader
onBack={() => history.push("/")}
onBack={() => onFilterChange({ parentId: "" })}
title="Benchmarks"
subTitle={subtitle}
subTitle="Select a benchmark"
/>
<Row justify="end">
<Row justify="space-between" style={{ marginBottom: "10px" }}>
<Space>{featuredVsAllBenchmarksToggle}</Space>
<Space style={{ width: "fit-content", float: "right" }}>
<NewResourceButton
onClick={showSubmitDrawer}
Expand All @@ -80,9 +105,9 @@ export function BenchmarkCards({ items, subtitle }: Props) {
<Typography.Title level={3}>{config.name}</Typography.Title>
</div>
}
onClick={() =>
history.push(`${document.location.pathname}?id=${config.id}`)
}
onClick={() => {
onFilterChange({ parentId: config.id, showFeatured: false });
}}
cover={
<img
alt="example"
Expand All @@ -91,6 +116,7 @@ export function BenchmarkCards({ items, subtitle }: Props) {
/>
}
>
<Row>Creator: {config.preferred_username}</Row>
<Row justify="end">
<Popconfirm
disabled={notCreator}
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/BenchmarkTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import { useHistory, Link } from "react-router-dom";
import { CheckSquareTwoTone } from "@ant-design/icons";
import { Plot } from "../../components";
import { Helmet } from "react-helmet";
import { FilterUpdate } from "../../pages/BenchmarkPage/BenchmarkFilter";

interface Props {
/**initial value for task filter */
benchmarkID: string;
onFilterChange: (value: FilterUpdate) => void;
}

function tableToPage(my_view: BenchmarkTableData) {
Expand Down Expand Up @@ -75,7 +77,7 @@ function tableToPage(my_view: BenchmarkTableData) {
}

/** A table that lists all systems */
export function BenchmarkTable({ benchmarkID }: Props) {
export function BenchmarkTable({ benchmarkID, onFilterChange }: Props) {
const [benchmark, setBenchmark] = useState<Benchmark>();
const [pageState, setPageState] = useState(PageState.loading);
const [byCreator, setByCreator] = useState<boolean>(false);
Expand Down Expand Up @@ -161,6 +163,10 @@ export function BenchmarkTable({ benchmarkID }: Props) {
<Helmet>
<title>ExplainaBoard - {benchmark.config.name} Benchmark</title>
</Helmet>
<PageHeader
title={<b style={{ fontSize: "30px" }}>{benchmarkID} Benchmark</b>}
onBack={() => onFilterChange({ parentId: "" })}
/>
<Descriptions
title={<b style={{ fontSize: "30px" }}>{benchmark.config.name}</b>}
>
Expand Down
60 changes: 60 additions & 0 deletions frontend/src/pages/BenchmarkPage/BenchmarkFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export const filterKeyMap = {
parentId: "parent_id",
showFeatured: "show_featured",
};

export type FilterUpdate = Partial<BenchmarkFilter>;
type QueryDict = { [key: string]: string };

export class BenchmarkFilter {
parentId: string | undefined;
showFeatured: boolean;

constructor(partial: FilterUpdate) {
Object.assign(this, partial);
this.showFeatured =
partial.showFeatured === undefined ? true : partial.showFeatured;
}

update(filterUpdate: FilterUpdate): BenchmarkFilter {
const filterValues = { ...this, ...filterUpdate };
return new BenchmarkFilter(filterValues);
}

toUrlParams(): URLSearchParams {
const queryParams = new URLSearchParams();
for (const [key, val] of Object.entries(
BenchmarkFilter.parseFilterToQuery(this)
)) {
queryParams.append(key, val);
}
return queryParams;
}

static parseQueryToFilter(query: URLSearchParams): BenchmarkFilter {
const filters: FilterUpdate = {};

const parentId = query.get(filterKeyMap.parentId);
filters.parentId = parentId || "";

const showFeatured = query.get(filterKeyMap.showFeatured);
if (filters.parentId === "") {
filters.showFeatured = showFeatured === "false" ? false : true;
} else {
// featured benchmarks are only available at
// the root page, so we set the filter to false here
filters.showFeatured = false;
}

return new BenchmarkFilter(filters);
}

static parseFilterToQuery(filter: Partial<BenchmarkFilter>): QueryDict {
const dict: QueryDict = {};

dict[filterKeyMap.parentId] = filter.parentId || "";
dict[filterKeyMap.showFeatured] = filter.showFeatured ? "true" : "false";

return dict;
}
}
Loading