Skip to content

Commit

Permalink
Merge pull request #342 from RTIInternational/irr-label-table
Browse files Browse the repository at this point in the history
Display each user's labels when adjudicating IRR mismatches
  • Loading branch information
AstridKery authored Jul 17, 2024
2 parents b238b0c + 519edc1 commit 45bd474
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 10 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
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
6 changes: 5 additions & 1 deletion frontend/src/containers/adminTable_container.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';

import { adminLabel, getAdmin, discardData } from '../actions/adminTables';
import { adminLabel, getAdmin, getIrrLog, discardData } from '../actions/adminTables';
import AdminTable from '../components/AdminTable';

const PROJECT_ID = window.PROJECT_ID;
Expand All @@ -11,6 +11,7 @@ const AdminTableContainer = (props) => <AdminTable {...props} />;
const mapStateToProps = (state) => {
return {
admin_data: state.adminTables.admin_data,
irr_log: state.adminTables.irr_log,
labels: state.smart.labels,
message: state.card.message,
admin_counts: state.adminTables.admin_counts
Expand All @@ -25,6 +26,9 @@ const mapDispatchToProps = (dispatch) => {
getAdmin: () => {
dispatch(getAdmin(PROJECT_ID));
},
getIrrLog: () => {
dispatch(getIrrLog(PROJECT_ID, true));
},
discardData: (dataID) => {
dispatch(discardData(dataID, PROJECT_ID));
}
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/reducers/adminTables.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { handleActions } from 'redux-actions';
import update from 'immutability-helper';

import { SET_ADMIN_DATA, SET_ADMIN_COUNTS } from '../actions/adminTables';
import { SET_ADMIN_DATA, SET_ADMIN_COUNTS, SET_IRR_LOG } from '../actions/adminTables';

const initialState = {
admin_data: [],
admin_counts: []
admin_counts: [],
irr_log: []
};

const adminTables = handleActions({
[SET_ADMIN_DATA]: (state, action) => {
return update(state, { admin_data: { $set: action.payload } } );
},
[SET_IRR_LOG]: (state, action) => {
return update(state, { irr_log: { $set: action.payload } } );
},
[SET_ADMIN_COUNTS]: (state, action) => {
return update(state, { admin_counts: { $set: action.payload } } );
}
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/styles/smart.scss
Original file line number Diff line number Diff line change
Expand Up @@ -751,3 +751,25 @@ li.disabled {
margin-right: 5px;
}
}

.admin-data-card-wrapper {
display: flex;

> :first-child {
flex-grow: 1;
display: block !important;

p {
max-width: 500px;
}
}

.irr-card {
width: 450px;
overflow-x: scroll
}

.btn-toolbar {
display: block;
}
}

0 comments on commit 45bd474

Please sign in to comment.