Skip to content

Commit

Permalink
fix(citSci): updates to dataset item paging, dataset progress
Browse files Browse the repository at this point in the history
- fixed a bug with dataset item paging. Now always requests first page, because items are returned in order of least viewed.
  - as part of this items are stored in a flat array rather than nested in pages.
- fixed a bug stopping the next button from being disabled when it should have been, and also made single-label questions default to not allow empty.
- fixed a bug with dataset progress bar. When the progress was refreshed from the server, the result was off by one because the number of responses
  was requested at the same time as sending the new response. To ensure the correct order, the progress is updated when the create-response response is received.
  • Loading branch information
peichins committed Jul 10, 2019
1 parent 8ebc491 commit a5a8d51
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 131 deletions.
3 changes: 2 additions & 1 deletion src/app/annotationViewer/positionLine.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ angular
*/
self.getWidth = function () {

if (self.containerWidth < 1) {
// a very small width suggests the element is not loaded so it might change.
if (self.containerWidth < 100) {
self.updateContainerWidth();
}
// don't return zero to avoid division by zero
Expand Down
2 changes: 1 addition & 1 deletion src/app/citizenScience/_citizenScience.scss
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
margin-right: auto;
position: relative;

display: block;
display: inline-block;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.7);
max-width: 100%;

Expand Down
209 changes: 100 additions & 109 deletions src/app/citizenScience/datasetProgress/citizenScienceSamples.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,155 +14,162 @@ csSamples.factory("CsSamples", [
var self = this;

/**
* CurrentIndex hold the current page number (0-indexed) and item is current item within the current page (0-indexed)
* Pages is an array of arrays of dataset items.
* currentIndex.page refers to the array index in the outer array
* currentIndex.item refers to the array index in the inner array
*
*/
self.resetlist = function () {
self.currentIndex = { page: -1, item: -1};
self.pages = [];
self.currentItemIndex = -1;
self.items = [];
};
self.resetlist();
self.currentItem = null;


/**
* Returns the number of items in the current page, or 0 if the indexes
* are out of bounds
* @return {number}
* Increment the current item index and set the current item property
* If there is no next item, request a page
* If we are near the last item, request a page.
*/
self.currentPageLength = function () {
if (self.currentIndex.page < 0) {
// first page has not been loaded yet - currentPage is still set to initialization value
return 0;
} else if (self.pages.length < self.currentIndex.page + 1) {
// current page doesn't exit yet
return 0;
} else {
return self.pages[self.currentIndex.page].data.length;
self.nextItem = function () {

var nextItemIndex = self.nextItemIndex();
if (nextItemIndex === false) {
self.requestPageOfItems(true);
return;
}

self.currentItemIndex = nextItemIndex;
self.currentItem = self.items[self.currentItemIndex];

var distanceFromEnd = self.items.length - self.currentItemIndex;
// request a new page when we are on the 2nd last item. Gives enough time to load everything if we are
// advancing very fast.
if (distanceFromEnd <= 2) {
self.requestPageOfItems(false);
}

};


/**
* Sets the currentItem property based on the current item indexes,
* with some checks to see if they are not out of bounds
* Sends a "viewed" progress event to the server
* The index of the next item.
* @return Integer or false if no next item
*/
self.setCurrentItem = function () {
if (self.currentIndex.page < self.pages.length &&
self.currentIndex.page > -1 &&
self.currentIndex.item > -1 &&
self.currentIndex.item < self.currentPageLength()) {
self.currentItem = self.pages[self.currentIndex.page].data[self.currentIndex.item];
ProgressEvent.createProgressEvent(self.currentItem.id, "viewed");
// check if there is another item after this, and if so, go to it.
if (!self.nextItemIndexes()) {
self.requestPageOfItems(false);
}
} else {
self.currentItem = false;
self.nextItemIndex = function () {

// this is the last item index so there is no next item index
if (self.currentItemIndex >= self.items.length - 1 || self.items.length === 0) {
return false;
}
return self.currentItemIndex + 1;

};


/**
* Returns the page number and item number of the next item
* or if there is no next item, returns false.
* This is basically incrementing the currentIndex.page, but with handling when
* we get to the end of the page (end of one of the inner arrays of self.pages)
* Append the items in the new page to the list of items,
* but remove duplicates. We expect some duplicates since we are requesting the first page of unseen data
* every time but doing it in anticipation. So we would get [a,b,c,d] then when we are at c, we request a page
* and get [c,d,e,f]. We need to remove the first 2 items before concatenating.
* @param newItems array of DatasetItems
* @returns Array of DatasetItems - the filtered array with existing items removed.
*
*/
self.nextItemIndexes = function () {
self.mergeNewPage = function (newItems, index) {

// there is no current item, so we don't want to go to the next.
if (!self.currentItem) {
return false;
}

// we are at the end of the last item of the last page, so there is no next item index
if (self.currentIndex.item === self.currentPageLength() - 1 && self.currentIndex.page === self.pages.length - 1) {
return false;
}
// check for overlap starting from two items ago
var fromIndex = Math.max(index - 2, 0);

// increment assuming that there is another item on this page
var nextItemIndex = self.currentIndex.item + 1;
var nextPageIndex = self.currentIndex.page;
var unseenExistingItemIds = self.items.slice(fromIndex).map(item => item.id);

// check if we passed the end of the page and go to next page if necessary
if(self.currentPageLength() < nextItemIndex + 1) {
nextPageIndex = nextPageIndex+ 1;
nextItemIndex = 0;
}
// for each of the last few items, search the array of new items for it and remove it if found.
unseenExistingItemIds.forEach(id => {
var newItemIndex = newItems.findIndex(x => x.id === id);
if (newItemIndex >= 0) {
newItems.splice(newItemIndex,1);
}
});

self.items = self.items.concat(newItems);

return {page: nextPageIndex, item: nextItemIndex};
return newItems;

};

self.setCurrentItem();

/**
* Adds a new page of items to the list of pages
* Adds a new page of items to the list of pages. Adds associated metadata to those items.
* @param thenSelectNewItem boolean; if true will update the current item to be the first item
* in the new page of items
*/
self.requestPageOfItems = function (thenSelectNewItem = false) {
var nextPageNum;
if (self.pages.length > 0) {
// the pagenumber to send with the request, based on incrementing the page
// number of the last page we currently have.
nextPageNum = self.pages[self.pages.length - 1].meta.paging.page + 1;
if (self.pages[self.pages.length - 1].meta.paging.maxPage < nextPageNum) {
// we have reached the last page already
return false;
}
} else {
nextPageNum = 1;

if (!self.datasetId) {
return;
}

// items are ordered by least viewed. Therefore, we always want page 1.
var nextPageNum = 1;

var currentIndex = self.currentItemIndex;

DatasetItem.datasetItems(self.datasetId, nextPageNum).then(x => {

// this should always be the same
self.totalItems = x.data.meta.paging.total;

if (x.data.data.length > 0) {
self.pages[nextPageNum - 1] = x.data;

var newItems = self.mergeNewPage(x.data.data, currentIndex);

if (thenSelectNewItem) {
self.currentIndex.page = nextPageNum - 1;
self.currentIndex.item = 0;
self.setCurrentItem();
self.nextItem();
}

var associationData = {
annotations: x.data.data,
annotationIds: x.data.data.map((dsi) => dsi.id),
recordingIds: x.data.data.map((dsi) => dsi.audioRecordingId)
self.addAssociations(newItems);

};
} else {
console.warn("Empty page of dataset items returned");
}

// this adds associated records to the data
return libraryCommon.getSiteMediaAndProject(associationData).then((y) => {
});

// todo: generalise the annotationLibraryCommon naming to be general
// currently we are piggybacking on annotationLibrary logic, which does
// almost exactly what we need but for AudioEvents instead of DatasetItems
y.annotations.forEach((datasetItem) => {
};

datasetItem.start = new Date(datasetItem.audioRecording.recordedDate.getTime() + datasetItem.startTimeSeconds * 1000);
datasetItem.end = new Date(datasetItem.audioRecording.recordedDate.getTime() + datasetItem.endTimeSeconds * 1000);

});
/**
* Adds the associated models to the dataset item
* - AudioRecording, Site, Project?, Media
* @param items
*/
self.addAssociations = function (items) {

});
var associationData = {
annotations: items,
annotationIds: items.map((dsi) => dsi.id),
recordingIds: items.map((dsi) => dsi.audioRecordingId)

};

} else {
console.warn("Empty page of dataset items returned");
}
// this adds associated records to the data
return libraryCommon.getSiteMediaAndProject(associationData).then((y) => {

// todo: generalise the annotationLibraryCommon naming to be general
// currently we are piggybacking on annotationLibrary logic, which does
// almost exactly what we need but for AudioEvents instead of DatasetItems
y.annotations.forEach((datasetItem) => {

datasetItem.start = new Date(datasetItem.audioRecording.recordedDate.getTime() + datasetItem.startTimeSeconds * 1000);
datasetItem.end = new Date(datasetItem.audioRecording.recordedDate.getTime() + datasetItem.endTimeSeconds * 1000);

});

});

};



self.publicFunctions = {

/**
Expand Down Expand Up @@ -192,26 +199,10 @@ csSamples.factory("CsSamples", [
* Increments the currentItemIndex and requests a new page if we are at the end of the current page
* If called while the currentItemIndex is the last item, this does function does nothing
*/
nextItem : function () {

var nextIndex = self.nextItemIndexes();
if (nextIndex) {
self.currentIndex.page = nextIndex.page;
self.currentIndex.item = nextIndex.item;
self.setCurrentItem();
} else {
// there is no next item so request a new page and go to the first item of it
self.requestPageOfItems(true);
}

// note: indexes are 0-indexed, paging is 1-indexed
var debug_message = {currentIndex: self.currentIndex, currentPageMeta: self.pages[self.pages.length - 1].meta.paging};
console.log("moved to next page of dataset items: ", debug_message);

},
nextItem : self.nextItem,

nextItemAvailable : function () {
var nextIndex = self.nextItemIndexes();
var nextIndex = self.nextItemIndex();
return Boolean(nextIndex);
},

Expand Down
6 changes: 4 additions & 2 deletions src/app/citizenScience/datasetProgress/datasetProgress.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ angular.module("bawApp.components.progress", ["bawApp.citizenScience.csSamples"]
});

$scope.$watch(() => CsSamples.currentItem(), (newVal, oldVal) => {
SampleLabels.reset(newVal.id);
if (newVal) {
SampleLabels.reset(newVal.id);
}
});

$scope.$on("autoNextTrigger", function (x) {
Expand Down Expand Up @@ -78,7 +80,7 @@ angular.module("bawApp.components.progress", ["bawApp.citizenScience.csSamples"]
return "Submit response";
} else {

if (self.allowEmpty || self.allowEmpty === undefined) {
if (SampleLabels.allowEmpty() || SampleLabels.allowEmpty() === undefined) {

if (CsSamples.nextItemAvailable()) {
return "Nothing here, next sample";
Expand Down
Loading

0 comments on commit a5a8d51

Please sign in to comment.