diff --git a/backend/django/core/serializers.py b/backend/django/core/serializers.py
index 5814757e..c9716c58 100644
--- a/backend/django/core/serializers.py
+++ b/backend/django/core/serializers.py
@@ -102,13 +102,21 @@ class Meta:
class IRRLogModelSerializer(serializers.ModelSerializer):
profile = serializers.StringRelatedField(many=False, read_only=True)
+ label_name = serializers.SerializerMethodField()
+ label_description = serializers.SerializerMethodField()
timestamp = serializers.DateTimeField(
default_timezone=pytz.timezone(TIME_ZONE_FRONTEND), format="%Y-%m-%d, %I:%M %p"
)
class Meta:
model = IRRLog
- fields = ("data", "profile", "label", "timestamp")
+ fields = ("data", "profile", "label", "label_name", "label_description", "timestamp")
+
+ def get_label_name(self, obj):
+ return obj.label.name if obj.label else None
+
+ def get_label_description(self, obj):
+ return obj.label.description if obj.label else None
class IRRLog(serializers.HyperlinkedModelSerializer):
diff --git a/backend/django/core/templates/projects/detail.html b/backend/django/core/templates/projects/detail.html
index fa134b86..39179cd0 100644
--- a/backend/django/core/templates/projects/detail.html
+++ b/backend/django/core/templates/projects/detail.html
@@ -285,6 +285,9 @@
{% endif %}
{% endif %}
{% endif %}
+ {% if project.percentage_irr > 0 %}
+
+ {% endif %}
@@ -346,6 +349,32 @@
xhttp.send();
}
+/*
+ * When the download IRR log button is pressed, download the IRR log as a csv file
+ */
+function downloadIRRLog(projectId) {
+ var url = `/api/download_irr_log/${projectId}/`;
+ var xhttp = new XMLHttpRequest();
+ xhttp.onreadystatechange = function() {
+ if (xhttp.readyState === 4 && xhttp.status === 200) {
+ var blob = new Blob([xhttp.response], {type: 'text/csv'});
+ var downloadUrl = window.URL.createObjectURL(blob);
+ var a = document.createElement('a');
+ a.href = downloadUrl;
+ a.download = 'irr_log_' + projectId + '.csv';
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
+ window.URL.revokeObjectURL(downloadUrl);
+ } else if (xhttp.readyState === 4 && xhttp.status !== 200) {
+ console.error('Error downloading the file:', xhttp.statusText);
+ }
+ };
+ xhttp.open('GET', url, true);
+ xhttp.responseType = 'blob'; // Set the response type to blob for binary data
+ xhttp.send();
+}
+
/*
* When the ingest datatable button is pressed, SMART pulls the entire
@@ -396,5 +425,7 @@
}
})
});
+
+
{% endblock %}
diff --git a/backend/django/core/templates/smart/smart.html b/backend/django/core/templates/smart/smart.html
index 9c84f3be..aa2188de 100644
--- a/backend/django/core/templates/smart/smart.html
+++ b/backend/django/core/templates/smart/smart.html
@@ -16,6 +16,7 @@
{% endif %}
window.ADMIN = {{admin}};
window.PROJECT_USES_IRR = {{project_uses_irr}};
+ window.PROJECT_SUGGESTION_MAX = {{project_suggestion_max}};
window.onload = function (e) {
$.ajax({
diff --git a/backend/django/core/urls/api.py b/backend/django/core/urls/api.py
index 04b4ab77..3f1cb436 100644
--- a/backend/django/core/urls/api.py
+++ b/backend/django/core/urls/api.py
@@ -87,6 +87,7 @@
re_path(r"^get_irr_metrics/(?P\d+)/$", api_admin.get_irr_metrics),
re_path(r"^heat_map_data/(?P\d+)/$", api_admin.heat_map_data),
re_path(r"^perc_agree_table/(?P\d+)/$", api_admin.perc_agree_table),
+ re_path(r"^irr_log/(?P\d+)/$", api_admin.irr_log),
re_path(r"^project_status/(?P\d+)/$", api_admin.get_project_status),
re_path(
r"^unassign_coder/(?P\d+)/(?P\d+)/$",
@@ -100,6 +101,7 @@
re_path(
r"^download_data/(?P\d+)/(?P\d)/$", api.download_data
),
+ re_path(r"^download_irr_log/(?P\d+)/$", api.download_irr_log),
re_path(
r"^download_model/(?P\d+)/(?P\d)/$", api.download_model
),
diff --git a/backend/django/core/views/api.py b/backend/django/core/views/api.py
index 7faec5da..90bb69cf 100644
--- a/backend/django/core/views/api.py
+++ b/backend/django/core/views/api.py
@@ -10,7 +10,7 @@
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
-from core.models import Project
+from core.models import Project, IRRLog
from core.permissions import IsAdminOrCreator
from core.templatetags import project_extras
from core.utils.util import get_labeled_data
@@ -136,6 +136,30 @@ def download_model(request, project_pk, unverified):
return response
+@api_view(["GET"])
+@permission_classes((IsAdminOrCreator,))
+def download_irr_log(request, project_pk):
+ response = HttpResponse(
+ content_type="text/csv",
+ headers={
+ "Content-Disposition": f'attachment; filename="irr_log_{project_pk}.csv"'
+ },
+ )
+
+ writer = csv.writer(response)
+ writer.writerow(["text", "label", "username", "timestamp"])
+
+ logs = IRRLog.objects.filter(data__project_id=project_pk).select_related(
+ "data", "profile", "label"
+ )
+
+ for log in logs:
+ label_name = log.label.name if log.label else ""
+ writer.writerow([log.data.text, label_name, log.profile.user, log.timestamp])
+
+ return response
+
+
@api_view(["POST"])
@permission_classes((IsAdminOrCreator,))
def import_database_table(request, project_pk):
diff --git a/backend/django/core/views/api_admin.py b/backend/django/core/views/api_admin.py
index 7863e986..ac4f65d2 100644
--- a/backend/django/core/views/api_admin.py
+++ b/backend/django/core/views/api_admin.py
@@ -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,
@@ -287,6 +288,27 @@ def perc_agree_table(request, project_pk):
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,))
def heat_map_data(request, project_pk):
diff --git a/backend/django/core/views/api_annotate.py b/backend/django/core/views/api_annotate.py
index af252025..2e3d885a 100644
--- a/backend/django/core/views/api_annotate.py
+++ b/backend/django/core/views/api_annotate.py
@@ -94,18 +94,15 @@ def get_labels(request, project_pk):
labels: The project labels
"""
project = Project.objects.get(pk=project_pk)
- labels = Label.objects.all().filter(project=project)
+ labels = Label.objects.filter(project=project)
+ total_labels = Label.objects.filter(project=project).count()
# If the number of labels is > 100, just return the first 100
serialized_labels = LabelSerializer(labels, many=True).data
if len(serialized_labels) > 100:
serialized_labels = serialized_labels[:100]
- return Response(
- {
- "labels": serialized_labels,
- }
- )
+ return Response({"labels": serialized_labels, "total_labels": total_labels})
@api_view(["GET"])
@@ -999,37 +996,91 @@ def get_label_history(request, project_pk):
project = Project.objects.get(pk=project_pk)
labels = Label.objects.all().filter(project=project)
- # irr data gets set to not IRR once it's finalized
- finalized_irr_data = IRRLog.objects.filter(data__irr_ind=False).values_list(
- "data__pk", flat=True
+
+ permission_filter = Q()
+ if project_extras.proj_permission_level(project, profile) < 2:
+ permission_filter &= Q(profile=profile)
+
+ admin_queue_data = DataQueue.objects.filter(
+ queue__project=project, queue__type="admin"
+ ).values_list("data__pk", flat=True)
+
+ pending_irr_data = IRRLog.objects.filter(
+ permission_filter & Q(data__in=admin_queue_data)
+ ).values_list("data__pk", flat=True)
+
+ incomplete_irr_data_labeled = DataLabel.objects.filter(
+ profile=profile, data__project=project_pk, data__irr_ind=True
+ ).values_list("data__pk", flat=True)
+ # incomplete irr data that was sent to adjudication is unlabeled - so in the IRRLog table, not DataLabel
+ incomplete_irr_data_adjudicated = (
+ IRRLog.objects.filter(
+ profile=profile, data__project=project_pk, data__irr_ind=True
+ )
+ .exclude(data__in=pending_irr_data)
+ .values_list("data__pk", flat=True)
+ )
+
+ incomplete_irr_data = list(
+ set(incomplete_irr_data_labeled) | set(incomplete_irr_data_adjudicated)
)
+
+ # finalized and historic IRR data have the same data pk's - we distinguish them later in this function
+ finalized_irr_data = IRRLog.objects.filter(
+ permission_filter & Q(data__project=project_pk, data__irr_ind=False)
+ ).values_list("data__pk", flat=True)
+
+ all_irr_data_pks = list(
+ set(incomplete_irr_data) | set(pending_irr_data) | set(finalized_irr_data)
+ )
+
if (
project_extras.proj_permission_level(project, profile) >= 2
or project.allow_coders_view_labels
):
labeled_data = DataLabel.objects.filter(
- data__project=project_pk, label__in=labels
- ).exclude(data__in=finalized_irr_data)
+ data__project=project_pk, label__in=labels, data__irr_ind=False
+ ).exclude(data__in=all_irr_data_pks)
else:
labeled_data = DataLabel.objects.filter(
- profile=profile, data__project=project_pk, label__in=labels
- ).exclude(data__in=finalized_irr_data)
+ profile=profile,
+ data__project=project_pk,
+ label__in=labels,
+ data__irr_ind=False,
+ ).exclude(data__in=all_irr_data_pks)
labeled_data_list = list(labeled_data.values_list("data__pk", flat=True))
- # even for an admin, we only get personal IRR data
- personal_irr_data = IRRLog.objects.filter(
- profile=profile, data__project=project_pk, label__isnull=False
- ).exclude(data__pk__in=labeled_data_list)
- irr_data_list = list(personal_irr_data.values_list("data__pk", flat=True))
+ # Incomplete and Finalized IRR are in Label table, not IRR Log table
+ all_relevant_irr_datalabels = DataLabel.objects.filter(
+ data__pk__in=all_irr_data_pks
+ )
# add the unlabeled data if selected
- total_data_list = labeled_data_list + irr_data_list
+ unlabeled_data_list = []
if request.GET.get("unlabeled") == "true":
- unlabeled_data = list(
+ unlabeled_data_list = list(
get_unlabeled_data(project_pk).values_list("pk", flat=True)
)
- total_data_list += unlabeled_data
+ total_data_list = list(
+ set(labeled_data_list + unlabeled_data_list + all_irr_data_pks)
+ )
+
+ data_types_dict = {}
+ for pk in total_data_list:
+ data_types_dict[pk] = "Labeled"
+ for pk in unlabeled_data_list:
+ data_types_dict[pk] = "Unlabeled"
+ for pk in incomplete_irr_data:
+ data_types_dict[pk] = "IRR Incomplete"
+ for pk in pending_irr_data:
+ data_types_dict[pk] = "IRR Pending"
+ for (
+ pk
+ ) in (
+ finalized_irr_data
+ ): # some "IRR Finalized" will be changed to "IRR Historic" later in this function
+ data_types_dict[pk] = "IRR Finalized"
# return the page indicated in the query, get total pages
current_page = request.GET.get("page")
@@ -1040,21 +1091,21 @@ def get_label_history(request, project_pk):
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'
+ "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')
-
+
+ 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)
+ order_field = "-" + order_field
+
+ all_data = Data.objects.filter(pk__in=data_types_dict.keys()).order_by(order_field)
# filter the results by the search terms
text_filter = request.GET.get("Text")
@@ -1072,8 +1123,8 @@ def get_label_history(request, project_pk):
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))]
+ page_data_types = [{"pk": d.pk, "type": data_types_dict[d.pk]} for d in page_data]
page_data_metadata_ids = [
d["metadata"] for d in DataMetadataIDSerializer(page_data, many=True).data
@@ -1087,9 +1138,7 @@ def get_label_history(request, project_pk):
]
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
+ data_types_df = pd.DataFrame(page_data_types).rename(columns={"pk": "id"})
if len(data_df) == 0:
return Response(
@@ -1100,6 +1149,11 @@ def get_label_history(request, project_pk):
}
)
+ data_df = pd.merge(data_df, data_types_df, on="id")
+ data_df["metadataIDs"] = page_data_metadata_ids
+ data_df["metadata"] = page_metadata
+ data_df["formattedMetadata"] = page_metadata_formatted
+
# get the labeled data into the correct format for returning
label_dict = {label.pk: label.name for label in labels}
labeled_data_df = pd.DataFrame(
@@ -1108,12 +1162,48 @@ def get_label_history(request, project_pk):
).data
).rename(columns={"data": "id", "label": "labelID", "verified": "verified_by"})
+ all_relevant_irr_logs = IRRLog.objects.filter(data__pk__in=all_irr_data_pks)
+
+ # contains all irr data that can be found in the IRR Log table ie pending and historic irr
irr_data_df = pd.DataFrame(
IRRLogModelSerializer(
- personal_irr_data.filter(data__pk__in=data_df["id"].tolist()), many=True
+ all_relevant_irr_logs.filter(
+ permission_filter & Q(data__pk__in=data_df["id"].tolist())
+ ),
+ many=True,
+ ).data
+ ).rename(columns={"data": "id", "label": "labelID"})
+
+ if not irr_data_df.empty:
+ # finalized_irr_data contains both finalized and historic irr data. It's historic if in this df.
+ irr_data_df["is_historic"] = irr_data_df["id"].apply(
+ lambda x: x in finalized_irr_data
+ )
+ else:
+ irr_data_df["is_historic"] = pd.Series(dtype=bool)
+
+ irr_incomplete_df = pd.DataFrame(
+ DataLabelModelSerializer(
+ all_relevant_irr_datalabels.filter(
+ profile=profile, data__pk__in=list(set(incomplete_irr_data))
+ ),
+ many=True,
+ ).data
+ ).rename(columns={"data": "id", "label": "labelID"})
+
+ irr_finalized_df = pd.DataFrame(
+ DataLabelModelSerializer(
+ all_relevant_irr_datalabels.filter(
+ permission_filter & Q(data__pk__in=list(set(finalized_irr_data)))
+ ),
+ many=True,
).data
).rename(columns={"data": "id", "label": "labelID"})
+ irr_data_df = pd.concat(
+ [irr_data_df, irr_incomplete_df, irr_finalized_df], axis=0
+ ).reset_index(drop=True)
+
if len(labeled_data_df) > 0:
labeled_data_df["verified"] = labeled_data_df["verified_by"].apply(
lambda x: "No" if x is None else "Yes"
@@ -1130,9 +1220,11 @@ def get_label_history(request, project_pk):
if len(irr_data_df) > 0:
irr_data_df["edit"] = "No"
- irr_data_df["label"] = irr_data_df["labelID"].apply(lambda x: label_dict[x])
+ irr_data_df["label"] = irr_data_df["labelID"].apply(
+ lambda x: label_dict[x] if x in label_dict else ""
+ )
irr_data_df["verified"] = (
- "N/A (IRR)" # Technically resolved IRR is verified but perhaps not this user's specific label so just NA
+ "N/A (IRR)" # Technically finalized IRR is verified but perhaps not this user's specific label so just NA
)
irr_data_df["verified_by"] = None
irr_data_df["pre_loaded"] = "No" # IRR only looks at unlabeled data
@@ -1144,7 +1236,17 @@ def get_label_history(request, project_pk):
# merge the data info with the label info
if len(all_labeled_stuff) > 0:
data_df = data_df.merge(all_labeled_stuff, on=["id"], how="left")
+ data_df.loc[data_df["is_historic"] == True, "type"] = "IRR Historic"
+ data_df.drop(columns=["is_historic"], inplace=True)
data_df["edit"] = data_df["edit"].fillna("Yes")
+ data_df.loc[(data_df["type"] == "IRR Incomplete"), "edit"] = "Yes"
+ data_df.loc[
+ (data_df["type"] == "IRR Incomplete") & (data_df["label"] == ""), "edit"
+ ] = "No"
+ data_df.loc[
+ (data_df["type"] == "IRR Pending") & (data_df["profile"].isna()), "edit"
+ ] = "No"
+ data_df.loc[(data_df["type"] == "IRR Finalized"), "edit"] = "Yes"
else:
data_df["edit"] = "Yes"
data_df["label"] = ""
diff --git a/backend/django/core/views/frontend.py b/backend/django/core/views/frontend.py
index 2bc756d9..0256bc89 100644
--- a/backend/django/core/views/frontend.py
+++ b/backend/django/core/views/frontend.py
@@ -10,6 +10,7 @@
from django.views.generic import DetailView, ListView, TemplateView, View
from django.views.generic.edit import DeleteView, UpdateView
from formtools.wizard.views import SessionWizardView
+from smart.settings import PROJECT_SUGGESTION_MAX
from core.forms import (
AdvancedWizardForm,
@@ -81,6 +82,8 @@ def get_context_data(self, **kwargs):
else:
ctx["project_uses_irr"] = "false"
+ ctx["project_suggestion_max"] = PROJECT_SUGGESTION_MAX
+
return ctx
diff --git a/backend/django/smart/settings.py b/backend/django/smart/settings.py
index cdc262c1..fcfde60e 100644
--- a/backend/django/smart/settings.py
+++ b/backend/django/smart/settings.py
@@ -227,6 +227,7 @@ class Dev(Configuration):
DATA_UPLOAD_MAX_MEMORY_SIZE = None
ADMIN_TIMEOUT_MINUTES = 15
+ PROJECT_SUGGESTION_MAX = os.environ.get("PROJECT_SUGGESTION_MAX", 10000)
class Prod(Dev):
diff --git a/frontend/src/actions/adminTables.js b/frontend/src/actions/adminTables.js
index b93a0f43..f5ec30c9 100644
--- a/frontend/src/actions/adminTables.js
+++ b/frontend/src/actions/adminTables.js
@@ -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
@@ -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,
diff --git a/frontend/src/components/AdminTable/IRRtable.jsx b/frontend/src/components/AdminTable/IRRtable.jsx
new file mode 100644
index 00000000..b37f79a4
--- /dev/null
+++ b/frontend/src/components/AdminTable/IRRtable.jsx
@@ -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 Error loading IRR data;
+ }
+ return (
+
+
+
+
+ User |
+ Label |
+ Description |
+
+
+
+ {Object.entries(irrEntry).map(([user, label], index) => (
+
+ {user} |
+ {label.name} |
+ {label.description} |
+
+ ))}
+
+
+
+ );
+};
+
+export default IRRtable;
diff --git a/frontend/src/components/AdminTable/index.jsx b/frontend/src/components/AdminTable/index.jsx
index c3e523be..d5740b9f 100644
--- a/frontend/src/components/AdminTable/index.jsx
+++ b/frontend/src/components/AdminTable/index.jsx
@@ -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) {
@@ -26,7 +28,22 @@ class AdminTable extends React.Component {
}
render() {
- const { admin_data, labels, message, adminLabel, discardData } = this.props;
+ const { admin_data, irr_log, 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;
+ if (entry.label === null) {
+ // situation where the irr data was adjudicated instead of labeled
+ irr_entry_formatted[username] = { name: "", description: "" };
+ } else {
+ irr_entry_formatted[username] = { name: entry.label_name, description: entry.label_description };
+ }
+ }
+ return irr_entry_formatted;
+ };
const columns = [
{
@@ -57,15 +74,22 @@ class AdminTable extends React.Component {
{row.original.message}
)}
-
+
+
+ { row.original.reason === "IRR" && irr_log.length &&
+
+ }
+
);
}
- }
+ },
+ // column for coder, label table
+
];
let page_sizes = [1];
@@ -118,7 +142,6 @@ class AdminTable extends React.Component {
AdminTable.propTypes = {
getAdmin: PropTypes.func.isRequired,
admin_data: PropTypes.arrayOf(PropTypes.object),
- labels: PropTypes.arrayOf(PropTypes.object),
message: PropTypes.string,
adminLabel: PropTypes.func.isRequired,
discardData: PropTypes.func.isRequired
diff --git a/frontend/src/components/DataCard/DataCard.jsx b/frontend/src/components/DataCard/DataCard.jsx
index efdf36d3..0b8752a8 100644
--- a/frontend/src/components/DataCard/DataCard.jsx
+++ b/frontend/src/components/DataCard/DataCard.jsx
@@ -10,6 +10,7 @@ import DataCardText from "./DataCardText";
import { useModifyLabel, useChangeToSkip, useLabels } from "../../hooks";
import DataCardLabelButtons from "./DataCardLabelButtons";
import DataCardDiscardButton from "./DataCardDiscardButton";
+import { PROJECT_SUGGESTION_MAX } from "../../store";
const DataCard = ({ data, page, actions }) => {
const { data: labels } = useLabels();
@@ -24,6 +25,7 @@ const DataCard = ({ data, page, actions }) => {
const handlers = getHandlers(allHandlers, page);
const labelCountLow = (labels) => labels.labels.length <= 5;
+ const labelCountHigh = (labels) => labels.total_labels >= PROJECT_SUGGESTION_MAX;
const show = {
skipButton: handlers.handleSkip != null,
@@ -31,9 +33,9 @@ const DataCard = ({ data, page, actions }) => {
text: true,
metadata: true,
metadataEdit: page !== PAGES.RECYCLE,
- labelButtons: labels && labelCountLow(labels) && handlers.handleSelectLabel != null,
- labelSuggestions: labels && !labelCountLow(labels) && handlers.handleSelectLabel != null,
- labelSelect: labels && !labelCountLow(labels) && handlers.handleSelectLabel != null,
+ labelButtons: labels && labelCountLow(labels) && (handlers.handleSelectLabel != null),
+ labelSuggestions: labels && (!labelCountLow(labels)) && (!labelCountHigh(labels)) && (handlers.handleSelectLabel != null),
+ labelSelect: labels && (!labelCountLow(labels)) && (handlers.handleSelectLabel != null),
discardButton: handlers.handleDiscard != null,
confirmationModal: page == PAGES.HISTORY && cardData.labelID // excludes unlabeled data
};
@@ -78,6 +80,10 @@ const DataCard = ({ data, page, actions }) => {
fn= { handlers.handleSelectLabel }
includeModal={show.confirmationModal}
/>
+
+ )}
+ {show.labelSelect && (
+
null,
id: "Expander"
},
+ {
+ accessorKey: "type",
+ filterFn: "includesString",
+ header: "Type",
+ id: "Type",
+ sortingFn: "alphanumeric"
+ },
{
accessorKey: "data",
filterFn: "includesString",
diff --git a/frontend/src/containers/adminTable_container.jsx b/frontend/src/containers/adminTable_container.jsx
index 5950c237..d886bc6c 100644
--- a/frontend/src/containers/adminTable_container.jsx
+++ b/frontend/src/containers/adminTable_container.jsx
@@ -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;
@@ -11,7 +11,7 @@ const AdminTableContainer = (props) => ;
const mapStateToProps = (state) => {
return {
admin_data: state.adminTables.admin_data,
- labels: state.smart.labels,
+ irr_log: state.adminTables.irr_log,
message: state.card.message,
admin_counts: state.adminTables.admin_counts
};
@@ -25,6 +25,9 @@ const mapDispatchToProps = (dispatch) => {
getAdmin: () => {
dispatch(getAdmin(PROJECT_ID));
},
+ getIrrLog: () => {
+ dispatch(getIrrLog(PROJECT_ID, true));
+ },
discardData: (dataID) => {
dispatch(discardData(dataID, PROJECT_ID));
}
diff --git a/frontend/src/reducers/adminTables.js b/frontend/src/reducers/adminTables.js
index 92188466..de8bf813 100644
--- a/frontend/src/reducers/adminTables.js
+++ b/frontend/src/reducers/adminTables.js
@@ -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 } } );
}
diff --git a/frontend/src/store.js b/frontend/src/store.js
index 1083b2d9..3cd146c3 100644
--- a/frontend/src/store.js
+++ b/frontend/src/store.js
@@ -4,4 +4,6 @@ export const PROJECT_ID = window.PROJECT_ID;
export const PROJECT_USES_IRR = window.PROJECT_USES_IRR;
+export const PROJECT_SUGGESTION_MAX = window.PROJECT_SUGGESTION_MAX;
+
export const queryClient = new QueryClient();
diff --git a/frontend/src/styles/smart.scss b/frontend/src/styles/smart.scss
index 94d84381..f86b87ed 100644
--- a/frontend/src/styles/smart.scss
+++ b/frontend/src/styles/smart.scss
@@ -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;
+ }
+}
\ No newline at end of file