diff --git a/.editorconfig b/.editorconfig index 47ae637..0a8a30b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,10 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true +[*{.yml,.js}] +indent_size = 2 +indent_style = space + [{*.yml,package.json}] indent_size = 2 diff --git a/css/sitesummary.css b/css/sitesummary.css index 5639861..6eafa6a 100644 --- a/css/sitesummary.css +++ b/css/sitesummary.css @@ -1,44 +1,62 @@ -.package-summary{ - margin-top: 0; +.site-summary { + margin-top: 0; } -.package-summary .message { - margin-left: 0; - margin-right: 0; - margin-top: 0; +.site-summary.message, +.site-summary .message { + margin-left: 0; + margin-right: 0; + margin-top: 0; } -.package-summary .grid-refresh-button { - margin-bottom: 0; -} - -.package-summary__title { - display:block; +.site-summary .grid-refresh-button { + margin-bottom: 0; } /* Due to a rule applied to `.cms .ss-gridfield > div` we have to be specific here */ .cms .ss-gridfield > div.site-summary__clearfix { - margin: 0; - clear: both; + margin: 0; + clear: both; } .gridfield-button-link { - margin-bottom: 12px; -} - -.package-summary table.ss-gridfield-table tr td.col-Summary { - padding: 0; + margin-bottom: 12px; } a.package-summary__anchor { - color: inherit; - text-decoration: inherit; - display: block; - padding: 8px; + color: inherit; + text-decoration: inherit; + display: block; + padding: 0 8px; + margin: 0 -8px; } a.package-summary__anchor:hover, a.package-summary__anchor:active { - color: inherit; - text-decoration: inherit; + color: inherit; + text-decoration: inherit; +} + +.package-summary__badge { + font-size: 0.8em; + padding: 3px 5px; + background: grey; + border-radius: 2px; + color: white; + position: relative; + top: -1px; +} +/* Colours for badge classes */ +.package-summary__badge--good { + background: #3fa142; +} +.package-summary__badge--warning { + background: #ff7f22; +} +.package-summary__badge--bad { + background: #d40404; +} + +.package-summary__description { + display:block; } diff --git a/javascript/CheckForUpdates.js b/javascript/CheckForUpdates.js index 8ba0cd9..84055f1 100644 --- a/javascript/CheckForUpdates.js +++ b/javascript/CheckForUpdates.js @@ -1,68 +1,68 @@ (function ($) { - $.entwine('ss', function ($) { - // p tag that holds button - $('#checkForUpdates').entwine({ - // Magically set by the magic get/set{thisMemberProperty} (see poll function below) - PollTimeout: null, - onclick: function () { - this.setLoading(); - }, - onmatch: function () { - // Poll the current job and update the front end status - if (this.getButton(true).length) { - this.setLoading(); - } - }, - setLoading: function () { - // Add warning message (set as data attribute on GridFieldRefreshButton) before - // first button row - $('.ss-gridfield-buttonrow').first().prepend( - '
' - ); - this.poll(); - }, - poll: function () { - var self = this; - $.ajax({ - url: self.getButton().data('check'), - async: true, - success: function (data) { - self.clearLoading(JSON.parse(data)); - }, - error: function (error) { - if (typeof console !== 'undefined') { - console.log(error); - } - } - }); - }, - getButton: function (disabled) { - let button = 'button'; - if (disabled) { - button += ':disabled'; - } - return this.children(button).first(); - }, - clearLoading: function (hasRunningJob) { + $.entwine('ss', function ($) { + // p tag that holds button + $('#checkForUpdates').entwine({ + // Magically set by the magic get/set{thisMemberProperty} (see poll function below) + PollTimeout: null, + onclick: function () { + this.setLoading(); + }, + onmatch: function () { + // Poll the current job and update the front end status + if (this.getButton(true).length) { + this.setLoading(); + } + }, + setLoading: function () { + // Add warning message (set as data attribute on GridFieldRefreshButton) before + // first button row + $('.ss-gridfield-buttonrow').first().prepend( + ' ' + ); + this.poll(); + }, + poll: function () { + var self = this; + $.ajax({ + url: self.getButton().data('check'), + async: true, + success: function (data) { + self.clearLoading(JSON.parse(data)); + }, + error: function (error) { + if (typeof console !== 'undefined') { + console.log(error); + } + } + }); + }, + getButton: function (disabled) { + let button = 'button'; + if (disabled) { + button += ':disabled'; + } + return this.children(button).first(); + }, + clearLoading: function (hasRunningJob) { - if (hasRunningJob === false) { - this.closest('fieldset.ss-gridfield').reload(); - return; - } + if (hasRunningJob === false) { + this.closest('fieldset.ss-gridfield').reload(); + return; + } - // Ensure the regular poll method is run - // Kill any existing timeout - clearTimeout(this.getPollTimeout()); + // Ensure the regular poll method is run + // Kill any existing timeout + clearTimeout(this.getPollTimeout()); - this.setPollTimeout(setTimeout( - function () { - $('#checkForUpdates').poll(); - }, - 5000 - )); - } - }); + this.setPollTimeout(setTimeout( + function () { + $('#checkForUpdates').poll(); + }, + 5000 + )); + } }); + }); }(jQuery)); diff --git a/src/Forms/GridFieldHtmlFragment.php b/src/Forms/GridFieldHtmlFragment.php index 4eadc40..4989e12 100644 --- a/src/Forms/GridFieldHtmlFragment.php +++ b/src/Forms/GridFieldHtmlFragment.php @@ -1,12 +1,15 @@ $this->link, 'Caption' => _t('GridFieldLinkButton.LINK_TO_ADDONS', 'Explore Addons') - ])->renderWith(__CLASS__); + ])->renderWith('GridFieldLinkButton'); return [$this->targetFragment => $fragment]; } diff --git a/src/Forms/GridFieldRefreshButton.php b/src/Forms/GridFieldRefreshButton.php index 7f2e329..ce05412 100644 --- a/src/Forms/GridFieldRefreshButton.php +++ b/src/Forms/GridFieldRefreshButton.php @@ -1,11 +1,26 @@ targetFragment => ArrayData::create(['Button' => $button->Field()])->renderWith(__CLASS__) + $this->targetFragment => ArrayData::create([ + 'Button' => $button->Field() + ])->renderWith('GridFieldRefreshButton') ]; } diff --git a/src/Model/Package.php b/src/Model/Package.php index 3becd91..55a8726 100644 --- a/src/Model/Package.php +++ b/src/Model/Package.php @@ -19,6 +19,11 @@ class Package extends DataObject 'Version' => 'Version', ]; + /** + * @var array badge definitions - a keyed array in the format of [Title => Type] {@see getBadges()} + */ + protected $badges = []; + /** * Strips vendor and 'silverstripe-' prefix from Name property * @return string More easily digestable module name for human consumers @@ -34,7 +39,60 @@ public function getTitle() */ public function getSummary() { - return $this->renderWith('Package_summary'); + $summary = $this->renderWith('Package_summary'); + $this->extend('updateSummary', $summary); + return $summary; + } + + /** + * Gives the summary template {@see getSummary()} a list of badges to show against a package + * + * badgeDefinitions are in the format [$title => $type] where: + * title is the unique string to display + * type is an optional class attribute (applied as a BEM modifier, by default) + * + * @return ArrayList + */ + public function getBadges() + { + $badgeDefinitions = $this->badges; + $badges = ArrayList::create(); + foreach ($badgeDefinitions as $title => $type) { + $badges->push(ArrayData::create([ + 'Title' => $title, + 'Type' => $type, + ])); + } + + $this->extend('updateBadges', $badgeDefinitions); + return $badges; + } + + /** + * Adds a badge to the list of badges {@see $badges} + * + * @param string $title + * @param string $type + * + * @return $this + */ + public function addBadge($title, $type) + { + $this->badges[$title] = $type; + return $this; + } + + /** + * Replaces the list of badges + * + * @param array $badges {@see $badges} + * + * @return $this + */ + public function setBadges($badges) + { + $this->badges = $badges; + return $this; } /** diff --git a/src/Reports/SiteSummary.php b/src/Reports/SiteSummary.php index 552af53..78e2c51 100644 --- a/src/Reports/SiteSummary.php +++ b/src/Reports/SiteSummary.php @@ -1,5 +1,9 @@ getConfig(); - $grid->addExtraClass('package-summary'); + $grid->addExtraClass('site-summary'); /** @var GridFieldExportButton $exportButton */ $exportButton = $config->getComponentByType(GridFieldExportButton::class); @@ -84,6 +88,30 @@ public function getReportField() return $grid; } + public function getCMSFields() + { + $fields = parent::getCMSFields(); + $alerts = $this->getAlerts(); + if ($alerts) { + $summaryInfo = ' '; + $fields->unshift(LiteralField::create('AlertSummary', $summaryInfo)); + } + return $fields; + } + + /** + * Return a list of alerts to display in a message box above the report + * A combination of free text fields - combined alerts as opposed to a message box per alert. + * + * @return array + */ + protected function getAlerts() + { + $alerts = []; + $this->extend('updateAlerts', $alerts); + return $alerts; + } + /** * Extract CMS and Framework version details from the records in the report * diff --git a/templates/Package_summary.ss b/templates/Package_summary.ss index 3b18066..04baae3 100644 --- a/templates/Package_summary.ss +++ b/templates/Package_summary.ss @@ -1,4 +1,11 @@ " class="package-summary__anchor" target="_blank" rel="noopener"> $Title.XML - <% if $Description %>$Description.XML<% end_if %> + + <% loop $Badges %> + $Title.XML + <% end_loop %> + + <% if $Description %> + $Description.XML + <% end_if %> diff --git a/tests/Forms/GridFieldLinkButtonTest.php b/tests/Forms/GridFieldLinkButtonTest.php index 4607336..68c867d 100644 --- a/tests/Forms/GridFieldLinkButtonTest.php +++ b/tests/Forms/GridFieldLinkButtonTest.php @@ -1,5 +1,7 @@ $name ]); $this->assertEquals($title, $testPackage->getTitle()); } + + /** + * Ensure that the definition key is always the output title + * and that the value is set as the Type. + */ + public function testBadges() + { + $testPackage = new Package(); + $testBadges = [ + 'A good Badge' => 'good', + 'A typeless badge' => null + ]; + $testPackage->setBadges($testBadges); + $badgeViewData = $testPackage->getBadges(); + + // Test expected data structure is correct + $this->assertInstanceOf('ArrayList', $badgeViewData); + $this->assertContainsOnlyInstancesOf('ArrayData', $badgeViewData->toArray()); + + // Test that the output format is correct + reset($testBadges); + foreach ($badgeViewData as $badgeData) { + $title = key($testBadges); + $type = current($testBadges); + $this->assertSame( + [ + 'Title' => $title, + 'Type' => $type, + ], + $badgeData->toMap() + ); + // testBadges is a keyed array, so shift the pointer manually + // (because we can't lookup by index) + next($testBadges); + } + } } diff --git a/tests/Reports/SiteSummaryTest.php b/tests/Reports/SiteSummaryTest.php index 9c09408..965a3ec 100644 --- a/tests/Reports/SiteSummaryTest.php +++ b/tests/Reports/SiteSummaryTest.php @@ -2,6 +2,7 @@ namespace BringYourOwnIdeas\Maintenance\Tests\Reports; +use BringYourOwnIdeas\Maintenance\Tests\Reports\Stubs\SiteSummaryExtensionStub; use Package; use SapphireTest; use SiteSummary; @@ -10,6 +11,10 @@ class SiteSummaryTest extends SapphireTest { protected static $fixture_file = 'Package.yml'; + protected $requiredExtensions = [ + SiteSummary::class => [SiteSummaryExtensionStub::class] + ]; + public function testSourceRecords() { $summaryReport = new SiteSummary; @@ -28,4 +33,13 @@ public function testOnlySilverStripeModulesAreShown() $this->assertEquals('silverstripe-module', $record->Type); } } + + public function testAlertsRenderAtopTheReportField() + { + $summaryReport = new SiteSummary; + $fields = $summaryReport->getCMSFields(); + $alertSummary = $fields->fieldByName('AlertSummary'); + $this->assertInstanceOf('LiteralField', $alertSummary); + $this->assertContains('Sound the alarm!', $alertSummary->getContent()); + } } diff --git a/tests/Reports/Stubs/SiteSummaryExtensionStub.php b/tests/Reports/Stubs/SiteSummaryExtensionStub.php new file mode 100644 index 0000000..5f17cd0 --- /dev/null +++ b/tests/Reports/Stubs/SiteSummaryExtensionStub.php @@ -0,0 +1,14 @@ +Alert! Alert!