-
Notifications
You must be signed in to change notification settings - Fork 4
feat: move admin access group grid to mui #911
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| /** | ||
| * @jest-environment jsdom | ||
| */ | ||
| import configureStore from "redux-mock-store"; | ||
| import thunk from "redux-thunk"; | ||
| import flushPromises from "flush-promises"; | ||
| import { | ||
| postRequest, | ||
| putRequest | ||
| } from "openstack-uicore-foundation/lib/utils/actions"; | ||
| import { saveAdminAccess } from "../admin-access-actions"; | ||
| import * as methods from "../../utils/methods"; | ||
|
|
||
| jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({ | ||
| __esModule: true, | ||
| ...jest.requireActual("openstack-uicore-foundation/lib/utils/actions"), | ||
| postRequest: jest.fn(), | ||
| putRequest: jest.fn() | ||
| })); | ||
|
|
||
| const requestMock = | ||
| (requestActionCreator, receiveActionCreator) => () => (dispatch) => { | ||
| if (requestActionCreator && typeof requestActionCreator === "function") { | ||
| dispatch(requestActionCreator({})); | ||
| } | ||
| return new Promise((resolve) => { | ||
| if (typeof receiveActionCreator === "function") { | ||
| dispatch(receiveActionCreator({ response: { id: 1 } })); | ||
| } else { | ||
| dispatch(receiveActionCreator); | ||
| } | ||
| resolve({ response: { id: 1 } }); | ||
| }); | ||
| }; | ||
|
|
||
| const rejectMock = () => () => () => Promise.reject(new Error("API error")); | ||
|
|
||
| describe("saveAdminAccess", () => { | ||
| const middlewares = [thunk]; | ||
| const mockStore = configureStore(middlewares); | ||
|
|
||
| beforeEach(() => { | ||
| jest.spyOn(methods, "getAccessTokenSafely").mockResolvedValue("TOKEN"); | ||
| postRequest.mockImplementation(requestMock); | ||
| putRequest.mockImplementation(requestMock); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| jest.restoreAllMocks(); | ||
| }); | ||
|
|
||
| describe("create path (entity has no id)", () => { | ||
| it("returns a Promise", async () => { | ||
| const store = mockStore({}); | ||
| const result = store.dispatch( | ||
| saveAdminAccess({ title: "Group A", members: [], summits: [] }) | ||
| ); | ||
| expect(result).toBeInstanceOf(Promise); | ||
| await expect(result).resolves.toBeUndefined(); | ||
| }); | ||
|
|
||
| it("dispatches ADMIN_ACCESS_ADDED then STOP_LOADING on success", async () => { | ||
| const store = mockStore({}); | ||
| store.dispatch( | ||
| saveAdminAccess({ title: "Group A", members: [], summits: [] }) | ||
| ); | ||
| await flushPromises(); | ||
|
|
||
| const actionTypes = store.getActions().map((a) => a.type); | ||
| expect(actionTypes).toContain("ADMIN_ACCESS_ADDED"); | ||
| expect(actionTypes).toContain("STOP_LOADING"); | ||
| expect(actionTypes.indexOf("STOP_LOADING")).toBeGreaterThan( | ||
| actionTypes.indexOf("ADMIN_ACCESS_ADDED") | ||
| ); | ||
| }); | ||
|
|
||
| it("still dispatches STOP_LOADING when postRequest rejects", async () => { | ||
| postRequest.mockImplementation(rejectMock); | ||
| const store = mockStore({}); | ||
| await store | ||
| .dispatch( | ||
| saveAdminAccess({ title: "Group A", members: [], summits: [] }) | ||
| ) | ||
| .catch(() => {}); | ||
| await flushPromises(); | ||
|
|
||
| const actionTypes = store.getActions().map((a) => a.type); | ||
| expect(actionTypes).toContain("STOP_LOADING"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("update path (entity has id)", () => { | ||
| it("returns a Promise", async () => { | ||
| const store = mockStore({}); | ||
| const result = store.dispatch( | ||
| saveAdminAccess({ id: 1, title: "Group A", members: [], summits: [] }) | ||
| ); | ||
| expect(result).toBeInstanceOf(Promise); | ||
| await expect(result).resolves.toBeUndefined(); | ||
| }); | ||
|
|
||
| it("dispatches ADMIN_ACCESS_UPDATED then STOP_LOADING on success", async () => { | ||
| const store = mockStore({}); | ||
| store.dispatch( | ||
| saveAdminAccess({ id: 1, title: "Group A", members: [], summits: [] }) | ||
| ); | ||
| await flushPromises(); | ||
|
|
||
| const actionTypes = store.getActions().map((a) => a.type); | ||
| expect(actionTypes).toContain("ADMIN_ACCESS_UPDATED"); | ||
| expect(actionTypes).toContain("STOP_LOADING"); | ||
| expect(actionTypes.indexOf("STOP_LOADING")).toBeGreaterThan( | ||
| actionTypes.indexOf("ADMIN_ACCESS_UPDATED") | ||
| ); | ||
| }); | ||
|
|
||
| it("still dispatches STOP_LOADING when putRequest rejects", async () => { | ||
| putRequest.mockImplementation(rejectMock); | ||
| const store = mockStore({}); | ||
| await store | ||
| .dispatch( | ||
| saveAdminAccess({ id: 1, title: "Group A", members: [], summits: [] }) | ||
| ) | ||
| .catch(() => {}); | ||
| await flushPromises(); | ||
|
|
||
| const actionTypes = store.getActions().map((a) => a.type); | ||
| expect(actionTypes).toContain("STOP_LOADING"); | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,12 +19,9 @@ import { | |
| createAction, | ||
| stopLoading, | ||
| startLoading, | ||
| showMessage, | ||
| showSuccessMessage, | ||
| authErrorHandler, | ||
| escapeFilterValue | ||
| } from "openstack-uicore-foundation/lib/utils/actions"; | ||
| import history from "../history"; | ||
| import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; | ||
| import { getAccessTokenSafely } from "../utils/methods"; | ||
| import { DEFAULT_PER_PAGE } from "../utils/constants"; | ||
|
|
||
|
|
@@ -94,8 +91,8 @@ export const getAdminAccesses = | |
| createAction(REQUEST_ADMIN_ACCESSES), | ||
| createAction(RECEIVE_ADMIN_ACCESSES), | ||
| `${window.API_BASE_URL}/api/v1/summit-administrator-groups`, | ||
| authErrorHandler, | ||
| { order, orderDir, term } | ||
| snackbarErrorHandler, | ||
| { order, orderDir, term, page, perPage } | ||
|
Comment on lines
+94
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# 1) Inspect snackbar/auth error handler implementations.
fd -i 'base-actions.js' | xargs -r rg -n -A20 -B5 'snackbarErrorHandler|authErrorHandler'
# 2) Inspect request helper semantics (resolve vs reject on errors).
rg -n -A30 -B10 --type=js 'export const getRequest|function getRequest|const getRequest'
rg -n -A30 -B10 --type=js 'export const postRequest|export const putRequest|export const deleteRequest'
# 3) Re-check caller expectation in list page.
rg -n -A8 -B4 --type=js 'getAdminAccess\(' src/pages/admin_access/admin-access-list-page.jsRepository: fntechgit/summit-admin Length of output: 2009 🏁 Script executed: # Check request-helper or actions utility to understand error handler contract
rg -n -A15 'error.*handler|errorHandler' src/utils/request-helper.js src/utils/actions.js 2>/dev/null | head -100
# Inspect the actual admin-access-actions.js to see full context of lines 94-95, 118-119, 190
cat -n src/actions/admin-access-actions.js | sed -n '85,110p'
cat -n src/actions/admin-access-actions.js | sed -n '110,130p'
cat -n src/actions/admin-access-actions.js | sed -n '185,195p'
# Find how request utilities handle error callbacks (getRequest, postRequest, etc.)
fd -type f 'request-helper.js' | xargs -r cat -n | head -150Repository: fntechgit/summit-admin Length of output: 2321 🏁 Script executed: # Find request-helper or actions utility files
find . -name "*request*" -o -name "*actions*" | grep -E "utils|lib" | head -20
# Look for getRequest implementation to understand error handler contract
rg -n "export.*getRequest|function getRequest" | head -20
# Check other error handler implementations for comparison
rg -n "ErrorHandler.*=" -A5 | head -100Repository: fntechgit/summit-admin Length of output: 5481 🏁 Script executed: # Check package.json for openstack-uicore-foundation version
cat package.json | grep -A2 -B2 "openstack-uicore-foundation"
# Look for any local request helper/wrapper that might provide more context
find src -name "*request*" -o -name "*helper*" | grep -v node_modules
# Check admin-access-list-page to understand caller expectations
cat -n src/pages/admin_access/admin-access-list-page.js | sed -n '55,80p'Repository: fntechgit/summit-admin Length of output: 1720 🏁 Script executed: # Look for any error handlers that explicitly return rejected promises or throw
rg -n "return.*reject|throw.*Error|Promise.reject" src/actions/ | head -30
# Check if any error handlers dispatch a rejection or error state
rg -n "ERROR|FAILURE" src/actions/admin-access-actions.js
# Look for patterns where handlers might suppress errors
rg -n "catch.*=>" src/actions/ | head -20Repository: fntechgit/summit-admin Length of output: 2140 🌐 Web query:
💡 Result: The library openstack-uicore-foundation is a collection of ReactJS and Redux components designed for the OpenStack marketing website [1][2]. It is not a general-purpose OpenStack API client or a low-level SDK for managing OpenStack infrastructure [1][2]. There is no evidence of a "getRequest" function or specific "Promise rejection semantics" within the openstack-uicore-foundation repository [1][2]. Queries regarding "getRequest" and error handling in an OpenStack context typically relate to other projects, such as: - The OpenStack SDK (for Python, Rust, or other languages), which provides standardized methods for API interaction and error handling [3][4][5]. - OpenStack API specifications regarding structured error responses [6]. - Other frameworks like Uvicore (a separate Python framework) that explicitly manage exception handlers [7]. If you are encountering a "getRequest" error, please verify the library or SDK you are using to interact with the OpenStack API, as it is likely not openstack-uicore-foundation [1][2][3]. For standard JavaScript/TypeScript applications interacting with OpenStack APIs, standard Promise-based error handling (using.catch or try/catch blocks with async/await) is the recommended approach to managing rejections [1][2]. Citations:
🏁 Script executed: # Check admin-access-actions test file for mocking patterns
cat -n src/actions/__tests__/admin-access-actions.test.js | head -80
# Look for other uses of getRequest to see error handler patterns
rg -n "getRequest" src/actions/ -B3 -A3 | head -150
# Check if there's documentation or type definitions for getRequest
find . -path ./node_modules -prune -o -name "*.d.ts" -exec grep -l "getRequest" {} \; 2>/dev/null | head -10Repository: fntechgit/summit-admin Length of output: 11707 🏁 Script executed: # Get the complete saveAdminAccess function
cat -n src/actions/admin-access-actions.js | sed -n '128,175p'Repository: fntechgit/summit-admin Length of output: 1723 🏁 Script executed: # Get the complete test case that checks rejection behavior
cat -n src/actions/__tests__/admin-access-actions.test.js | sed -n '77,95p'Repository: fntechgit/summit-admin Length of output: 837 🏁 Script executed: # Search for authErrorHandler implementation in the codebase
rg -n "export const authErrorHandler|export function authErrorHandler" -A10
# Also check if there's a local implementation or if it's all from openstack-uicore-foundation
rg -n "authErrorHandler" src/ --type=js | head -20Repository: fntechgit/summit-admin Length of output: 1273 Confirm error handler doesn't suppress rejections in the request chain.
Consider using 🤖 Prompt for AI Agents |
||
| )(params)(dispatch).then(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
|
|
@@ -118,7 +115,7 @@ export const getAdminAccess = (adminAccessId) => async (dispatch) => { | |
| null, | ||
| createAction(RECEIVE_ADMIN_ACCESS), | ||
| `${window.API_BASE_URL}/api/v1/summit-administrator-groups/${adminAccessId}`, | ||
| authErrorHandler | ||
| snackbarErrorHandler | ||
| )(params)(dispatch).then(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
|
|
@@ -128,52 +125,55 @@ export const resetAdminAccessForm = () => (dispatch) => { | |
| dispatch(createAction(RESET_ADMIN_ACCESS_FORM)({})); | ||
| }; | ||
|
|
||
| export const saveAdminAccess = | ||
| (entity, noAlert = false) => | ||
| async (dispatch) => { | ||
| const accessToken = await getAccessTokenSafely(); | ||
| export const saveAdminAccess = (entity) => async (dispatch) => { | ||
| const accessToken = await getAccessTokenSafely(); | ||
|
|
||
| dispatch(startLoading()); | ||
| dispatch(startLoading()); | ||
|
|
||
| const normalizedEntity = normalizeEntity(entity); | ||
| const params = { access_token: accessToken }; | ||
|
|
||
| if (entity.id) { | ||
| putRequest( | ||
| createAction(UPDATE_ADMIN_ACCESS), | ||
| createAction(ADMIN_ACCESS_UPDATED), | ||
| `${window.API_BASE_URL}/api/v1/summit-administrator-groups/${entity.id}`, | ||
| normalizedEntity, | ||
| authErrorHandler, | ||
| entity | ||
| )(params)(dispatch).then(() => { | ||
| if (!noAlert) | ||
| dispatch(showSuccessMessage(T.translate("admin_access.saved"))); | ||
| else dispatch(stopLoading()); | ||
| }); | ||
| } else { | ||
| const successMessage = { | ||
| title: T.translate("general.done"), | ||
| html: T.translate("admin_access.created"), | ||
| type: "success" | ||
| }; | ||
|
|
||
| postRequest( | ||
| createAction(UPDATE_ADMIN_ACCESS), | ||
| createAction(ADMIN_ACCESS_ADDED), | ||
| `${window.API_BASE_URL}/api/v1/summit-administrator-groups`, | ||
| normalizedEntity, | ||
| authErrorHandler, | ||
| entity | ||
| )(params)(dispatch).then((payload) => { | ||
| const normalizedEntity = normalizeEntity(entity); | ||
| const params = { access_token: accessToken }; | ||
|
|
||
| if (entity.id) { | ||
| return putRequest( | ||
| createAction(UPDATE_ADMIN_ACCESS), | ||
| createAction(ADMIN_ACCESS_UPDATED), | ||
| `${window.API_BASE_URL}/api/v1/summit-administrator-groups/${entity.id}`, | ||
| normalizedEntity, | ||
| snackbarErrorHandler, | ||
| entity | ||
| )(params)(dispatch) | ||
| .then(() => { | ||
| dispatch( | ||
| showMessage(successMessage, () => { | ||
| history.push(`/app/admin-access/${payload.response.id}`); | ||
| snackbarSuccessHandler({ | ||
| title: T.translate("general.success"), | ||
| html: T.translate("admin_access.saved") | ||
| }) | ||
| ); | ||
| }) | ||
| .finally(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
| } | ||
| }; | ||
| } | ||
| return postRequest( | ||
| createAction(UPDATE_ADMIN_ACCESS), | ||
| createAction(ADMIN_ACCESS_ADDED), | ||
| `${window.API_BASE_URL}/api/v1/summit-administrator-groups`, | ||
| normalizedEntity, | ||
| snackbarErrorHandler, | ||
| entity | ||
| )(params)(dispatch) | ||
| .then(() => { | ||
| dispatch( | ||
| snackbarSuccessHandler({ | ||
| title: T.translate("general.success"), | ||
| html: T.translate("admin_access.created") | ||
| }) | ||
| ); | ||
| }) | ||
| .finally(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
| }; | ||
|
|
||
| export const deleteAdminAccess = (adminAccessId) => async (dispatch) => { | ||
| const accessToken = await getAccessTokenSafely(); | ||
|
|
@@ -187,7 +187,7 @@ export const deleteAdminAccess = (adminAccessId) => async (dispatch) => { | |
| createAction(ADMIN_ACCESS_DELETED)({ adminAccessId }), | ||
| `${window.API_BASE_URL}/api/v1/summit-administrator-groups/${adminAccessId}`, | ||
| null, | ||
| authErrorHandler | ||
| snackbarErrorHandler | ||
| )(params)(dispatch).then(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests cover the happy-path dispatch sequence (ADDED/UPDATED → STOP_LOADING) but there is no test for the save failure path. A test that rejects
postRequest/putRequestwould confirm thatstopLoadingstill fires via.finally()— a future refactor could silently break that guarantee.