web: allow blocking pages on certain progress signals#2947
Conversation
TanStack Query does not provide a built-in way to notify when queries have finished refetching after being invalidated. The library author usually recommends relying on `useQuery` to react to query state changes, which makes sense in most cases. However, in Agama there is a use case that can be considered an exception to this rule, where components should not directly depend on query state but only be notified when their data is considered fresh after a certain moment. This is the case for the core/Page component, which will be changed to enter a "loading" state when receiving a signal from the Agama progress API. After progress completion, a proposal update signal may be received, causing related queries to be invalidated. At that point, TanStack Query triggers a refetch, and the core/Page component must wait until all affected queries have fully refetched before exiting the loading state. Exiting the loading state as soon as progress ends can result in rendering stale data, which is then replaced moments later, causing noticeable UI flicker. This hook allows tracking refetch completion so the loading state is exited only when data is fresh.
Adapt the `useProposalChanges` hook to use the previously introduced refetch-tracking hook, allowing it to detect when all involved queries have finished refetching after being invalidated. Once the refetch completes, a DOM-based event is emitted so that other parts of Agama can react accordingly. This will be primarily used by the core/Page component to exit the loading state gracefully and at the correct time.
PF/PageSectionProps and PF/PageGroupProps already define children; no need to wrap them with React.PropsWithChildren.
Work in progress adapting the core/Page component so it can enter a loading state while a progress of a given scope is active. This commit is intentionally sent as WIP to allow others to experiment with it while styles are being refactored. For now, only storage/ProposalPage is adapted to subscribe to the progress scopes it is interested in.
This commit continues the work from the previous one, further refining the core/Page component by removing unnecessary code and extracting the progress overlay logic into an internal component. Additionally, inline styles are replaced with a CSS class.
The tests were fixed by replacing `plainRender` with `installerRender`where needed and reordering imports to ensure that `test-utils` is imported before the component, allowing the centralized react-routter mocks to work as expected. Including `test-utils.tsx` in `jest.config.js#setupFilesAfterEnv` was because it caused the `ProductRegistrationAlertMock` to fail for unknown reasons.
Refactors how ProgressBackdrop handle state transitions during data refresh and adds few tests for it.
Although the use of useTrackQueriesRefetch worked well with the QueryObservers approach, using a subscription to the queryCache with the necessary filtering is considered a better practice. As a result, the hook has been updated to use this approach, while ensuring that all tests continue to pass. More info: * https://tkdodo.eu/blog/inside-react-query * https://tanstack.com/query/latest/docs/reference/QueryCache * TanStack/query#3292
| if ( | ||
| query.state.status === "success" && | ||
| query.state.dataUpdatedAt > startedAt && | ||
| !refetchedKeys.has(queryKey) | ||
| ) { |
There was a problem hiding this comment.
Maybe I'm misunderstanding something, but using query.state.fetchStatus, query.isStale, or similar properties to directly check here if a query is in a refetching state ended up making things more complex for this scenario IMHO. The hook would need to track multiple statuses transitions (e.g., from stale/invalidated to fetching, and then to success), which increases the complexity.
Instead, working with the dataUpdatedAt timestamp feels simpler and enought for this use case, based on my current understanding.
It would be nice if the library had a built-in way to subscribe to query refetches directly.
There was a problem hiding this comment.
It would be nice if the library had a built-in way to subscribe to query refetches directly.
I mean, a "decoupled" mechanism other than useQuery for use cases similar we have faced here. . The author argues that useQuery usage should be enough in most cases, but in situations like this, it feels like a more specialized solution would be useful.
imobachgs
left a comment
There was a problem hiding this comment.
Just some typos. Otherwise, it LGTM.
Co-authored-by: Imobach González Sosa <igonzalezsosa@suse.com>
Initially, internal `ProgressBackdrop` component at `core/Page`, relied on a global event system to detect when proposal-related queries finished refetching in order to consider an ongoing progress in the given scope was finished (check #2947). While this worked on the storage page, it failed on other pages becuae the _central_ `useProposalChanges` hook is invalidating an storage-related query, which is not refetched until navigating to a storage-related page. As a result, the progress backdrop could remain stuck on unrelated storage pages. This commit refactors the tracking mechanism to make it more flexible and page-aware: * Start exporting query keys as constants (COMMON_PROPOSAL_KEYS, STORAGE_MODEL_KEY, etc.) from their respective modules * Add the `additionalProgressKeys` prop to Page, allowing pages to specify extra query keys to track alongside the common proposal-related queries * Use `useTrackQueriesRefetch` directly into ProgressBackdrop, dropping the need for a custom global event system * Remove the proposal:updated event infrastructure (onProposalUpdated, dispatchProposalUpdated), which is no longer required With this change, pages can explicitly declare which additional queries must complete refetching before the UI is unblocked. For example, the storage `ProposalPage` now waits for the `storageModel` query in addition to the common proposal queries. While requiring pages to be explicit about the queries they depend on is not ideal, this preserves common proposal tracking as the default while preventing the UI from remaining blocked due to unrelated query invalidations.
Merge the new HTTP API. Each PR has been already reviewed, so it should be safe to merge it. * #1829 * #2508 * #2772 * #2826 * #2848 * #2860 * #2863 * #2866 * #2867 * #2869 * #2870 * #2871 * #2872 * #2873 * #2874 * #2875 * #2876 * #2877 * #2880 * #2881 * #2882 * #2884 * #2885 * #2886 * #2891 * #2892 * #2893 * #2894 * #2895 * #2896 * #2897 * #2898 * #2899 * #2900 * #2901 * #2902 * #2903 * #2904 * #2908 * #2909 * #2910 * #2912 * #2913 * #2914 * #2915 * #2916 * #2917 * #2918 * #2920 * #2921 * #2923 * #2924 * #2926 * #2928 * #2929 * #2930 * #2933 * #2934 * #2935 * #2936 * #2938 * #2939 * #2942 * #2943 * #2944 * #2945 * #2946 * #2947 * #2948 * #2950 * #2951 * #2952 * #2954 * #2955 * #2956 * #2957 * #2958 * #2959 * #2960 * #2961 * #2963 * #2964 * #2965 * #2967 * #2968 * #2969 * #2970 * #2971 * #2972 * #2974 * #2975 * #2977 * #2978 * #2980 * #2982 * #2983 * #2984 * #2988 * #2989 * #2991 * #2992 * #2993 * #2994 * #2995 * #2996 * #2997 * #2999
) As a follow up of #3143, restores the DASD format progress by adapting `DASDFormatProgress` to the new API v2 event model, using `DASDFormatChanged` websocket events to drive progress state and only rendering when formatting is actually active. To support injecting the per-device progress details into the existing backdrop overlay, `ProgressBackdrop` was extended with an `extraContent` prop, which can be used by pages to inject any extra content of their convenience to the progress overlay. This PR was also taken as an opportunity to improve the readability of the progress overlay, on hold since #2947, by wrapping its content in a Card layout, which provides a clear visual container, structured spacing, and scrollable content for long step messages. A `waitingLabel` prop was added as another minor improvement to let consumers override the default `"Refreshing data..."` message shown while queries finish refetching.
https://build.opensuse.org/request/show/1338003 by user IGonzalezSosa + anag_factory - Remove code to deal with the old HTTP API (gh#agama-project/agama#3258). - Allow downloading the user config (jsc#PED-15453). - Improve registration and software issues reporting (related to bsc#1258034). - Drop vulnerable dev dependency no longer needed (bsc#1259117). - Restores DASD format progress with API v2 event model (gh#agama-project/agama#3143). - Improves progress backdrop layout and readbility (gh#agama-project/agama#2947). - Add visual feedback on table row hover and focus (gh#agama-project/agama#3233). - Update web dependencies to their latest available versions as of 2026-03-02 (gh#agama-project/agama#3232). - Do not reset the configuration on product selection (bsc#1258032). - Do not show empty software page (related to bsc#1258922). - Restore the interfa
Prepare to release version 19. * #1829 * #2508 * #2772 * #2818 * #2826 * #2848 * #2860 * #2863 * #2864 * #2866 * #2867 * #2869 * #2870 * #2871 * #2872 * #2873 * #2874 * #2875 * #2876 * #2877 * #2880 * #2881 * #2882 * #2884 * #2885 * #2886 * #2891 * #2892 * #2893 * #2894 * #2895 * #2896 * #2897 * #2898 * #2899 * #2900 * #2901 * #2902 * #2903 * #2904 * #2908 * #2909 * #2910 * #2912 * #2913 * #2914 * #2915 * #2916 * #2917 * #2918 * #2920 * #2921 * #2923 * #2924 * #2926 * #2928 * #2929 * #2930 * #2933 * #2934 * #2935 * #2936 * #2937 * #2938 * #2939 * #2942 * #2943 * #2944 * #2945 * #2946 * #2947 * #2948 * #2949 * #2950 * #2951 * #2952 * #2954 * #2955 * #2956 * #2957 * #2958 * #2959 * #2960 * #2961 * #2963 * #2964 * #2965 * #2967 * #2968 * #2969 * #2970 * #2971 * #2972 * #2974 * #2975 * #2977 * #2978 * #2980 * #2981 * #2982 * #2983 * #2984 * #2988 * #2989 * #2990 * #2991 * #2992 * #2993 * #2994 * #2995 * #2996 * #2997 * #2998 * #2999 * #3000 * #3001 * #3002 * #3004 * #3005 * #3006 * #3007 * #3008 * #3009 * #3011 * #3012 * #3013 * #3014 * #3015 * #3016 * #3018 * #3019 * #3020 * #3021 * #3022 * #3023 * #3024 * #3025 * #3026 * #3027 * #3028 * #3029 * #3030 * #3031 * #3033 * #3034 * #3035 * #3036 * #3037 * #3039 * #3040 * #3041 * #3042 * #3043 * #3044 * #3045 * #3046 * #3047 * #3048 * #3049 * #3050 * #3051 * #3052 * #3053 * #3054 * #3055 * #3056 * #3057 * #3058 * #3060 * #3061 * #3062 * #3063 * #3064 * #3065 * #3066 * #3067 * #3068 * #3069 * #3070 * #3071 * #3072 * #3073 * #3074 * #3075 * #3076 * #3077 * #3078 * #3079 * #3086 * #3087 * #3088 * #3089 * #3090 * #3091 * #3092 * #3093 * #3094 * #3095 * #3096 * #3097 * #3098 * #3099 * #3100 * #3101 * #3102 * #3103 * #3104 * #3105 * #3106 * #3107 * #3108 * #3109 * #3110 * #3112 * #3113 * #3114 * #3115 * #3116 * #3117 * #3118 * #3119 * #3120 * #3122 * #3123 * #3124 * #3127 * #3128 * #3129 * #3130 * #3131 * #3133 * #3134 * #3135 * #3136 * #3137 * #3138 * #3139 * #3140 * #3141 * #3142 * #3143 * #3144 * #3145 * #3146 * #3147 * #3148 * #3149 * #3150 * #3151 * #3152 * #3153 * #3154 * #3155 * #3157 * #3158 * #3159 * #3160 * #3161 * #3162 * #3163 * #3164 * #3165 * #3166 * #3167 * #3168 * #3169 * #3170 * #3174 * #3175 * #3176 * #3177 * #3178 * #3179 * #3181 * #3182 * #3184 * #3185 * #3186 * #3188 * #3189 * #3190 * #3191 * #3192 * #3194 * #3195 * #3196 * #3197 * #3198 * #3199 * #3200 * #3201 * #3202 * #3203 * #3205 * #3206 * #3208 * #3209 * #3210 * #3213 * #3214 * #3215 * #3216 * #3217 * #3218 * #3219 * #3220 * #3222 * #3223 * #3224 * #3225 * #3226 * #3227 * #3228 * #3229 * #3230 * #3231 * #3232 * #3233 * #3234 * #3235 * #3236 * #3237 * #3238 * #3239 * #3240 * #3241 * #3242 * #3243 * #3244 * #3246 * #3247 * #3248 * #3250 * #3251 * #3252 * #3253 * #3254 * #3255 * #3256 * #3257 * #3258 * #3259 * #3260 * #3261 * #3262 * #3263 * #3265 * #3266 * #3267 * #3268 * #3269 * #3270 * #3271 * #3272 * #3273 * #3274 * #3275 * #3276 * #3277 * #3278 * #3279 * #3280 * #3281 * #3282 * #3283 * #3284 * #3285 * #3286 * #3287 * #3288 * #3289 * #3290 * #3291
A few weeks ago, in #2909, a small, non-blocking component was introduced to subtly inform users when actions are in progress . This PR introduces its blocking counterpart, allowing Agama pages to subscribe to specific progress signals and block user interactions until those progress events are complete.
The changes in this PR ended up being more complex than initially expected due to an edge case that caused an undesirable flickering effect: the progress indicator would disappear almost simultaneously, or even slightly before, certain proposal queries were invalidated and refetched. This led to a transition from the progress state to an outdated UI, which was not ideal.
To address this, a new hook has been implemented to subscribe to the TanStack Query cache, notifying when specific queries are considered fully refetched after a certain point. This is then used in conjunction with
useProposalChangesquery invalidation. As a result, it now emits a custom event once proposal-related queries are fully refetched, allowing the Page component to subscribe to this event and unmount the blocking overlay just before React performs the UI redraw.Please, note that using something like
const { isFetching } = useProposalChanges()wasn't suitable for this scenario. For further details, please refer to the code and commit messages.Additional notes
Currently, only the software pages (all of them) and the storage proposal page are subscribed to their respective progress scopes. The only pages missing at the moment are the network pages, but since the progress for network is not yet ready, TypeScript raises an error when trying to include these pages in this PR, as it doesn't recognize "network" as a valid scope.
It would be nice to allow pages to conditionally discard the blocking state based on the number of progress steps. This idea originated with the assumption that one-step progress (like for storage proposals) would be fast, though it might not be for certain users or stacks. It's difficult to predict since other factors such as connection speed, whether the page is remote or localhost, etc can have a significant impact. These factors could be explored further to refine the behavior, if/when time permits.
Another possibility, which is fully outside the scope of this PR for now because there is no current use case, is to allow pages to subscribe to multiple progress scopes, rather than just one.