Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"react-dropzone": "^4.2.13",
"react-final-form": "^6.5.9",
"react-google-maps": "^9.4.5",
"react-redux": "^5.0.7",
"react-redux": "^7.1.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-rte": "^0.16.3",
Expand Down
5 changes: 2 additions & 3 deletions src/actions/speaker-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -889,19 +889,18 @@ export const getSpeakersBySummit =
perPage = DEFAULT_PER_PAGE,
order = "full_name",
orderDir = DEFAULT_ORDER_DIR,
filters = {}
filters = []
) =>
async (dispatch, getState) => {
const { currentSummitState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
const filter = parseFilters(filters);
const filter = [...filters];

Comment on lines +892 to 899
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all callsites pass array filters (or update callsites if object filters remain).
# 1) Locate all invocations
rg -nP '\bgetSpeakersBySummit\s*\(' src -C2

# 2) Inspect likely filter-building paths
rg -nP '\bparseFilters\s*\(' src -C2
rg -nP '\bfilters\s*[:=]' src/pages/summit_speakers -C2

Repository: fntechgit/summit-admin

Length of output: 9547


🏁 Script executed:

# Get the actual getSpeakersBySummit function implementation
sed -n '880,950p' src/actions/speaker-actions.js | cat -n

Repository: fntechgit/summit-admin

Length of output: 2663


🏁 Script executed:

# Also check what parseFilters function does
rg -A 10 'export.*parseFilters\s*=|function\s+parseFilters\s*\(' src -C 3

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Check if filters are used elsewhere as objects in the speakers context
rg -n 'getSpeakersBySummit' src --type js -B 2 -A 2

Repository: fntechgit/summit-admin

Length of output: 2050


🏁 Script executed:

# Find how getSpeakersBySummit is dispatched in the component
sed -n '230,270p' src/pages/summit_speakers/summit-speakers-list-page/index.js | cat -n

Repository: fntechgit/summit-admin

Length of output: 1385


🏁 Script executed:

# Find the getRequest function to understand what it does with the metadata parameter
rg -n 'export.*getRequest\s*=|function\s+getRequest' src -A 5

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Check if filters property is accessed in metadata anywhere
rg -n 'metadata\.filters|payload\.filters' src --type js

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Check exportSummitSpeakers and surrounding context
sed -n '949,1000p' src/actions/speaker-actions.js | cat -n

Repository: fntechgit/summit-admin

Length of output: 2075


🏁 Script executed:

# Find getRequest definition
find src -name "*.js" | xargs rg -l "export.*getRequest" | head -5

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Look for getRequest usage and metadata handling
rg -n "getRequest\(" src/actions/speaker-actions.js -B 2 -A 10

Repository: fntechgit/summit-admin

Length of output: 2616


🏁 Script executed:

# Find the getRequest function definition
rg -n "export.*getRequest\s*=" src -A 20 | head -50

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Check the import of getRequest in speaker-actions.js
head -50 src/actions/speaker-actions.js | grep -n "import"

Repository: fntechgit/summit-admin

Length of output: 210


🏁 Script executed:

# Find where metadata is used in the request handling
rg -rn "metadata" src/actions/ --type js | grep -v node_modules | head -20

Repository: fntechgit/summit-admin

Length of output: 2557


🏁 Script executed:

# Check the imports at the top of speaker-actions.js for where getRequest comes from
sed -n '1,50p' src/actions/speaker-actions.js | cat -n

Repository: fntechgit/summit-admin

Length of output: 1861


🏁 Script executed:

# Find getRequest in api-calls or utils
find src -name "*api*" -o -name "*utils*" | xargs grep -l "getRequest" | head -5

Repository: fntechgit/summit-admin

Length of output: 139


🏁 Script executed:

# Look for where the metadata parameter (5th argument to getRequest) is used
rg -n "getRequest" src/utils -A 15 | head -80

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# getRequest is from an external library. Let's check where the metadata (5th parameter) is used
# by searching for how REQUEST_SPEAKERS_BY_SUMMIT and RECEIVE_SPEAKERS_BY_SUMMIT actions are handled
rg -n "REQUEST_SPEAKERS_BY_SUMMIT|RECEIVE_SPEAKERS_BY_SUMMIT" src --type js

Repository: fntechgit/summit-admin

Length of output: 839


🏁 Script executed:

# Check the reducer for speakers to see how metadata is used
rg -n "metadata" src/reducers --type js -B 2 -A 2

Repository: fntechgit/summit-admin

Length of output: 1468


🏁 Script executed:

# Let's inspect the full getSpeakersBySummit function call to getRequest
sed -n '920,945p' src/actions/speaker-actions.js | cat -n

Repository: fntechgit/summit-admin

Length of output: 962


🏁 Script executed:

# Check the reducer to see how REQUEST_SPEAKERS_BY_SUMMIT metadata is used
sed -n '72,110p' src/reducers/summit_speakers/summit-speakers-list-reducer.js | cat -n

Repository: fntechgit/summit-admin

Length of output: 1258


🏁 Script executed:

# Also check RECEIVE_SPEAKERS_BY_SUMMIT reducer case
sed -n '103,140p' src/reducers/summit_speakers/summit-speakers-list-reducer.js | cat -n

Repository: fntechgit/summit-admin

Length of output: 1283


🏁 Script executed:

# Check if getSpeakersBySummit is called anywhere with object-shaped filters
rg -n "getSpeakersBySummit" src --type js -B 5 -A 5 | grep -A 10 -B 10 "getSpeakersBySummit"

Repository: fntechgit/summit-admin

Length of output: 4731


🏁 Script executed:

# Look for any pattern where filters might be passed as an object to getSpeakersBySummit
rg -n "getSpeakersBySummit\s*\(" src/pages -A 2 -B 2

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Let's also check git history to see if filters parameter was recently changed from {} to []
git log --oneline -p src/actions/speaker-actions.js | grep -A 5 -B 5 "filters = " | head -30

Repository: fntechgit/summit-admin

Length of output: 960


🏁 Script executed:

# Check if filters metadata property is accessed anywhere after REQUEST_SPEAKERS_BY_SUMMIT
rg -n "filters" src/reducers/summit_speakers/summit-speakers-list-reducer.js

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# See if there are any tests that verify the metadata structure
find src -name "*.test.js" -o -name "*.spec.js" | xargs grep -l "getSpeakersBySummit" 2>/dev/null

Repository: fntechgit/summit-admin

Length of output: 50


🏁 Script executed:

# Check the full reducer to see if all metadata properties are used
cat src/reducers/summit_speakers/summit-speakers-list-reducer.js

Repository: fntechgit/summit-admin

Length of output: 5477


Replace array spread in metadata with explicit filters property to avoid polluting state with numeric keys.

Line 940 spreads array filters into the metadata object passed to getRequest, which creates numeric keys (0, 1, 2…) instead of a proper filters property. These numeric keys end up merged into Redux state via the reducer's ...rest spread, polluting the state structure and reducing clarity.

💡 Proposed fix
-        ...filters,
+        filters: normalizedFilters,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/actions/speaker-actions.js` around lines 892 - 899, The metadata object
passed into getRequest is spreading the array (filter) which creates numeric
keys (0,1,2...) and pollutes Redux state when the reducer merges via ...rest;
change the metadata to include an explicit filters property instead (e.g.,
metadata: { filters: filter, ...otherMeta }) so consumers like getRequest and
the reducer receive a proper filters array; update the call site in the async
action (the function using dispatch, getState and calling getRequest) to stop
using the array spread and use the named filters property.

dispatch(startLoading());

if (term) {
const filterTerm = buildTermFilter(term);

filter.push(filterTerm.join(","));
}

Expand Down
9 changes: 9 additions & 0 deletions src/components/GridFilter/actions/filter-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createAction } from "openstack-uicore-foundation/lib/utils/actions";

export const SAVE_FILTERS = "SAVE_FILTERS";

export const saveFilters =
(id, filters = [], joinOperator = "all") =>
(dispatch) => {
dispatch(createAction(SAVE_FILTERS)({ id, filters, joinOperator }));
};
83 changes: 83 additions & 0 deletions src/components/GridFilter/components/Dropdown.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright 2026 OpenStack Foundation
* 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 T from "i18n-react/dist/i18n-react";
import { Select, FormControl, MenuItem, InputLabel } from "@mui/material";
import PropTypes from "prop-types";

const Dropdown = ({
id,
value,
options,
placeholder,
label,
onChange,
...rest
}) => {
const finalPlaceholder =
placeholder || T.translate("general.select_an_option");

return (
<FormControl fullWidth>
{label && <InputLabel id={`${id}-label`}>{label}</InputLabel>}
<Select
value={value}
label={label}
onChange={onChange}
labelId={`${id}-label`}
displayEmpty
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
renderValue={(selected) => {
if (selected == null || selected === "") {
return <em>{finalPlaceholder}</em>;
}
const selectedOption = options.find(
({ value }) => value === selected
);
return selectedOption ? selectedOption.label : "";
}}
>
{options?.map((op) => (
<MenuItem key={`selectop-${op.value}`} value={op.value}>
{op.label}
</MenuItem>
))}
</Select>
</FormControl>
);
};

Dropdown.propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
options: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired,
label: PropTypes.string.isRequired
})
).isRequired,
label: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func.isRequired
};

Dropdown.defaultProps = {
value: null,
label: "",
placeholder: ""
};

export default Dropdown;
145 changes: 145 additions & 0 deletions src/components/GridFilter/components/Filter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* Copyright 2026 OpenStack Foundation
* 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 T from "i18n-react/dist/i18n-react";
import { Box, Grid2, IconButton } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import AddIcon from "@mui/icons-material/Add";
import PropTypes from "prop-types";
import Dropdown from "./Dropdown";
import ValueInput from "./ValueInput";
Comment on lines +14 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Import T before calling T.translate(...).

This file uses T on Line 47, Line 55, and Line 64, but never imports it, so the component will fail as soon as it renders.

Suggested fix
 import React from "react";
+import T from "i18n-react/dist/i18n-react";
 import { Button, Grid2 } from "react-bootstrap";

Also applies to: 47-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GridFilter/components/Filter.jsx` around lines 14 - 21, The
component uses T.translate(...) in Filter.jsx (calls to T.translate on the
Filter component around the label/button text) but T is never imported; add an
import for your project's i18n helper (e.g., import T from the project's i18n
module or 'i18n-react' / equivalent used in this repo) at the top of
src/components/GridFilter/components/Filter.jsx so the T.translate calls
(referenced in this file) resolve at render time.

import RoundButton from "./RoundButton";

const Filter = ({ id, value, criterias, onChange, onAdd, onDelete }) => {
const criteriaOptions = criterias.map(({ key, label }) => ({
value: key,
label
}));
const criteriaObj = criterias.find(({ key }) => key === value?.criteria);
const operatorOptions = criteriaObj?.operators || [];
const valueSettings = criteriaObj?.values || {};

const handleChange = (prop, val) => {
onChange({ ...value, [prop]: val });
};

const handleChangeCriteria = (ev) => {
const val = ev.target.value;
handleChange("criteria", val);
};

const handleChangeOperator = (ev) => {
const val = ev.target.value;
handleChange("operator", val);
};
Comment on lines +37 to +45
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset dependent fields when criteria changes.

When criteria changes, previous operator/value are kept, which can produce invalid filter combinations for the new criteria.

💡 Proposed fix
   const handleChangeCriteria = (ev) => {
     const val = ev.target.value;
-    handleChange("criteria", val);
+    onChange({
+      ...value,
+      criteria: val,
+      operator: "",
+      value: ""
+    });
   };

Also applies to: 63-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GridFilter/components/Filter.jsx` around lines 37 - 45, The
handlers keep stale dependent fields causing invalid combos: update
handleChangeCriteria to not only call handleChange("criteria", val) but also
reset dependent fields (call handleChange("operator", DEFAULT_OPERATOR) and
handleChange("value", "") or the appropriate empty/default for the selected
criteria); likewise update handleChangeOperator to reset the value (call
handleChange("value", "") or a type-appropriate default) after calling
handleChange("operator", val). Locate and modify the functions named
handleChangeCriteria and handleChangeOperator and use the existing handleChange
utility so new criteria/operator selections clear or set sensible defaults for
dependent fields.


const handleChangeValue = (ev) => {
const val = ev.target.value;
handleChange("value", val);
};

return (
<Grid2 container spacing={2} sx={{ alignItems: "center", mb: 2 }}>
<Grid2 size={11}>
<Box sx={{ display: "flex", alignItems: "center", gap: "14px" }}>
<Dropdown
id={`${id}-column`}
value={value?.criteria || ""}
placeholder={T.translate("grid_filter.select_criteria")}
options={criteriaOptions}
onChange={handleChangeCriteria}
/>
<Dropdown
id={`${id}-operator`}
value={value?.operator || ""}
placeholder={T.translate("grid_filter.select_operator")}
options={operatorOptions}
disabled={!value?.criteria}
onChange={handleChangeOperator}
/>
<ValueInput
id={`${id}-value`}
value={value?.value || ""}
type={valueSettings.type}
placeholder={T.translate("grid_filter.select_values")}
disabled={!value?.criteria}
// eslint-disable-next-line react/jsx-props-no-spreading
{...valueSettings.props}
onChange={handleChangeValue}
/>
Comment on lines +56 to +80
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In MUI Select and TextField, what does onChange receive and where is the current field value exposed?

💡 Result:

For MUI Select (Material UI): - onChange receives a SelectChangeEvent as its first argument. - The current selected value is exposed as event.target.value (any). [1] - The event signature is onChange(event: SelectChangeEvent, child?: object) => void. [2] For MUI TextField (Material UI): - onChange receives the usual event object from the underlying input. - The current text value is exposed as event.target.value. [3][4] Practically, this means: - Select: onChange={(event) => setState(event.target.value)} - TextField: onChange={(event) => setState(event.target.value)} (If you’re using TypeScript, MUI Select’s event.target.value may be typed as unknown depending on your setup; the docs still indicate you read it from event.target.value.) [2][5]

Citations:


🏁 Script executed:

# Find Dropdown and ValueInput components
fd -t f -e jsx -e js -e tsx -e ts | head -20

Repository: fntechgit/summit-admin

Length of output: 796


🏁 Script executed:

# Search for Dropdown component definition
rg "export.*Dropdown" -t jsx -t js -t tsx -t ts -A 5

Repository: fntechgit/summit-admin

Length of output: 94


🏁 Script executed:

# Search for ValueInput component definition
rg "export.*ValueInput" -t jsx -t js -t tsx -t ts -A 5

Repository: fntechgit/summit-admin

Length of output: 94


🏁 Script executed:

# Check the Filter.jsx file to understand context better
fd -t f -name "Filter.jsx" -exec cat {} \;

Repository: fntechgit/summit-admin

Length of output: 297


🏁 Script executed:

# Find Dropdown component definition
rg "export.*Dropdown|function Dropdown|const Dropdown" -A 10

Repository: fntechgit/summit-admin

Length of output: 2795


🏁 Script executed:

# Find ValueInput component definition
rg "export.*ValueInput|function ValueInput|const ValueInput" -A 10

Repository: fntechgit/summit-admin

Length of output: 1220


🏁 Script executed:

# Locate and read Filter.jsx file
find . -name "Filter.jsx" -type f

Repository: fntechgit/summit-admin

Length of output: 116


🏁 Script executed:

# Read complete Dropdown.jsx
cat src/components/GridFilter/components/Dropdown.jsx

Repository: fntechgit/summit-admin

Length of output: 2354


🏁 Script executed:

# Read complete Filter.jsx
cat src/components/GridFilter/components/Filter.jsx

Repository: fntechgit/summit-admin

Length of output: 4092


🏁 Script executed:

# Read complete ValueInput implementation
cat src/components/GridFilter/components/ValueInput/index.jsx

Repository: fntechgit/summit-admin

Length of output: 1651


Extract event.target.value in onChange handlers.

The Dropdown and ValueInput components pass onChange directly through to MUI Select and TextField, which emit event objects. The current handlers receive these event objects instead of the actual values, causing criteria, operator, and value to be set to event objects rather than user selections.

Suggested fix
          <Dropdown
            id={`${id}-column`}
            value={value.criteria}
            placeholder={T.translate("grid_filter.select_criteria")}
            // eslint-disable-next-line react/jsx-props-no-spreading
            {...criteriaSettings}
-           onChange={(val) => handleChange("criteria", val)}
+           onChange={(event) => handleChange("criteria", event.target.value)}
          />
          <Dropdown
            id={`${id}-operator`}
            value={value.operator}
            placeholder={T.translate("grid_filter.select_operator")}
            // eslint-disable-next-line react/jsx-props-no-spreading
            {...operatorSettings}
-           onChange={(val) => handleChange("operator", val)}
+           onChange={(event) => handleChange("operator", event.target.value)}
          />
          <ValueInput
            id={`${id}-value`}
            value={value.value}
            type={valueSettings.type}
            placeholder={T.translate("grid_filter.select_values")}
            // eslint-disable-next-line react/jsx-props-no-spreading
            {...valueSettings.props}
-           onChange={(val) => handleChange("value", val)}
+           onChange={(event) => handleChange("value", event.target.value)}
          />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Dropdown
id={`${id}-column`}
value={value.criteria}
placeholder={T.translate("grid_filter.select_criteria")}
// eslint-disable-next-line react/jsx-props-no-spreading
{...criteriaSettings}
onChange={(val) => handleChange("criteria", val)}
/>
<Dropdown
id={`${id}-operator`}
value={value.operator}
placeholder={T.translate("grid_filter.select_operator")}
// eslint-disable-next-line react/jsx-props-no-spreading
{...operatorSettings}
onChange={(val) => handleChange("operator", val)}
/>
<ValueInput
id={`${id}-value`}
value={value.value}
type={valueSettings.type}
placeholder={T.translate("grid_filter.select_values")}
// eslint-disable-next-line react/jsx-props-no-spreading
{...valueSettings.props}
onChange={(val) => handleChange("value", val)}
/>
<Dropdown
id={`${id}-column`}
value={value.criteria}
placeholder={T.translate("grid_filter.select_criteria")}
// eslint-disable-next-line react/jsx-props-no-spreading
{...criteriaSettings}
onChange={(event) => handleChange("criteria", event.target.value)}
/>
<Dropdown
id={`${id}-operator`}
value={value.operator}
placeholder={T.translate("grid_filter.select_operator")}
// eslint-disable-next-line react/jsx-props-no-spreading
{...operatorSettings}
onChange={(event) => handleChange("operator", event.target.value)}
/>
<ValueInput
id={`${id}-value`}
value={value.value}
type={valueSettings.type}
placeholder={T.translate("grid_filter.select_values")}
// eslint-disable-next-line react/jsx-props-no-spreading
{...valueSettings.props}
onChange={(event) => handleChange("value", event.target.value)}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GridFilter/components/Filter.jsx` around lines 44 - 68, The
onChange handlers for Dropdown and ValueInput are receiving DOM event objects
(from MUI Select/TextField) instead of the selected value, so update the three
handlers to extract the value from the event before calling handleChange: for
the Dropdown with id `${id}-column` use onChange={(e) =>
handleChange("criteria", e.target.value)}, for the Dropdown `${id}-operator` use
onChange={(e) => handleChange("operator", e.target.value)}, and for ValueInput
`${id}-value` use onChange={(e) => handleChange("value", e.target.value)}; keep
using the existing Dropdown, ValueInput and handleChange symbols so the rest of
the logic remains unchanged.

</Box>
</Grid2>
<Grid2 size={1}>
{value?.id !== "new" ? (
<IconButton
aria-label="delete-filter"
onClick={() => onDelete(value)}
size="large"
>
<DeleteIcon fontSize="large" />
</IconButton>
) : (
<RoundButton
variant="contained"
aria-label="add-filter"
onClick={() => onAdd()}
disabled={!value?.criteria || !value?.operator || !value?.value}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Allow valid falsy values (0, false) for filter input.

The current disable condition treats falsy values as missing, so valid values like 0 or false cannot be applied.

💡 Proposed fix
+  const hasValue =
+    value?.value !== null &&
+    value?.value !== undefined &&
+    value?.value !== "" &&
+    (!Array.isArray(value?.value) || value.value.length > 0);
@@
-            disabled={!value?.criteria || !value?.operator || !value?.value}
+            disabled={!value?.criteria || !value?.operator || !hasValue}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GridFilter/components/Filter.jsx` at line 97, The disable
logic in Filter.jsx currently treats any falsy value as missing, preventing
valid falsy inputs like 0 or false; update the disabled expression on the
element using the value variable so it only disables when criteria or operator
are null/undefined or when value is explicitly null/undefined/empty string (e.g.
replace disabled={!value?.criteria || !value?.operator || !value?.value} with a
check like value?.criteria == null || value?.operator == null || value?.value ==
null || (typeof value?.value === 'string' && value.value === '') so 0 and false
are allowed).

sx={{ ml: "4px" }}
>
<AddIcon fontSize="large" />
</RoundButton>
)}
</Grid2>
</Grid2>
);
};

Filter.propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.shape({
criteria: PropTypes.string,
operator: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.array
])
}),
criterias: PropTypes.arrayOf(
PropTypes.shape({
key: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
operators: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.string.isRequired,
label: PropTypes.string.isRequired
})
),
values: PropTypes.shape({
type: PropTypes.string.isRequired,
props: PropTypes.object.isRequired
})
})
).isRequired,
onChange: PropTypes.func.isRequired,
onAdd: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired
};

Filter.defaultProps = {
value: null
};

export default Filter;
59 changes: 59 additions & 0 deletions src/components/GridFilter/components/FilterButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Copyright 2026 OpenStack Foundation
* 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 { Chip, IconButton } from "@mui/material";
import FilterListIcon from "@mui/icons-material/FilterList";
import T from "i18n-react/dist/i18n-react";

const FilterButton = ({ filterCount, onClick, onDelete }) => {
if (filterCount > 0) {
return (
<Chip
icon={<FilterListIcon fontSize="large" />}
label={`${filterCount} ${T.translate("grid_filter.filters")}`}
onClick={onClick}
onDelete={onDelete}
sx={{
"& .MuiChip-label": { fontSize: "13px" },
backgroundColor: "grey.700",
color: "white",
"& .MuiChip-icon": { color: "white" },
"& .MuiChip-deleteIcon": {
color: "rgba(255,255,255,0.7)",
"&:hover": { color: "white" }
}
}}
/>
);
}

return (
<IconButton
size="large"
onClick={onClick}
sx={{ mr: 1, top: "-6px", position: "relative" }}
>
<FilterListIcon fontSize="large" />
</IconButton>
Comment on lines +43 to +49
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add an accessible label to the icon-only filter button.

The icon-only IconButton has no accessible name, so assistive tech cannot identify its action.

💡 Proposed fix
   return (
     <IconButton
+      aria-label={T.translate("grid_filter.filters")}
       size="large"
       onClick={onClick}
       sx={{ mr: 1, top: "-6px", position: "relative" }}
     >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<IconButton
size="large"
onClick={onClick}
sx={{ mr: 1, top: "-6px", position: "relative" }}
>
<FilterListIcon fontSize="large" />
</IconButton>
<IconButton
aria-label={T.translate("grid_filter.filters")}
size="large"
onClick={onClick}
sx={{ mr: 1, top: "-6px", position: "relative" }}
>
<FilterListIcon fontSize="large" />
</IconButton>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GridFilter/components/FilterButton.jsx` around lines 43 - 49,
The IconButton in FilterButton.jsx is icon-only and lacks an accessible name;
add an accessible label (e.g., an aria-label or title) to the IconButton element
so screen readers can identify it (use a descriptive string like "Open filters"
or wire it to a passed prop if the component accepts one), while keeping the
FilterListIcon child unchanged; ensure the attribute is applied on the
IconButton JSX where onClick is set so assistive tech can announce the button's
purpose.

);
};

FilterButton.propTypes = {
filterCount: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired
};

export default FilterButton;
44 changes: 44 additions & 0 deletions src/components/GridFilter/components/RoundButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright 2026 OpenStack Foundation
* 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 { Button } from "@mui/material";
import PropTypes from "prop-types";

const RoundButton = ({ children, sx = {}, ...props }) => (
<Button
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
sx={{
width: 40,
height: 40,
minWidth: "auto",
borderRadius: "50%",
padding: 0,
...sx
}}
>
{children}
</Button>
);

RoundButton.propTypes = {
children: PropTypes.node.isRequired,
sx: PropTypes.object
};

RoundButton.defaultProps = {
sx: {}
};

export default RoundButton;
Loading
Loading