diff --git a/web/src/containers/FilterToolbar.jsx b/web/src/containers/FilterToolbar.jsx index ccdfc3056f..912829b33e 100644 --- a/web/src/containers/FilterToolbar.jsx +++ b/web/src/containers/FilterToolbar.jsx @@ -12,7 +12,7 @@ // License for the specific language governing permissions and limitations // under the License. -import React, { useState } from 'react' +import React, { useState, useRef } from 'react' import { useDispatch } from 'react-redux' import PropTypes from 'prop-types' import { @@ -90,6 +90,8 @@ function FilterToolbar(props) { ) const [inputValue, setInputValue] = useState('') + const fuzzySearchTooltipRef = useRef() + function handleCategoryToggle(isOpen) { setIsCategoryDropdownOpen(isOpen) } @@ -195,7 +197,6 @@ function FilterToolbar(props) { isOpen={isCategoryDropdownOpen} dropdownItems={filterCategories.filter( (category) => (category.type === 'search' || - category.type === 'fuzzy-search' || category.type === 'select' || category.type === 'ternary' || category.type === 'checkbox') @@ -222,6 +223,7 @@ function FilterToolbar(props) { value={inputValue} placeholder={category.placeholder} onKeyDown={(event) => handleInputSend(event, category)} + ref={category.fuzzy ? fuzzySearchTooltipRef : null} /> - - ) } else if (category.type === 'select') { return ( - + {/* enclosing the FilterSelect with a div because setting the + tooltip ref on the FilterSelect does not work */} +
+ +
) } else if (category.type === 'ternary') { @@ -304,6 +285,10 @@ function FilterToolbar(props) { categoryName={category.title} showToolbarItem={currentCategory === category.title} > + {renderFilterInput(category, filters)} ))} diff --git a/web/src/containers/status/Filters.jsx b/web/src/containers/status/Filters.jsx index 791463f36b..d9e400be41 100644 --- a/web/src/containers/status/Filters.jsx +++ b/web/src/containers/status/Filters.jsx @@ -142,7 +142,7 @@ function filterPipelines(pipelines, filters, filterCategories, truncateEmpty) { // by going over the valid FILTER_CATEGORIES for (const category of filterCategories) { const key = category['key'] - const fuzzy = category['type'] === 'fuzzy-search' + const fuzzy = category['fuzzy'] const filter = filters[key] if (filter.length === 0) { continue diff --git a/web/src/pages/Builds.jsx b/web/src/pages/Builds.jsx index 59c4d9f885..6f3fd7e2d2 100644 --- a/web/src/pages/Builds.jsx +++ b/web/src/pages/Builds.jsx @@ -43,31 +43,36 @@ class BuildsPage extends React.Component { key: 'job_name', title: 'Job', placeholder: 'Filter by Job...', - type: 'fuzzy-search', + type: 'search', + fuzzy: true, }, { key: 'project', title: 'Project', placeholder: 'Filter by Project...', - type: 'fuzzy-search', + type: 'search', + fuzzy: true, }, { key: 'branch', title: 'Branch', placeholder: 'Filter by Branch...', - type: 'fuzzy-search', + type: 'search', + fuzzy: true, }, { key: 'pipeline', title: 'Pipeline', placeholder: 'Filter by Pipeline...', - type: 'fuzzy-search', + type: 'search', + fuzzy: true, }, { key: 'change', title: 'Change', placeholder: 'Filter by Change...', type: 'search', + fuzzy: false, }, { key: 'result', @@ -96,12 +101,14 @@ class BuildsPage extends React.Component { 'LOST', 'EXCEPTION', 'NO_HANDLE'], + fuzzy: false, }, { key: 'uuid', title: 'Build', placeholder: 'Filter by Build UUID...', type: 'search', + fuzzy: false, }, { key: 'held', @@ -112,7 +119,8 @@ class BuildsPage extends React.Component { 'All', 'Held Builds Only', 'Non Held Builds Only', - ] + ], + fuzzy: false, }, { key: 'voting', @@ -123,7 +131,8 @@ class BuildsPage extends React.Component { 'All', 'Voting Only', 'Non-Voting Only', - ] + ], + fuzzy: false, }, ] diff --git a/web/src/pages/Buildsets.jsx b/web/src/pages/Buildsets.jsx index 19783aa664..0dbe5d77cd 100644 --- a/web/src/pages/Buildsets.jsx +++ b/web/src/pages/Buildsets.jsx @@ -42,25 +42,29 @@ class BuildsetsPage extends React.Component { key: 'project', title: 'Project', placeholder: 'Filter by Project...', - type: 'fuzzy-search', + type: 'search', + fuzzy: true, }, { key: 'branch', title: 'Branch', placeholder: 'Filter by Branch...', - type: 'fuzzy-search', + type: 'search', + fuzzy: true, }, { key: 'pipeline', title: 'Pipeline', placeholder: 'Filter by Pipeline...', - type: 'fuzzy-search', + type: 'search', + fuzzy: true, }, { key: 'change', title: 'Change', placeholder: 'Filter by Change...', type: 'search', + fuzzy: false, }, { key: 'result', @@ -75,13 +79,15 @@ class BuildsetsPage extends React.Component { 'DEQUEUED', 'CONFIG_ERROR', 'NO_JOBS', - ] + ], + fuzzy: false, }, { key: 'uuid', title: 'Buildset', placeholder: 'Filter by Buildset UUID...', type: 'search', + fuzzy: false, }, ] diff --git a/web/src/pages/ConfigErrors.jsx b/web/src/pages/ConfigErrors.jsx index 308908cc0e..5e868f2664 100644 --- a/web/src/pages/ConfigErrors.jsx +++ b/web/src/pages/ConfigErrors.jsx @@ -54,24 +54,28 @@ class ConfigErrorsPage extends React.Component { title: 'Project', placeholder: 'Filter by project...', type: 'search', + fuzzy: false, }, { key: 'branch', title: 'Branch', placeholder: 'Filter by branch...', type: 'search', + fuzzy: false, }, { key: 'severity', title: 'Severity', placeholder: 'Filter by severity...', type: 'search', + fuzzy: false, }, { key: 'name', title: 'Name', placeholder: 'Filter by name...', type: 'search', + fuzzy: false, }, ] diff --git a/web/src/pages/PipelineDetails.jsx b/web/src/pages/PipelineDetails.jsx index e2ccc42850..4ab5a86d64 100644 --- a/web/src/pages/PipelineDetails.jsx +++ b/web/src/pages/PipelineDetails.jsx @@ -63,24 +63,30 @@ import { clearFilters, } from '../containers/status/Filters' -const filterCategories = [ +const filterCategories = (pipeline) => [ { key: 'project', title: 'Project', placeholder: 'Filter by Project...', - type: 'fuzzy-search', + type: 'search', + fuzzy: true, }, { key: 'change', title: 'Change', placeholder: 'Filter by Change...', - type: 'fuzzy-search', + type: 'search', + fuzzy: true, }, { key: 'queue', title: 'Queue', placeholder: 'Filter by Queue...', - type: 'fuzzy-search', + type: 'select', + // the last filter part makes sure we only provide options for queues + // which have a non-empty name + options: pipeline ? pipeline.change_queues.flat().map(q => q.name).filter(n => n): [], + fuzzy: true, } ] @@ -141,7 +147,7 @@ function PipelineDetailsPage({ const location = useLocation() const history = useHistory() - const filters = getFiltersFromUrl(location, filterCategories) + const filters = getFiltersFromUrl(location, filterCategories(pipeline)) const dispatch = useDispatch() const updateData = useCallback((tenant) => { @@ -199,7 +205,7 @@ function PipelineDetailsPage({ { handleFilterChange(newFilters, filterCategories, location, history) }} filters={filters} filterInputValidation={filterInputValidation} @@ -280,7 +286,7 @@ function PipelineDetailsPage({ title="No items found" icon={StreamIcon} action="Clear all filters" - onAction={() => clearFilters(location, history, filterCategories)} + onAction={() => clearFilters(location, history, filterCategories(pipeline))} > No items match this filter criteria. Remove some filters or clear all to show results. @@ -305,13 +311,13 @@ PipelineDetailsPage.propTypes = { function mapStateToProps(state, ownProps) { let pipeline = null if (state.status.status) { - const filters = getFiltersFromUrl(ownProps.location, filterCategories) + const filters = getFiltersFromUrl(ownProps.location, filterCategories(null)) // we need to work on a copy of the state..pipelines, because when mutating // the original, we couldn't reset or change the filters without reloading // from the backend first. const pipelines = global.structuredClone(state.status.status.pipelines) // Filter the state for this specific pipeline - pipeline = filterPipelines(pipelines, filters, filterCategories, false) + pipeline = filterPipelines(pipelines, filters, filterCategories(null), false) .find((p) => p.name === ownProps.match.params.pipelineName) || null pipeline = countPipelineItems(pipeline) } diff --git a/web/src/pages/PipelineOverview.jsx b/web/src/pages/PipelineOverview.jsx index d5a55be07b..77b7216959 100644 --- a/web/src/pages/PipelineOverview.jsx +++ b/web/src/pages/PipelineOverview.jsx @@ -57,34 +57,6 @@ import { EmptyBox } from '../containers/Errors' import { countPipelineItems } from '../containers/status/Misc' import { useDocumentVisibility, useInterval } from '../Hooks' -const filterCategories = [ - { - key: 'change', - title: 'Change', - placeholder: 'Filter by Change...', - type: 'fuzzy-search', - }, - { - key: 'project', - title: 'Project', - placeholder: 'Filter by Project...', - type: 'fuzzy-search', - }, - { - key: 'queue', - title: 'Queue', - placeholder: 'Filter by Queue...', - type: 'fuzzy-search', - }, - { - key: 'pipeline', - title: 'Pipeline', - placeholder: 'Filter by Pipeline...', - type: 'fuzzy-search', - }, -] - - function PipelineGallery({ pipelines, tenant, showAllPipelines, expandAll, isLoading, filters, onClearFilters, sortKey }) { // Filter out empty pipelines if necessary if (!showAllPipelines) { @@ -142,7 +114,7 @@ PipelineGallery.propTypes = { sortKey: PropTypes.string, } -function getPipelines(status, location) { +function getPipelines(status, location, filterCategories) { let pipelines = [] let stats = {} if (status) { @@ -169,6 +141,43 @@ function getPipelines(status, location) { } function PipelineOverviewPage() { + const status = useSelector((state) => state.status.status) + + const filterCategories = [ + { + key: 'change', + title: 'Change', + placeholder: 'Filter by Change...', + type: 'search', + fuzzy: true, + }, + { + key: 'project', + title: 'Project', + placeholder: 'Filter by Project...', + type: 'search', + fuzzy: true, + }, + { + key: 'queue', + title: 'Queue', + placeholder: 'Filter by Queue...', + type: 'select', + // the last filter part makes sure we only provide options for queues + // which have a non-empty name + options: status ? status.pipelines.map(p => p.change_queues).flat().map(q => q.name).filter(n => n) : [], + fuzzy: true, + }, + { + key: 'pipeline', + title: 'Pipeline', + placeholder: 'Filter by Pipeline...', + type: 'select', + options: status ? status.pipelines.map(p => p.name) : [], + fuzzy: true, + }, + ] + const location = useLocation() const history = useHistory() const filters = getFiltersFromUrl(location, filterCategories) @@ -182,8 +191,7 @@ function PipelineOverviewPage() { const isDocumentVisible = useDocumentVisibility() - const status = useSelector((state) => state.status.status) - const { pipelines, stats } = useMemo(() => getPipelines(status, location), [status, location]) + const { pipelines, stats } = useMemo(() => getPipelines(status, location, filterCategories), [status, location, filterCategories]) const isFetching = useSelector((state) => state.status.isFetching) const tenant = useSelector((state) => state.tenant)