Skip to content

Commit

Permalink
User tag screening (#735)
Browse files Browse the repository at this point in the history
* Make universal tagging view w/ filter form

* add user tag distinction to screening view

* lint

* Combine tagging views

* Removed v2 from current implementation

* Removed path for deleted view

* Formatted js files

* Fix tests

* Fix query parameters

* Updated reference search page with changes to reference filterset

* style and test fix

* updates

* improve queryset performance; reorder form

* fix css when abstracts are short

* use args instead of kwargs for simple reverse statements

* remove prefetch

Co-authored-by: Daniel Rabstejnek <rabstejnek@gmail.com>
Co-authored-by: Andy Shapiro <shapiromatron@gmail.com>
  • Loading branch information
3 people authored Nov 17, 2022
1 parent 5cfbac4 commit 0f06dd8
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 46 deletions.
5 changes: 5 additions & 0 deletions frontend/lit/NestedTag.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import ReactDOM from "react-dom";
import {getReferenceTagListUrl} from "shared/utils/urls";

import PaginatedReferenceList from "./components/PaginatedReferenceList";

Expand Down Expand Up @@ -61,6 +62,10 @@ class NestedTag {
};
ReactDOM.render(<PaginatedReferenceList settings={settings} canEdit={canEdit} />, el);
}

get_list_link() {
return getReferenceTagListUrl(this.assessment_id, this.data.pk);
}
}

export default NestedTag;
1 change: 1 addition & 0 deletions frontend/lit/Reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Reference {
this.data = data;
this._quickSearchText = `${data.title}-${data.year}-${data.authors}-${data.authors_short}`.toLowerCase();
this.tags = data.tags.map(tagId => tagtree.dict[tagId]);
this.userTags = data.user_tags ? data.user_tags.map(tagId => tagtree.dict[tagId]) : [];
}

static get_detail_url(id, subtype) {
Expand Down
16 changes: 9 additions & 7 deletions frontend/lit/ReferenceTreeBrowse/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,17 @@ class ReferenceTreeMain extends Component {
) : store.selectedTag === null ? (
<h4>Available references</h4>
) : (
<h4>
<span>
{selectedReferences && selectedReferences.length > 0
? `${filteredReferences.length} references tagged:`
: "References tagged:"}
</span>
<div className="d-flex align-items-center">
<h4>
<span>
{selectedReferences && selectedReferences.length > 0
? `${filteredReferences.length} references tagged:`
: "References tagged:"}
</span>
</h4>
<span className="ml-2 refTag">{store.selectedTag.get_full_name()}</span>
<span>{yearText}</span>
</h4>
</div>
)}
</div>
<div className="col-md-3">
Expand Down
46 changes: 38 additions & 8 deletions frontend/lit/TagReferences/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ class TagReferencesMain extends Component {
render() {
const {store} = this.props,
selectedReferencePk = store.selectedReference ? store.selectedReference.data.pk : null,
selectedReferenceTags = store.selectedReferenceTags ? store.selectedReferenceTags : [];
selectedReferenceTags = store.selectedReferenceTags ? store.selectedReferenceTags : [],
selectedReferenceUserTags = store.selectedReferenceUserTags
? store.selectedReferenceUserTags
: [];
return (
<div className="row">
<div className={store.filterClass} id="refFilter">
Expand Down Expand Up @@ -66,7 +69,11 @@ class TagReferencesMain extends Component {
{ref.tags.length > 0 ? (
<i
className="fa fa-tags"
title="tagged"
title={
store.config.conflict_resolution
? "has resolved tag(s)"
: "tagged"
}
aria-hidden="true"></i>
) : null}
</p>
Expand Down Expand Up @@ -118,9 +125,32 @@ class TagReferencesMain extends Component {
{selectedReferenceTags.map((tag, i) => (
<span
key={i}
title={tag.get_full_name()}
title={
store.config.conflict_resolution
? "Resolved Tag: ".concat(tag.get_full_name())
: tag.get_full_name()
}
className="refTag refTagEditing"
onClick={() => store.removeTag(tag)}>
onClick={
store.config.conflict_resolution
? null
: () => store.removeTag(tag)
}>
{this.state.showFullTag
? tag.get_full_name()
: tag.data.name}
</span>
))}
{selectedReferenceUserTags.map((tag, i) => (
<span
key={i}
title={tag.get_full_name()}
className="refTag refUserTag refTagEditing"
onClick={
store.config.conflict_resolution
? () => store.removeTag(tag)
: null
}>
{this.state.showFullTag
? tag.get_full_name()
: tag.data.name}
Expand All @@ -144,13 +174,13 @@ class TagReferencesMain extends Component {
extraActions={[
<div
className="dropdown-item cursor-pointer"
key={3}
key={4}
onClick={() => store.removeAllTags()}>
&nbsp;Remove all tags
</div>,
<div
className="dropdown-item cursor-pointer"
key={4}
key={5}
onClick={() => {
this.showFullTag.toggle();
this.setState({showFullTag: this.showFullTag.value});
Expand All @@ -163,7 +193,7 @@ class TagReferencesMain extends Component {
store.config.instructions.length > 0 ? (
<div
className="dropdown-item cursor-pointer"
key={5}
key={6}
onClick={() => store.setInstructionsModal(true)}>
&nbsp;View instructions
</div>
Expand Down Expand Up @@ -202,7 +232,7 @@ class TagReferencesMain extends Component {
<TagTree
tagtree={toJS(store.tagtree)}
handleTagClick={tag => store.addTag(tag)}
showTagHover={true}
showTagHoverAdd={true}
/>
</div>
<Modal
Expand Down
30 changes: 25 additions & 5 deletions frontend/lit/TagReferences/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Store {
@observable references = [];
@observable selectedReference = null;
@observable selectedReferenceTags = null;
@observable selectedReferenceUserTags = null;
@observable errorOnSave = false;
@observable filterClass = "";
@observable showInstructionsModal = false;
Expand All @@ -29,22 +30,37 @@ class Store {
@action.bound changeSelectedReference(reference) {
this.selectedReference = reference;
this.selectedReferenceTags = reference.tags.slice(0); // shallow copy
this.selectedReferenceUserTags = reference.userTags.slice(0);
}
@action.bound addTag(tag) {
if (
this.selectedReference &&
!_.find(this.selectedReferenceTags, el => el.data.pk === tag.data.pk)
!_.find(
this.config.conflict_resolution
? this.selectedReferenceUserTags
: this.selectedReferenceTags,
el => el.data.pk === tag.data.pk
)
) {
this.selectedReferenceTags.push(tag);
this.config.conflict_resolution
? this.selectedReferenceUserTags.push(tag)
: this.selectedReferenceTags.push(tag);
}
}
@action.bound removeTag(tag) {
_.remove(this.selectedReferenceTags, el => el.data.pk === tag.data.pk);
_.remove(
this.config.conflict_resolution
? this.selectedReferenceUserTags
: this.selectedReferenceTags,
el => el.data.pk === tag.data.pk
);
}
@action.bound saveAndNext() {
const payload = {
pk: this.selectedReference.data.pk,
tags: this.selectedReferenceTags.map(tag => tag.data.pk),
tags: this.config.conflict_resolution
? this.selectedReferenceUserTags.map(tag => tag.data.pk)
: this.selectedReferenceTags.map(tag => tag.data.pk),
},
success = () => {
const $el = $(this.saveIndicatorElement),
Expand All @@ -61,9 +77,11 @@ class Store {
$el.fadeIn().fadeOut({
complete: () => {
this.selectedReference.tags = toJS(this.selectedReferenceTags);
this.selectedReference.userTags = toJS(this.selectedReferenceUserTags);
this.references.splice(index, 1, toJS(this.selectedReference));
this.selectedReference = null;
this.selectedReferenceTags = null;
this.selectedReferenceUserTags = null;
if (this.references.length > index + 1) {
this.changeSelectedReference(this.references[index + 1]);
} else {
Expand All @@ -82,7 +100,9 @@ class Store {
).fail(failure);
}
@action.bound removeAllTags() {
this.selectedReferenceTags = [];
this.config.conflict_resolution
? (this.selectedReferenceUserTags = [])
: (this.selectedReferenceTags = []);
}
@action.bound setSaveIndicatorElement(el) {
this.saveIndicatorElement = el;
Expand Down
6 changes: 4 additions & 2 deletions frontend/lit/components/Reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ class Reference extends Component {
authors = data.authors || data.authors_short || reference.NO_AUTHORS_TEXT,
year = data.year || "",
actionItems = [
<ActionLink key={1} label="Edit reference" href={data.editReferenceUrl} />,
<ActionLink key={0} label="Edit reference tags" href={data.editTagUrl} />,
<ActionLink key={1} label="Edit reference" href={data.editReferenceUrl} />,
<ActionLink key={2} label="Delete reference" href={data.deleteReferenceUrl} />,
<ActionLink key={3} label="Tag history" href={data.tagHistoryUrl} />,
].concat(extraActions);
Expand Down Expand Up @@ -155,6 +155,7 @@ class Reference extends Component {
{data.abstract ? (
<div
className="abstracts resize-y p-2"
style={data.abstract.length > 1500 ? {height: "45vh"} : null}
dangerouslySetInnerHTML={
keywordDict
? {__html: markKeywords(data.abstract, keywordDict)}
Expand All @@ -166,9 +167,10 @@ class Reference extends Component {
<p>
{tags.map((tag, i) => (
<a
style={{color: "white"}}
key={i}
href={getReferenceTagListUrl(data.assessment_id, tag.data.pk)}
className="referenceTag badge badge-info mr-1">
className="refTag mt-1">
{tag.get_full_name()}
</a>
))}
Expand Down
28 changes: 15 additions & 13 deletions frontend/lit/components/TagTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ class TagNode extends Component {
};
}
render() {
const {tag, showReferenceCount, handleOnClick, selectedTag, showTagHover} = this.props,
tagClass =
tag === selectedTag
? "d-flex nestedTag selected align-items-center"
: "d-flex nestedTag align-items-center",
const {tag, showReferenceCount, handleOnClick, selectedTag, showTagHoverAdd} = this.props,
hasChildren = tag.children.length > 0,
expanderIcon = this.state.expanded ? "fa-minus" : "fa-plus",
toggleExpander = e => {
Expand All @@ -29,7 +25,9 @@ class TagNode extends Component {

return (
<>
<div className={tagClass} onClick={() => handleOnClick(tag)}>
<div
className={"d-flex nestedTag align-items-center"}
onClick={() => handleOnClick(tag)}>
<div
className="d-flex justify-content-end"
style={{width: (tag.depth - 1) * 10 + 25}}>
Expand All @@ -41,7 +39,11 @@ class TagNode extends Component {
</button>
) : null}
</div>
<div className={showTagHover ? "tagName" : null} style={{flex: 1}}>
<div
className={(showTagHoverAdd ? "tagHoverAdd" : "tagHoverSimple").concat(
tag === selectedTag ? " tagSelected" : ""
)}
style={{flex: 1}}>
<span>
{tag.data.name}
{showReferenceCount ? ` (${tag.get_references_deep().length})` : null}
Expand All @@ -56,7 +58,7 @@ class TagNode extends Component {
handleOnClick={handleOnClick}
showReferenceCount={showReferenceCount}
selectedTag={selectedTag}
showTagHover={showTagHover}
showTagHoverAdd={showTagHoverAdd}
/>
))
: null}
Expand All @@ -69,7 +71,7 @@ TagNode.propTypes = {
handleOnClick: PropTypes.func.isRequired,
showReferenceCount: PropTypes.bool.isRequired,
selectedTag: PropTypes.object,
showTagHover: PropTypes.bool,
showTagHoverAdd: PropTypes.bool,
};

@observer
Expand All @@ -82,7 +84,7 @@ class TagTree extends Component {
selectedTag,
untaggedHandleClick,
untaggedCount,
showTagHover,
showTagHoverAdd,
} = this.props;
return (
<div id="litTagtree" className="resize-y p-2 mt-2">
Expand All @@ -93,7 +95,7 @@ class TagTree extends Component {
handleOnClick={handleTagClick}
showReferenceCount={showReferenceCount}
selectedTag={selectedTag}
showTagHover={showTagHover}
showTagHoverAdd={showTagHoverAdd}
/>
))}
{untaggedHandleClick ? (
Expand All @@ -112,12 +114,12 @@ TagTree.propTypes = {
selectedTag: PropTypes.object,
untaggedCount: PropTypes.number,
untaggedHandleClick: PropTypes.func,
showTagHover: PropTypes.bool,
showTagHoverAdd: PropTypes.bool,
};
TagTree.defaultProps = {
showReferenceCount: false,
handleTagClick: h.noop,
showTagHover: false,
showTagHoverAdd: false,
};

export default TagTree;
13 changes: 13 additions & 0 deletions hawc/apps/lit/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pandas as pd
from django.apps import apps
from django.contrib.postgres.aggregates import ArrayAgg
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.db import models
Expand Down Expand Up @@ -351,6 +352,18 @@ def prune_tags(self, root_tag, pruned_tags, descendants: bool = False):
)
return self.exclude(query).distinct("pk")

def user_tags(self, user_id: int) -> dict[int, list[int]]:
# Return a dictionary of reference_id: list[tag_ids] items for all references in a queryset
# TODO - update to annotate queryset with Django 4.1?
# https://docs.djangoproject.com/en/4.1/ref/contrib/postgres/expressions/#arraysubquery-expressions
UserReferenceTag = apps.get_model("lit", "UserReferenceTag")
user_qs = (
UserReferenceTag.objects.filter(reference__in=self, user=user_id)
.annotate(tag_ids=ArrayAgg("tags__id"))
.values_list("reference_id", "tag_ids")
)
return {reference_id: tag_ids for reference_id, tag_ids in user_qs}


class ReferenceManager(BaseManager):

Expand Down
1 change: 1 addition & 0 deletions hawc/apps/lit/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ def to_representation(self, instance):
)
ret["editReferenceUrl"] = reverse("lit:ref_edit", args=(instance.pk,))
ret["deleteReferenceUrl"] = reverse("lit:ref_delete", args=(instance.pk,))
ret["tagHistoryUrl"] = reverse("lit:tag-history", args=(instance.pk,))

ret["identifiers"] = [ident.to_dict() for ident in instance.identifiers.all()]
ret["searches"] = [search.to_dict() for search in instance.searches.all()]
Expand Down
2 changes: 1 addition & 1 deletion hawc/apps/lit/templates/lit/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ <h3>Manually added references</h3>

window.app.startup("litStartup", function(lit){
let tagtree = new lit.TagTree(config.tags[0], config.assessment_id, null);
tagtree.render(document.getElementById('tags'))
tagtree.render(document.getElementById('tags'), {handleTagClick: (x) => {window.location.href = x.get_list_link()}})
});

const histo = document.getElementById("referenceYearHistogram");
Expand Down
Loading

0 comments on commit 0f06dd8

Please sign in to comment.