Skip to content

Commit

Permalink
Merge branch 'dev' into disable_large_project_suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
AstridKery committed Jul 17, 2024
2 parents c6d43f4 + 45bd474 commit 57ed9b8
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 48 deletions.
1 change: 1 addition & 0 deletions backend/django/core/urls/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
re_path(r"^get_irr_metrics/(?P<project_pk>\d+)/$", api_admin.get_irr_metrics),
re_path(r"^heat_map_data/(?P<project_pk>\d+)/$", api_admin.heat_map_data),
re_path(r"^perc_agree_table/(?P<project_pk>\d+)/$", api_admin.perc_agree_table),
re_path(r"^irr_log/(?P<project_pk>\d+)/$", api_admin.irr_log),
re_path(r"^project_status/(?P<project_pk>\d+)/$", api_admin.get_project_status),
re_path(
r"^unassign_coder/(?P<project_pk>\d+)/(?P<profile_id>\d+)/$",
Expand Down
21 changes: 21 additions & 0 deletions backend/django/core/views/api_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from postgres_stats.aggregates import Percentile
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from core.serializers import IRRLogModelSerializer

from core.models import (
AssignedData,
Expand Down Expand Up @@ -286,6 +287,26 @@ def perc_agree_table(request, project_pk):
user_agree = perc_agreement_table_data(project)
return Response({"data": user_agree})

@api_view(["GET"])
@permission_classes((IsAdminOrCreator,))
def irr_log(request, project_pk):
"""
Gets IRR user labels for a project. Optionally filters to include only
logs with label disagreements (i.e., data in the admin queue) based on a query parameter.
"""
project = Project.objects.get(pk=project_pk)

admin_queue_only = request.query_params.get('admin', 'false').lower() == 'true'

irr_log = IRRLog.objects.filter(data__project=project)
if admin_queue_only:
irr_log = irr_log.filter(data__queues__type='admin')

irr_log_serialized = IRRLogModelSerializer(irr_log, many=True).data

return Response({"irr_log": irr_log_serialized})



@api_view(["GET"])
@permission_classes((IsAdminOrCreator,))
Expand Down
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 @@ -1034,15 +1034,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 @@ -1051,24 +1067,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 @@ -1135,12 +1154,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
28 changes: 28 additions & 0 deletions frontend/src/actions/adminTables.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import { queryClient } from "../store";
export const SET_ADMIN_DATA = 'SET_ADMIN_DATA';
export const SET_DISCARDED_DATA = 'SET_DISCARDED_DATA';
export const SET_ADMIN_COUNTS = 'SET_ADMIN_COUNTS';
export const SET_IRR_LOG = 'SET_IRR_LOG';

export const set_admin_data = createAction(SET_ADMIN_DATA);
export const set_discarded_data = createAction(SET_DISCARDED_DATA);
export const set_admin_counts = createAction(SET_ADMIN_COUNTS);
export const set_irr_log = createAction(SET_IRR_LOG);


//get the skipped data for the admin Table
Expand Down Expand Up @@ -52,6 +54,32 @@ export const getAdmin = (projectID) => {
};
};

export const getIrrLog = (projectID, adminOnly = false) => {
let apiURL = `/api/irr_log/${projectID}/`;

if (adminOnly) apiURL += '?admin=true';

return dispatch => {
return fetch(apiURL, getConfig())
.then(response => {
if (response.ok) {
return response.json();
} else {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
})
.then(response => {
if ('error' in response) {
return dispatch(setMessage(response.error));
} else {
dispatch(set_irr_log(response.irr_log));
}
});
};
};

export const adminLabel = (dataID, labelID, projectID) => {
let payload = {
labelID: labelID,
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/components/AdminTable/IRRtable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { Fragment } from "react";
import { Card, Table } from "react-bootstrap";

const IRRtable = ({ irrEntry }) => {
if (!irrEntry || !Object.keys(irrEntry).length){
return <Fragment>Error loading IRR data</Fragment>;
}
return (
<Card className="irr-card d-flex flex-column m-0 p-3" >
<Table striped bordered hover>
<thead>
<tr>
<th>User</th>
<th>Label</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{Object.entries(irrEntry).map(([user, label], index) => (
<tr key={index}>
<td style={{ wordWrap: "break-word", whiteSpace: "normal" }}>{user}</td>
<td style={{ wordWrap: "break-word", whiteSpace: "normal" }}>{label.name}</td>
<td style={{ wordWrap: "break-word", whiteSpace: "normal" }}>{label.description}</td>
</tr>
))}
</tbody>
</Table>
</Card>
);
};

export default IRRtable;
39 changes: 32 additions & 7 deletions frontend/src/components/AdminTable/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import PropTypes from "prop-types";
import ReactTable from "react-table-6";
import CodebookLabelMenuContainer from "../../containers/codebookLabelMenu_container";
import DataCard, { PAGES } from "../DataCard/DataCard";
import IRRtable from "./IRRtable";

class AdminTable extends React.Component {
componentDidMount() {
this.props.getAdmin();
this.props.getIrrLog();
}

getText(row) {
Expand All @@ -26,7 +28,23 @@ class AdminTable extends React.Component {
}

render() {
const { admin_data, labels, message, adminLabel, discardData } = this.props;
const { admin_data, irr_log, labels, message, adminLabel, discardData } = this.props;

const getIrrEntry = data_id => {
const relevant_irr_entries = irr_log.filter(entry => entry.data === data_id);
const irr_entry_formatted = {};
for (let entry of relevant_irr_entries) {
const username = entry.profile;
const label_id = entry.label;
if (!label_id) {
// situation where the irr data was adjudicated instead of labeled
irr_entry_formatted[username] = { name: "", description: "" };
} else {
irr_entry_formatted[username] = labels.find(label => label.pk === label_id);
}
}
return irr_entry_formatted;
};

const columns = [
{
Expand Down Expand Up @@ -57,15 +75,22 @@ class AdminTable extends React.Component {
<p style={{ whiteSpace: "normal" }}>{row.original.message}</p>
</div>
)}
<DataCard
data={row.original}
page={PAGES.ADMIN}
actions={{ onSelectLabel: adminLabel, onDiscard: discardData }}
/>
<div className="admin-data-card-wrapper">
<DataCard
data={row.original}
page={PAGES.ADMIN}
actions={{ onSelectLabel: adminLabel, onDiscard: discardData }}
/>
{ row.original.reason === "IRR" && irr_log.length &&
<IRRtable irrEntry={getIrrEntry(row.original.id)} />
}
</div>
</div>
);
}
}
},
// column for coder, label table

];

let page_sizes = [1];
Expand Down
Loading

0 comments on commit 57ed9b8

Please sign in to comment.