diff --git a/cypress/realm-export.json b/cypress/realm-export.json index e923b50d86..0ea19a84d2 100644 --- a/cypress/realm-export.json +++ b/cypress/realm-export.json @@ -69,9 +69,15 @@ "description": "${role_default-roles}", "composite": true, "composites": { - "realm": ["offline_access", "uma_authorization"], + "realm": [ + "offline_access", + "uma_authorization" + ], "client": { - "account": ["manage-account", "view-profile"] + "account": [ + "manage-account", + "view-profile" + ] } }, "clientRole": false, @@ -748,6 +754,7 @@ "core_edit_mines", "core_edit_investigations", "core_edit_variances", + "core_view_admin_route", "core_environmental_reports", "core_edit_permits", "nris_view_all", @@ -759,7 +766,49 @@ "core_edit_securities", "core_geospatial", "mds_regional_mines", - "mds_users" + "mds_users", + "core_edit_requirements", + "c_mds_major_mines_administrative", + "digdag_admin", + "docman_manager", + "core_edit_code", + "core_edit_now_dates", + "core_edit_emli_contacts", + "cs_mds_geospatial", + "c_mds_users", + "cs_mds_tsf", + "c_mds_permit_mines", + "c_mds_administrative_mines", + "c_mds_overlord", + "cs_mds_abandoned_mines", + "core_edit_project_decision_packages", + "Chief Inspector", + "core_edit_historical_amendments", + "c_mds_health_safety", + "Data Cleanup Team", + "MDS Keycloak Realm Admin", + "core_executive_view", + "c_idir", + "cs_mds_investigations", + "cs_mds_permit_template_conditions", + "Administrative Users", + "mds_chief_inspector", + "mds_data_cleanup", + "core_edit_major_mine_applications", + "c_mds_super_admin", + "cs_mds_securities", + "core_edit_template_conditions", + "mds_application_admins", + "core_edit_tsf", + "core_edit_project_summaries", + "core_edit_incidents", + "Core Application Admins", + "core_edit_explosives_permits", + "mds_administrative_users", + "test_full_permissions", + "cs_mds_executive", + "core_edit_information_requirements_table", + "core_admin" ] } }, @@ -858,7 +907,9 @@ "composite": true, "composites": { "client": { - "realm-management": ["query-clients"] + "realm-management": [ + "query-clients" + ] } }, "clientRole": true, @@ -1022,7 +1073,10 @@ "composite": true, "composites": { "client": { - "realm-management": ["query-users", "query-groups"] + "realm-management": [ + "query-users", + "query-groups" + ] } }, "clientRole": true, @@ -1106,7 +1160,9 @@ "composite": true, "composites": { "client": { - "account": ["manage-account-links"] + "account": [ + "manage-account-links" + ] } }, "clientRole": true, @@ -1129,7 +1185,9 @@ "composite": true, "composites": { "client": { - "account": ["view-consent"] + "account": [ + "view-consent" + ] } }, "clientRole": true, @@ -1148,16 +1206,23 @@ "clientRole": false, "containerId": "standard" }, - "requiredCredentials": ["password"], + "requiredCredentials": [ + "password" + ], "otpPolicyType": "totp", "otpPolicyAlgorithm": "HmacSHA1", "otpPolicyInitialCounter": 0, "otpPolicyDigits": 6, "otpPolicyLookAheadWindow": 1, "otpPolicyPeriod": 30, - "otpSupportedApplications": ["FreeOTP", "Google Authenticator"], + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], "webAuthnPolicyRpEntityName": "keycloak", - "webAuthnPolicySignatureAlgorithms": ["ES256"], + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], "webAuthnPolicyRpId": "", "webAuthnPolicyAttestationConveyancePreference": "not specified", "webAuthnPolicyAuthenticatorAttachment": "not specified", @@ -1167,7 +1232,9 @@ "webAuthnPolicyAvoidSameAuthenticatorRegister": false, "webAuthnPolicyAcceptableAaguids": [], "webAuthnPolicyPasswordlessRpEntityName": "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], "webAuthnPolicyPasswordlessRpId": "", "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", @@ -1179,14 +1246,18 @@ "scopeMappings": [ { "clientScope": "offline_access", - "roles": ["offline_access"] + "roles": [ + "offline_access" + ] } ], "clientScopeMappings": { "account": [ { "client": "account-console", - "roles": ["manage-account"] + "roles": [ + "manage-account" + ] } ] }, @@ -1201,7 +1272,9 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": ["/realms/standard/account/*"], + "redirectUris": [ + "/realms/standard/account/*" + ], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -1217,8 +1290,18 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": ["web-origins", "profile", "roles", "email"], - "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, { "id": "6769a1d0-e2f7-4535-b6c8-e0f2c40d7cc1", @@ -1230,7 +1313,9 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": ["/realms/standard/account/*"], + "redirectUris": [ + "/realms/standard/account/*" + ], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -1258,8 +1343,18 @@ "config": {} } ], - "defaultClientScopes": ["web-origins", "profile", "roles", "email"], - "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, { "id": "d3533e96-2506-4553-a4e4-ac70e400d57d", @@ -1285,8 +1380,18 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": ["web-origins", "profile", "roles", "email"], - "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, { "id": "169b2d1e-dc91-4edc-afd8-d8170a4a41cf", @@ -1312,8 +1417,18 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": ["web-origins", "profile", "roles", "email"], - "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, { "id": "38bf108d-56a4-46a3-9929-feaa6fca039a", @@ -1323,8 +1438,12 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": ["*"], - "webOrigins": ["*"], + "redirectUris": [ + "*" + ], + "webOrigins": [ + "*" + ], "notBefore": 0, "bearerOnly": false, "consentRequired": false, @@ -1454,7 +1573,12 @@ "bceidboth", "email" ], - "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, { "id": "9e61e34d-c140-4083-8cdc-70e87037963f", @@ -1480,8 +1604,18 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": ["web-origins", "profile", "roles", "email"], - "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, { "id": "a1c0963c-8a39-4578-9412-2dfada8f2324", @@ -1493,8 +1627,12 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": ["/admin/standard/console/*"], - "webOrigins": ["+"], + "redirectUris": [ + "/admin/standard/console/*" + ], + "webOrigins": [ + "+" + ], "notBefore": 0, "bearerOnly": false, "consentRequired": false, @@ -1528,8 +1666,18 @@ } } ], - "defaultClientScopes": ["web-origins", "profile", "roles", "email"], - "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] } ], "clientScopes": [ @@ -2045,8 +2193,19 @@ } } ], - "defaultDefaultClientScopes": ["role_list", "profile", "email", "roles", "web-origins"], - "defaultOptionalClientScopes": ["offline_access", "address", "phone", "microprofile-jwt"], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], "browserSecurityHeaders": { "contentSecurityPolicyReportOnly": "", "xContentTypeOptions": "nosniff", @@ -2058,7 +2217,9 @@ }, "smtpServer": {}, "eventsEnabled": false, - "eventsListeners": ["jboss-logging"], + "eventsListeners": [ + "jboss-logging" + ], "enabledEventTypes": [], "adminEventsEnabled": false, "adminEventsDetailsEnabled": false, @@ -2073,7 +2234,9 @@ "subType": "anonymous", "subComponents": {}, "config": { - "allow-default-scopes": ["true"] + "allow-default-scopes": [ + "true" + ] } }, { @@ -2083,8 +2246,12 @@ "subType": "anonymous", "subComponents": {}, "config": { - "host-sending-registration-request-must-match": ["true"], - "client-uris-must-match": ["true"] + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] } }, { @@ -2094,7 +2261,9 @@ "subType": "authenticated", "subComponents": {}, "config": { - "allow-default-scopes": ["true"] + "allow-default-scopes": [ + "true" + ] } }, { @@ -2158,7 +2327,9 @@ "subType": "anonymous", "subComponents": {}, "config": { - "max-clients": ["200"] + "max-clients": [ + "200" + ] } } ], @@ -2169,8 +2340,12 @@ "providerId": "rsa-enc-generated", "subComponents": {}, "config": { - "priority": ["100"], - "algorithm": ["RSA-OAEP"] + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] } }, { @@ -2179,7 +2354,9 @@ "providerId": "rsa-generated", "subComponents": {}, "config": { - "priority": ["100"] + "priority": [ + "100" + ] } }, { @@ -2188,7 +2365,9 @@ "providerId": "aes-generated", "subComponents": {}, "config": { - "priority": ["100"] + "priority": [ + "100" + ] } }, { @@ -2197,8 +2376,12 @@ "providerId": "hmac-generated", "subComponents": {}, "config": { - "priority": ["100"], - "algorithm": ["HS256"] + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] } } ] @@ -2894,4 +3077,4 @@ "clientPolicies": { "policies": [] } -} +} \ No newline at end of file diff --git a/services/common/jest.config.js b/services/common/jest.config.js index 7e519b840b..0f2d5c596f 100644 --- a/services/common/jest.config.js +++ b/services/common/jest.config.js @@ -8,7 +8,7 @@ module.exports = { }, ], }, - maxWorkers: 4, + maxWorkers: '50%', verbose: true, testEnvironmentOptions: { customExportConditions: [""], diff --git a/services/common/src/components/common/ScrollSidePageWrapper.tsx b/services/common/src/components/common/ScrollSidePageWrapper.tsx index c89ec1d8db..59db00f6f0 100644 --- a/services/common/src/components/common/ScrollSidePageWrapper.tsx +++ b/services/common/src/components/common/ScrollSidePageWrapper.tsx @@ -8,6 +8,7 @@ interface ScrollSidePageWrapperProps { content: ReactNode; menuProps?: ScrollSideMenuProps; header: ReactNode; + extraItems?: ReactNode; headerHeight?: number; view?: "default" | "steps" | "anchor"; } @@ -15,14 +16,6 @@ interface ScrollSidePageWrapperProps { export const coreHeaderHeight = 62; // match scss variable $header-height const msHeaderHeight = 80; -interface ExtraMenuItemProps { - children: ReactNode; -} - -interface ScrollSidePageWrapperSubcomponents { - ExtraMenuItem: React.FC; -} - /** * A wrapper component that provides a side menu and a content area. The side menu can be fixed to the top of the page. * The menu links will act as an achor to sections in the content area scroll to the section when clicked, and highlight the active section. @@ -35,12 +28,12 @@ interface ScrollSidePageWrapperSubcomponents { * * */ -const ScrollSidePageWrapper: FC & ScrollSidePageWrapperSubcomponents = ({ +const ScrollSidePageWrapper: FC = ({ menuProps, content, header, + extraItems, view = "anchor", - children, headerHeight = 170, }) => { const [isFixedTop, setIsFixedTop] = useState(false); @@ -81,8 +74,6 @@ const ScrollSidePageWrapper: FC & ScrollSidePageWrap const menuTopOffset = hasHeader || isFixedTop ? topOffset : 0; const contentTopOffset = hasHeader && isFixedTop ? headerHeight : 0; - const extraItems = React.Children.map(children, child => (child as ReactElement).type?.displayName === 'ExtraMenuItem' ? child : null); - return (
{hasHeader && ( @@ -101,7 +92,7 @@ const ScrollSidePageWrapper: FC & ScrollSidePageWrap {/* the 24 matches the margin/padding on the menu/content. Looks nicer */} - {extraItems} + {extraItems ? extraItems : ''}
)}
@@ -111,11 +102,4 @@ const ScrollSidePageWrapper: FC & ScrollSidePageWrap ); }; -const ExtraMenuItem: FC = ({ children }) => ( -
{children}
-); - -ExtraMenuItem.displayName = 'ExtraMenuItem'; -ScrollSidePageWrapper.ExtraMenuItem = ExtraMenuItem; - export default ScrollSidePageWrapper; diff --git a/services/common/src/components/forms/FormWrapper.tsx b/services/common/src/components/forms/FormWrapper.tsx index 01b5a5cda8..749ab7eb7f 100644 --- a/services/common/src/components/forms/FormWrapper.tsx +++ b/services/common/src/components/forms/FormWrapper.tsx @@ -66,6 +66,7 @@ export interface FormWrapperProps { loading?: boolean; isEditMode?: boolean; scrollOnToggleEdit?: boolean; + layout?: "inline" | "horizontal" | "vertical"; } const FormWrapper: FC> = ({ @@ -73,6 +74,7 @@ const FormWrapper: FC> = ({ isModal = false, scrollOnToggleEdit = true, children, + layout, ...props }) => { const providerValues = { @@ -102,7 +104,7 @@ const FormWrapper: FC> = ({ return (
{ if (!value.isEditMode) { return ; } + + const ariaLabel = props.label || props.input.name; + return ( { } >
+
{ + console.log(action); console.log(action.error); console.log(action.error.stack); }; diff --git a/services/common/src/redux/selectors/permitSelectors.spec.ts b/services/common/src/redux/selectors/permitSelectors.spec.ts index b538e284a1..3f2ebee223 100644 --- a/services/common/src/redux/selectors/permitSelectors.spec.ts +++ b/services/common/src/redux/selectors/permitSelectors.spec.ts @@ -9,6 +9,7 @@ import { permitReducer } from "@mds/common/redux/reducers/permitReducer"; import { storeEditingConditionFlag, storeEditingPreambleFlag, + storePermits, } from "@mds/common/redux/actions/permitActions"; import { PERMITS } from "@mds/common/constants/reducerTypes"; import * as MOCK from "@mds/common/tests/mocks/dataMocks"; @@ -51,10 +52,17 @@ describe("permitSelectors", () => { expect(actual).toEqual(permit); }); it("`getLatestAmendmentByPermitGuid returns the latest permit amendment", () => { + + const storeAction = storePermits({ records: MOCK.PERMITS }); + + const permit = MOCK.PERMITS[0]; + + const storeState = permitReducer({} as any, storeAction); + const localMockState = { - [PERMITS]: { permits: mockPermits }, + [PERMITS]: storeState, }; - const permit = MOCK.PERMITS[0]; + const latestAmendment = getLatestAmendmentByPermitGuid(permit.permit_guid)( localMockState as RootState ); diff --git a/services/common/src/redux/slices/permitServiceSlice.ts b/services/common/src/redux/slices/permitServiceSlice.ts index 4564c13ad1..07e1161714 100644 --- a/services/common/src/redux/slices/permitServiceSlice.ts +++ b/services/common/src/redux/slices/permitServiceSlice.ts @@ -31,7 +31,7 @@ const permitExtractionStatusMap = { SUCCESS: PermitExtractionStatus.complete, }; -interface PermitExtraction { +export interface PermitExtraction { task_status: PermitExtractionStatus; task_id: string; } diff --git a/services/common/src/tests/handlers.ts b/services/common/src/tests/handlers.ts index c64f9dd9d5..4fe6382748 100644 --- a/services/common/src/tests/handlers.ts +++ b/services/common/src/tests/handlers.ts @@ -3,6 +3,8 @@ import { GEOMARK_DATA, HELP_GUIDE_CORE, HELP_GUIDE_MS, + MINE_REPORT_CATEGORY_OPTIONS, + PERMIT_CONDITION_EXTRACTION, PROJECT, PROJECT_SUMMARY_MINISTRY_COMMENTS, } from "@mds/common/tests/mocks/dataMocks"; @@ -26,6 +28,21 @@ const projectHandlers = [ ), ]; +const permitHandlers = [ + http.get("/%3CAPI_URL%3E/mines/permits/condition-extraction", async () => { + return HttpResponse.json({ + "tasks": PERMIT_CONDITION_EXTRACTION + }) + }), + http.get("/%3CAPI_URL%3E/mines/permits/condition-category-codes", async () => { + return HttpResponse.json({ + "tasks": MINE_REPORT_CATEGORY_OPTIONS + }) + }), + + +] + const helpHandler = http.get("/%3CAPI_URL%3E/help/:helpKey", async ({ request, params }) => { const { helpKey } = params; const url = new URL(request.url); @@ -37,6 +54,6 @@ const helpHandler = http.get("/%3CAPI_URL%3E/help/:helpKey", async ({ request, p return HttpResponse.json(response); }); -const commonHandlers = [...geoSpatialHandlers, ...projectHandlers, helpHandler]; +const commonHandlers = [...geoSpatialHandlers, ...projectHandlers, helpHandler, ...permitHandlers]; export default commonHandlers; diff --git a/services/common/src/tests/mocks/dataMocks.tsx b/services/common/src/tests/mocks/dataMocks.tsx index f86ec17d85..9b0f8bc84a 100644 --- a/services/common/src/tests/mocks/dataMocks.tsx +++ b/services/common/src/tests/mocks/dataMocks.tsx @@ -20,6 +20,7 @@ import { VC_CONNECTION_STATES, VC_CRED_ISSUE_STATES, } from "@mds/common/constants"; +import { PermitExtraction } from "@mds/common/redux/slices/permitServiceSlice"; export const createMockHeader = () => ({ headers: { @@ -1132,6 +1133,13 @@ export const MINE_TSF_REQUIRED_REPORTS_HASH = { "faa99067-3639-4d9c-a3e5-5401df15ad4b": "5 year DSR", }; +export const PERMIT_CONDITION_EXTRACTION = [ + { + "task_id": "abc123", + "task_status": "SUCCESS", + } +] + export const PERMITS: IPermit[] = [ { permit_id: "283", @@ -1157,6 +1165,20 @@ export const PERMITS: IPermit[] = [ permit_conditions_last_updated_by: "Condition Updater", permit_conditions_last_updated_date: "2019-04-04", has_permit_conditions: false, + condition_categories: [ + { + condition_category_code: "HSC", + description: "Health and Safety", + display_order: 0, + step: 'A.' + }, + { + condition_category_code: "RCC", + description: "Reclamation", + display_order: 1, + step: 'B.' + } + ], conditions: [ { permit_condition_id: 1639, @@ -1379,6 +1401,7 @@ export const PERMITS: IPermit[] = [ authorization_end_date: null, liability_adjustment: 1000000, description: "Initial permit issued.", + condition_categories: [], related_documents: [ { permit_id: 283, @@ -1464,6 +1487,7 @@ export const PERMITS: IPermit[] = [ conditions: [], is_generated_in_core: false, preamble_text: null, + condition_categories: [], }, ], remaining_static_liability: null, diff --git a/services/core-api/app/api/mines/permits/permit_conditions/models/permit_condition_category.py b/services/core-api/app/api/mines/permits/permit_conditions/models/permit_condition_category.py index de1310d931..4531a92743 100644 --- a/services/core-api/app/api/mines/permits/permit_conditions/models/permit_condition_category.py +++ b/services/core-api/app/api/mines/permits/permit_conditions/models/permit_condition_category.py @@ -59,6 +59,7 @@ def get_all(cls): def search(cls, query=None, exclude=None, limit=None): quer = cls.query \ .filter_by(deleted_ind=False) + if query: quer = quer.filter(db.func.lower(cls.description).ilike(f'%{query.lower()}%')) else: @@ -77,9 +78,7 @@ def search(cls, query=None, exclude=None, limit=None): if limit: quer = quer.limit(limit) elif query: - quer = quer.limit(5) - - + quer = quer.limit(7) return quer.all() @@ -96,4 +95,16 @@ def find_by_permit_amendment_id(cls, permit_amendment_id): return cls.query \ .filter_by(permit_amendment_id=permit_amendment_id, deleted_ind=False) \ .order_by(cls.display_order) \ - .all() \ No newline at end of file + .all() + @classmethod + def delete_all_by_permit_amendment_id(cls, permit_amendment_id): + to_delete = cls.query \ + .filter_by( + permit_amendment_id=permit_amendment_id, + deleted_ind=False + ).all() + + for cat in to_delete: + cat.delete(commit=False) + + db.session.commit() diff --git a/services/core-api/app/api/mines/permits/permit_conditions/models/permit_conditions.py b/services/core-api/app/api/mines/permits/permit_conditions/models/permit_conditions.py index 7f9d81de99..a4312a69d8 100644 --- a/services/core-api/app/api/mines/permits/permit_conditions/models/permit_conditions.py +++ b/services/core-api/app/api/mines/permits/permit_conditions/models/permit_conditions.py @@ -118,18 +118,20 @@ def delete_all_by_permit_amendment_id(cls, permit_amendment_id, commit=False): parent_permit_condition_id=None, deleted_ind=False).order_by(cls.display_order).all() for condition in parent_conditions: - condition.delete_condition() + condition.delete_condition(commit=commit) if commit: condition.save() - def delete_condition(self): + def delete_condition(self, commit=False): if self.all_sub_conditions is not None: subconditions = [c for c in self.all_sub_conditions if c.deleted_ind == False] if len(subconditions) > 0: for item in subconditions: item.deleted_ind = True - item.delete_condition() + item.delete_condition(commit=commit) + if commit: + item.save() self.deleted_ind = True diff --git a/services/core-api/app/api/mines/permits/permit_conditions/resources/permit_condition_category_resource.py b/services/core-api/app/api/mines/permits/permit_conditions/resources/permit_condition_category_resource.py index 9aefd086d9..58d14a9a6d 100644 --- a/services/core-api/app/api/mines/permits/permit_conditions/resources/permit_condition_category_resource.py +++ b/services/core-api/app/api/mines/permits/permit_conditions/resources/permit_condition_category_resource.py @@ -24,5 +24,5 @@ def get(self): return PermitConditionCategory.search( query = data['query'], exclude = data.get('exclude'), - limit = data.get('limit') or 5, + limit = data.get('limit') or 7, ) diff --git a/services/core-api/app/api/mines/permits/permit_extraction/create_permit_conditions.py b/services/core-api/app/api/mines/permits/permit_extraction/create_permit_conditions.py index f469c978a3..8cddcf9180 100644 --- a/services/core-api/app/api/mines/permits/permit_extraction/create_permit_conditions.py +++ b/services/core-api/app/api/mines/permits/permit_extraction/create_permit_conditions.py @@ -31,8 +31,8 @@ 5: 'LIS', } -# For conditions that don't match any category, put them in the "General" category -DEFAULT_CATEGORY = 'GEC' +# For conditions that don't match any category, put them in a "Terms and conditions" category +DEFAULT_CATEGORY_TEXT = 'Terms and Conditions' def create_permit_conditions_from_task(task: PermitExtractionTask): """ @@ -50,7 +50,7 @@ def create_permit_conditions_from_task(task: PermitExtractionTask): if not has_category: top_level_section = PermitConditionResult( section='A', - condition_text='General' + condition_text=DEFAULT_CATEGORY_TEXT ) for c in conditions: c.set_section(top_level_section) @@ -58,6 +58,7 @@ def create_permit_conditions_from_task(task: PermitExtractionTask): num_categories = 0 + default_section = None for idx, condition in enumerate(conditions): if condition.is_top_level_section: @@ -67,6 +68,8 @@ def create_permit_conditions_from_task(task: PermitExtractionTask): display_order=num_categories, step=condition.step ) + if condition.condition_text == DEFAULT_CATEGORY_TEXT: + default_section = section_category current_category = section_category num_categories += 1 else: @@ -75,7 +78,18 @@ def create_permit_conditions_from_task(task: PermitExtractionTask): title_cond = None - category_code = current_category or DEFAULT_CATEGORY + if not current_category and not default_section: + default_section = _create_permit_condition_category( + condition=PermitConditionResult( + section='A', + condition_text=DEFAULT_CATEGORY_TEXT + ), + permit_amendment=task.permit_amendment, + display_order=num_categories, + step='A' + ) + + category_code = current_category or default_section if condition.condition_title: title_cond = _create_title_condition(task, category_code, condition, parent, idx, type_code) diff --git a/services/core-api/app/api/mines/permits/permit_extraction/resources/permit_condition_extraction_resource.py b/services/core-api/app/api/mines/permits/permit_extraction/resources/permit_condition_extraction_resource.py index f402addae9..49308e924c 100644 --- a/services/core-api/app/api/mines/permits/permit_extraction/resources/permit_condition_extraction_resource.py +++ b/services/core-api/app/api/mines/permits/permit_extraction/resources/permit_condition_extraction_resource.py @@ -4,6 +4,9 @@ from app.api.mines.permits.permit_amendment.models.permit_amendment_document import ( PermitAmendmentDocument, ) +from app.api.mines.permits.permit_conditions.models.permit_condition_category import ( + PermitConditionCategory, +) from app.api.mines.permits.permit_conditions.models.permit_conditions import ( PermitConditions, ) @@ -117,6 +120,7 @@ def delete(self): args = parser.parse_args() PermitConditions.delete_all_by_permit_amendment_id(args['permit_amendment_id'], commit=True) + PermitConditionCategory.delete_all_by_permit_amendment_id(args['permit_amendment_id']) class PermitConditionExtractionProgressResource(Resource, UserMixin): diff --git a/services/core-api/tests/mines/permit/resources/test_permit_condition_category_resource.py b/services/core-api/tests/mines/permit/resources/test_permit_condition_category_resource.py index b016cb6fdd..d5b8129628 100644 --- a/services/core-api/tests/mines/permit/resources/test_permit_condition_category_resource.py +++ b/services/core-api/tests/mines/permit/resources/test_permit_condition_category_resource.py @@ -14,114 +14,6 @@ from tests.factories import PermitAmendmentFactory, create_mine_and_permit -# POST -def test_get_permit_conditions_by_permit_amendment_by_guid(test_client, db_session, auth_headers): - mine, permit = create_mine_and_permit() - permit_amendment = permit.permit_amendments[0] - - data = { - "condition_category_code": "GEC", - "condition_type_code": "LIS", - "step": "A", - "display_order": 4, - "description": "TEST" - } - - post_resp = test_client.post( - f'/mines/{permit_amendment.mine_guid}/permits/{permit_amendment.permit_guid}/amendments/{permit_amendment.permit_amendment_guid}/conditions', - headers=auth_headers['full_auth_header'], - json=data) - post_data = json.loads(post_resp.data.decode()) - assert post_resp.status_code == 201, post_resp.response - assert str(post_data['permit_amendment_id']) == str(permit_amendment.permit_amendment_id) - -# DELETE -def test_delete_permit_condition(test_client, db_session, auth_headers): - mine, permit = create_mine_and_permit() - permit_amendment = permit.permit_amendments[0] - condition = permit_amendment.conditions[0] - - delete_resp = test_client.delete( - f'/mines/{permit_amendment.mine_guid}/permits/{permit_amendment.permit_guid}/amendments/{permit_amendment.permit_amendment_guid}/conditions/{condition.permit_condition_guid}', - headers=auth_headers['full_auth_header']) - - # the API returned success - assert delete_resp.status_code == 204 - # the first condition should now be deleted - assert permit_amendment.conditions[0].permit_condition_guid != condition.permit_condition_guid - # deleted items should be filtered out - assert permit_amendment.conditions[0].deleted_ind != True - -# PUT -def test_put_permit_condition(test_client, db_session, auth_headers): - mine, permit = create_mine_and_permit() - permit_amendment = permit.permit_amendments[0] - condition = permit_amendment.conditions[0] - - data = { - "permit_condition_guid": condition.permit_condition_guid, - "permit_amendment_id": condition.permit_amendment_id, - "condition_category_code": condition.condition_category_code, - "condition_type_code": condition.condition_type_code, - "condition": "edited", - "display_order": "2", - } - - put_resp = test_client.put( - f'/mines/{permit_amendment.mine_guid}/permits/{permit_amendment.permit_guid}/amendments/{permit_amendment.permit_amendment_guid}/conditions/{condition.permit_condition_guid}', - headers=auth_headers['full_auth_header'], - json=data) - - # the API returned success - assert put_resp.status_code == 200 - - response_json = put_resp.json - assert "permit_condition_guid" in response_json - permit_condition_guid = response_json["permit_condition_guid"] - - # Fetch the updated condition using the class method - updated_condition = PermitConditions.find_by_permit_condition_guid(permit_condition_guid) - - # Access the versioning table - version_records = list(updated_condition.versions) - - # Ensure there are version records - assert len(version_records) == 1 - - # Get the latest version record - latest_version = version_records[len(version_records) - 1] - - # Assert the latest version has the updated values - assert latest_version.condition == data['condition'] - - assert latest_version.permit_amendment_id == data['permit_amendment_id'] - assert latest_version.condition_category_code == data['condition_category_code'] - assert latest_version.condition_type_code == data['condition_type_code'] - - data_b = { - "permit_condition_guid": condition.permit_condition_guid, - "permit_amendment_id": condition.permit_amendment_id, - "condition_category_code": condition.condition_category_code, - "condition_type_code": condition.condition_type_code, - "condition": "version 2", - "display_order": "3", - } - - put_resp_b = test_client.put( - f'/mines/{permit_amendment.mine_guid}/permits/{permit_amendment.permit_guid}/amendments/{permit_amendment.permit_amendment_guid}/conditions/{condition.permit_condition_guid}', - headers=auth_headers['full_auth_header'], - json=data_b) - - assert put_resp_b.status_code == 200 - - version_records = list(updated_condition.versions) - - # Ensure there are now 2 version records - assert len(version_records) == 2 - - latest_version = version_records[len(version_records) - 1] - assert latest_version.condition == data_b['condition'] - def test_get_permit_condition_categories(test_client, db_session, auth_headers): """Test getting all permit condition categories.""" @@ -296,7 +188,7 @@ def fetch_categories(): assert get_resp.status_code == 200 return get_resp.json['records'] - + records = fetch_categories() assert len(records) > 0 # Create new category and verify that it gets returned when @@ -309,9 +201,9 @@ def fetch_categories(): permit_amendment_id=None ) - new_records = fetch_categories() + nr = fetch_categories() - assert len(new_records) == len(records) + 1 + assert len(nr) == len(records) + 1 # Create new category and verify that it does not get returned when # a permit amendment is specified diff --git a/services/core-web/jest.config.js b/services/core-web/jest.config.js index 6a94846269..0285a38263 100644 --- a/services/core-web/jest.config.js +++ b/services/core-web/jest.config.js @@ -8,7 +8,7 @@ module.exports = { }, ], }, - maxWorkers: 4, + maxWorkers: '50%', verbose: true, testEnvironmentOptions: { url: "http://localhost", diff --git a/services/core-web/src/components/Forms/MajorProject/MajorProjectSearchForm.tsx b/services/core-web/src/components/Forms/MajorProject/MajorProjectSearchForm.tsx index 81189bf5bc..e2ba39b2f9 100644 --- a/services/core-web/src/components/Forms/MajorProject/MajorProjectSearchForm.tsx +++ b/services/core-web/src/components/Forms/MajorProject/MajorProjectSearchForm.tsx @@ -15,6 +15,7 @@ interface MajorProjectsSearchFormProps { isAdvanceSearch: boolean; } + export const MajorProjectsSearchForm: FC = ({ handleSubmit, toggleAdvancedSearch, diff --git a/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.tsx b/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.tsx index 0c26835309..1ac4b94d34 100644 --- a/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.tsx +++ b/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.tsx @@ -1,7 +1,7 @@ import React, { FC } from "react"; import { Link } from "react-router-dom"; import { Button, Col, Row } from "antd"; -import { uniqBy, flattenDeep } from "lodash"; +import { uniqBy, flattenDeep, uniq } from "lodash"; import * as Strings from "@mds/common/constants/strings"; import { PROJECT_SUMMARY_STATUS_CODES, @@ -48,7 +48,7 @@ const transformRowData = (projects, mineCommodityHash) => project_lead_name: project.project_lead_name, commodity: project?.mine?.mine_type && project.mine.mine_type.length > 0 - ? uniqBy( + ? uniq( flattenDeep( project.mine.mine_type.reduce((result, type) => { if (type.mine_type_detail && type.mine_type_detail.length > 0) { diff --git a/services/core-web/src/components/mine/Permit/PermitConditionCategory.spec.tsx b/services/core-web/src/components/mine/Permit/PermitConditionCategory.spec.tsx new file mode 100644 index 0000000000..114f0384c4 --- /dev/null +++ b/services/core-web/src/components/mine/Permit/PermitConditionCategory.spec.tsx @@ -0,0 +1,124 @@ +import React from "react"; +import { render, fireEvent, screen } from "@testing-library/react"; +import { ReduxWrapper } from "@mds/common/tests/utils/ReduxWrapper"; +import { EditPermitConditionCategoryInline } from "./PermitConditionCategory"; + +const mockCategory = { + condition_category_code: "TEST-CAT", + step: 'A', + display_order: 1, + description: "Test Category" +}; + +const mockProps = { + category: mockCategory, + conditionCount: 0, + currentPosition: 1, + categoryCount: 3, + onChange: jest.fn(), + onDelete: jest.fn(), + moveUp: jest.fn(), + moveDown: jest.fn() +}; + +const initialState = {}; + +describe("PermitConditionCategory", () => { + it("renders category title with count in view mode", () => { + render( + + + + ); + + expect(screen.getByText(`A. Test Category (0)`)).toBeInTheDocument(); + }); + + it("switches to edit mode on click", () => { + render( + + + + ); + + fireEvent.click(screen.getByText(`A. Test Category (0)`)); + expect(screen.getByRole("textbox", { name: 'step' })).toBeInTheDocument(); + }); + + it("calls moveUp when up arrow clicked", () => { + render( + + + + ); + + fireEvent.click(screen.getByText(`A. Test Category (0)`)); + const upButton = screen.getByRole("button", { name: "Move Category Up" }); + fireEvent.click(upButton); + + expect(mockProps.moveUp).toHaveBeenCalledWith(mockCategory); + }); + + it("calls moveDown when down arrow clicked", () => { + render( + + + + ); + + fireEvent.click(screen.getByText(`A. Test Category (0)`)); + const upButton = screen.getByRole("button", { name: "Move Category Down" }); + fireEvent.click(upButton); + + expect(mockProps.moveDown).toHaveBeenCalledWith(mockCategory); + }); + + it("disables delete button when condition count > 0", () => { + render( + + + + ); + + fireEvent.click(screen.getByText(`A. Test Category (1)`)); + const deleteButton = screen.getByRole("button", { name: "Delete Category" }); + + expect(deleteButton).toBeDisabled(); + }); + + it("enables delete button when condition count = 0", () => { + render( + + + + ); + + fireEvent.click(screen.getByText(`A. Test Category (0)`)); + const deleteButton = screen.getByRole("button", { name: "Delete Category" }); + + expect(deleteButton).not.toBeDisabled(); + + fireEvent.click(deleteButton); + + const confirmDeleteButton = screen.getByRole("button", { name: "Yes, Delete Category" }); + fireEvent.click(confirmDeleteButton); + + expect(mockProps.onDelete).toHaveBeenCalledWith(mockCategory); + }); + + it("submits form with updated values", async () => { + render( + + + + ); + + fireEvent.click(screen.getByText(`A. Test Category (0)`)); + + const stepInput = screen.getByRole("textbox", { name: 'step' }); + fireEvent.change(stepInput, { target: { value: "B" } }); + + const submitButton = screen.getByRole("button", { name: "Confirm" }); + expect(submitButton).not.toBeDisabled(); + }); +}); diff --git a/services/core-web/src/components/mine/Permit/PermitConditionCategory.tsx b/services/core-web/src/components/mine/Permit/PermitConditionCategory.tsx index f69a57a91f..56e7a8f6c7 100644 --- a/services/core-web/src/components/mine/Permit/PermitConditionCategory.tsx +++ b/services/core-web/src/components/mine/Permit/PermitConditionCategory.tsx @@ -6,12 +6,10 @@ import { IPermitConditionCategory } from "@mds/common/interfaces"; import { Button, Popconfirm, Row, Tooltip, Typography } from "antd"; import React, { useState } from "react"; import { Field } from "redux-form"; -import { useDispatch, useSelector } from "react-redux"; -import { TRASHCAN } from "@/constants/assets"; -import CoreButton from "@mds/common/components/common/CoreButton"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowDown, faArrowUp, faTrash } from "@fortawesome/pro-light-svg-icons"; import PermitConditionCategorySelector from "./PermitConditionCategorySelector"; +import { required } from "@mds/common/redux/utils/Validate"; export interface IPermitConditionCategoryProps { onChange: (category: IPermitConditionCategory) => void | Promise; @@ -24,10 +22,12 @@ export interface IPermitConditionCategoryProps { categoryCount: number; } -export const PermitConditionCategory = (props: IPermitConditionCategoryProps) => { +export const EditPermitConditionCategoryInline = (props: IPermitConditionCategoryProps) => { const [isEditMode, setIsEditMode] = useState(false); - const enableEditMode = () => { + const enableEditMode = (evt) => { + evt.stopPropagation(); + evt.preventDefault(); setIsEditMode(true); }; @@ -40,61 +40,63 @@ export const PermitConditionCategory = (props: IPermitConditionCategoryProps) => props.onDelete(cat); } + if (!isEditMode) { + return ( + +
+ {props.category.step ? `${props.category.step}. ` : ''}{props.category.description} ({props.conditionCount}) +
+
+ ); + } + return ( -
- - - {!isEditMode && ( - - {props.category.step} {props.category.description} ({props.conditionCount}) - - )} + + + + + - {isEditMode && ( + 0} + placement="topRight" + title={ <> - - - - - 0} - placement="topRight" - title={ - <> - Are you sure you want to delete {props.category.description}? - This action cannot be undone. - - } - onConfirm={() => handleDelete(props.category)} - okText="Yes, Delete Category" - cancelText="No" - > -
+ } + onConfirm={() => handleDelete(props.category)} + okText="Yes, Delete Category" + cancelText="No" + > +