Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,57 @@
CREATE TABLE IF NOT EXISTS minespace_user_role (
minespace_user_role_code VARCHAR(3) PRIMARY KEY,
description VARCHAR(100) NOT NULL,
active_ind boolean DEFAULT true NOT NULL,
create_user VARCHAR(60) NOT NULL,
create_timestamp timestamp with time zone DEFAULT now() NOT NULL,
update_user VARCHAR(60) NOT NULL,
update_timestamp timestamp with time zone DEFAULT now() NOT NULL
);
COMMENT ON TABLE minespace_user_role IS 'Roles that a minespace user may have with a mine';

INSERT INTO minespace_user_role (
minespace_user_role_code,
description,
create_user,
update_user
) VALUES ('PMT', 'Permittee', 'system-mds', 'system-mds'),
('MMG', 'Mine Manager', 'system-mds', 'system-mds'),
('ADM', 'MineSpace Administrator', 'system-mds', 'system-mds'),
('EMP', 'Employee', 'system-mds', 'system-mds'),
('CON', 'Contractor/Consultant', 'system-mds', 'system-mds'),
('AGT', 'Agent', 'system-mds', 'system-mds'),
('NUL', 'General Public/Researcher', 'system-mds', 'system-mds');

CREATE TABLE IF NOT EXISTS minespace_user_role_xref (
minespace_user_role_xref_guid UUID DEFAULT gen_random_uuid() PRIMARY KEY,
minespace_user_role_code VARCHAR(3) NOT NULL,
minespace_user_id INT NOT NULL,
mine_guid UUID NOT NULL,
is_pending BOOLEAN NOT NULL,
deleted_ind BOOLEAN DEFAULT false NOT NULL,
create_user VARCHAR(60) NOT NULL,
create_timestamp timestamp with time zone DEFAULT now() NOT NULL,
update_user VARCHAR(60) NOT NULL,
update_timestamp timestamp with time zone DEFAULT now() NOT NULL,

CONSTRAINT fk_minespace_user_role_code FOREIGN KEY (minespace_user_role_code)
REFERENCES minespace_user_role(minespace_user_role_code),
CONSTRAINT fk_minespace_user_id FOREIGN KEY (minespace_user_id)
REFERENCES minespace_user(user_id),
CONSTRAINT fk_minespace_user_mine_role FOREIGN KEY (mine_guid)
REFERENCES mine(mine_guid)
);
COMMENT ON TABLE minespace_user_role_xref IS 'Xref between the minespace user and the role they have (or requested to have) for a mine';

CREATE TABLE IF NOT EXISTS minespace_user_document_xref (
minespace_user_document_xref_guid UUID DEFAULT gen_random_uuid() PRIMARY KEY,
minespace_user_id INT NOT NULL,
document_manager_guid UUID NOT NULL,
document_name VARCHAR(255) NOT NULL,
upload_date DATE DEFAULT now() NOT NULL,
deleted_ind BOOLEAN DEFAULT false NOT NULL,

CONSTRAINT fk_minespace_user_id FOREIGN KEY (minespace_user_id)
REFERENCES minespace_user(user_id)
);
COMMENT ON TABLE minespace_user_document_xref IS 'Authorization letter document tied to minespace user access request';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- Create minespace_user_request table to track user access request submissions
CREATE TABLE IF NOT EXISTS minespace_user_request (
minespace_user_request_id SERIAL PRIMARY KEY,
user_sub VARCHAR(255) NOT NULL UNIQUE,
minespace_user_id INT,
submitted_timestamp timestamp with time zone DEFAULT now() NOT NULL,
role_requested VARCHAR(3),
business_name VARCHAR(255),
access_request_text VARCHAR(100),
ministry_contact VARCHAR(100),
permittee JSONB,
request_status SMALLINT DEFAULT 0 NOT NULL,
create_user VARCHAR(60) NOT NULL,
create_timestamp timestamp with time zone DEFAULT now() NOT NULL,
update_user VARCHAR(60) NOT NULL,
update_timestamp timestamp with time zone DEFAULT now() NOT NULL,

CONSTRAINT fk_minespace_user_id FOREIGN KEY (minespace_user_id)
REFERENCES minespace_user(user_id),
CONSTRAINT fk_role_requested FOREIGN KEY (role_requested)
REFERENCES minespace_user_role(minespace_user_role_code)
);

COMMENT ON TABLE minespace_user_request IS 'Tracks minespace user access request form submissions. The user_sub from the BCeID token uniquely identifies the request and can be linked to minespace_user once created.';
COMMENT ON COLUMN minespace_user_request.user_sub IS 'BCeID sub claim from JWT token - uniquely identifies the user';
COMMENT ON COLUMN minespace_user_request.minespace_user_id IS 'Foreign key to minespace_user - will be null until the user record is created';
COMMENT ON COLUMN minespace_user_request.submitted_timestamp IS 'When the user submitted the access request form';
COMMENT ON COLUMN minespace_user_request.role_requested IS 'The role/position the user requested';
COMMENT ON COLUMN minespace_user_request.business_name IS 'Business name for contractors/consultants registered with Business BCeID';
COMMENT ON COLUMN minespace_user_request.access_request_text IS 'Additional text from the user about mines/permits not in the system';
COMMENT ON COLUMN minespace_user_request.ministry_contact IS 'Email of MCM Inspector or staff member the user has been in contact with';
COMMENT ON COLUMN minespace_user_request.permittee IS 'Permittee contact information in JSON format (name, title, email, phone)';
COMMENT ON COLUMN minespace_user_request.request_status IS 'Status of the request: 0 = Pending, 1 = Approved, 2 = Rejected';

-- Create index on user_sub for efficient lookups
CREATE INDEX idx_minespace_user_request_user_sub ON minespace_user_request(user_sub);
6 changes: 4 additions & 2 deletions services/common/src/components/common/ActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const generateActionMenuItems = (actionItems: ITableAction[], record) =>

export interface IHeaderAction {
key: string;
label: string;
label: string | ReactNode;
icon?: ReactNode;
clickFunction: () => void | Promise<void>;
}
Expand All @@ -52,13 +52,15 @@ export const ActionMenuButton: FC<{
buttonProps?: ButtonProps;
useEllipsis?: boolean; // View as ellipsis instead of a button
dataTestId?: string;
dropdownIcon?: ReactNode; // Optional custom icon for the dropdown
}> = ({
actions,
buttonText = "Action",
buttonProps,
disabled = false,
useEllipsis = false,
dataTestId = "",
dropdownIcon,
}) => {
const items = generateActionMenuItems((actions as unknown) as ITableAction[], null);

Expand All @@ -79,7 +81,7 @@ export const ActionMenuButton: FC<{
<Dropdown menu={{ items }} placement="bottomLeft" disabled={disabled}>
<Button {...mergedButtonProps} data-testid={dataTestId}>
{!useEllipsis && buttonText}
{!useEllipsis && <DownOutlined />}
{!useEllipsis && (dropdownIcon || <DownOutlined />)}
</Button>
</Dropdown>
);
Expand Down
5 changes: 4 additions & 1 deletion services/common/src/components/forms/RenderMultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const RenderMultiSelect: FC<MultiSelectProps> = (props) => {
showNA,
meta,
input,
help,
} = props;
const debouncedSearch = useRef(debounce(onSearch, 500)).current;

Expand Down Expand Up @@ -83,7 +84,6 @@ export const RenderMultiSelect: FC<MultiSelectProps> = (props) => {
((meta.error && <span>{meta.error}</span>) ||
(meta.warning && <span>{meta.warning}</span>))
}
getValueProps={() => input.value !== "" && { value: input.value }}
>
<Select
loading={props.loading}
Expand All @@ -96,11 +96,14 @@ export const RenderMultiSelect: FC<MultiSelectProps> = (props) => {
id={props.id ?? props.input.name}
onSearch={debouncedSearch}
options={data}
value={input.value === "" ? [] : input.value}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I had weird issues with the mines not being pre-populated from the form values and had to make this change. In making this change, I had to manually apply the aria-required (a bunch of snapshot tests showed it being removed. Now it added in a few aria-required=false but I'm okay with that)

onChange={input.onChange}
filterOption={filterOption || caseInsensitiveLabelFilter}
aria-required={props.required}
showArrow
{...extraProps}
></Select>
{help && <div className={`form-item-help ${input?.name}-form-help`}>{help}</div>}
</Form.Item>
</div>
);
Expand Down
3 changes: 3 additions & 0 deletions services/common/src/constants/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export const DOCUMENT_MANAGER_DOCUMENT = (documentManagerGuid) =>
`/documents/${documentManagerGuid}`;
export const MINESPACE_USER = (mine_guid?) => `/users/minespace${mine_guid ? `?${queryString.stringify({ mine_guid })}` : ""}`;
export const UPDATE_MINESPACE_USER = (id) => `/users/minespace/${id}`;
export const NEW_MINESPACE_USER_MINES = (params = {}) => `/users/minespace/mines?${queryString.stringify(params)}`;
export const NEW_MINESPACE_USER_DOCUMENTS = `/users/minespace/documents`;
export const NEW_MINESPACE_USER_ACCESS_REQUEST = `/users/minespace/access-request`;
export const PROVINCE_CODES = "/parties/sub-division-codes";

// MCM contacts
Expand Down
1 change: 1 addition & 0 deletions services/common/src/constants/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const STORE_CURRENT_USER_MINE_VERIFIED_STATUS = "STORE_CURRENT_USER_MINE_
export const STORE_MINE_LIST = "STORE_MINE_LIST";
export const STORE_MINE = "STORE_MINE";
export const STORE_MINE_NAME_LIST = "STORE_MINE_NAME_LIST";
export const STORE_MINE_SEARCH_RESULTS_FOR_NEW_USER = "STORE_MINE_SEARCH_RESULTS_FOR_NEW_USER";
export const STORE_SUBSCRIBED_MINES = "STORE_SUBSCRIBED_MINES";
export const STORE_MINE_BASIC_INFO_LIST = "STORE_MINE_BASIC_INFO_LIST";
export const STORE_STATUS_OPTIONS = "STORE_STATUS_OPTIONS";
Expand Down
4 changes: 4 additions & 0 deletions services/common/src/constants/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ export enum FORM {

// Permit Generation
GENERATE_PERMIT = "GENERATE_PERMIT",

// Minespace Users
MINESPACE_USER_ACCESS_APPROVAL = "MINESPACE_USER_ACCESS_APPROVAL",
NEW_MINESPACE_USER = "NEW_MINESPACE_USER",
}
11 changes: 11 additions & 0 deletions services/common/src/constants/strings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ export const BUSINESS_ROLES = {
projectLead: "PRL",
};

export const MINESPACE_POSITIONS = [
{ label: "Permittee", value: "PMT" },
{ label: "Mine Manager", value: "MMG" },
{ label: "MineSpace Administrator", value: "ADM" },
{ label: "Employee", value: "EMP" },
{ label: "Contractor/Consultant", value: "CON" },
{ label: "Agent", value: "AGT" },
];
// not a valid role, used only for the form on MS
export const NULL_MINESPACE_POSITION = { label: "General Public/Researcher", value: "NUL" };

export const NOT_APPLICABLE = "N/A";

// MDS email
Expand Down
50 changes: 49 additions & 1 deletion services/common/src/interfaces/minespaceUser.interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
export interface IMinespaceUserRole {
mine_guid: string;
minespace_user_role_code: string;
is_pending: boolean;
minespace_user_role_xref_guid?: string;
}

export interface IMinespaceUserDocument {
document_manager_guid: string;
document_name: string;
upload_date?: string;
}

export interface IMinespaceUserPermittee {
name: string;
title: string;
email: string;
phone: string;
}

export interface IMinespaceUserAccessRequest {
minespace_user_request_id?: number;
role_requested: string;
business_name?: string;
permittee?: IMinespaceUserPermittee;
submitted_timestamp?: string;
request_status?: number; // 0 = Pending, 1 = Approved, 2 = Rejected
access_request_text?: string;
ministry_contact?: string;
// Form-only fields
mineNotInList?: boolean;
consent_privacy?: boolean;
consent_electronic?: boolean;
}

export interface IMinespaceUser {
user_id: number;
sub: string;
Expand All @@ -9,9 +44,22 @@ export interface IMinespaceUser {
identity_provider: string;
last_logged_in: string;
mines: string[];
user_roles?: IMinespaceUserRole[];
documents?: IMinespaceUserDocument[];
delegation_letter?: IMinespaceUserDocument[];
access_request?: IMinespaceUserAccessRequest;
}

export interface IMinespaceUserMine {
mine_guid: string;
mine_name: string;
}
mine_no: string;
}

export interface MineSearchResultForNewUser {
mine_guid: string;
mine_name: string | null;
mine_no: string;
permit_guid: string | null;
permit_no: string | null;
}
13 changes: 13 additions & 0 deletions services/common/src/redux/actionCreators/mineActionCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,16 @@ export const deleteMineComment = (mineGuid, commentGuid) => (dispatch) => {
dispatch(error(NetworkReducerTypes.DELETE_MINE_COMMENT));
});
};

export const fetchMineSearchResultsForNewUser = (searchTerm: string) => (dispatch) => {
dispatch(request(NetworkReducerTypes.GET_MINE_NAME_LIST));
dispatch(showLoading());
return CustomAxios()
.get(ENVIRONMENT.apiUrl + API.NEW_MINESPACE_USER_MINES({ search: searchTerm }), createRequestHeader())
.then((response) => {
dispatch(success(NetworkReducerTypes.GET_MINE_NAME_LIST));
dispatch(mineActions.storeMineSearchResultsForNewUser(response.data));
})
.catch(() => dispatch(error(NetworkReducerTypes.GET_MINE_NAME_LIST)))
.finally(() => dispatch(hideLoading()));
};
7 changes: 7 additions & 0 deletions services/common/src/redux/actions/authenticationActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@ export const storeUserAccessData = (roles = {}) => ({
roles,
},
});

export const storeIsProponent = (data: boolean) => ({
type: ActionTypes.STORE_IS_PROPONENT,
payload: {
data,
},
});
5 changes: 5 additions & 0 deletions services/common/src/redux/actions/mineActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export const storeMineComments = (payload) => ({
payload,
});

export const storeMineSearchResultsForNewUser = (payload) => ({
type: ActionTypes.STORE_MINE_SEARCH_RESULTS_FOR_NEW_USER,
payload,
});

export const storeEpicInfo = (payload) => ({
type: ActionTypes.STORE_MINE_EPIC_INFO,
payload,
Expand Down
8 changes: 7 additions & 1 deletion services/common/src/redux/reducers/authenticationReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IUserInfo } from "@mds/common/interfaces";
import { SystemFlagEnum } from "@mds/common/constants/enums";
import { RootState } from "@mds/common/redux/rootState";
import * as ReducerTypes from "@mds/common/constants/reducerTypes";
import { Feature, isFeatureEnabled } from "@mds/common/utils/featureFlag";

interface IAuthenticationReducerState {
isAuthenticated: boolean;
Expand Down Expand Up @@ -53,9 +54,14 @@ export const authenticationReducer = (state = initialState, action) => {
redirect: GLOBAL_ROUTES?.MINES?.route,
};
case ActionTypes.STORE_USER_ACCESS_DATA:
// keycloak roles
const roles = action.payload.roles;
const hasRoles = roles?.length > 0;
const msNewLoginEnabled = isFeatureEnabled(Feature.MINESPACE_SIGNUP);
return {
...state,
userAccessData: action.payload.roles,
userAccessData: roles ?? [],
redirect: hasRoles || !msNewLoginEnabled ? state.redirect : GLOBAL_ROUTES?.NEW_USER?.route,
};
case ActionTypes.STORE_IS_PROPONENT:
return {
Expand Down
10 changes: 9 additions & 1 deletion services/common/src/redux/reducers/mineReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RootState } from "@mds/common/redux/rootState";
import * as actionTypes from "@mds/common/constants/actionTypes";
import { MINES } from "@mds/common/constants/reducerTypes";
import { createItemMap, createItemIdsArray } from "../utils/helpers";
import { IMine, IMineComment, IMineDocument, IMineSearch, ItemMap } from "@mds/common/interfaces";
import { IMine, IMineComment, IMineDocument, IMineSearch, ItemMap, MineSearchResultForNewUser } from "@mds/common/interfaces";
/**
* @file mineReducer.js
* all data associated with new mine/existing mine records is handled witnin this reducer.
Expand All @@ -19,6 +19,7 @@ interface MineState {
mineComments: IMineComment[];
currentUserVerifiedMines: IMine[];
currentUserUnverifiedMinesMines: IMine[];
mineSearchResultsForNewUser: MineSearchResultForNewUser[];
}

const initialState: MineState = {
Expand All @@ -33,6 +34,7 @@ const initialState: MineState = {
mineComments: [],
currentUserVerifiedMines: [],
currentUserUnverifiedMinesMines: [],
mineSearchResultsForNewUser: [],
};

export const mineReducer = (state: MineState = initialState, action) => {
Expand Down Expand Up @@ -85,6 +87,11 @@ export const mineReducer = (state: MineState = initialState, action) => {
...state,
mineComments: action.payload.records,
};
case actionTypes.STORE_MINE_SEARCH_RESULTS_FOR_NEW_USER:
return {
...state,
mineSearchResultsForNewUser: action.payload.mines,
};
default:
return state;
}
Expand All @@ -108,5 +115,6 @@ export const getCurrentUserVerifiedMines = (state: RootState): IMine[] =>
export const getCurrentUserUnverifiedMines = (state: RootState): IMine[] =>
state[MINES].currentUserUnverifiedMinesMines;
export const getMineComments = (state: RootState): IMineComment[] => state[MINES].mineComments;
export const getMineSearchResultsForNewUser = (state: RootState) => state[MINES].mineSearchResultsForNewUser;

export default mineReducerObject;
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export const getFormattedUserName = createSelector([getUserInfo], (userInfo) =>
return identity_provider === "idir" ? `idir\\${preferred_username}` : preferred_username;
});

export const isNewProponent = createSelector([getUserAccessData], (roles) => {
if (!(roles?.length > 0)) {
return true;
}
return false;
})

export const userHasRole = (role: string) =>
createSelector([getUserAccessData], (roles) => {
return roles.includes(USER_ROLES[role] ?? role);
Expand Down
1 change: 1 addition & 0 deletions services/common/src/redux/selectors/mineSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const getMineDocuments = (state) => mineReducer.getMineDocuments(state);
export const getSubscribedMines = (state) => mineReducer.getSubscribedMines(state).records;
export const getSubscribedMinesLoaded = (state) => mineReducer.getSubscribedMines(state).loaded;
export const getMineComments = (state) => mineReducer.getMineComments(state);
export const getMineSearchResultsForNewUser = (state) => mineReducer.getMineSearchResultsForNewUser(state);

export const getMineById = (mineGuid: string) =>
createSelector([getMines], (mines) => {
Expand Down
Loading
Loading