Skip to content

Commit

Permalink
Merry christmas david fisher! (#906)
Browse files Browse the repository at this point in the history
This one got away from me!  In this one PR we have some nice improvements in the lifecycle of books and cases:

1. Now when you judge a query doc pair for a book we update in the background any associated Cases.  This eliminates the need to manually refresh the case ratings from the book!   

1. Band-aid the awkwardness that Cases have Queries/Ratings while Books have QueryDocPairs + Judgements, and that there are attributes on a Query like `information need`, `notes`, or `options` that don't exist on a QueryDocPair.   So now we preserve them, which facilitates round tripping better.   Upload a Book?  Upload a Case?  It doesn't matter ;-)  Long term we need to merge these two data structures...   

1. When Books were first added to Quepid, they didn't follow the same permissions/sharing structure that Scores and Cases did.  Now you can share a Book with multiple Teams!

1. And as an aside, anyone can edit a custom scorer, not just the Owner of it.
  • Loading branch information
epugh authored Jan 2, 2024
1 parent 6e7d9fd commit 247e7c7
Show file tree
Hide file tree
Showing 78 changed files with 934 additions and 312 deletions.
1 change: 1 addition & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

//= require bootstrap/dist/js/bootstrap.bundle
//= require jquery
//= require rails-ujs

//= require vega
//= require vega-lite
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ angular.module('QuepidApp')
function ($rootScope, $uibModalInstance) {
var ctrl = this;

// the whole canDelete this may not make sense to have as we don't really support
// changing up these permissions..
ctrl.canDelete = false;

$rootScope.$watch('currentUser', function() {
Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/components/frog_report/_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ <h4 style="margin-top: 60px;">Distribution of Queries Needing Ratings</h4>
</div>
<div class="modal-footer">
<a class="btn btn-default" ng-if="ctrl.theCase.bookName" ng-click="ctrl.refreshRatingsFromBook()" ng-disabled="processingPrompt.inProgress">
<i class="glyphicon glyphicon-refresh"></i>
<i class="glyphicon glyphicon-refresh" ng-class="{'fa-spin': processingPrompt.inProgress}"></i>
Refresh ratings from book <i>{{ctrl.theCase.bookName}}</i>
</a>
<button class="btn btn-primary" ng-click="ctrl.cancel()" ng-disabled="processingPrompt.inProgress">Close</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ angular.module('QuepidApp')
angular.forEach(queriesSvc.queries, function(q) {
countMissingRatings = countMissingRatings + q.currentScore.countMissingRatings;
});

return countMissingRatings;
};

Expand All @@ -178,7 +177,6 @@ angular.module('QuepidApp')
}
missingRatingsTable[missingCount].count = missingRatingsTable[missingCount].count + 1;
});

return missingRatingsTable;
};

Expand All @@ -193,19 +191,13 @@ angular.module('QuepidApp')
ctrl.totalNumberOfRatingsNeeded = function () {
var totalNumberOfRatingsNeeded = 0;
angular.forEach(queriesSvc.queries, function(q) {

if (q.docs.length === 0){
$scope.totalNumberQueriesWithoutResults += 1;
}
else {
$scope.totalNumberQueriesWithResults += 1;
if (q.depthOfRating){
totalNumberOfRatingsNeeded = totalNumberOfRatingsNeeded + q.depthOfRating;
}
else {
totalNumberOfRatingsNeeded = totalNumberOfRatingsNeeded + ctrl.depthOfRating();
}
}
totalNumberOfRatingsNeeded = totalNumberOfRatingsNeeded + q.docs.length;

});
$scope.totalNumberOfRatingsNeeded = totalNumberOfRatingsNeeded;
Expand Down Expand Up @@ -242,10 +234,10 @@ angular.module('QuepidApp')
};

ctrl.refreshRatingsFromBook = function () {
//$uibModalInstance.close(ctrl.options);
bookSvc.refreshCaseRatingsFromBook(ctrl.theCase.caseNo, ctrl.theCase.bookId)
$scope.processingPrompt.inProgress = true;
bookSvc.refreshCaseRatingsFromBook(ctrl.theCase.caseNo, ctrl.theCase.bookId, false)
.then(function() {
$scope.processingPrompt.inProgress = true;
$scope.processingPrompt.inProgress = false;
$uibModalInstance.close();

flash.success = 'Ratings have been refreshed.';
Expand Down
14 changes: 10 additions & 4 deletions app/assets/javascripts/components/judgements/_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ <h3 class="modal-title">Judgements</h3>
<i class='fa fa-spinner fa-spin'></i>
</p>
</div>
<div class="alert alert-warning" role="alert" ng-if="ctrl.share.acase.queriesCount === 0">
<div class="alert alert-warning" role="alert" ng-if="ctrl.populateBook && ctrl.share.acase.queriesCount === 0">
You do not have any queries to populate your book of judgements with.
</div>

Expand Down Expand Up @@ -80,6 +80,12 @@ <h3 class="modal-title">Judgements</h3>
&nbsp;&nbsp;<button ng-show="ctrl.populateBook" type="button" class="btn btn-sm btn-default" ng-click="isAdvancedPopulateConfigCollapsed = !isAdvancedPopulateConfigCollapsed">Advanced</button>

</label>
<br/>
<label>
<input id="sync-queries" type="checkbox" ng-model='ctrl.createMissingQueries' ng-disabled="ctrl.share.books.length === 0 || ctrl.share.teams.length === 0"> Create missing Queries
<span class="glyphicon glyphicon-question-sign" aria-hidden="true" popover-trigger="'mouseenter'" popover-placement="right" uib-popover="Create queries in the Case thare are defined in the Book."></span>
</input>
</label>

<span ng-show="ctrl.populateBook">
<div uib-collapse="!isAdvancedPopulateConfigCollapsed">
Expand All @@ -89,7 +95,7 @@ <h3 class="modal-title">Judgements</h3>
<label class="col-sm-3 control-label">Populate Judgements?</label>
<div class="col-sm-9">
<input id="populate-judgements" type="checkbox" ng-model='ctrl.populateJudgements'/>
<p class="help-block">Take the ratings currently made in the Case and use them as Judgements when populating theBook.</p>
<p class="help-block">Take the ratings currently made in the Case and use them as Judgements when populating the Book.</p>
</div>
</div>
</div>
Expand All @@ -114,10 +120,10 @@ <h3 class="modal-title">Judgements</h3>
<i class="glyphicon glyphicon-book"></i>
Make Judgements!
</a>

<a class="btn btn-default pull-left" ng-click="ctrl.refreshRatingsFromBook()" ng-disabled="processingPrompt.inProgress || ctrl.populateBook || !ctrl.activeBookId || ctrl.share.acase.bookId !== ctrl.activeBookId" >
<i class="glyphicon glyphicon-refresh"></i>
<i class="glyphicon glyphicon-refresh" ng-class="{'fa-spin': processingPrompt.inProgress}"></i>
Refresh ratings from book <i>{{ctrl.activeBookName}}</i>
<span ng-if="ctrl.createMissingQueries">and creating missing queries</span>
</a>

<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ angular.module('QuepidApp')
ctrl.refreshOnly = false;
ctrl.updateAssociatedBook = false;
ctrl.populateJudgements = false;
ctrl.createMissingQueries = false;

$rootScope.$watch('currentUser', function() {
if ( $rootScope.currentUser ) {
Expand Down Expand Up @@ -128,14 +129,15 @@ angular.module('QuepidApp')

ctrl.refreshRatingsFromBook = function () {
//$uibModalInstance.close(ctrl.options);
bookSvc.refreshCaseRatingsFromBook(ctrl.share.acase.caseNo, ctrl.activeBookId)
$scope.processingPrompt.inProgress = true;
bookSvc.refreshCaseRatingsFromBook(ctrl.share.acase.caseNo, ctrl.activeBookId, ctrl.createMissingQueries)
.then(function() {
$scope.processingPrompt.inProgress = true;
$scope.processingPrompt.inProgress = false;
$uibModalInstance.close();

flash.success = 'Ratings have been refreshed.';
}, function(response) {
$scope.processingPrompt.inProgress = false;

$scope.processingPrompt.error = response.data.statusText;
});
};
Expand Down
1 change: 0 additions & 1 deletion app/assets/javascripts/controllers/queriesCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ angular.module('QuepidApp')
function pickCaseScorer() {
var modalInstance = $uibModal.open({
templateUrl: 'views/pick_scorer.html',
backdrop: 'static',
controller: 'ScorerCtrl',
resolve: {
parent: function() {
Expand Down
18 changes: 17 additions & 1 deletion app/assets/javascripts/controllers/scorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ angular.module('QuepidApp')
$scope.gotoScorers = gotoScorers;
$scope.ok = ok;
$scope.scorers = [];
$scope.communalScorers = [];
$scope.communalScorers = [];
$scope.selectScorer = selectScorer;
$scope.usingDefaultScorer = usingDefaultScorer;
$scope.communalScorersOnly = configurationSvc.isCommunalScorersOnly();
Expand All @@ -32,6 +32,7 @@ angular.module('QuepidApp')
.then(function() {
$scope.userScorers = scorerSvc.scorers;
$scope.communalScorers = scorerSvc.communalScorers;
$scope.scorerAccessible = scorerAccessible();
});

function cancel() {
Expand All @@ -48,6 +49,21 @@ angular.module('QuepidApp')

$uibModalInstance.close($scope.activeScorer);
}

function scorerAccessible() {
let scorerAccessible = false;

if ($scope.activeScorer.scorerId) {
angular.forEach($scope.userScorers.concat($scope.communalScorers), function (scorer){
if (scorer.scorerId === $scope.activeScorer.scorerId){
scorerAccessible = true;
return scorerAccessible;
}
});
}
return scorerAccessible;

}

function selectScorer(scorer) {
var name = (!scorer ? 'none' : scorer.name);
Expand Down
15 changes: 8 additions & 7 deletions app/assets/javascripts/controllers/wizardModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,13 +532,14 @@ angular.module('QuepidApp')
// we need to restore the TMDB demo settings if that matches our URL for the next screen.
var searchEngine = $scope.pendingWizardSettings.searchEngine;
var newUrl = $scope.pendingWizardSettings.searchUrl;
if (settingsSvc.demoSettingsChosen(searchEngine, newUrl)){
var settingsToUse = settingsSvc.getDemoSettings($scope.pendingWizardSettings.searchEngine);
$scope.pendingWizardSettings.idField = settingsToUse.idField;
$scope.pendingWizardSettings.titleField = settingsToUse.titleField;
$scope.pendingWizardSettings.additionalFields = settingsToUse.additionalFields;
$scope.pendingWizardSettings.queryParams = settingsToUse.queryParams;
}

var settingsToUse = settingsSvc.pickSettingsToUse(searchEngine, newUrl);


$scope.pendingWizardSettings.idField = settingsToUse.idField;
$scope.pendingWizardSettings.titleField = settingsToUse.titleField;
$scope.pendingWizardSettings.additionalFields = settingsToUse.additionalFields;
$scope.pendingWizardSettings.queryParams = settingsToUse.queryParams;
}

$scope.validateFieldSpec = validateFieldSpec;
Expand Down
6 changes: 3 additions & 3 deletions app/assets/javascripts/services/bookSvc.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ angular.module('QuepidApp')
});
};

this.refreshCaseRatingsFromBook = function(caseId, bookId) {
// http POST api/books/<int:bookId>/case/<int:caseId>/refresh
this.refreshCaseRatingsFromBook = function(caseId, bookId, createMissingQueries) {
// http POST api/books/<int:bookId>/case/<int:caseId>/refresh?sync_queries=<bool:createMissingQueries>

var payload = {
};

return $http.put('api/books/' + bookId + '/cases/' + caseId + '/refresh', payload)
return $http.put('api/books/' + bookId + '/cases/' + caseId + '/refresh?create_missing_queries=' + createMissingQueries, payload)
.then(function(response) {
console.log('refreshed ratings' + response.data);
});
Expand Down
3 changes: 3 additions & 0 deletions app/assets/templates/views/pick_scorer.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<h3 class="modal-title">How would you like to score this case?</h3>
</div>
<div class="modal-body">
<div ng-if="!scorerAccessible" class="alert alert-warning" role="alert">
The scorer {{activeScorer.name}} used by this case is NOT shared via a team with you, so if you switch away from it you won't have access to it again.
</div>
<div class="row" style="max-height:300px;overflow-y:auto">
<h4>Select from defaults:</h4>
<ul class="list-group">
Expand Down
2 changes: 1 addition & 1 deletion app/assets/templates/views/snapshotModal.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<h3 class="modal-title">Take a Snapshot of all your queries?</h3>
</div>
<div class="modal-body">
<p>Record the current results of your queries is useful for two purposes: 1) to compare future work against this snapshot and 2) to faciliate creating ratings. A snapshot
<p>Recording the current results of your queries is useful for compare future work against this point in time as well as to use in comparing multiple Cases. A snapshot
stores the individual queries in a case, the ratings made, and optionally all the fields returned for the documents.</p>

<label>Snapshot Name</label>
Expand Down
8 changes: 7 additions & 1 deletion app/controllers/api/v1/books/populate_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ def update
query_doc_pair.position = pair[:position]
query_doc_pair.document_fields = pair[:document_fields].to_json

query = @case.queries.find_by(query_text: query_doc_pair.query_text)

query_doc_pair.information_need = query.information_need
query_doc_pair.notes = query.notes
query_doc_pair.options = query.options

if pair[:rating]
rating = @case.queries.find_by(query_text: query_doc_pair.query_text).ratings.find_by(doc_id: query_doc_pair.doc_id)
rating = query.ratings.find_by(doc_id: query_doc_pair.doc_id)

# we are smart and just look up the correct user id from rating.user_id via the database, no API data needed.
judgement = query_doc_pair.judgements.find_or_create_by user_id: rating.user_id
Expand Down
35 changes: 6 additions & 29 deletions app/controllers/api/v1/books/refresh_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,17 @@ class RefreshController < Api::ApiController
before_action :set_case, only: [ :update ]
before_action :check_case, only: [ :update ]

# rubocop:disable Metrics/MethodLength
def update
@counts = {}
rating_count = 0
query_count = 0

@book.rated_query_doc_pairs.each do |query_doc_pair|
query = Query.find_or_initialize_by(case: @case, query_text: query_doc_pair.query_text)

count_of_judgements = query_doc_pair.judgements.rateable.size
summed_rating = query_doc_pair.judgements.rateable.sum(&:rating)
# only calculate this if we have valid judgements. If everything is unrateable, then don't proceed.
next unless count_of_judgements.positive?
@create_missing_queries = 'true' == params[:create_missing_queries]

rating = Rating.find_or_initialize_by(query: query, doc_id: query_doc_pair.doc_id)
service = RatingsManager.new(@book, { create_missing_queries: @create_missing_queries })
service.sync_ratings_for_case(@case)

rating.rating = if @book.support_implicit_judgements?
summed_rating / count_of_judgements
else
# explicit judgements only work with integers.
(summed_rating / count_of_judgements).round
end

query_count += 1 if query.new_record?
rating_count += 1 if rating.new_record?

rating.save
query.save
end
@counts['queries_created'] = query_count
@counts['ratings_created'] = rating_count
@counts = {}
@counts['queries_created'] = service.queries_created
@counts['ratings_created'] = service.ratings_created
respond_with @counts
end
# rubocop:enable Metrics/MethodLength
end
end
end
Expand Down
7 changes: 3 additions & 4 deletions app/controllers/api/v1/books_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ def show

def create
@book = Book.new(book_params)

team = Team.find_by(id: params[:book][:team_id])
@book.teams << team
if @book.save
# first = 1 == current_user.cases.count
# Analytics::Tracker.track_case_created_event current_user, @case, first
respond_with @book
else
render json: @book.errors, status: :bad_request
Expand Down Expand Up @@ -92,7 +91,7 @@ def destroy
private

def book_params
params.require(:book).permit(:team_id, :scorer_id, :selection_strategy_id, :name, :support_implicit_judgements,
params.require(:book).permit(:scorer_id, :selection_strategy_id, :name, :support_implicit_judgements,
:show_rank)
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/import/books_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def create

@book = Book.new

@book.team = Team.find(team_id)
@book.teams << Team.find(team_id)

scorer_name = params_to_use[:scorer][:name]
unless Scorer.exists?(name: scorer_name)
Expand Down
Loading

0 comments on commit 247e7c7

Please sign in to comment.