Skip to content

Commit

Permalink
Merge pull request #340 from RTIInternational/history-sorting-update
Browse files Browse the repository at this point in the history
Allow sorting across all data in history table
  • Loading branch information
AstridKery authored Jul 2, 2024
2 parents 39afa6c + 5992bd2 commit b238b0c
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 38 deletions.
44 changes: 29 additions & 15 deletions backend/django/core/views/api_annotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,15 +1037,31 @@ def get_label_history(request, project_pk):
current_page = 1
page = int(current_page) - 1

page_size = 100
all_data = Data.objects.filter(pk__in=total_data_list).order_by("text")
metadata_objects = MetaDataField.objects.filter(project=project)
sort_by = request.GET.get("sort-by")
reverse = request.GET.get("reverse", "false").lower() == "true"
sort_options = {
'data': 'text',
'label': 'datalabel__label__name',
'profile': 'datalabel__profile__user__username',
'timestamp': 'datalabel__timestamp',
'verified': 'datalabel__verified__pk',
'verified_by': 'datalabel__verified__verified_by__user__username',
'pre_loaded': 'datalabel__pre_loaded'
}

order_field = sort_options.get(sort_by, 'text')

if reverse:
order_field = '-' + order_field

all_data = Data.objects.filter(pk__in=total_data_list).order_by(order_field)

# filter the results by the search terms
text_filter = request.GET.get("Text")
if text_filter is not None and text_filter != "":
all_data = all_data.filter(text__icontains=text_filter)

metadata_objects = MetaDataField.objects.filter(project=project)
for m in metadata_objects:
m_filter = request.GET.get(str(m))
if m_filter is not None and m_filter != "":
Expand All @@ -1054,24 +1070,27 @@ def get_label_history(request, project_pk):
).values_list("data__pk", flat=True)
all_data = all_data.filter(pk__in=data_with_metadata_filter)

page_size = 100
total_pages = math.ceil(len(all_data) / page_size)

pre_sorted = False
page_data = all_data[page * page_size : min((page + 1) * page_size, len(all_data))]
# get the metadata IDs needed for metadata editing

page_data_metadata_ids = [
d["metadata"] for d in DataMetadataIDSerializer(page_data, many=True).data
]

page_data = DataSerializer(page_data, many=True).data

# derive the metadata fields in the forms needed for the table
all_metadata = [c.popitem("metadata")[1] for c in page_data]
all_metadata_formatted = [
page_metadata = [c.popitem("metadata")[1] for c in page_data]
page_metadata_formatted = [
{c.split(":")[0].replace(" ", "_"): c.split(":")[1] for c in inner_list}
for inner_list in all_metadata
for inner_list in page_metadata
]

data_df = pd.DataFrame(page_data).rename(columns={"pk": "id", "text": "data"})
data_df["metadataIDs"] = page_data_metadata_ids
data_df["metadata"] = page_metadata
data_df["formattedMetadata"] = page_metadata_formatted

if len(data_df) == 0:
return Response(
{
Expand Down Expand Up @@ -1138,12 +1157,7 @@ def get_label_history(request, project_pk):
# TODO: annotate uses pk while everything else uses ID. Let's fix this
data_df["pk"] = data_df["id"]

# now add back on the metadata fields
results = data_df.fillna("").to_dict(orient="records")
for i in range(len(results)):
results[i]["metadata"] = all_metadata[i]
results[i]["formattedMetadata"] = all_metadata_formatted[i]
results[i]["metadataIDs"] = page_data_metadata_ids[i]

return Response(
{
Expand Down
104 changes: 86 additions & 18 deletions frontend/src/components/History/HistoryTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import {
import React, { Fragment, useState, useEffect } from "react";
import { Button, Form, OverlayTrigger, Table, Tooltip } from "react-bootstrap";

import { DebouncedInput, GrayBox, H4 } from "../ui";
import { GrayBox, H4 } from "../ui";
import DataCard, { PAGES } from "../DataCard/DataCard";
import FilterForm from "./FilterForm";
import { useHistory, useVerifyLabel } from "../../hooks";
import { useHistory, useToggleVerifyLabel } from "../../hooks";
import { PROJECT_USES_IRR } from "../../store";

const defaultColumns = {
Expand Down Expand Up @@ -88,11 +87,23 @@ const HistoryTable = () => {
const [page, setPage] = useState(0);
const [unlabeled, setUnlabeled] = useState(false);
const [filters, setFilters] = useState({ Text: "" });
const [sortBy, setSortBy] = useState("data");
const [reverseSort, setReverseSort] = useState(false);
const [filtersInitialized, setFiltersInitialized] = useState(false);
const [shouldRefetch, setShouldRefetch] = useState(false);

const { data: historyData, refetch: refetchHistory } = useHistory(page + 1, unlabeled, filters);
const { mutate: toggleVerifyLabel } = useVerifyLabel();
const { data: historyData, refetch: refetchHistory } = useHistory(page + 1, unlabeled, filters, sortBy, reverseSort);
const { mutate: toggleVerifyLabel } = useToggleVerifyLabel();

const sortOptions = {
data: "Data",
label: "Label",
profile: "Labeled By",
timestamp: "Timestamp",
verified: "Verified",
verified_by: "Verified By",
pre_loaded: "Pre-Loaded",
};

const metadataColumnsAccessorKeys = [];
if (historyData) {
Expand All @@ -115,11 +126,8 @@ const HistoryTable = () => {
const metadataFields = historyData.metadata_fields || [];
const filterDefault = getFilterDefault(metadataFields);
setFilters(filterDefault);
setShouldRefetch(true);
};

const handleApplyFilter = (event) => {
event.preventDefault();
setSortBy("data");
setReverseSort(false);
setPage(0);
setShouldRefetch(true);
};
Expand All @@ -132,6 +140,17 @@ const HistoryTable = () => {
}));
};

const handleSortInputChange = (event) => {
const { value } = event.target;
setSortBy(value);
};

function handleApply(event) {
event.preventDefault();
setPage(0);
setShouldRefetch(true);
}

useEffect(() => { // initialize filters
if (historyData && !filtersInitialized) {
const metadataFields = historyData.metadata_fields || [];
Expand Down Expand Up @@ -262,15 +281,64 @@ const HistoryTable = () => {
</GrayBox>
</div>
)}
<div className="mt-3">
<div className="mt-3 d-flex">
<GrayBox>
<H4>Filters</H4>
< FilterForm
filters={filters}
handleInputChange={handleFilterInputChange}
resetFilters={resetFilters}
handleSubmit={handleApplyFilter}
/>
<form onSubmit={handleApply}>
<H4>Filters</H4>
<div className="form-group history-filter-row">
<label className="control-label history-filter-label">Data</label>
<input
className="form-control"
type="text"
name="Text"
style={{ width: "fit-content" }}
value={filters.Text}
onChange={handleFilterInputChange}
/>
</div>
{Object.keys(filters).map(field => {
return field !== "Text" ? (
<div className="form-group history-filter-row" key={field}>
<label className="control-label history-filter-label">{field}</label>
<input
className="form-control"
style={{ width: "fit-content" }}
type="text"
name={field}
value={filters[field] || ""}
onChange={handleFilterInputChange}
/>
</div>
) : null;
})}
<H4>Sorting</H4>
<div className="form-group history-filter-row">
<label className="control-label history-filter-label">Sort By:</label>
<select
className="form-control"
style={{ width: "fit-content" }}
name="sort-by"
onChange={handleSortInputChange}
value={sortBy}
>
{Object.entries(sortOptions).map(([key, value]) => (
<option key={key} value={key}>{value}</option>
))}
</select>
</div>
<div className="form-group history-filter-row">
<input
type="checkbox"
className="m-0"
checked={reverseSort}
aria-checked={reverseSort}
onChange={() => setReverseSort(!reverseSort)}
/>
<label className="control-label history-filter-label ml-1 m-0">Reverse sort order</label>
</div>
<Button className="mr-3" variant="primary" type="submit">Apply</Button>
<Button variant="secondary" onClick={resetFilters}>Reset</Button>
</form>
</GrayBox>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export { default as useLabels } from "./useLabels";
export { default as useMetadataValue } from "./useMetadataValue";
export { default as useModifyLabel } from "./useModifyLabel";
export { default as useSuggestedLabels } from "./useSuggestedLabels";
export { default as useVerifyLabel } from "./useToggleVerifyLabel";
export { default as useToggleVerifyLabel } from "./useToggleVerifyLabel";
4 changes: 2 additions & 2 deletions frontend/src/hooks/useHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { useQuery } from "@tanstack/react-query";
import { PROJECT_ID } from "../store";
import { getConfig } from "../utils/fetch_configs";

const useHistory = (page, unlabeled, filters) =>
const useHistory = (page, unlabeled, filters, sortBy, reverse) =>
useQuery({
queryKey: ["history", PROJECT_ID, page, unlabeled],
queryFn: () =>
fetch(`/api/get_label_history/${PROJECT_ID}/?${new URLSearchParams({ page, unlabeled, ...filters }).toString()}`, getConfig)
fetch(`/api/get_label_history/${PROJECT_ID}/?${new URLSearchParams({ page, unlabeled, ...filters, "sort-by": sortBy, reverse }).toString()}`, getConfig)
.then((res) => res.json())
});

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/hooks/useToggleVerifyLabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useMutation } from "@tanstack/react-query";
import { PROJECT_ID, queryClient } from "../store";
import { postConfig } from "../utils/fetch_configs";

const useVerifyLabel = () =>
const useToggleVerifyLabel = () =>
useMutation({
mutationFn: ({ dataID }) =>
fetch(`/api/toggle_verify_label/${dataID}/`, postConfig({ dataID })),
Expand All @@ -12,4 +12,4 @@ const useVerifyLabel = () =>
}
});

export default useVerifyLabel;
export default useToggleVerifyLabel;

0 comments on commit b238b0c

Please sign in to comment.