Reworking the pulls list to use native filter

Signed-off-by: Roland Dalmulder <contact@rolandd.com>
This commit is contained in:
Roland Dalmulder 2020-11-28 17:09:53 +01:00
parent cb1b242f1b
commit 2f62b5716d
No known key found for this signature in database
GPG Key ID: FD49814C56AE3AF9
10 changed files with 71 additions and 393 deletions

View File

@ -93,7 +93,7 @@ abstract class AbstractController
*
* @since 2.0
*/
protected function initializeState(AbstractModel $model)
protected function initializeState($model)
{
$state = new Registry;

View File

@ -97,7 +97,7 @@ class DisplayController extends AbstractController
*
* @since 2.0
*/
protected function initializeState(AbstractModel $model)
protected function initializeState($model)
{
$state = parent::initializeState($model);
$app = $this->getApplication();

View File

@ -43,10 +43,15 @@ class FetchController extends AbstractController
// Fetch our page from the session
$page = $session->get('com_patchtester_fetcher_page', 1);
$model = new PullsModel('com_patchtester.fetch', null, Factory::getDbo());
$model = new PullsModel;
// Initialize the state for the model
$model->setState($this->initializeState($model));
$state = $this->initializeState($model);
foreach ($state as $key => $value)
{
$model->setState($key, $value);
}
$status = $model->requestFromGithub($page);
}

View File

@ -8,13 +8,10 @@
namespace PatchTester\Model;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormFactoryAwareTrait;
use Exception;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\FormBehaviorTrait;
use Joomla\CMS\Pagination\Pagination;
use Joomla\Registry\Registry;
use Joomla\CMS\MVC\Model\ListModel;
use PatchTester\GitHub\Exception\UnexpectedResponse;
use PatchTester\Helper;
@ -23,11 +20,8 @@ use PatchTester\Helper;
*
* @since 2.0
*/
class PullsModel extends AbstractModel
class PullsModel extends ListModel
{
use FormBehaviorTrait;
use FormFactoryAwareTrait;
/**
* The object context
*
@ -45,20 +39,30 @@ class PullsModel extends AbstractModel
protected $sortFields = array('pulls.pull_id', 'pulls.title');
/**
* Instantiate the model.
* Constructor.
*
* @param string $context The model context.
* @param Registry $state The model state.
* @param \JDatabaseDriver $db The database adpater.
* @param array $config An optional associative array of configuration settings.
*
* @since 4.0.0
* @throws Exception
*
* @since 2.0
*/
public function __construct($context, Registry $state = null,
\JDatabaseDriver $db = null
) {
parent::__construct($state, $db);
public function __construct($config = [])
{
$config = [];
$this->context = $context;
if (empty($config['filter_fields']))
{
$config['filter_fields'] = [
'applied',
'rtc',
'npm',
'label',
'branch',
];
}
parent::__construct($config);
}
/**
@ -82,7 +86,7 @@ class PullsModel extends AbstractModel
$this->getState()->get('list.limit')
);
$db = $this->getDb();
$db = $this->getDbo();
$query = $db->getQuery(true)
->select($db->quoteName(['name', 'color']))
->from($db->quoteName('#__patchtester_pulls_labels'));
@ -143,7 +147,7 @@ class PullsModel extends AbstractModel
*/
protected function getList($query, $limitstart = 0, $limit = 0)
{
return $this->getDb()->setQuery($query, $limitstart, $limit)
return $this->getDbo()->setQuery($query, $limitstart, $limit)
->loadObjectList();
}
@ -184,7 +188,7 @@ class PullsModel extends AbstractModel
protected function getListQuery()
{
// Create a new query object.
$db = $this->getDb();
$db = $this->getDbo();
$query = $db->getQuery(true);
$labelQuery = $db->getQuery(true);
@ -318,121 +322,6 @@ class PullsModel extends AbstractModel
return $query;
}
/**
* Method to get the starting number of items for the data set.
*
* @return integer The starting number of items available in the data set.
*
* @since 2.0
*/
public function getStart()
{
$store = $this->getStoreId('getStart');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
$start = $this->getState()->get('list.start', 0);
$limit = $this->getState()->get('list.limit', 20);
$total = $this->getTotal();
if ($start > $total - $limit)
{
$start = max(0, (int) (ceil($total / $limit) - 1) * $limit);
}
// Add the total to the internal cache.
$this->cache[$store] = $start;
return $this->cache[$store];
}
/**
* Method to get the total number of items for the data set.
*
* @return integer The total number of items available in the data set.
*
* @since 2.0
*/
public function getTotal()
{
// Get a storage key.
$store = $this->getStoreId('getTotal');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
// Load the total and add the total to the internal cache.
$this->cache[$store] = (int) $this->getListCount(
$this->getListQueryCache()
);
return $this->cache[$store];
}
/**
* Returns a record count for the query.
*
* @param \JDatabaseQuery|string $query The query.
*
* @return integer Number of rows for query.
*
* @since 2.0
*/
protected function getListCount($query)
{
// Use fast COUNT(*) on JDatabaseQuery objects if there no GROUP BY or HAVING clause:
if ($query instanceof \JDatabaseQuery && $query->type == 'select'
&& $query->group === null
&& $query->having === null)
{
$query = clone $query;
$query->clear('select')->clear('order')->select('COUNT(*)');
$this->getDb()->setQuery($query);
return (int) $this->getDb()->loadResult();
}
// Otherwise fall back to inefficient way of counting all results.
$this->getDb()->setQuery($query)->execute();
return (int) $this->getDb()->getNumRows();
}
/**
* Method to get a Pagination object for the data set.
*
* @return Pagination A Pagination object for the data set.
*
* @since 2.0
*/
public function getPagination()
{
// Get a storage key.
$store = $this->getStoreId('getPagination');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
// Create the pagination object and add the object to the internal cache.
$this->cache[$store] = new Pagination(
$this->getTotal(), $this->getStart(),
(int) $this->getState()->get('list.limit', 20)
);
return $this->cache[$store];
}
/**
* Retrieves the array of authorized sort fields
*
@ -460,8 +349,8 @@ class PullsModel extends AbstractModel
// If on page 1, dump the old data
if ($page === 1)
{
$this->getDb()->truncateTable('#__patchtester_pulls');
$this->getDb()->truncateTable('#__patchtester_pulls_labels');
$this->getDbo()->truncateTable('#__patchtester_pulls');
$this->getDbo()->truncateTable('#__patchtester_pulls_labels');
}
try
@ -564,8 +453,8 @@ class PullsModel extends AbstractModel
',',
[
(int) $pull->number,
$this->getDb()->quote($label->name),
$this->getDb()->quote($label->color),
$this->getDbo()->quote($label->name),
$this->getDbo()->quote($label->color),
]
);
}
@ -573,16 +462,16 @@ class PullsModel extends AbstractModel
// Build the data object to store in the database
$pullData = [
(int) $pull->number,
$this->getDb()->quote(
$this->getDbo()->quote(
HTMLHelper::_('string.truncate', $pull->title, 150)
),
$this->getDb()->quote(
$this->getDbo()->quote(
HTMLHelper::_('string.truncate', $pull->body, 100)
),
$this->getDb()->quote($pull->pull_request->html_url),
$this->getDbo()->quote($pull->pull_request->html_url),
(int) $isRTC,
(int) $isNPM,
$this->getDb()->quote($branch),
$this->getDbo()->quote($branch),
];
$data[] = implode(',', $pullData);
@ -597,8 +486,8 @@ class PullsModel extends AbstractModel
try
{
$this->getDb()->setQuery(
$this->getDb()->getQuery(true)
$this->getDbo()->setQuery(
$this->getDbo()->getQuery(true)
->insert('#__patchtester_pulls')
->columns(
['pull_id', 'title', 'description', 'pull_url',
@ -607,7 +496,7 @@ class PullsModel extends AbstractModel
->values($data)
);
$this->getDb()->execute();
$this->getDbo()->execute();
}
catch (\RuntimeException $exception)
{
@ -625,13 +514,13 @@ class PullsModel extends AbstractModel
{
try
{
$this->getDb()->setQuery(
$this->getDb()->getQuery(true)
$this->getDbo()->setQuery(
$this->getDbo()->getQuery(true)
->insert('#__patchtester_pulls_labels')
->columns(['pull_id', 'name', 'color'])
->values($labels)
);
$this->getDb()->execute();
$this->getDbo()->execute();
}
catch (\RuntimeException $exception)
{
@ -662,73 +551,6 @@ class PullsModel extends AbstractModel
*/
public function truncateTable()
{
$this->getDb()->truncateTable('#__patchtester_pulls');
}
/**
* Get the filter form
*
* @param array $data data
* @param boolean $loadData load current data
*
* @return Form|null The \JForm object or null if the form can't be found
*
* @since 3.2
*/
public function getFilterForm($data = array(), $loadData = true)
{
// Try to locate the filter form automatically. Example: ContentModelArticles => "filter_articles"
if (empty($this->filterFormName))
{
$classNameParts = explode('Model', \get_called_class());
if (\count($classNameParts) >= 2)
{
$this->filterFormName = 'filter_' . str_replace('\\', '', strtolower($classNameParts[1]));
}
}
if (empty($this->filterFormName))
{
return null;
}
try
{
// Get the form.
return $this->loadForm($this->context . '.filter', $this->filterFormName, array('control' => '', 'load_data' => $loadData));
}
catch (\RuntimeException $e)
{
}
return null;
}
/**
* Function to get the active filters
*
* @return array Associative array in the format: array('filter_published' => 0)
*
* @since 3.2
*/
public function getActiveFilters()
{
$activeFilters = array();
if (!empty($this->filter_fields))
{
foreach ($this->filter_fields as $filter)
{
$filterName = 'filter.' . $filter;
if (property_exists($this->state, $filterName) && (!empty($this->state->{$filterName}) || is_numeric($this->state->{$filterName})))
{
$activeFilters[$filter] = $this->state->get($filterName);
}
}
}
return $activeFilters;
$this->getDbo()->truncateTable('#__patchtester_pulls');
}
}

View File

@ -43,7 +43,7 @@ abstract class AbstractHtmlView extends AbstractView
*
* @since 4.0.0
*/
public function __construct(AbstractModel $model, \SplPriorityQueue $paths = null)
public function __construct($model, \SplPriorityQueue $paths = null)
{
parent::__construct($model);

View File

@ -32,7 +32,7 @@ abstract class AbstractView
*
* @since 4.0.0
*/
public function __construct(AbstractModel $model)
public function __construct($model)
{
$this->model = $model;
}

View File

@ -8,6 +8,7 @@
namespace PatchTester\View\Pulls;
use Exception;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
@ -54,14 +55,16 @@ class PullsHtmlView extends DefaultHtmlView
/**
* Form object for search filters
*
* @var Form
* @var Form
* @since 4.1.0
*/
public $filterForm;
/**
* The active search filters
*
* @var array
* @var array
* @since 4.1.0
*/
public $activeFilters;
@ -69,7 +72,7 @@ class PullsHtmlView extends DefaultHtmlView
* The model state
*
* @var Registry
* @since 2.0
* @since 2.0.0
*/
protected $state;
@ -86,9 +89,10 @@ class PullsHtmlView extends DefaultHtmlView
*
* @return string The rendered view.
*
* @since 2.0
* @since 2.0.0
* @throws Exception
*/
public function render()
public function render(): string
{
if (!extension_loaded('openssl'))
{
@ -100,7 +104,6 @@ class PullsHtmlView extends DefaultHtmlView
$this->envErrors[] = Text::_('COM_PATCHTESTER_REQUIREMENT_HTTPS');
}
// Only process the data if there are no environment errors
if (!count($this->envErrors))
{
$this->state = $this->model->getState();
@ -122,10 +125,8 @@ class PullsHtmlView extends DefaultHtmlView
$this->addToolbar();
// Make text strings available in the JavaScript API
Text::script('COM_PATCHTESTER_CONFIRM_RESET');
// Set a warning on 4.0 branch
if (version_compare(JVERSION, '4.0', 'ge'))
{
Factory::getApplication()->enqueueMessage(Text::_('COM_PATCHTESTER_40_WARNING'), 'warning');
@ -139,7 +140,7 @@ class PullsHtmlView extends DefaultHtmlView
*
* @return void
*
* @since 2.0
* @since 2.0.0
*/
protected function addToolbar(): void
{
@ -168,45 +169,4 @@ class PullsHtmlView extends DefaultHtmlView
ToolbarHelper::preferences('com_patchtester');
}
/**
* Returns an array of values to be used for pagination limits
*
* @return array
*
* @since 4.0.0
*/
protected function getLimitOptions()
{
return [
5 => Text::_('J5'),
10 => Text::_('J10'),
15 => Text::_('J15'),
20 => Text::_('J20'),
25 => Text::_('J25'),
30 => Text::_('J30'),
50 => Text::_('J50'),
100 => Text::_('J100'),
200 => Text::_('J200'),
500 => Text::_('J500'),
0 => Text::_('JALL'),
];
}
/**
* Returns an array of fields the table can be sorted by
*
* @return array
*
* @since 2.0
*/
protected function getSortFields()
{
return [
'a.title ASC' => Text::_('JGLOBAL_TITLE_ASC'),
'a.title DESC' => Text::_('JGLOBAL_TITLE_DESC'),
'a.pull_id ASC' => Text::_('COM_PATCHTESTER_PULL_ID_ASC'),
'a.pull_id DESC' => Text::_('COM_PATCHTESTER_PULL_ID_DESC'),
];
}
}

View File

@ -6,7 +6,6 @@
* @license GNU General Public License version 2 or later
*/
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
@ -14,127 +13,14 @@ use Joomla\CMS\Router\Route;
/** @var \PatchTester\View\Pulls\PullsHtmlView $this */
$searchToolsOptions = array(
'filtersHidden' => true,
'filterButton' => true,
'defaultLimit' => Factory::getApplication()->get('list_limit', 20),
'searchFieldSelector' => '#filter_search',
'selectorFieldName' => 'client_id',
'showSelector' => false,
'orderFieldSelector' => '#list_fullordering',
'showNoResults' => false,
'noResultsText' => '',
'formSelector' => '#adminForm',
);
HTMLHelper::_('behavior.core');
HTMLHelper::_('searchtools.form', '#adminForm', $searchToolsOptions);
HTMLHelper::_('behavior.multiselect');
HTMLHelper::_('stylesheet', 'com_patchtester/octicons.css', ['version' => '3.5.0', 'relative' => true]);
HTMLHelper::_('script', 'com_patchtester/patchtester.js', ['version' => 'auto', 'relative' => true]);
$listOrder = $this->escape($this->state->get('list.fullordering', 'a.pull_id DESC'));
$listLimit = (int) ($this->state->get('list.limit'));
$filterApplied = $this->escape($this->state->get('filter.applied'));
$filterBranch = $this->escape($this->state->get('filter.branch'));
$filterLabel = $this->state->get('filter.label');
$filterRtc = $this->escape($this->state->get('filter.rtc'));
$filterNpm = $this->escape($this->state->get('filter.npm'));
$visible = '';
if ($filterApplied || $filterBranch || $filterLabel || $filterRtc || $filterNpm)
{
$visible = 'js-stools-container-filters-visible';
}
?>
<form action="<?php echo Route::_('index.php?option=com_patchtester&view=pulls'); ?>" method="post" name="adminForm" id="adminForm">
<div class="row">
<div class="col-md-12">
<div id="j-main-container" class="j-main-container">
<?php echo LayoutHelper::render('joomla.searchtools.default', ['view' => $this]); ?>
<div class="js-stools" role="search">
<div class="js-stools-container-bar">
<div class="btn-toolbar">
<div class="btn-group mr-2">
<div class="input-group">
<label for="filter_search" class="sr-only">
<?php echo Text::_('COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION'); ?>
</label>
<input type="text" name="filter_search" id="filter_search" value="<?php echo $this->escape($this->state->get('filter.search')); ?>" class="form-control" placeholder="<?php echo Text::_('JSEARCH_FILTER'); ?>">
<div role="tooltip" id="filter[search]-desc">
<?php echo $this->escape(Text::_('COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION')); ?>
</div>
<span class="input-group-append">
<button type="submit" class="btn btn-primary" aria-label="<?php echo Text::_('JSEARCH_FILTER_SUBMIT'); ?>">
<span class="fa fa-search" aria-hidden="true"></span>
</button>
</span>
</div>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary hasTooltip js-stools-btn-filter">
<?php echo Text::_('JFILTER_OPTIONS'); ?>
<span class="fa fa-angle-down" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-primary js-stools-btn-clear mr-2">
<?php echo Text::_('JSEARCH_FILTER_CLEAR'); ?>
</button>
</div>
<div class="ordering-select">
<div class="js-stools-field-list">
<select name="list_fullordering" id="list_fullordering" class="custom-select" onchange="this.form.submit()">
<option value=""><?php echo Text::_('JGLOBAL_SORT_BY'); ?></option>
<?php echo HTMLHelper::_('select.options', $this->getSortFields(), 'value', 'text', $listOrder); ?>
</select>
</div>
<div class="js-stools-field-list">
<span class="sr-only">
<label id="list_limit-lbl" for="list_limit"><?php echo Text::_('JGLOBAL_LIST_LIMIT'); ?></label>
</span>
<select name="list_limit" id="list_limit" class="custom-select" onchange="this.form.submit()">
<?php echo HTMLHelper::_('select.options', $this->getLimitOptions(), 'value', 'text', $listLimit); ?>
</select>
</div>
</div>
</div>
</div>
<!-- Filters div -->
<div class="js-stools-container-filters clearfix <?php echo $visible; ?>">
<div class="js-stools-field-filter">
<select name="filter_applied" class="custom-select" onchange="this.form.submit();">
<option value=""><?php echo Text::_('COM_PATCHTESTER_FILTER_APPLIED_PATCHES'); ?></option>
<option value="yes"<?php echo $filterApplied === 'yes' ? ' selected="selected"' : ''; ?>><?php echo Text::_('COM_PATCHTESTER_APPLIED'); ?></option>
<option value="no"<?php echo $filterApplied === 'no' ? ' selected="selected"' : ''; ?>><?php echo Text::_('COM_PATCHTESTER_NOT_APPLIED'); ?></option>
</select>
</div>
<div class="js-stools-field-filter">
<select name="filter_rtc" class="custom-select" onchange="this.form.submit();">
<option value=""><?php echo Text::_('COM_PATCHTESTER_FILTER_RTC_PATCHES'); ?></option>
<option value="yes"<?php echo $filterRtc === 'yes' ? ' selected="selected"' : ''; ?>><?php echo Text::_('COM_PATCHTESTER_RTC'); ?></option>
<option value="no"<?php echo $filterRtc === 'no' ? ' selected="selected"' : ''; ?>><?php echo Text::_('COM_PATCHTESTER_NOT_RTC'); ?></option>
</select>
</div>
<div class="js-stools-field-filter">
<select name="filter_npm" class="custom-select" onchange="this.form.submit();">
<option value=""><?php echo Text::_('COM_PATCHTESTER_FILTER_NPM_PATCHES'); ?></option>
<option value="yes"<?php echo $filterNpm === 'yes' ? ' selected="selected"' : ''; ?>><?php echo Text::_('COM_PATCHTESTER_NPM'); ?></option>
<option value="no"<?php echo $filterNpm === 'no' ? ' selected="selected"' : ''; ?>><?php echo Text::_('COM_PATCHTESTER_NOT_NPM'); ?></option>
</select>
</div>
<div class="js-stools-field-filter">
<select name="filter_label[]" class="custom-select" multiple="" onchange="this.form.submit();" size="5">
<option value=""><?php echo Text::_('COM_PATCHTESTER_FILTER_LABEL'); ?></option>
<?php echo HTMLHelper::_('select.options', $this->labels, 'text', 'text', $filterLabel, false); ?>
</select>
</div>
<div class="js-stools-field-filter">
<select name="filter_branch" class="custom-select" onchange="this.form.submit();">
<option value=""><?php echo Text::_('COM_PATCHTESTER_FILTER_BRANCH'); ?></option>
<?php echo HTMLHelper::_('select.options', $this->branches, 'text', 'text', $filterBranch, false); ?>
</select>
</div>
</div>
</div>
<div id="j-main-container" class="j-main-container">
<?php if (empty($this->items)) : ?>
<div class="alert alert-info">

View File

@ -5,7 +5,7 @@
name="search"
type="text"
inputmode="search"
description="COM_PATCHTESTER_FILTER_SEARCH_DESC"
description="COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION"
hint="JSEARCH_FILTER"
/>
@ -69,8 +69,14 @@
<fields name="list">
<field
name="fullordering"
type="hidden"
/>
type="list"
onchange="this.form.submit();"
>
<option value="a.title ASC">JGLOBAL_TITLE_ASC</option>
<option value="a.title DESC">JGLOBAL_TITLE_DESC</option>
<option value="a.pull_id ASC">COM_PATCHTESTER_PULL_ID_ASC</option>
<option value="a.pull_id DESC">COM_PATCHTESTER_PULL_ID_DESC</option>
</field>
<field
name="limit"

View File

@ -78,7 +78,6 @@ COM_PATCHTESTER_FILTER_BRANCH="- Select Target Branch -"
COM_PATCHTESTER_FILTER_LABEL="- Select Label -"
COM_PATCHTESTER_FILTER_NPM_PATCHES="Filter NPM Patches"
COM_PATCHTESTER_FILTER_RTC_PATCHES="Filter RTC Patches"
COM_PATCHTESTER_FILTER_SEARCH_DESC="Search in title, alias and note. Prefix with ID: to search for a patch ID."
COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION="Search the list by title or prefix with 'id:' to search by Pull ID."
COM_PATCHTESTER_GITHUB="GitHub"
COM_PATCHTESTER_HEADING_FETCH_DATA="Fetching GitHub Data"