Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions includes/ProgressService.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ public function trackProgress( int $articleId, int $tableId, UserIdentity $user,
->ignore()
->execute();

if ( !$dbw->insertId() ) {
if (
$dbw->affectedRows() === 0 &&
!$this->progressExists( $articleId, $tableId, $user, $entityId )
) {
$this->logger->error(
'Failed to track progress for user {user} on article {article} and table {table} with entity {entity}.',
[
Expand All @@ -92,6 +95,29 @@ public function trackProgress( int $articleId, int $tableId, UserIdentity $user,
return StatusValue::newGood();
}

/**
* Check whether a progress row already exists.
* @param int $articleId
* @param int $tableId
* @param UserIdentity $user,
* @param mixed $entityId
*/
private function progressExists( int $articleId, int $tableId, UserIdentity $user, mixed $entityId ): bool {
$dbr = $this->connectionProvider->getPrimaryDatabase();

return (bool)$dbr->newSelectQueryBuilder()
->select( '1' )
->from( 'table_progress_tracking' )
->where( [
'page_id' => $articleId,
'table_id' => $tableId,
'user_id' => $user->getId(),
'entity_id' => $entityId,
] )
->caller( __METHOD__ )
->fetchField();
}

/**
* Delete the progress of a user on a specific article and table.
*
Expand All @@ -115,18 +141,6 @@ public function deleteProgress( int $articleId, int $tableId, UserIdentity $user
->caller( __METHOD__ )
->execute();

if ( $dbw->affectedRows() === 0 ) {
$this->logger->error(
'Failed to delete progress; user: {user}, article: {article}, table: {table}, entity: {entity}.',
[
'user' => $user->getName(),
'article' => $articleId,
'table' => $tableId,
'entity' => $entityId,
] );
return StatusValue::newFatal( 'An error occured. Please try again later.' );
}

return StatusValue::newGood();
}
}
70 changes: 49 additions & 21 deletions resources/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,51 @@ var ProgressTracker = {
}.bind( this ) );
},

getStoredProgress: function ( tableId ) {
let progress = this.progressData.get( tableId );

if ( progress ) {
return progress.slice();
}

try {
const stored = sessionStorage.getItem( `${this.options.storageKey}-${this.pageId}-${tableId}` );
if ( stored ) {
return JSON.parse( stored );
}
} catch ( e ) {
console.error( "[TableProgressTracking]: Could not read from SessionStorage.", e );
}

return [];
},

saveProgress: function ( tableId, progress ) {
this.progressData.set( tableId, progress );

try {
sessionStorage.setItem( `${this.options.storageKey}-${this.pageId}-${tableId}`, JSON.stringify( progress ) );
} catch ( e ) {
console.error( "[TableProgressTracking]: Could not write to SessionStorage.", e );
}
},

updateProgressForRow: function ( tableId, rowId, isChecked ) {
let progress = this.getStoredProgress( tableId );

if ( isChecked ) {
if ( !progress.includes( rowId ) ) {
progress.push( rowId );
}
} else {
progress = progress.filter( id => id !== rowId );
}

this.saveProgress( tableId, progress );

return progress;
},

setupEventListeners: function() {
const tables = document.querySelectorAll( this.options.selectors.table );

Expand Down Expand Up @@ -188,16 +233,15 @@ var ProgressTracker = {
return;
}

// if the http request fails, we need to revert the checkbox to its original state
const originalState = checkbox.checked;
const requestedState = checkbox.checked;

let table = checkbox.closest( 'table' );
table.classList.add( this.options.classes.saving );

const api = new mw.Rest();

try {
if ( checkbox.checked ) {
if ( requestedState ) {
await api.post( `/progress-tracking/${this.pageId}/${tableId}`, {
entity_id: rowId,
token: mw.user.tokens.get( 'csrfToken' )
Expand All @@ -209,30 +253,14 @@ var ProgressTracker = {
} );
}

let progress = this.progressData.get( tableId ) || [];

if ( checkbox.checked ) {
if ( !progress.includes( rowId ) ) {
progress.push( rowId );
}
} else {
progress = progress.filter( id => id !== rowId );
}

try {
sessionStorage.setItem( `${this.options.storageKey}-${this.pageId}-${tableId}`, JSON.stringify( progress ) );
} catch ( e ) {
console.error( "[TableProgressTracking]: Could not write to SessionStorage.", e );
}

this.progressData.set( tableId, progress );
this.updateProgressForRow( tableId, rowId, requestedState );
this.syncTableCheckboxes( table, tableId );

} catch ( error ) {
console.error( '[TableProgressTracking]: Error updating progress.', error );

// lets revert back to the orginal state
checkbox.checked = !originalState;
checkbox.checked = !requestedState;

table.classList.add( this.options.classes.error );
} finally {
Expand Down
Loading