Add resource filter for the main page#225
Conversation
|
🦋 Changeset detectedLatest commit: cd00075 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
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. |
|
The labels can only be dismissed by clicking the X unlike Juno where the entire label can be clicked. 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. Filter should be written in the URL to allow reloading or linking. |
| // 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) || []); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
That's correct, I wrapped both filter sets into a memo
| }; | ||
| }, [categories, currentArea, overview, advancedView]); | ||
|
|
||
| function handleFilterChange(filter) { |
There was a problem hiding this comment.
If I was to write ?category= (empty), then I can produce URLs of category=,[more params], which I think would be illegal?
There was a problem hiding this comment.
The test does not test verify that the URL does not get polluted like this, we should add an expectation for that.
There was a problem hiding this comment.
That's correct, I modified the handlers to handle this case properly. I also included a test case.
| // navigate to the selected area | ||
| // navigate to the selected area while also resetting the resource filter | ||
| function onTabChange(selectedArea) { | ||
| navigate(`/${selectedArea}`); |
There was a problem hiding this comment.
I think we should to { replace: false } here for safety?
There was a problem hiding this comment.
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("$")) { |
There was a problem hiding this comment.
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 ^$?
There was a problem hiding this comment.
The searchTerm matching supports regular expressions now instead of relying on a specialized syntax
| 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(() => { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 = {} }) => { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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
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
add searchTerm clear await


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.