Skip to content

Commit

Permalink
NEW: Adding a option to run a batch job to the front end
Browse files Browse the repository at this point in the history
  • Loading branch information
kmayo-ss committed Jul 30, 2014
1 parent d25adca commit 093322f
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 29 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,22 @@ The external links module is a task and ModelAdmin to track and to report on bro
5. Run in your browser - `/dev/build` to rebuild the database.
6. Run the following task *http://path.to.silverstripe/dev/tasks/CheckExternalLinks* to check for broken external links

## Report ##

A new report is added called 'External Broken links report' from here you can also start a new job which is run
via AJAX and in batches of 10 so it can be run via content editors who do not have access to jobs or tasks.

## Dev task ##

Run the following task *http://path.to.silverstripe/dev/tasks/CheckExternalLinks* to check your site for external
broken links.

## Queued job ##

If you have the queuedjobs module installed you can set the task to be run every so ofter
Add the following yml config to config.yml in mysite/_config have the the task run once every day (86400 seconds)

`---
Name: externallinkssettings
---
```
CheckExternalLinks:
Delay: 86400`
Delay: 86400
```
70 changes: 60 additions & 10 deletions code/controllers/CMSExternalLinks.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,69 @@

class CMSExternalLinks_Controller extends Controller {

private static $allowed_actions = array('createQueuedReport');
private static $allowed_actions = array('getJobStatus', 'clear', 'start');

/*
* Respond to Ajax requests for info on a running job
* also calls continueJob and clear depending on the status of the job
*
* @return string JSON string detailing status of the job
*/
public function getJobStatus() {
$trackID = Session::get('ExternalLinksTrackID');

This comment has been minimized.

Copy link
@tractorcow

tractorcow Jul 30, 2014

What if another user comes to make a report? Or if the user logs out and then in again? You could end up with multiple concurrent reports. Perhaps we should limit it to one report at any time, and block creation of further reports while one is already running. Maybe all users should be able to view the status of the report progress as well.

if (!$trackID) return;
$noPages = Versioned::get_by_stage('SiteTree', 'Live')->count();
$result = BrokenExternalPageTrack::get()
->filter('TrackID', $trackID)
->exclude('PageID', 0);
$completedPages = count($result);

public function createQueuedReport() {
if (!Permission::check('ADMIN')) return;
echo json_encode(array(
'TrackID' => $trackID,
'Completed' => $completedPages,
'Total' => $noPages
));

// setup external links job
$externalLinks = new CheckExternalLinksJob();
$job = singleton('QueuedJobService');
$jobID = $job->queueJob($externalLinks);
if ($completedPages >= $noPages) {
$this->clear();
} else {
$this->continueJob();
}
}

/*
* Clears the tracking id and any surplus entries for the BrokenExternalPageTrack model
*/
public function clear() {
// clear any old entries
$trackID = Session::get('ExternalLinksTrackID');
$oldEntries = BrokenExternalPageTrack::get()
->exclude('TrackID', $trackID);
foreach ($oldEntries as $entry) {
$entry->delete();
}
Session::clear('ExternalLinksTrackID');
}

/*
* Starts a broken external link check
*/
public function start() {
$track = BrokenExternalPageTrack::create();
$track->write();
$track->TrackID = $track->ID;

This comment has been minimized.

Copy link
@tractorcow

tractorcow Jul 30, 2014

Why is this object created without a page? Are you using this as a "master" object to control all the individual tracks for each page? It would be less vague if you used a separate dataobject as the "master", or simply assume that all objects in the DB belong to the same run (if only one process is allowed at any time, and discard them all at the end).

$track->write();

Session::set('ExternalLinksTrackID', $track->ID);

$this->continueJob();
}

// redirect to the jobs page
$admin = QueuedJobsAdmin::create();
$this->Redirect($admin->Link());
/*
* Continues a broken external link check
*/
public function continueJob() {
$task = new CheckExternalLinks();
$task->run(null);
}
}
3 changes: 2 additions & 1 deletion code/jobs/CheckExternalLinksJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public function process() {
}

$task = new CheckExternalLinks();
$task->run($page);
$task->pageToProcess = $page;
$task->run();

// and now we store the new list of remaining children
$this->pagesToProcess = $remainingPages;
Expand Down
10 changes: 10 additions & 0 deletions code/model/BrokenExternalLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ function canView($member = false) {
return Permission::checkMember($member, $codes);
}
}

class BrokenExternalPageTrack extends DataObject {

This comment has been minimized.

Copy link
@tractorcow

tractorcow Jul 30, 2014

BrokenExternalPageTrack should probably be either moved to BrokenExternalPageTrack.php or renamed to BrokenExternalLink_PageTrack. The convention is that the filename of a class can be determined by inspecting the class name (the part before the first underscore in the class).

PHPDoc to explain the purpose and meaning of this class would be good.

Other comments on usage of this class above.

private static $db = array(
'TrackID' => 'Int'
);

private static $has_one = array(
'Page' => 'Page'
);
}
7 changes: 4 additions & 3 deletions code/reports/BrokenExternalLinksReport.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,20 @@ public function sourceRecords() {
}

public function getCMSFields() {
Requirements::javascript('externallinks/javascript/BrokenExternalLinksReport.js');
$fields = parent::getCMSFields();
if (class_exists('AbstractQueuedJob')) {
$button = '<a href = "%s"><button class="externalLinksReport" type="button">%s</button></a>';
$button = '<button id="externalLinksReport" type="button">%s</button>';
$runReportButton = new LiteralField(
'runReport',
sprintf(
$button,
'admin/externallinks/createQueuedReport',
_t('ExternalBrokenLinksReport.RUNREPORT', 'Create new report')
)
);
$fields->push($runReportButton);
$reportResultSpan = '<span id="ReportHolder"></span></ br><span id="ReportProgress"></span>';

$reportResultSpan = '</ br></ br><h3 id="ReportHolder"></h3>';
$reportResult = new LiteralField('ResultTitle', $reportResultSpan);
$fields->push($reportResult);
}
Expand Down
30 changes: 19 additions & 11 deletions code/tasks/CheckExternalLinks.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

class CheckExternalLinks extends BuildTask {
public static $pageToProcess;
protected $title = 'Checking broken External links in the SiteTree';

protected $description = 'A task that records external broken links in the SiteTree';
Expand All @@ -10,19 +11,20 @@ class CheckExternalLinks extends BuildTask {
private $completedPages;
private $totalPages;

public function getCompletedPages() {
return $this->completedPages;
}

public function getTotalPages() {
return $this->totalPages;
}

function run($request) {
if (isset($request->ID)) {
$pages = $request;
$trackID = Session::get('ExternalLinksTrackID');
if (isset($this->pageToProcess)) {
$pages = $this->pageToProcess;
} else {
$pages = Versioned::get_by_stage('SiteTree', 'Live');
if ($trackID) {
$result = BrokenExternalPageTrack::get()
->filter('TrackID', $trackID);
$pages = Versioned::get_by_stage('SiteTree', 'Live')
->exclude('ID', $result->column('PageID'))
->limit(10);
} else {
$pages = Versioned::get_by_stage('SiteTree', 'Live');
}
}
foreach ($pages as $page) {
++$this->totalPages;
Expand Down Expand Up @@ -93,6 +95,12 @@ function run($request) {
}
}
++$this->completedPages;
if ($trackID) {
$trackPage = new BrokenExternalPageTrack();
$trackPage->PageID = $page->ID;
$trackPage->TrackID = $trackID;
$trackPage->write();
}
}

// run this again if queued jobs exists and is a valid int
Expand Down
45 changes: 45 additions & 0 deletions javascript/BrokenExternalLinksReport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
(function($) {
$('#externalLinksReport').entwine({
onclick: function() {
$(this).start();
$(this).poll();
},
start: function() {
// initiate a new job
$('#ReportHolder').empty();
$('#ReportHolder').text('Running report 0%');
$('#ReportHolder').append('<span class="ss-ui-loading-icon"></span>');
$.ajax({url: "admin/externallinks/start", async: true, timeout: 1000 });
},
poll: function() {

This comment has been minimized.

Copy link
@tractorcow

tractorcow Jul 30, 2014

If this method is to simply check the progress of the job it should do that. getJobStatus is misnamed, since it doesn't get the status, but creates additional background tasks.

// poll the current job and update the front end status
$.ajax({
url: "admin/externallinks/getJobStatus",
async: true,
success: function(data) {
var obj = $.parseJSON(data);
if (!obj) return;
var completed = obj.Completed ? obj.Completed : 0;
var total = obj.Total ? obj.Total : 0;
if (total > 0 && completed == total) {
$('#ReportHolder').text('Report Finished ' + completed + '/' + total);
} else {
setTimeout(function() { $('#externalLinksReport').poll(); }, 1);
}
if (total && completed) {
if (completed < total) {
var percent = (completed / total) * 100;
$('#ReportHolder').text('Running report ' + completed + '/' +
total + ' (' + percent.toFixed(2) + '%)');
$('#ReportHolder').
append('<span class="ss-ui-loading-icon"></span>');
}
}
},
error: function(e) {
console.log(e);
}
});
}
});
}(jQuery));

0 comments on commit 093322f

Please sign in to comment.