-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[MDS-6294] - User management (#3341)
* Refactor user management and implement user versioning. Removed legacy Redux-based user management in favor of a centralized user mechanism using `userSlice`. Added database and API support for user versioning with history tracking to enable auditability. Updated related frontend components and tests to align with the new structure. * move IUser interface into interfaces directory * remove comment * fixed rebase issue with network reducers * update tests * update tests * add role requirement to user profile resource * added error log to user resource * added error log to user resource * added error log to user resource * remove get_core_users network reducer type * update help test assert * update help test assert * update help test assert
- Loading branch information
1 parent
98b736e
commit 3ba16b8
Showing
37 changed files
with
689 additions
and
436 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
CREATE TABLE "user" | ||
( | ||
sub VARCHAR PRIMARY KEY, | ||
email VARCHAR NOT NULL, | ||
given_name VARCHAR NOT NULL, | ||
family_name VARCHAR NOT NULL, | ||
display_name VARCHAR NOT NULL, | ||
idir_username VARCHAR NOT NULL, | ||
identity_provider VARCHAR NOT NULL, | ||
idir_user_guid VARCHAR NOT NULL, | ||
last_logged_in TIMESTAMPTZ, | ||
create_user VARCHAR(255) NOT NULL, | ||
create_timestamp timestamp with time zone DEFAULT now() NOT NULL, | ||
update_user VARCHAR(255) NOT NULL, | ||
update_timestamp timestamp with time zone DEFAULT now() NOT NULL, | ||
deleted_ind BOOLEAN DEFAULT false | ||
); | ||
|
||
ALTER TABLE "user" | ||
OWNER TO mds; | ||
|
||
-- | ||
-- Name: TABLE user; Type: COMMENT; Schema: public; Owner: mds | ||
-- | ||
|
||
COMMENT ON TABLE "user" IS 'User Profile data sourced from keycloak'; |
24 changes: 24 additions & 0 deletions
24
migrations/sql/V2024.12.11.22.32__add_user_version_history_table.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
-- This file was generated by the generate_history_table_ddl command | ||
-- The file contains the corresponding history table definition for the {table} table | ||
CREATE TABLE user_version ( | ||
create_user VARCHAR(60), | ||
create_timestamp TIMESTAMP WITHOUT TIME ZONE, | ||
update_user VARCHAR(60), | ||
update_timestamp TIMESTAMP WITHOUT TIME ZONE, | ||
deleted_ind BOOLEAN default FALSE, | ||
sub VARCHAR NOT NULL, | ||
email VARCHAR, | ||
given_name VARCHAR, | ||
family_name VARCHAR, | ||
display_name VARCHAR, | ||
idir_username VARCHAR, | ||
identity_provider VARCHAR, | ||
idir_user_guid VARCHAR, | ||
transaction_id BIGINT NOT NULL, | ||
end_transaction_id BIGINT, | ||
operation_type SMALLINT NOT NULL, | ||
PRIMARY KEY (sub, transaction_id) | ||
); | ||
CREATE INDEX ix_user_version_operation_type ON user_version (operation_type); | ||
CREATE INDEX ix_user_version_end_transaction_id ON user_version (end_transaction_id); | ||
CREATE INDEX ix_user_version_transaction_id ON user_version (transaction_id); |
6 changes: 6 additions & 0 deletions
6
migrations/sql/V2024.12.11.22.33__add_user_version_history_table_backfill.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
-- This file was generated by the generate_history_table_ddl command | ||
-- The file contains the data migration to backfill history records for the {table} table | ||
with transaction AS (insert into transaction(id) values(DEFAULT) RETURNING id) | ||
insert into user_version (transaction_id, operation_type, end_transaction_id, "create_user", "create_timestamp", "update_user", "update_timestamp", "deleted_ind", "sub", "email", "given_name", "family_name", "display_name", "idir_username", "identity_provider", "idir_user_guid") | ||
select t.id, '0', null, "create_user", "create_timestamp", "update_user", "update_timestamp", "deleted_ind", "sub", "email", "given_name", "family_name", "display_name", "idir_username", "identity_provider", "idir_user_guid" | ||
from "user",transaction t; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from "./userInfo.interface"; | ||
export * from "./user.interface"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export interface IUser { | ||
sub: string; | ||
display_name: string; | ||
email: string; | ||
family_name: string; | ||
given_name: string; | ||
last_logged_in: string; | ||
} |
20 changes: 0 additions & 20 deletions
20
services/common/src/redux/actionCreators/userActionCreator.js
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { configureStore } from "@reduxjs/toolkit"; | ||
import { userReducer, fetchUser, getUser } from "./userSlice"; // Adjust the import path as necessary | ||
import { ENVIRONMENT, USER_PROFILE } from "@mds/common/constants"; | ||
import CustomAxios from "@mds/common/redux/customAxios"; | ||
|
||
const showLoadingMock = jest | ||
.fn() | ||
.mockReturnValue({ type: "SHOW_LOADING", payload: { show: true } }); | ||
const hideLoadingMock = jest | ||
.fn() | ||
.mockReturnValue({ type: "HIDE_LOADING", payload: { show: false } }); | ||
|
||
jest.mock("@mds/common/redux/customAxios"); | ||
jest.mock("react-redux-loading-bar", () => ({ | ||
showLoading: () => showLoadingMock, | ||
hideLoading: () => hideLoadingMock, | ||
})); | ||
|
||
describe("userSlice", () => { | ||
let store; | ||
|
||
beforeEach(() => { | ||
store = configureStore({ | ||
reducer: { | ||
user: userReducer, | ||
}, | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe("fetchUser", () => { | ||
const mockResponse = { | ||
data: { | ||
sub: "mock-sub", | ||
display_name: "Mock User", | ||
email: "[email protected]", | ||
family_name: "MockFamily", | ||
given_name: "MockGiven", | ||
last_logged_in: "2023-10-01T12:00:00.000Z", | ||
}, | ||
}; | ||
|
||
it("should fetch user data successfully", async () => { | ||
(CustomAxios as jest.Mock).mockImplementation(() => ({ | ||
get: jest.fn().mockResolvedValue(mockResponse), | ||
})); | ||
|
||
await store.dispatch(fetchUser()); | ||
const state = store.getState().user; | ||
|
||
// Verify loading state management | ||
expect(showLoadingMock).toHaveBeenCalledTimes(1); | ||
expect(hideLoadingMock).toHaveBeenCalledTimes(1); | ||
|
||
// Verify state update | ||
expect(getUser({ user: state })).toEqual(mockResponse.data); | ||
expect(CustomAxios).toHaveBeenCalledWith({ errorToastMessage: "default" }); | ||
}); | ||
|
||
it("should handle API error", async () => { | ||
const error = new Error("API Error"); | ||
(CustomAxios as jest.Mock).mockImplementation(() => ({ | ||
get: jest.fn().mockRejectedValue(error), | ||
})); | ||
|
||
await store.dispatch(fetchUser()); | ||
const state = store.getState().user; | ||
|
||
// Check user state remains null on error | ||
expect(getUser({ user: state })).toBeNull(); | ||
}); | ||
|
||
it("should construct the correct endpoint URL", async () => { | ||
const getMock = jest.fn().mockResolvedValue(mockResponse); | ||
(CustomAxios as jest.Mock).mockImplementation(() => ({ | ||
get: getMock, | ||
})); | ||
|
||
await store.dispatch(fetchUser()); | ||
|
||
expect(getMock).toHaveBeenCalledWith( | ||
`${ENVIRONMENT.apiUrl}${USER_PROFILE()}`, | ||
expect.any(Object) | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { createAppSlice, rejectHandler } from "@mds/common/redux/createAppSlice"; | ||
import { createRequestHeader } from "@mds/common/redux/utils/RequestHeaders"; | ||
import { hideLoading, showLoading } from "react-redux-loading-bar"; | ||
import CustomAxios from "@mds/common/redux/customAxios"; | ||
import { ENVIRONMENT, USER_PROFILE } from "@mds/common/constants"; | ||
import { IUser } from "@mds/common/interfaces"; | ||
|
||
export const userReducerType = "user"; | ||
|
||
interface UserState { | ||
user: IUser; | ||
} | ||
|
||
const initialState: UserState = { | ||
user: null, | ||
}; | ||
|
||
const userSlice = createAppSlice({ | ||
name: userReducerType, | ||
initialState, | ||
reducers: (create) => ({ | ||
fetchUser: create.asyncThunk( | ||
async (_: undefined, thunkApi) => { | ||
const headers = createRequestHeader(); | ||
thunkApi.dispatch(showLoading()); | ||
|
||
const response = await CustomAxios({ | ||
errorToastMessage: "default", | ||
}).get(`${ENVIRONMENT.apiUrl}${USER_PROFILE()}`, headers); | ||
|
||
thunkApi.dispatch(hideLoading()); | ||
return response.data; | ||
}, | ||
{ | ||
fulfilled: (state: UserState, action) => { | ||
state.user = action.payload; | ||
}, | ||
rejected: (state: UserState, action) => { | ||
rejectHandler(action); | ||
}, | ||
} | ||
), | ||
}), | ||
selectors: { | ||
getUser: (state) => state.user, | ||
}, | ||
}); | ||
|
||
export const { getUser } = userSlice.selectors; | ||
export const { fetchUser } = userSlice.actions; | ||
export const userReducer = userSlice.reducer; | ||
|
||
export default userReducer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,6 @@ import { | |
VC_CONNECTION_STATES, | ||
VC_CRED_ISSUE_STATES, | ||
} from "@mds/common/constants"; | ||
import { PermitExtraction } from "@mds/common/redux/slices/permitServiceSlice"; | ||
|
||
export const createMockHeader = () => ({ | ||
headers: { | ||
|
@@ -8967,3 +8966,12 @@ export const HELP_GUIDE_MS = { | |
}, | ||
], | ||
}; | ||
|
||
export const USER = { | ||
sub: '1234', | ||
displayName: 'Testerson, Test EMLI:EX', | ||
email: '[email protected]', | ||
family_name: 'Testerson', | ||
given_name: 'Test', | ||
last_logged_in: '2022-08-08T20:59:01.482461+00:00', | ||
} |
Oops, something went wrong.