Skip to content

Add resource filter for the main page#225

Open
VoigtS wants to merge 17 commits into
mainfrom
resource_search
Open

Add resource filter for the main page#225
VoigtS wants to merge 17 commits into
mainfrom
resource_search

Conversation

@VoigtS
Copy link
Copy Markdown
Member

@VoigtS VoigtS commented Apr 9, 2026

As time went on, the amount of available resources and categories increased which made the UI navigation more difficult.
This PR adds a search that the user can utilize to filter for a specific resources.

@VoigtS VoigtS requested a review from a team as a code owner April 9, 2026 18:48
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 9, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://sapcc.github.io/LimesUI/pr-preview/pr-225/

Built to branch gh-pages at 2026-05-07 20:02 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 9, 2026

🦋 Changeset detected

Latest commit: cd00075

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sapcc/limes-ui Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread src/components/mainView/Overview.js Outdated
Comment thread src/components/mainView/Overview.js Outdated
wagnerd3
wagnerd3 previously approved these changes Apr 10, 2026
@VoigtS VoigtS marked this pull request as draft April 13, 2026 08:44
@VoigtS
Copy link
Copy Markdown
Member Author

VoigtS commented Apr 13, 2026

I'm moving this to a draft for now. The current implementation has a small issue that searching for translated category or resource names that contain empty spaces themselfes causes a searchTerm split.

This can be fixed by splitting for a different charackter like a "+" smybol. However, this leads to specialised syntax where the user needs to be informed about the valid syntax and I don't want to introduce more explanations to the UI.

Therefore I would like to rethink the implementation. I currently lean towards a greenhouse UI pill based filtering.
This might introduce presentational issues in combination with the tabnavigation but it might be worth it.

@VoigtS VoigtS marked this pull request as ready for review April 15, 2026 17:52
@VoigtS VoigtS requested a review from wagnerd3 April 29, 2026 12:53
@SuperSandro2000
Copy link
Copy Markdown
Member

The labels can only be dismissed by clicking the X unlike Juno where the entire label can be clicked.

image

When switching between tabs like from Compute to PAYG, the filter is reset.

Filters that are already selected, can be selected in the dropdown again, than flicker the UI and are not greyed out or marked as already selected.

I just selected category compute and afterwards the dropdown is no longer working, unless I switch tabs or click somewhere else.

image

Filter should be written in the URL to allow reloading or linking.

@VoigtS
Copy link
Copy Markdown
Member Author

VoigtS commented Apr 29, 2026

The labels can only be dismissed by clicking the X unlike Juno where the entire label can be clicked.
-> I checked the Juno UI's (f.e. Doop). Pills can only be removed by clicking on the "X" as well.

When switching between tabs like from Compute to PAYG, the filter is reset.
-> This is by design. Each tab contains different filter settings (Categories and Resources) and should not bleed into another Area.

Filters that are already selected, can be selected in the dropdown again, than flicker the UI and are not greyed out or marked as already selected.
-> The same flickering happens in the greenhouse UI's. That's because the Box text has to be cleared when you select a filter.

I just selected category compute and afterwards the dropdown is no longer working, unless I switch tabs or click somewhere else.
-> That's a Juno combobox issue. I can't fix that.

Filter should be written in the URL to allow reloading or linking.
-> They are written into the URL.

Comment thread src/components/mainView/Overview.js Outdated
Comment thread src/components/mainView/Overview.js Outdated
// Filter categories based of search parameters
const [searchParams] = useSearchParams();
const searchTerm = searchParams.get(SEARCH_TERM) || "";
const categoryFilters = new Set(searchParams.get(FILTER_TYPES.category.key)?.split(",").filter(Boolean) || []);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When you have Sets which are created on every render, will the useMemo remember them? I guess the references won't be the same, so this won't work, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That's correct, I wrapped both filter sets into a memo

};
}, [categories, currentArea, overview, advancedView]);

function handleFilterChange(filter) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If I was to write ?category= (empty), then I can produce URLs of category=,[more params], which I think would be illegal?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The test does not test verify that the URL does not get polluted like this, we should add an expectation for that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That's correct, I modified the handlers to handle this case properly. I also included a test case.

Comment thread src/components/mainView/Overview.js Outdated
// navigate to the selected area
// navigate to the selected area while also resetting the resource filter
function onTabChange(selectedArea) {
navigate(`/${selectedArea}`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should to { replace: false } here for safety?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is the standard behavior for navigate, but I included it to make it explicit.

const nameLower = name.toLowerCase();
const nameTranslated = t(name).toLowerCase();

if (term.endsWith("$")) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we document this somehow? o.O This is a bit counter intuitive for people who know Regex - I'm not sure if we should just allow regexes instead? so that it's clear what you can do with ^$?

Copy link
Copy Markdown
Member Author

@VoigtS VoigtS May 7, 2026

Choose a reason for hiding this comment

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

The searchTerm matching supports regular expressions now instead of relying on a specialized syntax

Comment thread src/components/mainView/Overview.js Outdated
const searchTerm = searchParams.get(SEARCH_TERM) || "";
const categoryFilters = new Set(searchParams.get(FILTER_TYPES.category.key)?.split(",").filter(Boolean) || []);
const resourceFilters = new Set(searchParams.get(FILTER_TYPES.resource.key)?.split(",").filter(Boolean) || []);
const filteredCategories = React.useMemo(() => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Technically, the filteredCategories are across all tabs - which we don't need. It won't be noticed, as we later filter them down again. But with the current amount of entries, this won't be a major hit for performance, it's only that we could already filter it here, too.

Copy link
Copy Markdown
Member Author

@VoigtS VoigtS May 7, 2026

Choose a reason for hiding this comment

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

This point is true and led me to extract the whole filter read logic into its own hook. The Overview component just fetches the result. The hook now provides prefiltered categories for the currentArea and the result set of filtered categories.

The first value set is used for the OverviewFilter, this prevents the duplication of the serviceType to category resolution that we handle within the hook.
The latter value is used to display the correct categories and resources depending of the filters a user sets. This reduces complexity within the renderArea function

const DebouncedSearchInput = ({ onChange, initialValue = "", delay = 300, styling = "" }) => {
const [inputValue, setInputValue] = useState(initialValue);
const isFirstRender = useRef(true);
const DebouncedSearchInput = ({ onChange, initialValue = "", delay = 300, styling = "", opts = {} }) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this fire an unnecessary render cycle 300ms after the actual cycle when I switch tabs?
AI hinted me to this, but I am not knowledgeable enough in React to judge it. useEffect calls setInputValue, which triggers useEffect on inputValue. Which then fires this onChange with inputValue after the debounce, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You bring up an interesting point. But the issue is a different one than described.
Normally it shouldn't be the case, because on tab change the initialValue does not change.
The actual issue that I noticed during this is that the debouncedInput component itself renders twice on the first tab change. All subsequent tab changes are good.

It appears that on the first render, the useEffect runs and queues a state update for the inputValue. When the tab changes, the parent rerenders the input field and the pending state update triggers.
The meaning for the the indication that onChange is called after 300ms: It does not, because the first effect call is catched by the firstRender check. React then knows that the internal state is an unchanged empty string and does not trigger a callback to the event loop.

A proper mitigation here is to only trigger this state sync useEffect if the callers value does indeed change from the previous one. I included this change and added a comment.

@VoigtS VoigtS force-pushed the resource_search branch from 80d036a to cf2ee6d Compare May 7, 2026 14:07
VoigtS and others added 14 commits May 7, 2026 16:38
rename CCloud to SAP Cloud Infrastructure for the AppShell when not in embedded mode
users can search for categories and resources.
search terms can be chained. <category> <category> displays the selected categories
<category> <resource>$ will display the matching resource of the category.
if a non-exact match is entered, like <category> <resource> and a non-exact matching ersource is matched in another category, that category will be displayed
todo: only show categories / resources depending on the view mode (advanced view)
optimize rerendering, the implemented useEffect causes a unnecessary rerender on start
remove hardcoded param values and clean up the components
disallow empty filters (e.g. category= or resource=)
disallow duplicates that are provided via URL entries
Co-authored-by: Daniel Wagner <154379032+wagnerd3@users.noreply.github.com>
overview: fix categoryFilters and resourceFilters memoization
1. prefilter categories for the current area and the user filter settings
The logic is extracted into its own hook useOverviewFilters
2. matchName: use regex matching for searchTerm inputs
3. handle malformed URL settings for URLs with prefiltered emptystring
make url history {replace: false} explicit
@VoigtS VoigtS force-pushed the resource_search branch from 003bcb2 to 0498cd6 Compare May 7, 2026 14:43
regex name matching: implement a fallback when invalid regexes are caught
debouncedSearchInput: fix initialValue sync when the caller demands it
The issue was that the searchInput compontent rendered twice during the first tab change.
That's because react queued the setInputValue() call from the useEffect during the first render.
Subsequent tab changes were ok, because the effect did not run again.
The fix is that the intialValue gets compared with the previous one and only triggers a state sync if it's really necessary
@VoigtS VoigtS requested a review from wagnerd3 May 7, 2026 19:47
add searchTerm clear await
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants