Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,24 @@ export const getCSRFToken = () => {
* @returns an object containing the appropriate impersonation requst headers, based on redux state
*/
export const getConsoleRequestHeaders = (): ConsoleRequestHeaders => {
const store = storeHandler.getStore();
if (!store) return undefined;
const state = store.getState();
const store = (window as any).consoleStore || storeHandler.getStore();

const headers: ConsoleRequestHeaders = {
'X-CSRFToken': getCSRFToken(),
};

// Set impersonation headers
const { kind, name } = getImpersonate(state) || {};
if ((kind === 'User' || kind === 'Group') && name) {
// Even if we are impersonating a group, we still need to set Impersonate-User to something or k8s will complain
headers['Impersonate-User'] = name;
if (kind === 'Group') {
headers['Impersonate-Group'] = name;
// If store is available, add impersonation headers
if (store) {
const state = store.getState();

// Set impersonation headers
const { kind, name } = getImpersonate(state) || {};
if ((kind === 'User' || kind === 'Group') && name) {
// Even if we are impersonating a group, we still need to set Impersonate-User to something or k8s will complain
headers['Impersonate-User'] = name;
if (kind === 'Group') {
headers['Impersonate-Group'] = name;
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions frontend/public/redux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const store = createStore(
composeEnhancers(applyMiddleware(thunk)),
);

// Make store globally available for dynamic plugins
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this what storeHandler.getStore() is for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on my research and learning, I found the different storeHandler is used:
Main console: console/dynamic-plugin-sdk/src/app/storeHandler (source)
Plugin: @openshift-console/dynamic-plugin-sdk/lib/app/storeHandler (compiled)

And this leads to the different store to be used. By setting the store to the shared global space, we can make sure that same store get used. This is my understanding, what do you think? @TheRealJon

Copy link
Member

Choose a reason for hiding this comment

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

@Leo6Leo, we've always had a singleton Redux store instance. It's already declared globally in the storeHandler you referenced. @vojtechszocs could you clarify how this should work?

Copy link
Contributor

Choose a reason for hiding this comment

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

@Leo6Leo @TheRealJon

The root cause of the problem seems to be that storeHandler.setStore function is not called right after the Redux store instance is created and initialized in public/redux.ts module.

So I think the only change we need for this bugfix is to modify public/redux.ts like so:

// ...
import storeHandler from '@console/dynamic-plugin-sdk/src/app/storeHandler';
// ...

storeHandler.setStore(store);

export default store;

Bugfix PRs should contain minimal changes to address the immediate issue. We can further improve the Redux handling code in follow-up non-bugfix PRs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After applying @vojtechszocs 's fix

I have performed the fix @vojtechszocs suggested locally, I can see that the store is successfully initialized, but that doesn't solve the problem with the storeHandler.getStore() returning undefined in the getConsoleRequestHeaders() function, calling it from the file dynamic-demo-plugin/src/components/UtilityConsumer.tsx. But I think this change makes sense to me and it is necessary, since the store should be set when the store is being created.

Further investigation

One key observation I found is that: whether storeHandler.getStore() returning undefined or the real store value in the getConsoleRequestHeaders() function, it depends on where it get called.

Screenshot 2025-09-24 at 4 22 59 PM
  • When getConsoleRequestHeaders() is called from somewhere else, the function proceed as expected:
Screenshot 2025-09-24 at 4 22 21 PM

Therefore, I think the root cause I found here is still valid.

different storeHandler is used:
Main console: console/dynamic-plugin-sdk/src/app/storeHandler (source)
Plugin: @openshift-console/dynamic-plugin-sdk/lib/app/storeHandler (compiled)

And here is a quote from the LLM that I think is a reasonable explanation to me:

The console uses Webpack Module Federation to load dynamic plugins. Which means:
Main Application Context: our main app (console/frontend) has its own module context with its own instance of storeHandler.
Plugin Context: the dynamic plugin (console/dynamic-demo-plugin) runs in a separate webpack container/module context and gets its own separate instance of storeHandler

So I think the approach of making the store become global makes sense. As suggested, we should only have 1 redux store, should we consider making that store become global?

Copy link
Member

Choose a reason for hiding this comment

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

I think if we do fix it this way, it should be commented with a FIXME and a CONSOLE project issue to look further into this. We probably shouldn't leak the internal application state into the window scope as a long-term solution.

(window as any).consoleStore = store;

export const applyReduxExtensions = (reducerExtensions: ResolvedExtension<ReduxReducer>[]) => {
const pluginReducers: ReducersMapObject = {};

Expand Down