Skip to content
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

[MDS-6294] - User management #3341

Merged
merged 14 commits into from
Dec 13, 2024
Merged
26 changes: 26 additions & 0 deletions migrations/sql/V2024.12.10.15.15__create_users_table.sql
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,
matbusby-fw marked this conversation as resolved.
Show resolved Hide resolved
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';
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);
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;
matbusby-fw marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions services/common/src/constants/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,6 @@ export const REGIONS_LIST = "/regions";
// App Help
export const APP_HELP = (helpKey: string, params?: { system?: string; help_guid?: string }) =>
`/help/${helpKey}?${queryString.stringify(params)}`;

// User
export const USER_PROFILE = () => "/users/profile";
3 changes: 0 additions & 3 deletions services/common/src/constants/networkReducerTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,6 @@ export enum NetworkReducerTypes {
CREATE_PROJECT_LINKS = "CREATE_PROJECT_LINKS",
DELETE_PROJECT_LINK = "DELETE_PROJECT_LINK",

// Core Users
GET_CORE_USERS = "GET_CORE_USERS",

// Incidents
CREATE_MINE_INCIDENT = "CREATE_MINE_INCIDENT",
GET_INCIDENTS = "GET_INCIDENTS",
Expand Down
1 change: 1 addition & 0 deletions services/common/src/interfaces/user/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./userInfo.interface";
export * from "./user.interface";
8 changes: 8 additions & 0 deletions services/common/src/interfaces/user/user.interface.ts
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 services/common/src/redux/actionCreators/userActionCreator.js

This file was deleted.

8 changes: 0 additions & 8 deletions services/common/src/redux/actions/userActions.js

This file was deleted.

2 changes: 0 additions & 2 deletions services/common/src/redux/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import searchReducerObject from "./reducers/searchReducer";
import securitiesReducerObject from "./reducers/securitiesReducer";
import staticContentReducerObject from "./reducers/staticContentReducer";
import tailingsReducerObject from "./reducers/tailingsReducer";
import userReducerObject from "./reducers/userReducer";
import varianceReducerObject from "./reducers/varianceReducer";
import workInformationReducerObject from "./reducers/workInformationReducer";
import verifiableCredentialReducerObject from "./reducers/verifiableCredentialReducer";
Expand All @@ -40,7 +39,6 @@ export const permitReducer = permitReducerObject;
export const reportReducer = reportReducerObject;
export const searchReducer = searchReducerObject;
export const staticContentReducer = staticContentReducerObject;
export const userReducer = userReducerObject;
export const varianceReducer = varianceReducerObject;
export const securitiesReducer = securitiesReducerObject;
export const orgbookReducer = orgbookReducerObject;
Expand Down
9 changes: 6 additions & 3 deletions services/common/src/redux/reducers/rootReducerShared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
securitiesReducer,
staticContentReducer,
tailingsReducer,
userReducer,
varianceReducer,
verifiableCredentialReducer,
workInformationReducer,
Expand All @@ -37,13 +36,17 @@ import regionsReducer from "@mds/common/redux/slices/regionsSlice";
import complianceCodeReducer, { complianceCodeReducerType } from "../slices/complianceCodesSlice";
import spatialDataReducer, { spatialDataReducerType } from "../slices/spatialDataSlice";
import permitServiceReducer, { permitServiceReducerType } from "../slices/permitServiceSlice";
import searchConditionCategoriesReducer, { searchConditionCategoriesType } from "../slices/permitConditionCategorySlice";
import searchConditionCategoriesReducer, {
searchConditionCategoriesType,
} from "../slices/permitConditionCategorySlice";
import helpReducer, { helpReducerType } from "../slices/helpSlice";

const networkReducers = Object.fromEntries(Object.entries(NetworkReducerTypes).map(([key, value]) =>
[NetworkReducerTypes[key], createReducer(networkReducer, value)]
));

import userReducer, { userReducerType } from "@mds/common/redux/slices/userSlice";

export const sharedReducer = {
...activityReducer,
...authenticationReducer,
Expand All @@ -67,7 +70,6 @@ export const sharedReducer = {
...securitiesReducer,
...staticContentReducer,
...tailingsReducer,
...userReducer,
...varianceReducer,
...verifiableCredentialReducer,
...workInformationReducer,
Expand All @@ -81,5 +83,6 @@ export const sharedReducer = {
[permitServiceReducerType]: permitServiceReducer,
[helpReducerType]: helpReducer,
[searchConditionCategoriesType]: searchConditionCategoriesReducer,
[userReducerType]: userReducer,
...networkReducers
};
26 changes: 0 additions & 26 deletions services/common/src/redux/reducers/userReducer.js

This file was deleted.

22 changes: 0 additions & 22 deletions services/common/src/redux/selectors/userSelectors.js

This file was deleted.

90 changes: 90 additions & 0 deletions services/common/src/redux/slices/userSlice.spec.ts
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: "mock@example.com",
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)
);
});
});
});
53 changes: 53 additions & 0 deletions services/common/src/redux/slices/userSlice.ts
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;
10 changes: 9 additions & 1 deletion services/common/src/tests/mocks/dataMocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -8967,3 +8966,12 @@ export const HELP_GUIDE_MS = {
},
],
};

export const USER = {
sub: '1234',
displayName: 'Testerson, Test EMLI:EX',
email: 'test@test.ca',
family_name: 'Testerson',
given_name: 'Test',
last_logged_in: '2022-08-08T20:59:01.482461+00:00',
}
Loading
Loading