summaryrefslogtreecommitdiff
path: root/web/src/containers/filters
diff options
context:
space:
mode:
authorMatthieu Huin <mhuin@redhat.com>2021-05-05 00:27:40 +0200
committerMatthieu Huin <mhuin@redhat.com>2021-09-09 19:13:55 +0200
commit7eff6490a474270d3c298532c352ab7b81867566 (patch)
tree205ad70895cafa279b2d7910e118b3b361349269 /web/src/containers/filters
parentfd599b1019ce48af8cf40688d05f67e3d8fb61c1 (diff)
downloadzuul-7eff6490a474270d3c298532c352ab7b81867566.tar.gz
Web UI: add checkbox, selects to filter toolbar
Support three new filter types: checkbox, select and ternary select. The ternary select tool is meant to support fields that can be set to True, False, or ignored. Use this feature to allow filtering builds by held status (i.e. show builds that triggered a autohold only) or by voting status (i.e. show only voting jobs). Selects let a user choose among predefined values. Use a select to help a user choose a result value when searching builds and buildsets. Build page: add a thumbtack icon next to a build's status badge if that build triggered an autohold. Change-Id: I40b06c83ed069e0756c7f8b00430d36a36230bfa
Diffstat (limited to 'web/src/containers/filters')
-rw-r--r--web/src/containers/filters/Checkbox.jsx50
-rw-r--r--web/src/containers/filters/Select.jsx122
-rw-r--r--web/src/containers/filters/TernarySelect.jsx113
3 files changed, 285 insertions, 0 deletions
diff --git a/web/src/containers/filters/Checkbox.jsx b/web/src/containers/filters/Checkbox.jsx
new file mode 100644
index 000000000..6fdd7af26
--- /dev/null
+++ b/web/src/containers/filters/Checkbox.jsx
@@ -0,0 +1,50 @@
+// Copyright 2021 Red Hat, Inc
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import {
+ Checkbox,
+} from '@patternfly/react-core'
+
+
+function FilterCheckbox(props) {
+ const { filters, category, onFilterChange } = props
+
+ function onChange(checked) {
+ const newFilters = {
+ ...filters,
+ [category.key]: checked ? [1,] : [],
+ }
+ onFilterChange(newFilters)
+ }
+
+ return (
+ <Checkbox
+ label={category.placeholder}
+ isChecked={[...filters[category.key]].pop() ? true : false}
+ onChange={onChange}
+ aria-label={category.placeholder}
+ />
+ )
+}
+
+FilterCheckbox.propTypes = {
+ onFilterChange: PropTypes.func.isRequired,
+ filters: PropTypes.object.isRequired,
+ category: PropTypes.object.isRequired,
+}
+
+export { FilterCheckbox } \ No newline at end of file
diff --git a/web/src/containers/filters/Select.jsx b/web/src/containers/filters/Select.jsx
new file mode 100644
index 000000000..3e99c8c26
--- /dev/null
+++ b/web/src/containers/filters/Select.jsx
@@ -0,0 +1,122 @@
+// Copyright 2021 Red Hat, Inc
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+
+import {
+ Chip,
+ ChipGroup,
+ Select,
+ SelectOption,
+ SelectVariant,
+} from '@patternfly/react-core'
+
+
+function FilterSelect(props) {
+ const { filters, category } = props
+
+ const [isOpen, setIsOpen] = useState(false)
+ const [selected, setSelected] = useState(filters[category.key])
+ const [options, setOptions] = useState(category.options)
+
+ function onToggle(isOpen) {
+ setSelected(filters[category.key])
+ setIsOpen(isOpen)
+ }
+
+ function onSelect(event, selection) {
+ const { onFilterChange, filters, category } = props
+
+ const newSelected = selected.includes(selection)
+ ? selected.filter(item => item !== selection)
+ : [...selected, selection]
+
+ setSelected(newSelected)
+ const newFilters = {
+ ...filters,
+ [category.key]: newSelected,
+ }
+ onFilterChange(newFilters)
+ }
+
+ function onClear() {
+ const { onFilterChange, filters, category } = props
+ setSelected([])
+ setIsOpen(false)
+ const newFilters = {
+ ...filters,
+ [category.key]: [],
+ }
+ onFilterChange(newFilters)
+ }
+
+ function chipGroupComponent() {
+ const { filters, category } = props
+ const chipped = filters[category.key]
+ return (
+ <ChipGroup>
+ {chipped.map((currentChip, index) => (
+ <Chip
+ isReadOnly={index === 0 ? true : false}
+ key={currentChip}
+ onClick={(e) => onSelect(e, currentChip)}
+ >
+ {currentChip}
+ </Chip>
+ ))}
+ </ChipGroup>
+ )
+ }
+
+ function onCreateOption(newValue) {
+ const newOptions = [...options, newValue]
+ setOptions(newOptions)
+ }
+
+ return (
+ <Select
+ chipGroupProps={{ numChips: 1, expandedText: 'Hide' }}
+ variant={SelectVariant.typeaheadMulti}
+ typeAheadAriaLabel={category.title}
+ onToggle={onToggle}
+ onClear={onClear}
+ onSelect={onSelect}
+ selections={filters[category.key]}
+ isOpen={isOpen}
+ isCreatable="true"
+ onCreateOption={onCreateOption}
+ placeholderText={category.placeholder}
+ chipGroupComponent={chipGroupComponent}
+ maxHeight={300}
+ >
+ {
+ options.map((option, index) => (
+ <SelectOption
+ key={index}
+ value={option}
+ />
+ ))
+ }
+ </Select >
+ )
+}
+
+FilterSelect.propTypes = {
+ onFilterChange: PropTypes.func.isRequired,
+ filters: PropTypes.object.isRequired,
+ category: PropTypes.object.isRequired,
+}
+
+export { FilterSelect }
diff --git a/web/src/containers/filters/TernarySelect.jsx b/web/src/containers/filters/TernarySelect.jsx
new file mode 100644
index 000000000..9905db6df
--- /dev/null
+++ b/web/src/containers/filters/TernarySelect.jsx
@@ -0,0 +1,113 @@
+// Copyright 2021 Red Hat, Inc
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+
+import {
+ Select,
+ SelectOption,
+ SelectVariant,
+} from '@patternfly/react-core'
+
+
+function FilterTernarySelect(props) {
+ /* The ternary select expects the options to be in order: All/True/False */
+ const { filters, category } = props
+
+ const [isOpen, setIsOpen] = useState(false)
+ const options = category.options
+ const _getSelected = (value) => {
+ switch (value) {
+ case 1:
+ case '1':
+ return category.options[1]
+ case 0:
+ case '0':
+ return category.options[2]
+ default:
+ return null
+ }
+ }
+ let _selected = _getSelected([...filters[category.key]].pop())
+ const [selected, setSelected] = useState(_selected)
+
+ function onToggle(isOpen) {
+ setIsOpen(isOpen)
+ }
+
+ function onSelect(event, selection) {
+ const { onFilterChange, filters, category } = props
+
+ let _selection = (selection === selected) ? null : selection
+
+ setSelected(_selection)
+ const setNewFilter = (value) => {
+ switch (value) {
+ case category.options[1]:
+ return [1,]
+ case category.options[2]:
+ return [0,]
+ default:
+ return []
+ }
+ }
+ const newFilters = {
+ ...filters,
+ [category.key]: setNewFilter(_selection),
+ }
+ onFilterChange(newFilters)
+ setIsOpen(false)
+ }
+
+ function onClear() {
+ const { onFilterChange, filters, category } = props
+ setSelected(null)
+ setIsOpen(false)
+ const newFilters = {
+ ...filters,
+ [category.key]: [],
+ }
+ onFilterChange(newFilters)
+ }
+
+ return (
+ <Select
+ variant={SelectVariant.single}
+ placeholderText={category.placeholder}
+ isOpen={isOpen}
+ onToggle={onToggle}
+ onSelect={onSelect}
+ onClear={onClear}
+ selections={_selected}
+ >
+ {
+ options.map((option, index) => (
+ <SelectOption
+ key={index}
+ value={option}
+ />
+ ))
+ }
+ </Select>
+ )
+}
+
+FilterTernarySelect.propTypes = {
+ onFilterChange: PropTypes.func.isRequired,
+ filters: PropTypes.object.isRequired,
+ category: PropTypes.object.isRequired,
+}
+
+export { FilterTernarySelect } \ No newline at end of file