Merge "web: Use a select filter for pipelines and queues on status page"
This commit is contained in:
commit
cbdcbdca0a
@ -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}
|
||||
/>
|
||||
<Button
|
||||
variant={ButtonVariant.control}
|
||||
@ -232,39 +234,18 @@ function FilterToolbar(props) {
|
||||
</Button>
|
||||
</InputGroup>
|
||||
)
|
||||
} else if (category.type === 'fuzzy-search') {
|
||||
return (
|
||||
<InputGroup>
|
||||
<Tooltip
|
||||
content="Wildcard search with * placeholders">
|
||||
<TextInput
|
||||
name={`${category.key}-input`}
|
||||
id={`${category.key}-input`}
|
||||
type="search"
|
||||
aria-label={`${category.key} filter`}
|
||||
onChange={handleInputChange}
|
||||
value={inputValue}
|
||||
placeholder={category.placeholder}
|
||||
onKeyDown={(event) => handleInputSend(event, category)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant={ButtonVariant.control}
|
||||
aria-label="search button for search input"
|
||||
onClick={(event) => handleInputSend(event, category)}
|
||||
>
|
||||
<SearchIcon />
|
||||
</Button>
|
||||
</InputGroup>
|
||||
)
|
||||
} else if (category.type === 'select') {
|
||||
return (
|
||||
<InputGroup>
|
||||
<FilterSelect
|
||||
onFilterChange={onFilterChange}
|
||||
filters={filters}
|
||||
category={category}
|
||||
/>
|
||||
{/* enclosing the FilterSelect with a div because setting the
|
||||
tooltip ref on the FilterSelect does not work */}
|
||||
<div ref={category.fuzzy ? fuzzySearchTooltipRef : null}>
|
||||
<FilterSelect
|
||||
onFilterChange={onFilterChange}
|
||||
filters={filters}
|
||||
category={category}
|
||||
/>
|
||||
</div>
|
||||
</InputGroup>
|
||||
)
|
||||
} else if (category.type === 'ternary') {
|
||||
@ -304,6 +285,10 @@ function FilterToolbar(props) {
|
||||
categoryName={category.title}
|
||||
showToolbarItem={currentCategory === category.title}
|
||||
>
|
||||
<Tooltip
|
||||
reference={fuzzySearchTooltipRef}
|
||||
content="Wildcard search with * placeholders"
|
||||
/>
|
||||
{renderFilterInput(category, filters)}
|
||||
</ToolbarFilter>
|
||||
))}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -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({
|
||||
<Level>
|
||||
<LevelItem>
|
||||
<FilterToolbar
|
||||
filterCategories={filterCategories}
|
||||
filterCategories={filterCategories(pipeline)}
|
||||
onFilterChange={(newFilters) => { 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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user