From d34d942413a4845af5775d76c260540981311242 Mon Sep 17 00:00:00 2001 From: Demian Katz <demian.katz@villanova.edu> Date: Mon, 4 Feb 2019 16:46:52 -0500 Subject: [PATCH] Simplify facet label handling (#1291) - Replace hard-coded activateAllFacets() logic with a configuration-based label section list. --- config/vufind/EDS.ini | 17 +++++ config/vufind/Primo.ini | 17 +++++ config/vufind/Search2.ini | 8 +++ config/vufind/Summon.ini | 18 ++++++ config/vufind/facets.ini | 19 ++++++ config/vufind/reserves.ini | 4 ++ config/vufind/website.ini | 4 ++ .../src/VuFind/Controller/AbstractSearch.php | 4 -- .../VuFind/src/VuFind/Search/Base/Params.php | 62 ++++++++++--------- .../VuFind/src/VuFind/Search/EDS/Params.php | 31 ++++++---- .../VuFind/src/VuFind/Search/Primo/Params.php | 27 +++----- .../src/VuFind/Search/Search2/Params.php | 11 ++++ .../VuFind/src/VuFind/Search/Solr/Params.php | 50 +++------------ .../src/VuFind/Search/SolrReserves/Params.php | 7 +++ .../src/VuFind/Search/SolrWeb/Params.php | 7 +++ .../src/VuFind/Search/Summon/Params.php | 60 +++++------------- .../Controller/SearchApiController.php | 1 - .../templates/search/advanced/ranges.phtml | 2 +- .../templates/search/history-table.phtml | 2 +- 19 files changed, 199 insertions(+), 152 deletions(-) diff --git a/config/vufind/EDS.ini b/config/vufind/EDS.ini index 0354ae03da0..7edd57c981c 100644 --- a/config/vufind/EDS.ini +++ b/config/vufind/EDS.ini @@ -108,6 +108,23 @@ SEARCHMODE = "Search Mode" ; above for more details. [FacetsTop] +; This section controls where facet labels are retrieved from when facets are not +; explicitly configured. +[FacetLabels] +; This setting lists configuration sections containing facet field => label +; mappings. Later values will override earlier values. These mappings will be used +; only when a label is not explicitly configured (i.e. through SideFacets settings). +; If you customize your facet display, be sure to add any new facet configuration +; sections to this list to ensure proper display in search history, etc. +labelSections[] = Advanced_Facets +labelSections[] = FacetsTop +labelSections[] = Facets + +; This setting lists configuration settings defining checkbox facets. If you use +; a custom section to configure additional facets, be sure to add it to this list +; so labels display correctly in history, the advanced search editor, etc. +checkboxSections[] = CheckboxFacets + ; Facet display settings [Results_Settings] ; By default, the side facets will only show 6 facets and then the "show more" diff --git a/config/vufind/Primo.ini b/config/vufind/Primo.ini index fc39d3362d9..36cbd6934b3 100644 --- a/config/vufind/Primo.ini +++ b/config/vufind/Primo.ini @@ -92,6 +92,23 @@ domain = Source ; Top facets (not used by default) [FacetsTop] +; This section controls where facet labels are retrieved from when facets are not +; explicitly configured. +[FacetLabels] +; This setting lists configuration sections containing facet field => label +; mappings. Later values will override earlier values. These mappings will be used +; only when a label is not explicitly configured (i.e. through SideFacets settings). +; If you customize your facet display, be sure to add any new facet configuration +; sections to this list to ensure proper display in search history, etc. +labelSections[] = Advanced_Facets +labelSections[] = FacetsTop +labelSections[] = Facets + +; This setting lists configuration settings defining checkbox facets. If you use +; a custom section to configure additional facets, be sure to add it to this list +; so labels display correctly in history, the advanced search editor, etc. +checkboxSections[] = CheckboxFacets + ; Checkbox facets are facets, which are getting displayed as checkboxes ; pcAvailability is a special facet. It's used to show all records found in ; PrimoCentral without checking the local availability against a holdingsfile. diff --git a/config/vufind/Search2.ini b/config/vufind/Search2.ini index e1ef3905d43..a1cb2eea47c 100644 --- a/config/vufind/Search2.ini +++ b/config/vufind/Search2.ini @@ -203,6 +203,14 @@ publishDate = "adv_search_year" [ResultsTop] topic_facet = "Suggested Topics" +[FacetLabels] +labelSections[] = Advanced_Facets +labelSections[] = HomePage_Facets +labelSections[] = ResultsTop +labelSections[] = Results +labelSections[] = ExtraFacetLabels +checkboxSections[] = CheckboxFacets + [ExtraFacetLabels] long_lat = "Geographic Search" diff --git a/config/vufind/Summon.ini b/config/vufind/Summon.ini index e7126909d1c..283ed7e0b4a 100644 --- a/config/vufind/Summon.ini +++ b/config/vufind/Summon.ini @@ -147,6 +147,24 @@ PublicationDate = "adv_search_year" ; share year string w/advanced search page [FacetsTop] SubjectTerms = "Suggested Topics" +; This section controls where facet labels are retrieved from when facets are not +; explicitly configured. +[FacetLabels] +; This setting lists configuration sections containing facet field => label +; mappings. Later values will override earlier values. These mappings will be used +; only when a label is not explicitly configured (i.e. through SideFacets settings). +; If you customize your facet display, be sure to add any new facet configuration +; sections to this list to ensure proper display in search history, etc. +labelSections[] = Advanced_Facets +labelSections[] = HomePage_Facets +labelSections[] = FacetsTop +labelSections[] = Facets + +; This setting lists configuration settings defining checkbox facets. If you use +; a custom section to configure additional facets, be sure to add it to this list +; so labels display correctly in history, the advanced search editor, etc. +checkboxSections[] = CheckboxFacets + ; Facet display settings [Results_Settings] ; By default, the side facets will only show 6 facets and then the "show more" diff --git a/config/vufind/facets.ini b/config/vufind/facets.ini index cfa85dd9bac..be5e7c6fae2 100644 --- a/config/vufind/facets.ini +++ b/config/vufind/facets.ini @@ -23,6 +23,25 @@ publishDate = "adv_search_year" ; share year string w/advanced search pa [ResultsTop] topic_facet = "Suggested Topics" +; This section controls where facet labels are retrieved from when facets are not +; explicitly configured. +[FacetLabels] +; This setting lists configuration sections containing facet field => label +; mappings. Later values will override earlier values. These mappings will be used +; only when a label is not explicitly configured (i.e. through SideFacets settings). +; If you customize your facet display, be sure to add any new facet configuration +; sections to this list to ensure proper display in search history, etc. +labelSections[] = Advanced +labelSections[] = HomePage +labelSections[] = ResultsTop +labelSections[] = Results +labelSections[] = ExtraFacetLabels + +; This setting lists configuration settings defining checkbox facets. If you use +; a custom section to configure additional facets, be sure to add it to this list +; so labels display correctly in history, the advanced search editor, etc. +checkboxSections[] = CheckboxFacets + ; This section is used to specify labels for facets that may be applied by parts ; of VuFind other than the facet lists defined in this file (for example, the ; hierarchical browse of the BrowseController, or the Geographic Search). diff --git a/config/vufind/reserves.ini b/config/vufind/reserves.ini index e8ad3aee308..6341d532692 100644 --- a/config/vufind/reserves.ini +++ b/config/vufind/reserves.ini @@ -24,6 +24,10 @@ department_str = "Department" instructor_str = "Instructor" course_str = "Course" +[FacetLabels] +labelSections[] = Facets +checkboxSections[] = CheckboxFacets + [Autocomplete] enabled = true diff --git a/config/vufind/website.ini b/config/vufind/website.ini index 03f9f3a7377..011fa32cce9 100644 --- a/config/vufind/website.ini +++ b/config/vufind/website.ini @@ -25,6 +25,10 @@ category = "Category" linktype = "Link Type" subject = "Subject" +[FacetLabels] +labelSections[] = Facets +checkboxSections[] = CheckboxFacets + [Results_Settings] ; By default, how many values should we show for each facet? (-1 for no limit) facet_limit = 30 diff --git a/module/VuFind/src/VuFind/Controller/AbstractSearch.php b/module/VuFind/src/VuFind/Controller/AbstractSearch.php index e0c81e2e7e2..80792d8f4e1 100644 --- a/module/VuFind/src/VuFind/Controller/AbstractSearch.php +++ b/module/VuFind/src/VuFind/Controller/AbstractSearch.php @@ -436,9 +436,6 @@ class AbstractSearch extends AbstractBase } } - // Activate facets so we get appropriate descriptions in the filter list: - $savedSearch->getParams()->activateAllFacets('Advanced'); - // Make the object available to the view: return $savedSearch; } @@ -735,7 +732,6 @@ class AbstractSearch extends AbstractBase $this->params()->fromQuery('facetop', 'AND') == 'OR' ); $list = $facets[$facet]['data']['list'] ?? []; - $params->activateAllFacets(); $facetLabel = $params->getFacetLabel($facet); $view = $this->createViewModel( diff --git a/module/VuFind/src/VuFind/Search/Base/Params.php b/module/VuFind/src/VuFind/Search/Base/Params.php index 144ce83e701..8760e08692c 100644 --- a/module/VuFind/src/VuFind/Search/Base/Params.php +++ b/module/VuFind/src/VuFind/Search/Base/Params.php @@ -130,6 +130,14 @@ class Params */ protected $extraFacetLabels = []; + /** + * Config sections to search for facet labels if no override configuration + * is set. + * + * @var array + */ + protected $defaultFacetLabelSections = ['ExtraFacetLabels']; + /** * Checkbox facet configuration * @@ -198,6 +206,23 @@ class Params // Make sure we have some sort of query object: $this->query = new Query(); + + // Set up facet label settings, to be used as fallbacks if specific facets + // are not already configured: + $config = $configLoader->get($options->getFacetsIni()); + $sections = $config->FacetLabels->labelSections + ?? $this->defaultFacetLabelSections; + foreach ($sections as $section) { + foreach ($config->$section ?? [] as $field => $label) { + $this->extraFacetLabels[$field] = $label; + } + } + + // Activate all relevant checkboxes, also important for labeling: + $checkboxSections = $config->FacetLabels->checkboxSections ?? []; + foreach ($checkboxSections as $checkboxSection) { + $this->initCheckboxFacets($checkboxSection); + } } /** @@ -923,21 +948,22 @@ class Params // Extract the facet field name from the filter, then add the // relevant information to the array. list($fieldName) = explode(':', $filter); - $this->checkboxFacets[$fieldName][] + $this->checkboxFacets[$fieldName][$filter] = ['desc' => $desc, 'filter' => $filter]; } /** * Get a user-friendly string to describe the provided facet field. * - * @param string $field Facet field name. - * @param string $value Facet value. + * @param string $field Facet field name. + * @param string $value Facet value. + * @param string $default Default field name (null for default behavior). * - * @return string Human-readable description of field. + * @return string Human-readable description of field. * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function getFacetLabel($field, $value = null) + public function getFacetLabel($field, $value = null, $default = null) { if (!isset($this->facetConfig[$field]) && !isset($this->extraFacetLabels[$field]) @@ -949,7 +975,8 @@ class Params return $this->facetConfig[$field]; } return isset($this->extraFacetLabels[$field]) - ? $this->extraFacetLabels[$field] : 'unrecognized_facet_label'; + ? $this->extraFacetLabels[$field] + : ($default ?: 'unrecognized_facet_label'); } /** @@ -1597,29 +1624,6 @@ class Params $this->query = QueryAdapter::deminify($minified->t); } - /** - * Load all available facet settings. This is mainly useful for showing - * appropriate labels when an existing search has multiple filters associated - * with it. - * - * @param string $preferredSection Section to favor when loading settings; - * if multiple sections contain the same facet, this section's description - * will be favored. - * - * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function activateAllFacets($preferredSection = false) - { - // By default, there is only 1 set of facet settings, so this function isn't - // really necessary. However, in the Search History screen, we need to - // use this for Solr-based Search Objects, so we need this dummy method to - // allow other types of Search Objects to co-exist with Solr-based ones. - // See the Solr Search Object for details of how this works if you need to - // implement context-sensitive facet settings in another module. - } - /** * Override the normal search behavior with an explicit array of IDs that must * be retrieved. diff --git a/module/VuFind/src/VuFind/Search/EDS/Params.php b/module/VuFind/src/VuFind/Search/EDS/Params.php index fe1b62460d3..da84b472c33 100644 --- a/module/VuFind/src/VuFind/Search/EDS/Params.php +++ b/module/VuFind/src/VuFind/Search/EDS/Params.php @@ -55,6 +55,15 @@ class Params extends \VuFind\Search\Base\Params */ protected $extraFilterList = []; + /** + * Config sections to search for facet labels if no override configuration + * is set. + * + * @var array + */ + protected $defaultFacetLabelSections + = ['Advanced_Facets', 'FacetsTop', 'Facets']; + /** * Is the request using this parameters objects for setup only? * @@ -287,25 +296,23 @@ class Params extends \VuFind\Search\Base\Params /** * Get a user-friendly string to describe the provided facet field. * - * @param string $field Facet field name. - * @param string $value Facet value. - * - * @return string Human-readable description of field. + * @param string $field Facet field name. + * @param string $value Facet value. + * @param string $default Default field name (null for default behavior). * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @return string Human-readable description of field. */ - public function getFacetLabel($field, $value = null) + public function getFacetLabel($field, $value = null, $default = null) { - //Also store Limiter/Search Mode IDs/Values in the config file - $facetId = $field; + // Also store Limiter/Search Mode IDs/Values in the config file if (substr($field, 0, 6) == 'LIMIT|') { $facetId = substr($field, 6); - } - if (substr($field, 0, 11) == 'SEARCHMODE|') { + } elseif (substr($field, 0, 11) == 'SEARCHMODE|') { $facetId = substr($field, 11); + } else { + $facetId = $field; } - return isset($this->facetConfig[$facetId]) - ? $this->facetConfig[$facetId] : $facetId; + return parent::getFacetLabel($facetId, $value, $default ?: $facetId); } /** diff --git a/module/VuFind/src/VuFind/Search/Primo/Params.php b/module/VuFind/src/VuFind/Search/Primo/Params.php index 5338d197dcb..7b4cc91a806 100644 --- a/module/VuFind/src/VuFind/Search/Primo/Params.php +++ b/module/VuFind/src/VuFind/Search/Primo/Params.php @@ -40,6 +40,15 @@ use VuFindSearch\ParamBag; */ class Params extends \VuFind\Search\Base\Params { + /** + * Config sections to search for facet labels if no override configuration + * is set. + * + * @var array + */ + protected $defaultFacetLabelSections + = ['Advanced_Facets', 'FacetsTop', 'Facets']; + /** * Create search backend parameters for advanced features. * @@ -99,22 +108,4 @@ class Params extends \VuFind\Search\Base\Params } return ucwords(str_replace('_', ' ', $str)); } - - /** - * Load all available facet settings. This is mainly useful for showing - * appropriate labels when an existing search has multiple filters associated - * with it. - * - * @param string $preferredSection Section to favor when loading settings; if - * multiple sections contain the same facet, this section's description will - * be favored. - * - * @return void - */ - public function activateAllFacets($preferredSection = false) - { - $this->initFacetList('Facets', 'Results_Settings'); - $this->initFacetList('Advanced_Facets', 'Advanced_Facet_Settings'); - $this->initCheckboxFacets(); - } } diff --git a/module/VuFind/src/VuFind/Search/Search2/Params.php b/module/VuFind/src/VuFind/Search/Search2/Params.php index a139cb3f340..fb3530a5747 100644 --- a/module/VuFind/src/VuFind/Search/Search2/Params.php +++ b/module/VuFind/src/VuFind/Search/Search2/Params.php @@ -39,6 +39,17 @@ namespace VuFind\Search\Search2; */ class Params extends \VuFind\Search\Solr\Params { + /** + * Config sections to search for facet labels if no override configuration + * is set. + * + * @var array + */ + protected $defaultFacetLabelSections = [ + 'Advanced_Facets', 'HomePage_Facets', 'ResultsTop', 'Results', + 'ExtraFacetLabels' + ]; + /** * Initialize facet settings for the advanced search screen. * diff --git a/module/VuFind/src/VuFind/Search/Solr/Params.php b/module/VuFind/src/VuFind/Search/Solr/Params.php index 06ac6f635ef..912c8ac7084 100644 --- a/module/VuFind/src/VuFind/Search/Solr/Params.php +++ b/module/VuFind/src/VuFind/Search/Solr/Params.php @@ -84,6 +84,15 @@ class Params extends \VuFind\Search\Base\Params */ protected $facetHelper; + /** + * Config sections to search for facet labels if no override configuration + * is set. + * + * @var array + */ + protected $defaultFacetLabelSections + = ['Advanced', 'HomePage', 'ResultsTop', 'Results', 'ExtraFacetLabels']; + /** * Constructor * @@ -103,9 +112,6 @@ class Params extends \VuFind\Search\Base\Params if (isset($config->LegacyFields)) { $this->facetAliases = $config->LegacyFields->toArray(); } - if (isset($config->ExtraFacetLabels)) { - $this->extraFacetLabels = $config->ExtraFacetLabels->toArray(); - } if (isset($config->Results_Settings->sorted_by_index) && count($config->Results_Settings->sorted_by_index) > 0 ) { @@ -307,44 +313,6 @@ class Params extends \VuFind\Search\Base\Params } } - /** - * Initialize facet settings for the standard search screen. - * - * @return void - */ - public function initBasicFacets() - { - $this->initFacetList('ResultsTop', 'Results_Settings'); - $this->initFacetList('Results', 'Results_Settings'); - } - - /** - * Load all available facet settings. This is mainly useful for showing - * appropriate labels when an existing search has multiple filters associated - * with it. - * - * @param string $preferredSection Section to favor when loading settings; if - * multiple sections contain the same facet, this section's description will - * be favored. - * - * @return void - */ - public function activateAllFacets($preferredSection = false) - { - // Based on preference, change the order of initialization to make sure - // that preferred facet labels come in last. - if ($preferredSection == 'Advanced') { - $this->initHomePageFacets(); - $this->initBasicFacets(); - $this->initAdvancedFacets(); - } else { - $this->initHomePageFacets(); - $this->initAdvancedFacets(); - $this->initBasicFacets(); - } - $this->initCheckboxFacets(); - } - /** * Add filters to the object based on values found in the request object. * diff --git a/module/VuFind/src/VuFind/Search/SolrReserves/Params.php b/module/VuFind/src/VuFind/Search/SolrReserves/Params.php index c115b436aa8..96a3c70d877 100644 --- a/module/VuFind/src/VuFind/Search/SolrReserves/Params.php +++ b/module/VuFind/src/VuFind/Search/SolrReserves/Params.php @@ -40,4 +40,11 @@ namespace VuFind\Search\SolrReserves; */ class Params extends \VuFind\Search\Solr\Params { + /** + * Config sections to search for facet labels if no override configuration + * is set. + * + * @var array + */ + protected $defaultFacetLabelSections = ['Facets']; } diff --git a/module/VuFind/src/VuFind/Search/SolrWeb/Params.php b/module/VuFind/src/VuFind/Search/SolrWeb/Params.php index 6f4bd1d8cfc..e05fcb12e93 100644 --- a/module/VuFind/src/VuFind/Search/SolrWeb/Params.php +++ b/module/VuFind/src/VuFind/Search/SolrWeb/Params.php @@ -38,4 +38,11 @@ namespace VuFind\Search\SolrWeb; */ class Params extends \VuFind\Search\Solr\Params { + /** + * Config sections to search for facet labels if no override configuration + * is set. + * + * @var array + */ + protected $defaultFacetLabelSections = ['Facets']; } diff --git a/module/VuFind/src/VuFind/Search/Summon/Params.php b/module/VuFind/src/VuFind/Search/Summon/Params.php index 2b4acec0e3f..505b7921593 100644 --- a/module/VuFind/src/VuFind/Search/Summon/Params.php +++ b/module/VuFind/src/VuFind/Search/Summon/Params.php @@ -58,6 +58,15 @@ class Params extends \VuFind\Search\Base\Params */ protected $dateFacetSettings = []; + /** + * Config sections to search for facet labels if no override configuration + * is set. + * + * @var array + */ + protected $defaultFacetLabelSections + = ['Advanced_Facets', 'HomePage_Facets', 'FacetsTop', 'Facets']; + /** * Constructor * @@ -134,20 +143,18 @@ class Params extends \VuFind\Search\Base\Params /** * Get a user-friendly string to describe the provided facet field. * - * @param string $field Facet field name. - * @param string $value Facet value. - * - * @return string Human-readable description of field. + * @param string $field Facet field name. + * @param string $value Facet value. + * @param string $default Default field name (null for default behavior). * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @return string Human-readable description of field. */ - public function getFacetLabel($field, $value = null) + public function getFacetLabel($field, $value = null, $default = null) { // The default use of "Other" for undefined facets doesn't work well with // checkbox facets -- we'll use field names as the default within the Summon // search object. - return isset($this->facetConfig[$field]) - ? $this->facetConfig[$field] : $field; + return parent::getFacetLabel($field, $value, $default ?: $field); } /** @@ -400,41 +407,4 @@ class Params extends \VuFind\Search\Base\Params $this->initAdvancedFacets(); } } - - /** - * Initialize facet settings for the standard search screen. - * - * @return void - */ - public function initBasicFacets() - { - $this->initFacetList('Facets', 'Results_Settings'); - } - - /** - * Load all available facet settings. This is mainly useful for showing - * appropriate labels when an existing search has multiple filters associated - * with it. - * - * @param string $preferredSection Section to favor when loading settings; if - * multiple sections contain the same facet, this section's description will - * be favored. - * - * @return void - */ - public function activateAllFacets($preferredSection = false) - { - // Based on preference, change the order of initialization to make sure - // that preferred facet labels come in last. - if ($preferredSection == 'Advanced') { - $this->initHomePageFacets(); - $this->initBasicFacets(); - $this->initAdvancedFacets(); - } else { - $this->initHomePageFacets(); - $this->initAdvancedFacets(); - $this->initBasicFacets(); - } - $this->initCheckboxFacets(); - } } diff --git a/module/VuFindApi/src/VuFindApi/Controller/SearchApiController.php b/module/VuFindApi/src/VuFindApi/Controller/SearchApiController.php index f670a6902cd..579c65c1526 100644 --- a/module/VuFindApi/src/VuFindApi/Controller/SearchApiController.php +++ b/module/VuFindApi/src/VuFindApi/Controller/SearchApiController.php @@ -114,7 +114,6 @@ class SearchApiController extends \VuFind\Controller\AbstractSearch $results = $this->getResultsManager()->get($this->searchClassId); $options = $results->getOptions(); $params = $results->getParams(); - $params->activateAllFacets(); $viewParams = [ 'config' => $config, diff --git a/themes/bootstrap3/templates/search/advanced/ranges.phtml b/themes/bootstrap3/templates/search/advanced/ranges.phtml index 7f51cb42262..8e3de488cb0 100644 --- a/themes/bootstrap3/templates/search/advanced/ranges.phtml +++ b/themes/bootstrap3/templates/search/advanced/ranges.phtml @@ -1,5 +1,5 @@ <?php if (isset($this->ranges) && !empty($this->ranges)): ?> - <?php $params = $this->searchParams($this->searchClassId); $params->activateAllFacets(); ?> + <?php $params = $this->searchParams($this->searchClassId); ?> <?php foreach ($this->ranges as $current): $escField = $this->escapeHtmlAttr($current['field']); ?> <?php $extraInputAttribs = ($current['type'] == 'date') ? 'maxlength="4" ' : ''; ?> <fieldset class="range"> diff --git a/themes/bootstrap3/templates/search/history-table.phtml b/themes/bootstrap3/templates/search/history-table.phtml index fcb62013317..20e9585d569 100644 --- a/themes/bootstrap3/templates/search/history-table.phtml +++ b/themes/bootstrap3/templates/search/history-table.phtml @@ -18,7 +18,7 @@ ?></a> </td> <td> - <?php $info->getParams()->activateAllFacets(); foreach ($info->getParams()->getFilterList(true) as $field => $filters): ?> + <?php foreach ($info->getParams()->getFilterList(true) as $field => $filters): ?> <?php foreach ($filters as $i => $filter): ?> <?php if ($filter['operator'] == 'NOT') echo $this->transEsc('NOT') . ' '; if ($filter['operator'] == 'OR' && $i > 0) echo $this->transEsc('OR') . ' '; ?> <strong><?=$this->transEsc($field)?></strong>: <?=$this->escapeHtml($filter['displayText'])?><br/> -- GitLab