diff --git a/.github/workflows/permit-service.unit.yaml b/.github/workflows/permit-service.unit.yaml index 729b12ab61..b351d09fe2 100644 --- a/.github/workflows/permit-service.unit.yaml +++ b/.github/workflows/permit-service.unit.yaml @@ -28,8 +28,8 @@ jobs: env: DOCKER_BUILDKIT: 1 run: | - docker compose -f docker-compose.yaml run -e AZURE_API_KEY=testkey haystack coverage run -m pytest - docker compose -f docker-compose.yaml run -e AZURE_API_KEY=testkey haystack coverage xml + docker compose -f docker-compose.yaml run -e AZURE_API_KEY=testkey -e DOCUMENTINTELLIGENCE_API_KEY=testkey haystack coverage run -m pytest + docker compose -f docker-compose.yaml run -e AZURE_API_KEY=testkey -e DOCUMENTINTELLIGENCE_API_KEY=testkey haystack coverage xml sed -i "s/\/code/\/github\/workspace\/services\/permits/g" services/permits/coverage.xml - name: Upload test coverage results uses: actions/upload-artifact@v4 diff --git a/docker-compose.yaml b/docker-compose.yaml index d886b4164b..7f40e59421 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -134,7 +134,7 @@ services: container_name: core_api_celery build: context: services/core-api - entrypoint: ./celery.sh + entrypoint: ./celery_dev.sh ports: - 5556:5555 volumes: diff --git a/migrations/sql/V2024.12.03.13.1__add_permit_condition_meta_column.sql b/migrations/sql/V2024.12.03.13.1__add_permit_condition_meta_column.sql new file mode 100644 index 0000000000..e02455da4f --- /dev/null +++ b/migrations/sql/V2024.12.03.13.1__add_permit_condition_meta_column.sql @@ -0,0 +1 @@ +ALTER TABLE permit_conditions ADD COLUMN IF NOT EXISTS meta JSONB NULL; \ No newline at end of file diff --git a/migrations/sql/V2024.12.06.11.1__add_mine_report_definition_report_name_column.sql b/migrations/sql/V2024.12.06.11.1__add_mine_report_definition_report_name_column.sql new file mode 100644 index 0000000000..fa7811efa3 --- /dev/null +++ b/migrations/sql/V2024.12.06.11.1__add_mine_report_definition_report_name_column.sql @@ -0,0 +1 @@ +ALTER TABLE mine_report_permit_requirement ADD COLUMN report_name VARCHAR(255) NULL; \ No newline at end of file diff --git a/migrations/sql/V2024.12.10.15.15__create_users_table.sql b/migrations/sql/V2024.12.10.15.15__create_users_table.sql new file mode 100644 index 0000000000..4c3dec709d --- /dev/null +++ b/migrations/sql/V2024.12.10.15.15__create_users_table.sql @@ -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'; \ No newline at end of file diff --git a/migrations/sql/V2024.12.11.15.1__add_permit_condition_history_meta_column.sql b/migrations/sql/V2024.12.11.15.1__add_permit_condition_history_meta_column.sql new file mode 100644 index 0000000000..308ea64e79 --- /dev/null +++ b/migrations/sql/V2024.12.11.15.1__add_permit_condition_history_meta_column.sql @@ -0,0 +1 @@ +ALTER TABLE permit_conditions_version ADD COLUMN IF NOT EXISTS meta JSONB NULL; \ No newline at end of file diff --git a/migrations/sql/V2024.12.11.22.32__add_user_version_history_table.sql b/migrations/sql/V2024.12.11.22.32__add_user_version_history_table.sql new file mode 100644 index 0000000000..1c0f870edf --- /dev/null +++ b/migrations/sql/V2024.12.11.22.32__add_user_version_history_table.sql @@ -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); diff --git a/migrations/sql/V2024.12.11.22.33__add_user_version_history_table_backfill.sql b/migrations/sql/V2024.12.11.22.33__add_user_version_history_table_backfill.sql new file mode 100644 index 0000000000..7641a3298f --- /dev/null +++ b/migrations/sql/V2024.12.11.22.33__add_user_version_history_table_backfill.sql @@ -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; diff --git a/services/common/src/components/project/ProjectDescriptionTab.tsx b/services/common/src/components/project/ProjectDescriptionTab.tsx index 4d4f10f1b0..c8180a85f6 100644 --- a/services/common/src/components/project/ProjectDescriptionTab.tsx +++ b/services/common/src/components/project/ProjectDescriptionTab.tsx @@ -395,7 +395,7 @@ const ProjectDescriptionTab = () => { Project Description Overview - diff --git a/services/common/src/components/project/__snapshots__/ProjectDescriptionTab.spec.tsx.snap b/services/common/src/components/project/__snapshots__/ProjectDescriptionTab.spec.tsx.snap index b8da1de048..a687f63768 100644 --- a/services/common/src/components/project/__snapshots__/ProjectDescriptionTab.spec.tsx.snap +++ b/services/common/src/components/project/__snapshots__/ProjectDescriptionTab.spec.tsx.snap @@ -27,6 +27,7 @@ exports[`ProjectDescriptionTab renders properly 1`] = ` > - - -
- - ); - } -} - -Home.propTypes = propTypes; - -const mapStateToProps = (state) => ({ - staticContentLoadingIsComplete: getStaticContentLoadingIsComplete(state), -}); - -const mapDispatchToProps = (dispatch) => - bindActionCreators( - { - loadBulkStaticContent, - fetchInspectors, - fetchProjectLeads, - }, - dispatch - ); - -export default connect(mapStateToProps, mapDispatchToProps)(AuthenticationGuard(Home)); diff --git a/services/core-web/src/components/Home.tsx b/services/core-web/src/components/Home.tsx new file mode 100644 index 0000000000..1d7fa09288 --- /dev/null +++ b/services/core-web/src/components/Home.tsx @@ -0,0 +1,110 @@ +import React, { FC, useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { BackTop, Button, Layout } from "antd"; +import { ArrowUpOutlined } from "@ant-design/icons"; +import MediaQuery from "react-responsive"; +import LoadingBar from "react-redux-loading-bar"; +import { getStaticContentLoadingIsComplete } from "@mds/common/redux/selectors/staticContentSelectors"; +import { + fetchInspectors, + fetchProjectLeads, + loadBulkStaticContent, +} from "@mds/common/redux/actionCreators/staticContentActionCreator"; +import { detectDevelopmentEnvironment, detectIE, detectTestEnvironment } from "@mds/common/utils"; +import DashboardRoutes from "@/routes/DashboardRoutes"; +import { AuthenticationGuard } from "@/HOC/AuthenticationGuard"; +import WarningBanner, { WARNING_TYPES } from "@/components/common/WarningBanner"; +import NavBar from "./navigation/NavBar"; +import Loading from "./common/Loading"; +import { useLocation } from "react-router-dom"; +import { fetchUser, getUser } from "@mds/common/redux/slices/userSlice"; + +/** + * @func Home contains the navigation and wraps the Dashboard routes. Home should not contain any redux logic/state. + * Home is wrapped in AuthenticationGuard which checks keycloak authorization. + */ + +export const Home: FC = () => { + const location = useLocation(); + const dispatch = useDispatch(); + + const staticContentLoadingIsComplete = useSelector(getStaticContentLoadingIsComplete); + const user = useSelector(getUser); + + const [isIE, setIsIE] = useState(false); + const [isTest, setIsTest] = useState(false); + const [isDev, setIsDev] = useState(false); + const [isMobile, setIsMobile] = useState(true); + const [activeNavButton, setActiveNavButton] = useState(""); + const [isMenuOpen, setIsMenuOpen] = useState(false); + + useEffect(() => { + setIsIE(detectIE()); + setIsTest(detectTestEnvironment()); + setIsDev(detectDevelopmentEnvironment()); + loadStaticContent(); + }, []); + + useEffect(() => { + handleActiveButton(location.pathname); + setIsMenuOpen(false); + }, [location]); + + const handleActiveButton = (path) => { + setActiveNavButton(path); + }; + + const handleIEClose = () => { + setIsIE(false); + }; + + const handleMobileWarningClose = () => { + setIsMobile(false); + }; + + const toggleHamburgerMenu = () => { + setIsMenuOpen(!isMenuOpen); + }; + + const loadStaticContent = () => { + dispatch(loadBulkStaticContent()); + dispatch(fetchInspectors()); + dispatch(fetchProjectLeads()); + dispatch(fetchUser()); + }; + + if (!staticContentLoadingIsComplete || !user) { + return ; + } + return ( + +
+ + + {isTest && } + {isIE && } + + {isMobile && !isDev && ( + + )} + + + + + + + +
+ + ); +}; + +export default AuthenticationGuard(Home); diff --git a/services/core-web/src/components/mine/Permit/PermitConditions.tsx b/services/core-web/src/components/mine/Permit/PermitConditions.tsx index 1bf3a30b5e..20d9de122e 100644 --- a/services/core-web/src/components/mine/Permit/PermitConditions.tsx +++ b/services/core-web/src/components/mine/Permit/PermitConditions.tsx @@ -339,7 +339,7 @@ const PermitConditions: FC = ({ Report #{index + 1} + Report #{index + 1}{cond.mineReportPermitRequirement?.report_name ? ` - ${cond.mineReportPermitRequirement.report_name}` : ''} } className="report-collapse" > diff --git a/services/core-web/src/components/mine/Projects/ProjectSummary.tsx b/services/core-web/src/components/mine/Projects/ProjectSummary.tsx index 0b549bca97..b9a9d67e5e 100644 --- a/services/core-web/src/components/mine/Projects/ProjectSummary.tsx +++ b/services/core-web/src/components/mine/Projects/ProjectSummary.tsx @@ -18,7 +18,6 @@ import { AMS_STATUS_CODES_SUCCESS, AMS_STATUS_CODE_FAIL, AMS_ENVIRONMENTAL_MANAGEMENT_ACT_TYPES, - SystemFlagEnum, } from "@mds/common"; import { getMineById } from "@mds/common/redux/reducers/mineReducer"; import withFeatureFlag from "@mds/common/providers/featureFlags/withFeatureFlag"; @@ -37,7 +36,6 @@ import ProjectSummaryForm, { } from "@mds/common/components/projectSummary/ProjectSummaryForm"; import { fetchRegions } from "@mds/common/redux/slices/regionsSlice"; import { clearProjectSummary } from "@mds/common/redux/actions/projectActions"; -import { getSystemFlag } from "@mds/common/redux/selectors/authenticationSelectors"; import { cancelConfirmWrapper } from "@mds/common/components/forms/RenderCancelButton"; import { fetchActivities } from "@mds/common/redux/actionCreators/activityActionCreator"; import { getUserInfo } from "@mds/common/redux/selectors/authenticationSelectors"; @@ -54,10 +52,6 @@ export const ProjectSummary: FC = () => { mode: string; }>(); const userInfo = useSelector(getUserInfo); - - const systemFlag = useSelector(getSystemFlag); - const isCore = systemFlag === SystemFlagEnum.core; - const mine = useSelector((state) => getMineById(state, mineGuid)); const formattedProjectSummary = useSelector(getFormattedProjectSummary); const project = useSelector(getProject); @@ -215,19 +209,15 @@ export const ProjectSummary: FC = () => { if (!status_code || isNewProject) { status_code = "DFT"; - } else if (!newActiveTab) { - if (isCore) { - status_code = formValues.status_code; - } else { - status_code = "SUB"; - } + } else if (!newActiveTab && status_code === "DFT") { + status_code = "SUB"; is_historic = false; if (amsFeatureEnabled) { message = null; } } - if (isCore && !isNewProject) { + if (!isNewProject && newActiveTab) { status_code = formValues.status_code; } diff --git a/services/core-web/src/tests/actionCreators/userActionCreator.spec.js b/services/core-web/src/tests/actionCreators/userActionCreator.spec.js deleted file mode 100644 index 0e9da98282..0000000000 --- a/services/core-web/src/tests/actionCreators/userActionCreator.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import MockAdapter from "axios-mock-adapter"; -import axios from "axios"; -import { fetchCoreUsers } from "@mds/common/redux/actionCreators/userActionCreator"; -import * as genericActions from "@mds/common/redux/actions/genericActions"; -import { ENVIRONMENT } from "@mds/common"; -import * as API from "@mds/common/constants/API"; -import * as MOCK from "@/tests/mocks/dataMocks"; - -const dispatch = jest.fn(); -const requestSpy = jest.spyOn(genericActions, "request"); -const successSpy = jest.spyOn(genericActions, "success"); -const errorSpy = jest.spyOn(genericActions, "error"); -const mockAxios = new MockAdapter(axios); - -beforeEach(() => { - mockAxios.reset(); - dispatch.mockClear(); - requestSpy.mockClear(); - successSpy.mockClear(); - errorSpy.mockClear(); -}); - -describe("`fetchCoreUsers` action creator", () => { - const url = ENVIRONMENT.apiUrl + API.CORE_USER; - it("Request successful, dispatches `success` with correct response", () => { - const mockResponse = { data: { success: true } }; - mockAxios.onGet(url).reply(200, mockResponse); - return fetchCoreUsers()(dispatch).then(() => { - expect(requestSpy).toHaveBeenCalledTimes(1); - expect(successSpy).toHaveBeenCalledTimes(1); - expect(dispatch).toHaveBeenCalledTimes(3); - }); - }); - - it("Request failure, dispatches `error` with correct response", () => { - mockAxios.onGet(url, MOCK.createMockHeader()).reply(418, MOCK.ERROR); - return fetchCoreUsers()(dispatch).then(() => { - expect(requestSpy).toHaveBeenCalledTimes(1); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(dispatch).toHaveBeenCalledTimes(2); - }); - }); -}); diff --git a/services/core-web/src/tests/components/Forms/reports/__snapshots__/ReportPermitRequirementForm.spec.tsx.snap b/services/core-web/src/tests/components/Forms/reports/__snapshots__/ReportPermitRequirementForm.spec.tsx.snap index 08861c4da0..7cb3885376 100644 --- a/services/core-web/src/tests/components/Forms/reports/__snapshots__/ReportPermitRequirementForm.spec.tsx.snap +++ b/services/core-web/src/tests/components/Forms/reports/__snapshots__/ReportPermitRequirementForm.spec.tsx.snap @@ -73,6 +73,70 @@ exports[`RequestReportForm renders form properly 1`] = `
+
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+