From c95eef835d3e752651591069d774e3c5a9702539 Mon Sep 17 00:00:00 2001 From: jannisvisser Date: Mon, 18 Mar 2024 11:29:31 +0100 Subject: [PATCH 1/8] feat: remove old event functionality AB#26940 --- .../API-admin-user/Upload_mock_data.feature | 127 +++--- .../Use_dev_menu.feature | 72 +-- .../dashboard-page/Use_chat_section.feature | 414 +++++++++--------- .../View_area_of_focus_section.feature | 28 +- features/README.md | 3 +- features/pipeline-user/Run_pipeline.feature | 161 ++++--- .../app/components/chat/chat.component.html | 18 +- .../src/app/components/chat/chat.component.ts | 9 +- .../event-speech-bubble.component.html | 20 - .../backend-mock-scenario.component.ts | 12 - .../src/app/services/event.service.ts | 2 - .../IBF-dashboard/src/assets/i18n/en.json | 16 - .../1710755059526-RemoveActiveTrigger.ts | 17 + .../admin-area/services/event-area.service.ts | 6 +- .../src/api/event/event-place-code.entity.ts | 3 - .../src/api/event/event.service.ts | 127 +----- .../api/notification/notification.service.ts | 5 +- .../notification/whatsapp/whatsapp.service.ts | 6 +- 18 files changed, 442 insertions(+), 604 deletions(-) create mode 100644 services/API-service/migration/1710755059526-RemoveActiveTrigger.ts diff --git a/features/API-admin-user/Upload_mock_data.feature b/features/API-admin-user/Upload_mock_data.feature index 9bf8c6775..92db76cd3 100644 --- a/features/API-admin-user/Upload_mock_data.feature +++ b/features/API-admin-user/Upload_mock_data.feature @@ -1,82 +1,67 @@ @api-admin-user Feature: Upload mock data -Background: - Given a logged-in user on the Swagger UI page + Background: + Given a logged-in user on the Swagger UI page -Scenario: Upload mock data for all countries & disaster-types at once - Given the user is using the `/api/scripts/mock-all` endpoint - Given the user has filled in the right `secret` (depends on server/environment) - Given the user has filled in 'true' or 'false' for 'triggered' - When the user clicks 'Execute' - Then mock data is uploaded for all countries and all disaster-types - And all are triggered if 'triggered=true' is chosen - And all are non-triggered if 'triggered=false' is chosen - And it includes data for all dynamic admin-area layers defined for that country & disaster-type - And it includes data for dynamic point layers ('Glofas stations' / 'Typhoon track') - And it includes dynamic raster data ('flood extent' / 'rainfall extent') - And only 1 event is uploaded per country and disaster-type (relevant for `typhoon` only) - And the dashboard should be opened/refreshed to check all of this + Scenario: Upload mock data for all countries & disaster-types at once + Given the user is using the `/api/scripts/mock-all` endpoint + Given the user has filled in the right `secret` (depends on server/environment) + Given the user has filled in 'true' or 'false' for 'triggered' + When the user clicks 'Execute' + Then mock data is uploaded for all countries and all disaster-types + And all are triggered if 'triggered=true' is chosen + And all are non-triggered if 'triggered=false' is chosen + And it includes data for all dynamic admin-area layers defined for that country & disaster-type + And it includes data for dynamic point layers ('Glofas stations' / 'Typhoon track') + And it includes dynamic raster data ('flood extent' / 'rainfall extent') + And only 1 event is uploaded per country and disaster-type (relevant for `typhoon` only) + And the dashboard should be opened/refreshed to check all of this -Scenario: Upload mock data for TRIGGERED state (active event) for specific country & disaster-types - Given the user is using the `/api/scripts/mock-dynamic-data` endpoint - Given the user has filled in the right `secret` (depends on server/environment) - Given the user has filled in 'triggered = true' - Given the user has filled in 'removeEvents = true' - Given the user has filled in 'country' and 'disaster-type' (exact right formats) - When the user clicks 'Execute' - Then mock data is uploaded for the chosen 'country' and 'disaster-type' - And it is in triggered state - And all the data as described in previous scenario is uploaded - And the dashboard should be opened/refreshed to check all of this + Scenario: Upload mock data for TRIGGERED state (active event) for specific country & disaster-types + Given the user is using the `/api/scripts/mock-dynamic-data` endpoint + Given the user has filled in the right `secret` (depends on server/environment) + Given the user has filled in 'triggered = true' + Given the user has filled in 'removeEvents = true' + Given the user has filled in 'country' and 'disaster-type' (exact right formats) + When the user clicks 'Execute' + Then mock data is uploaded for the chosen 'country' and 'disaster-type' + And it is in triggered state + And all the data as described in previous scenario is uploaded + And the dashboard should be opened/refreshed to check all of this -Scenario: Upload mock data for NON-TRIGGERED state (no event) for specific country & disaster-types - Given everything the same as previous scenario - Given the user has filled in 'triggered = false' - When the user clicks 'Execute' - Then mock data is uploaded for the chosen 'country' and 'disaster-type' - And it is in non-triggered state - And all the data as described in previous scenario is uploaded - And the dashboard should be opened/refreshed to check all of this + Scenario: Upload mock data for NON-TRIGGERED state (no event) for specific country & disaster-types + Given everything the same as previous scenario + Given the user has filled in 'triggered = false' + When the user clicks 'Execute' + Then mock data is uploaded for the chosen 'country' and 'disaster-type' + And it is in non-triggered state + And all the data as described in previous scenario is uploaded + And the dashboard should be opened/refreshed to check all of this -Scenario: Upload mock data for OLD-EVENT state for specific country & disaster-types - Given user has first created an active event (see scenario TRIGGERE state) - Given the user is subsquently using the `/api/scripts/mock-dynamic-data` endpoint a 2nd time - Given the user changes 'triggered' to 'false' - Given the user changes 'removeEvents' to 'false' - Given the user leaves all other input the same - When the user clicks 'Execute' - Then mock data is uploaded for the chosen 'country' and 'disaster-type' - And it is updating the existing event to become inactive, and thus an 'old event' - And the dashboard should be opened/refreshed to check all of this - And the dashboard is in NON-TRIGGERED state - And the map is showing no triggered areas - And the chat section is showing EAP-actions for all old triggered areas - And see other feature files to look for correct behaviour of old event, by Ctrl+F on 'OLD-EVENT' + Scenario: Upload Typhoon-specific events + Given the disaster-type is 'Typhoon' + Given the user uses the '/api/scripts/mock-typhoon-scenario' endpoint + Given the user fills in one of the available 'scenario' options + Given the user fills in 'eventNr = 1' + When the user clicks 'Execute' + Then the mock data is uploaded for the given 'scenario' + - eventTrigger: exact same result as using '/api/scripts/mock-dynamic-data' endpoint with 'triggered = true' + - noEvent: exact same result as using '/api/scripts/mock-dynamic-data' endpoint with 'triggered = false' + - eventNoTrigger: produces event that does not reach trigger threshold + - eventAfterLandfall: produces (triggered) event that has already made landfall (i.e. leadTime = '0-hour') + - eventNoLandafall: produces (triggered) event with a track that does not make landfall -Scenario: Upload Typhoon-specific events - Given the disaster-type is 'Typhoon' - Given the user uses the '/api/scripts/mock-typhoon-scenario' endpoint - Given the user fills in one of the available 'scenario' options - Given the user fills in 'eventNr = 1' - When the user clicks 'Execute' - Then the mock data is uploaded for the given 'scenario' - - eventTrigger: exact same result as using '/api/scripts/mock-dynamic-data' endpoint with 'triggered = true' - - noEvent: exact same result as using '/api/scripts/mock-dynamic-data' endpoint with 'triggered = false' - - eventNoTrigger: produces event that does not reach trigger threshold - - eventAfterLandfall: produces (triggered) event that has already made landfall (i.e. leadTime = '0-hour') - - eventNoLandafall: produces (triggered) event with a track that does not make landfall - -Scenario: Upload 2nd/3rd/etc. Typhoon event - Given the disaster-type is 'Typhoon' - Given user has already created an event (see above) - Given the user is subsquently using the `/api/scripts/mock-typhoon-scenario` - Given the user fills in 'eventNr' as 2/3/etc - When the user clicks 'Execute' - Then mock data is uploaded for a 2nd/3rd/etc event - And the dashboard should be opened/refreshed to check all of this - And the chat-section should show 2/3/etc event buttons in the 2nd speech-bubble - And the timeline-section should show 2/3/etc active lead-time buttons + Scenario: Upload 2nd/3rd/etc. Typhoon event + Given the disaster-type is 'Typhoon' + Given user has already created an event (see above) + Given the user is subsquently using the `/api/scripts/mock-typhoon-scenario` + Given the user fills in 'eventNr' as 2/3/etc + When the user clicks 'Execute' + Then mock data is uploaded for a 2nd/3rd/etc event + And the dashboard should be opened/refreshed to check all of this + And the chat-section should show 2/3/etc event buttons in the 2nd speech-bubble + And the timeline-section should show 2/3/etc active lead-time buttons diff --git a/features/IBF-portal-admin-user/Use_dev_menu.feature b/features/IBF-portal-admin-user/Use_dev_menu.feature index f4177520f..8e30de972 100644 --- a/features/IBF-portal-admin-user/Use_dev_menu.feature +++ b/features/IBF-portal-admin-user/Use_dev_menu.feature @@ -1,44 +1,44 @@ @ibf-portal-admin-user Feature: Use dev menu section -Background: - Given a logged-in "admin" user on the dashboard page + Background: + Given a logged-in "admin" user on the dashboard page -Scenario: Open and view Dev Menu - When the user clicks on menu-icon in the top left of Dashboard page - Then the menu section open up - And it contains a menu list - And the menu list contains 'Version', 'Country', 'Load Mock Scenario', 'Activation Report' + Scenario: Open and view Dev Menu + When the user clicks on menu-icon in the top left of Dashboard page + Then the menu section open up + And it contains a menu list + And the menu list contains 'Version', 'Country', 'Load Mock Scenario', 'Activation Report' -Scenario: Click on Version - When the user clicks "Version" - Then a new tab opens on the IBF Github repository, specifically to the version tag + Scenario: Click on Version + When the user clicks "Version" + Then a new tab opens on the IBF Github repository, specifically to the version tag -Scenario: Switch Country - When the user clicks "country" dropdown of menu section - Then the Country's dropdown section open up - And it contains list of different countries with radio button - And the user select any of the country from the list - When the user selects a country - Then the Dashboard page switches to that country + Scenario: Switch Country + When the user clicks "country" dropdown of menu section + Then the Country's dropdown section open up + And it contains list of different countries with radio button + And the user select any of the country from the list + When the user selects a country + Then the Dashboard page switches to that country -Scenario: View Load Mock Data popup - When the user clicks on "Load Mock Scenario" from menu - Then the 'Load Mock Scenario' popup open up - And it generates a question whether the user wants to mock the situation for selected country - And it contains 3 buttons "cancel", "Old event", "No Trigger", "Trigger" buttons - And it ask user to 'enter the secret' to switch to mock mode + Scenario: View Load Mock Data popup + When the user clicks on "Load Mock Scenario" from menu + Then the 'Load Mock Scenario' popup open up + And it generates a question whether the user wants to mock the situation for selected country + And it contains 3 buttons "cancel", "No Trigger", "Trigger" buttons + And it ask user to 'enter the secret' to switch to mock mode -Scenario: Load mock data successfully - Given the user has opened the mock data popup - And the user has filled in the correct secret - And clicks "Old event" or "No Trigger" or "Trigger" - Then the mock data is loaded in the back-end - And it appears also in the dashboard - And the mock data popup closes - -Scenario: Load mock data unsuccessfully - Given the user has opened the mock data popup - And the user has filled in an incorrect secret - And clicks "Old event" or "No Trigger" or "Trigger" - Then a message appears that says 'Failed to set mock scenario' + Scenario: Load mock data successfully + Given the user has opened the mock data popup + And the user has filled in the correct secret + And clicks "No Trigger" or "Trigger" + Then the mock data is loaded in the back-end + And it appears also in the dashboard + And the mock data popup closes + + Scenario: Load mock data unsuccessfully + Given the user has opened the mock data popup + And the user has filled in an incorrect secret + And clicks "No Trigger" or "Trigger" + Then a message appears that says 'Failed to set mock scenario' diff --git a/features/IBF-portal-user/dashboard-page/Use_chat_section.feature b/features/IBF-portal-user/dashboard-page/Use_chat_section.feature index 18dca7570..60411599c 100644 --- a/features/IBF-portal-user/dashboard-page/Use_chat_section.feature +++ b/features/IBF-portal-user/dashboard-page/Use_chat_section.feature @@ -1,213 +1,207 @@ @ibf-portal-user Feature: View and use chat section -Background: - Given a logged-in user on the dashboard page - Given logged in for a specific "country" - -Scenario: View chat section - When the user enters the dashboard page - Then the user sees the Chat section on the left of the page - And it contains multiple "speech-bubbles" with information - And the speech-bubbles have grey background in NON-TRIGGERED mode - And the speech-bubbles have purple background in TRIGGERED mode - And each speech-bubble has a timestamp (of now) in the bottomright corner - And the 1st speech-bubble gives information on last model run (see below) - And the 2nd speech-bubble gives general trigger information (see below) - And if TRIGGERED or OLD-EVENT a 3rd speech-bubble gives further instructions - -Scenario: View last model run information - When the user views the 1st speech bubble - Then it first says: "Hello " - And it mentions the date and time of the last model run update. - And it has red background if the last model run date is too long ago - And the threshold for this is if it is more than 1 upload-interval + 10% ago (e.g. 1 day + 10% in case of floods) - And it contains 4 buttons 'About Trigger' and 'Video Guide' and 'Activation Log' and 'Export View' - -Scenario: Click 'About Trigger' - When the user clicks on "About Trigger" button - Then a new tab opens with the EAP-document - And if it is a Google Sheet it might scroll automatically to the specific 'trigger' section of the EAP - And if it is a PDF this is not possible and it loads at the top. - -Scenario: Click 'Video Guide' - When the user clicks on 'Video Guide' button - Then a popup opens where the video can be played - -Scenario: Click 'Activation Log' - When the user clicks on 'Activation Log' button - Then the 'activation-log' page opens up in a new tab - And it contains 'disaster-activation-data' for all the countries - And it contains 'country-code','disaster-type','placecode','name','startDate','endDate','stopped','manuallyStopped','exposureIndicator','exposureValue','databaseId' - And it is in a table - And it can easily be copied to Excel - -Scenario: Click 'Export view' - When the user clicks the "Export View" button in the header - Then the popup open with the message and shows a link to take screenshot - And the user can follow the instructions provided - And can close the popup window if do not need to take screenshot - -Scenario: View general trigger information - When the user views the 2nd speech bubble - Then if NON-TRIGGERED it mentions there are 'no triggers' - And if TRIGGERED it mentions there is a trigger - And if EVENT-WITHOUT-TRIGGER it mentions an activated event below threshold ('typhoon' only) - And it mentions when the event this trigger belongs to first started - And it mentions for when the trigger is expected - And it mentions the name of the event if applicable ('typhoon' only) - And the exact UX copy differs between disaster-types (Potentially: document in more detail) - And for 'drought' if there are multiple triggered lead-times, then the other ones are mentioned here with instructions to look at them - -Scenario: View general trigger information with 2 or more active events - Given the selected 'disaster-type' is 'typhoon' - Given there are 2 or more active events (see 'API-admin-user/Upload_mock_data.feature' for instructions how to upload additional events) - When the user views the 2nd speech bubble - Then it mentions a paragraph per event with event-details such as name, start-date, arrival-time. - And it contains a button for each event - And the first event/button is the selected event and has a 'selected styling' - And the other buttons have a 'non-selected' styling - And all info in the map by default refers to the 1st event - And the timeline sections has just as many active timeline-buttons as there are events - -Scenario: View general trigger information when event already made landfall ('typhoon' only) - Given the selected 'disaster-type' is 'typhoon' - Given the event has already made landfall (i.e. leadTime = '0-hour') - When the user views the 2nd speech bubble - Then it mentions - in addition to earlier scenarios - that it already made landfall - -Scenario: View general trigger information with clear-out warning or message - Given the 'showMonthlyEapActions' is 'true' (currently only for Kenya Droughts) - Given 1 or more 'droughtForecastSeasons' are provided (October & March, for Kenya Droughts) - Given the current month (month of last (mock) pipeline run) is one month before one of the 'droughtForecastSeasons' - When the user views the 2nd speech bubble - Then - in addition to the normal information - it mentions a second paragraph - And it is red-colored and bold - And it reads that the EAP-actions related to the current trigger will be automatically cleared out after this month - ------ - Given - instead - that the current month (month of last (mock) pipeline run) is exactly one of the 'droughtForecastSeasons' - Then the message reads that the EAP-actions have been automatically cleared out after this month - -Scenario: Switch event - Given the selected 'disaster-type' is 'typhoon' - Given there are 2 or more active events (see 'API-admin-user/Upload_mock_data.feature' for instructions how to upload additional events) - When the user clicks a non-selected event button - Then that button switches to active state - And the previously active button switches to inactive state - And the selected timeline-button in the timeline-section also switches to the one related to the new event - And all data in the dashboard switches to the new event - -Scenario: View overview & further instructions - Given the dashboard is in TRIGGERED state - Given the selected "admin level" is the "default admin level" - Given there are no areas for which the trigger is stopped - When the user views the 3rd speech bubble - Then it mentions an overview of the triggered areas, sorted by the "action unit" of that disaster type (e.g. "exposed population") - And it mentions instructions that you can click an area in the map - And if done so, that you can perform additional actions per area. - And no such speech bubble is available on other admin levels then the default admin level - -Scenario: View overview & further instructions WITH "stopped" areas - Given there are areas for which the trigger is stopped - Given for the rest the same as scenario above - Then the same applies as scenario above - And additionally there is a separate 4th speech bubble which lists stopped areas - And it is grey colorded (as "NON-TRIGGERED") with black border - And it mentions the stopped areas in same order as scenario above - And it mentions instructions that you can click a stopped area in the map - -Scenario: Click on area in triggered areas list in chat - Given the dashboard is in TRIGGERED state - Given the selected "admin level" is the "default admin level" - When the user views the 3rd speech bubble - And the user clicks on the name of a triggered area - Then the map zooms down on the selected area - And a new speech bubble appears in the chat section - And it is pointed to the right instead of the left - And it contains the "admin area" type and name - And it contains the relevant "action unit" type and value (e.g. "Exposed population" or "Potential cases") - And it contains a button to go back to the list of all triggered areas - And it contains a list of all EAP-actions (same for every area) with "area of focus" name and "action" description - And it shows which EAP-actions are already "checked" via the "checkbox" - And it contains a disabled "save" button - And it contains an enabled "stop trigger/alert" button - -Scenario: Click on area in stopped areas list in chat - Given the area is "stopped" - When the user selects a stopped area from map (see 'Use_map_section.feature') - Given the dashboard is in TRIGGERED state - Given the selected "admin level" is the "default admin level" - When the user views the 4rd speech bubble - And the user clicks on the name of an area - Then the map zooms down on the selected area - And a new speech bubble appears in the chat section - And it is grey colorded (as "NON-TRIGGERED") with black border - And it mentions the start date of this trigger - And the stop date - And the user who stopped it - And it contains a button to go back to the list of all triggered areas - -Scenario: View chat-section after area-selection in map - Given the area is not "stopped" - Given EAP-actions are static and not monthly ('showMonthlyEapActions = false') - When the user selects a triggered area from map (see 'Use_map_section.feature') - Then the speech bubbles giving overview of active and stopped areas disappear - And a new speech bubble appears in the chat section - And it is pointed to the right instead of the left - And it contains the "admin area" type and name - And it contains the relevant "action unit" type and value (e.g. "Exposed population" or "Potential cases") - And it contains a button to go back to the list of all triggered areas - And it contains a list of all EAP-actions (same for every area) with "area of focus" name and "action" description - And it shows which EAP-actions are already "checked" via the "checkbox" - And it contains a disabled "save" button - And it contains an enabled "stop trigger/alert" button - -Scenario: View chat-section after area-selection in map with monthly actions - Given the area is not "stopped" - Given EAP-actions are monthly ('showMonthlyEapActions = true') - currently only Kenya and Ethiopia Drought - When the user selects a triggered area from map (see 'Use_map_section.feature') - Then everything happens as above - And additionally above the EAP-action a sentence appears on which sources lead to the actions - And it mentions how many actions there are - And only the action up until the current month are shown - And if there is an overlap of seasons, actions not relevant to the selected season are not shown - And behind the action in brackets and bold the month to complete the action is shown - -Scenario: View chat-section after area-selection in map of "stopped" area - Given the area is "stopped" - When the user selects a triggered area from map (see 'Use_map_section.feature') - Then the speech bubbles giving overview of active and stopped areas disappear - And a new speech bubble appears in the chat section - And it is grey colorded (as "NON-TRIGGERED") with black border - And it mentions the start date of this trigger - And the stop date - And the user who stopped it - -Scenario: View EAP-actions per area in OLD-EVENT mode - Given the dashboard is in OLD-EVENT mode - When the users views the chat section - Then the additional actions per area automatically show for ALL triggered areas - And the 'stop trigger/alert' button is disabled - -Scenario: Check or uncheck EAP-actions per triggered area - Given the EAP-action speech-bubble is showing for one or more areas - When the user checks or unchecks an EAP-action - Then the 'save' button enables - - When the user makes further changes that amount to the original selection - Then the 'save' button disables again - - When the user clicks the 'save' button - Then a popup appears that the database is updated - And it closes again by clicking outside of it - And (after refreshing) the Area-of-Focus summary will have updated (see 'Use_area_of_focus_section.feature') - -Scenario: Stop trigger - Given the dashboard is in TRIGGERED mode - When the user clicks the 'stop trigger/alert' button - Then a popup appears that asks if you are sure - And if confirmed the dashboard updates - And the area will now show as grey with black border in the map - And the lists of active vs stopped areas in the chat section will have updated - And the event is now stopped with today's date in the database - And as such reflected in the "activation report" \ No newline at end of file + Background: + Given a logged-in user on the dashboard page + Given logged in for a specific "country" + + Scenario: View chat section + When the user enters the dashboard page + Then the user sees the Chat section on the left of the page + And it contains multiple "speech-bubbles" with information + And the speech-bubbles have grey background in NON-TRIGGERED mode + And the speech-bubbles have purple background in TRIGGERED mode + And each speech-bubble has a timestamp (of now) in the bottomright corner + And the 1st speech-bubble gives information on last model run (see below) + And the 2nd speech-bubble gives general trigger information (see below) + And if TRIGGERED a 3rd speech-bubble gives further instructions + + Scenario: View last model run information + When the user views the 1st speech bubble + Then it first says: "Hello " + And it mentions the date and time of the last model run update. + And it has red background if the last model run date is too long ago + And the threshold for this is if it is more than 1 upload-interval + 10% ago (e.g. 1 day + 10% in case of floods) + And it contains 4 buttons 'About Trigger' and 'Video Guide' and 'Activation Log' and 'Export View' + + Scenario: Click 'About Trigger' + When the user clicks on "About Trigger" button + Then a new tab opens with the EAP-document + And if it is a Google Sheet it might scroll automatically to the specific 'trigger' section of the EAP + And if it is a PDF this is not possible and it loads at the top. + + Scenario: Click 'Video Guide' + When the user clicks on 'Video Guide' button + Then a popup opens where the video can be played + + Scenario: Click 'Activation Log' + When the user clicks on 'Activation Log' button + Then the 'activation-log' page opens up in a new tab + And it contains 'disaster-activation-data' for all the countries + And it contains 'country-code','disaster-type','placecode','name','startDate','endDate','stopped','manuallyStopped','exposureIndicator','exposureValue','databaseId' + And it is in a table + And it can easily be copied to Excel + + Scenario: Click 'Export view' + When the user clicks the "Export View" button in the header + Then the popup open with the message and shows a link to take screenshot + And the user can follow the instructions provided + And can close the popup window if do not need to take screenshot + + Scenario: View general trigger information + When the user views the 2nd speech bubble + Then if NON-TRIGGERED it mentions there are 'no triggers' + And if TRIGGERED it mentions there is a trigger + And if EVENT-WITHOUT-TRIGGER it mentions an activated event below threshold ('typhoon' only) + And it mentions when the event this trigger belongs to first started + And it mentions for when the trigger is expected + And it mentions the name of the event if applicable ('typhoon' only) + And the exact UX copy differs between disaster-types (Potentially: document in more detail) + And for 'drought' if there are multiple triggered lead-times, then the other ones are mentioned here with instructions to look at them + + Scenario: View general trigger information with 2 or more active events + Given the selected 'disaster-type' is 'typhoon' + Given there are 2 or more active events (see 'API-admin-user/Upload_mock_data.feature' for instructions how to upload additional events) + When the user views the 2nd speech bubble + Then it mentions a paragraph per event with event-details such as name, start-date, arrival-time. + And it contains a button for each event + And the first event/button is the selected event and has a 'selected styling' + And the other buttons have a 'non-selected' styling + And all info in the map by default refers to the 1st event + And the timeline sections has just as many active timeline-buttons as there are events + + Scenario: View general trigger information when event already made landfall ('typhoon' only) + Given the selected 'disaster-type' is 'typhoon' + Given the event has already made landfall (i.e. leadTime = '0-hour') + When the user views the 2nd speech bubble + Then it mentions - in addition to earlier scenarios - that it already made landfall + + Scenario: View general trigger information with clear-out warning or message + Given the 'showMonthlyEapActions' is 'true' (currently only for Kenya Droughts) + Given 1 or more 'droughtForecastSeasons' are provided (October & March, for Kenya Droughts) + Given the current month (month of last (mock) pipeline run) is one month before one of the 'droughtForecastSeasons' + When the user views the 2nd speech bubble + Then - in addition to the normal information - it mentions a second paragraph + And it is red-colored and bold + And it reads that the EAP-actions related to the current trigger will be automatically cleared out after this month + ------ + Given - instead - that the current month (month of last (mock) pipeline run) is exactly one of the 'droughtForecastSeasons' + Then the message reads that the EAP-actions have been automatically cleared out after this month + + Scenario: Switch event + Given the selected 'disaster-type' is 'typhoon' + Given there are 2 or more active events (see 'API-admin-user/Upload_mock_data.feature' for instructions how to upload additional events) + When the user clicks a non-selected event button + Then that button switches to active state + And the previously active button switches to inactive state + And the selected timeline-button in the timeline-section also switches to the one related to the new event + And all data in the dashboard switches to the new event + + Scenario: View overview & further instructions + Given the dashboard is in TRIGGERED state + Given the selected "admin level" is the "default admin level" + Given there are no areas for which the trigger is stopped + When the user views the 3rd speech bubble + Then it mentions an overview of the triggered areas, sorted by the "action unit" of that disaster type (e.g. "exposed population") + And it mentions instructions that you can click an area in the map + And if done so, that you can perform additional actions per area. + And no such speech bubble is available on other admin levels then the default admin level + + Scenario: View overview & further instructions WITH "stopped" areas + Given there are areas for which the trigger is stopped + Given for the rest the same as scenario above + Then the same applies as scenario above + And additionally there is a separate 4th speech bubble which lists stopped areas + And it is grey colorded (as "NON-TRIGGERED") with black border + And it mentions the stopped areas in same order as scenario above + And it mentions instructions that you can click a stopped area in the map + + Scenario: Click on area in triggered areas list in chat + Given the dashboard is in TRIGGERED state + Given the selected "admin level" is the "default admin level" + When the user views the 3rd speech bubble + And the user clicks on the name of a triggered area + Then the map zooms down on the selected area + And a new speech bubble appears in the chat section + And it is pointed to the right instead of the left + And it contains the "admin area" type and name + And it contains the relevant "action unit" type and value (e.g. "Exposed population" or "Potential cases") + And it contains a button to go back to the list of all triggered areas + And it contains a list of all EAP-actions (same for every area) with "area of focus" name and "action" description + And it shows which EAP-actions are already "checked" via the "checkbox" + And it contains a disabled "save" button + And it contains an enabled "stop trigger/alert" button + + Scenario: Click on area in stopped areas list in chat + Given the area is "stopped" + When the user selects a stopped area from map (see 'Use_map_section.feature') + Given the dashboard is in TRIGGERED state + Given the selected "admin level" is the "default admin level" + When the user views the 4rd speech bubble + And the user clicks on the name of an area + Then the map zooms down on the selected area + And a new speech bubble appears in the chat section + And it is grey colorded (as "NON-TRIGGERED") with black border + And it mentions the start date of this trigger + And the stop date + And the user who stopped it + And it contains a button to go back to the list of all triggered areas + + Scenario: View chat-section after area-selection in map + Given the area is not "stopped" + Given EAP-actions are static and not monthly ('showMonthlyEapActions = false') + When the user selects a triggered area from map (see 'Use_map_section.feature') + Then the speech bubbles giving overview of active and stopped areas disappear + And a new speech bubble appears in the chat section + And it is pointed to the right instead of the left + And it contains the "admin area" type and name + And it contains the relevant "action unit" type and value (e.g. "Exposed population" or "Potential cases") + And it contains a button to go back to the list of all triggered areas + And it contains a list of all EAP-actions (same for every area) with "area of focus" name and "action" description + And it shows which EAP-actions are already "checked" via the "checkbox" + And it contains a disabled "save" button + And it contains an enabled "stop trigger/alert" button + + Scenario: View chat-section after area-selection in map with monthly actions + Given the area is not "stopped" + Given EAP-actions are monthly ('showMonthlyEapActions = true') - currently only Kenya and Ethiopia Drought + When the user selects a triggered area from map (see 'Use_map_section.feature') + Then everything happens as above + And additionally above the EAP-action a sentence appears on which sources lead to the actions + And it mentions how many actions there are + And only the action up until the current month are shown + And if there is an overlap of seasons, actions not relevant to the selected season are not shown + And behind the action in brackets and bold the month to complete the action is shown + + Scenario: View chat-section after area-selection in map of "stopped" area + Given the area is "stopped" + When the user selects a triggered area from map (see 'Use_map_section.feature') + Then the speech bubbles giving overview of active and stopped areas disappear + And a new speech bubble appears in the chat section + And it is grey colorded (as "NON-TRIGGERED") with black border + And it mentions the start date of this trigger + And the stop date + And the user who stopped it + + Scenario: Check or uncheck EAP-actions per triggered area + Given the EAP-action speech-bubble is showing for one or more areas + When the user checks or unchecks an EAP-action + Then the 'save' button enables + + When the user makes further changes that amount to the original selection + Then the 'save' button disables again + + When the user clicks the 'save' button + Then a popup appears that the database is updated + And it closes again by clicking outside of it + And (after refreshing) the Area-of-Focus summary will have updated (see 'Use_area_of_focus_section.feature') + + Scenario: Stop trigger + Given the dashboard is in TRIGGERED mode + When the user clicks the 'stop trigger/alert' button + Then a popup appears that asks if you are sure + And if confirmed the dashboard updates + And the area will now show as grey with black border in the map + And the lists of active vs stopped areas in the chat section will have updated + And the event is now stopped with today's date in the database + And as such reflected in the "activation report" \ No newline at end of file diff --git a/features/IBF-portal-user/dashboard-page/View_area_of_focus_section.feature b/features/IBF-portal-user/dashboard-page/View_area_of_focus_section.feature index 6bf0f8f69..73b8075a6 100644 --- a/features/IBF-portal-user/dashboard-page/View_area_of_focus_section.feature +++ b/features/IBF-portal-user/dashboard-page/View_area_of_focus_section.feature @@ -1,19 +1,19 @@ @ibf-portal-user Feature: View area-of-focus section -Background: - Given a logged-in user on the dashboard page + Background: + Given a logged-in user on the dashboard page -Scenario: View area of focus section in NON-TRIGGERED mode - When the user enters the dashboard page - Then this section is not visible + Scenario: View area of focus section in NON-TRIGGERED mode + When the user enters the dashboard page + Then this section is not visible -Scenario: View area of focus section in TRIGGERED or OLD-EVENT mode - When the user enters the dashboard page - Then the user sees the Area-of-focus summary at the bottom of the middle column - And it mentions an 'Actions Summary' title - And it has grey background in OLD-EVENT mode and light-purple background in TRIGGERED mode - And it mentions a list of 7 "areas of focus" with an icon for each - And for each "area of focus" it lists the "checked" number of actions vs. the total number of actions - And the total number of actions is the nr of triggered areas times the nr of actions in that area - And the "checked" number of actions is based on actions in the chat-section (see 'Use_chat_section.feature') + Scenario: View area of focus section in TRIGGERED mode + When the user enters the dashboard page + Then the user sees the Area-of-focus summary at the bottom of the middle column + And it mentions an 'Actions Summary' title + And it has light-purple background + And it mentions a list of 7 "areas of focus" with an icon for each + And for each "area of focus" it lists the "checked" number of actions vs. the total number of actions + And the total number of actions is the nr of triggered areas times the nr of actions in that area + And the "checked" number of actions is based on actions in the chat-section (see 'Use_chat_section.feature') diff --git a/features/README.md b/features/README.md index 4fbce451c..8ee0168fe 100644 --- a/features/README.md +++ b/features/README.md @@ -4,8 +4,7 @@ IMPORTANT: - A major distinction in every feature is whether the Portal is in TRIGGERED or NON-TRIGGERED mode. - These distinctions are made within each file. -- There can be a 3rd (more rare) scenario: OLD-EVENT. This is sometimes explicity mentioned. Where not further specified, it falls under NON-TRIGGERED. -- A 4th scenario (currently 'typhoon' only) is EVENT-WITHOUT-TRIGGER +- A 3rd scenario (currently 'typhoon' only) is EVENT-WITHOUT-TRIGGER (this needs updating) ### For IBF-portal user diff --git a/features/pipeline-user/Run_pipeline.feature b/features/pipeline-user/Run_pipeline.feature index 5a2a1df2e..385af02b4 100644 --- a/features/pipeline-user/Run_pipeline.feature +++ b/features/pipeline-user/Run_pipeline.feature @@ -1,87 +1,86 @@ @pipeline-user Feature: Run pipeline -Background: - Given a script which has first successfully authenticated as user with 'admin' role - Given disaster-type 'X' (floods | heavy-rain | drought | malaria | dengue | typhoon) - -Scenario: Successfully run pipeline - Given all below scenarios that are applicable given disaster-type 'X' are successfully performed - When the pipeline has finished running - Then - if triggered - you receive an email if you are personally subscribed in mailchimp with the right tag (dependent on 'country' and 'production vs other') - And the email is sent to the 'IBF Teams channel' if it is a production trigger - And further details of the email are described in the specific scenario 'Successfully trigger email creation and sending' - - And the dashboard is updated when looking at it - And the top speech bubble in the chat section mentions the new 'last model run timestamp' - And the dashboard is in 'general triggered state' if triggered - And it is in 'general non-triggered state' if not triggered - And it is in 'old event mode' (mentioned in chat section) if a recent triggered event is now not actively triggered any more - And further details are specified in all detailed scenarios in 'IBF-portal-user/dashboard-page/' - -Scenario: Successfully upload exposure data - Given any disaster-type - When running this part of the script - Then a success status code is returned - And in the dashboard the exposure layers are visible with individual and aggregate values as expected - -Scenario: Successfully upload raster file - Given disaster-type is 'floods' | 'heavy-rain' - When running this part of the script - Then a success status code is returned - And in the dashboard the ' extent' is visible as expected - -Scenario: Successfully upload typhoon track data - Given disaster-type is 'typhoon' - When running this part of the script - Then a success status code is returned - And in the dashboard a track layer is visible as expected - -Scenario: Successfully upload Glofas station forecast data - Given disaster-type is 'floods' - When running this part of the script - Then a success status code is returned - And in the dashboard the Glofas station alert states and values are updated as expected - -Scenario: Successfully upload 'trigger per lead-time' data - Given disaster-type is 'floods' - When running this part of the script - Then a success status code is returned - And in the dashboard the timeline-buttons have colored/alert states as expected - And this means that triggered lead-times are purple colored, even if they are non-active - -Scenario: Successfully trigger email creation and sending - Given any disaster-type - When running this part of the script - Then a success status code is returned - - And on the server first a check is performed on 'triggered' state - And it only continues with email creation + sending when triggered - - And the email has a subject with ': () - - And the email has a purple-colored header - And it contains the country logo's - And it contains a a title ' Trigger Notification' - And it contains today's date - And it contains a list of 'lead times' for which a trigger is activated - - And in the main text this general info is repeated - And a button to go to the dashboard is included - And it comes with direct links to video and/or PDF manuals if available (which is country-dependent) - And a button with link to the EAP is included - And a button to join a pre-made static WhatsApp/Telegram group is included - - And per triggered leadtime a list of triggered admin-areas is included - And it contains per admin-area the predicted exposure - And also the 'alert level' (currently only filled for 'floods') - - And at the bottom it contains a Trigger Statement - - And the email is automatically sent to all 'subscribed' users in 'Mailchimp' with the right tag - And e.g. the tag 'Zambia' is used only on production-server 'ibf-zambia' - And the corresponding tag 'Zambia test' is used on all other environments (local/test/staging) for testing purposes - + Background: + Given a script which has first successfully authenticated as user with 'admin' role + Given disaster-type 'X' (floods | heavy-rain | drought | malaria | dengue | typhoon) + + Scenario: Successfully run pipeline + Given all below scenarios that are applicable given disaster-type 'X' are successfully performed + When the pipeline has finished running + Then - if triggered - you receive an email if you are personally subscribed in mailchimp with the right tag (dependent on 'country' and 'production vs other') + And the email is sent to the 'IBF Teams channel' if it is a production trigger + And further details of the email are described in the specific scenario 'Successfully trigger email creation and sending' + + And the dashboard is updated when looking at it + And the top speech bubble in the chat section mentions the new 'last model run timestamp' + And the dashboard is in 'general triggered state' if triggered + And it is in 'general non-triggered state' if not triggered + And further details are specified in all detailed scenarios in 'IBF-portal-user/dashboard-page/' + + Scenario: Successfully upload exposure data + Given any disaster-type + When running this part of the script + Then a success status code is returned + And in the dashboard the exposure layers are visible with individual and aggregate values as expected + + Scenario: Successfully upload raster file + Given disaster-type is 'floods' | 'heavy-rain' + When running this part of the script + Then a success status code is returned + And in the dashboard the ' extent' is visible as expected + + Scenario: Successfully upload typhoon track data + Given disaster-type is 'typhoon' + When running this part of the script + Then a success status code is returned + And in the dashboard a track layer is visible as expected + + Scenario: Successfully upload Glofas station forecast data + Given disaster-type is 'floods' + When running this part of the script + Then a success status code is returned + And in the dashboard the Glofas station alert states and values are updated as expected + + Scenario: Successfully upload 'trigger per lead-time' data + Given disaster-type is 'floods' + When running this part of the script + Then a success status code is returned + And in the dashboard the timeline-buttons have colored/alert states as expected + And this means that triggered lead-times are purple colored, even if they are non-active + + Scenario: Successfully trigger email creation and sending + Given any disaster-type + When running this part of the script + Then a success status code is returned + + And on the server first a check is performed on 'triggered' state + And it only continues with email creation + sending when triggered + + And the email has a subject with ': () + + And the email has a purple-colored header + And it contains the country logo's + And it contains a a title ' Trigger Notification' + And it contains today's date + And it contains a list of 'lead times' for which a trigger is activated + + And in the main text this general info is repeated + And a button to go to the dashboard is included + And it comes with direct links to video and/or PDF manuals if available (which is country-dependent) + And a button with link to the EAP is included + And a button to join a pre-made static WhatsApp/Telegram group is included + + And per triggered leadtime a list of triggered admin-areas is included + And it contains per admin-area the predicted exposure + And also the 'alert level' (currently only filled for 'floods') + + And at the bottom it contains a Trigger Statement + + And the email is automatically sent to all 'subscribed' users in 'Mailchimp' with the right tag + And e.g. the tag 'Zambia' is used only on production-server 'ibf-zambia' + And the corresponding tag 'Zambia test' is used on all other environments (local/test/staging) for testing purposes + diff --git a/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html b/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html index f7c6624b2..713f142c2 100644 --- a/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html +++ b/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html @@ -44,7 +44,7 @@ - - - { - if (this.eventService.isOldEvent()) { - this.filteredActiveAreas = [...this.triggeredAreas]; - this.filteredStoppedAreas = []; - } else { - this.filteredActiveAreas = []; - this.filteredStoppedAreas = []; - } + this.filteredActiveAreas = []; + this.filteredStoppedAreas = []; }; private setupChatText = () => { diff --git a/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html b/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html index 8b54ca553..4d6ebf509 100644 --- a/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html +++ b/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html @@ -238,26 +238,6 @@ - -

-

-
-

{{ countryName }} {{ disasterTypeLabel }}?", "alert-input-secret-placeholder": "Please enter the reset secret", "alert-button-cancel": "Cancel", - "alert-button-old-event": "Old event", "alert-button-no-trigger": "No Trigger", "alert-button-trigger": "Trigger", "alert-error-api-error": "Failed to set mock scenario.", @@ -150,9 +149,6 @@ "welcome": "First warning on {{firstLeadTimeDate}}", "welcome-below-trigger": "First trigger on {{firstTriggerLeadTimeDate}}." }, - "active-event-no-trigger": { - "welcome": "There is no active alert any more, as can be seen in the map, which always reflects the latest forecast. However, you can still see here the EAP-actions that relate to the event that started on {{ startDate }} and ended on {{ endDate }}. These actions stay visible here for 7 days after the end-date." - }, "active-event": { "overview": "There are {{ nrTriggeredAreas }} {{ adminAreaLabelPlural }} exposed. They are listed below by alert level and by {{ actionIndicator}}. Only triggered {{adminAreaLabelPlural}} are highlighted in the map with a red outline.", "overview-below-trigger": "There are {{ nrTriggeredAreas }} {{ adminAreaLabelPlural }} exposed. They are listed below by alert level and by {{ actionIndicator}}.", @@ -210,9 +206,6 @@ "welcome": "A {{ disasterTypeLabel }} trigger warning was issued on {{ startDate }} for {{ firstLeadTimeDate }}.", "welcome-duration": "A {{ disasterTypeLabel }} trigger warning was issued on {{ startDate }} for {{ firstLeadTimeDate }} with a duration of {{ duration}} month(s)." }, - "active-event-no-trigger": { - "welcome": "There is no active trigger any more, as can be seen in the map, which always reflects the latest forecast. However, you can still see here the EAP-actions that relate to the event that started on {{ startDate }} and ended on {{ endDate }}. These actions stay visible here for 7 days after the end-date." - }, "clear-out": { "regional": { "warning": "Please be aware that the EAP-actions for some areas related to this trigger will be automatically cleared out by the IBF-Portal after this month, due to the start of the next rainy season in those areas.", @@ -297,9 +290,6 @@ "header-ongoing": "Ongoing alert: {{ disasterTypeLabel }}", "welcome": "An alert for {{ disasterTypeLabel }} was issued on {{ startDate }}. It is estimated to arrive around {{ leadTime }} {{ timeUnit }}(s) from now." }, - "active-event-no-trigger": { - "welcome": "There is no active alert any more, as can be seen in the map, which always reflects the latest forecast. However, you can still see here the EAP-actions that relate to the event that started on {{ startDate }} and ended on {{ endDate }}. These actions stay visible here for 7 days after the end-date." - }, "active-event": { "overview": "There are {{ nrTriggeredAreas }} {{ adminAreaLabelPlural }} above alert threshold. They are listed below in order of {{ actionIndicator}} and are highlighted in the map with a red outline.", "instruction": "Please select an area to monitor by selecting from the list or on the map and manage the alert and the preplanned anticipatory actions of each area.", @@ -322,9 +312,6 @@ "header-ongoing": "Ongoing alert: {{ disasterTypeLabel }}", "welcome": "An alert for {{ disasterTypeLabel }} was issued on {{ startDate }}. The alert is issued for {{ firstLeadTimeDate }}." }, - "active-event-no-trigger": { - "welcome": "There is no alert active any more (as you can see in the map). However, you can still see here the SOP actions that relate to the event that started on {{ startDate }} and ended on {{ endDate }}. These actions stay visible here until the end of the month." - }, "active-event": { "overview": "There are {{ nrTriggeredAreas }} {{ adminAreaLabelPlural }} above alert threshold. They are listed below in order of {{ actionIndicator}} and are highlighted in the map with a red outline.", "instruction": "You can click on the month tabs above the map to see the predictions for each month. Please select an area to monitor by selecting from the list or on the map and manage the trigger and the preplanned anticipatory actions of each area.", @@ -350,9 +337,6 @@ "header": "Alert: {{ disasterTypeLabel }}", "welcome": "An alert for {{ disasterTypeLabel }} was issued on {{ startDate }}. The alert is issued for {{ firstLeadTimeDate }}." }, - "active-event-no-trigger": { - "welcome": "There is no alert active any more (as you can see in the map). However, you can still see here the SOP actions that relate to the event that started on {{ startDate }} and ended on {{ endDate }}. These actions stay visible here until the end of the month." - }, "active-event": { "overview": "There are {{ nrTriggeredAreas }} {{ adminAreaLabelPlural }} above alert threshold. They are listed below in order of {{ actionIndicator}} and are highlighted in the map with a red outline.", "instruction": "You can click on the month tabs above the map to see the predictions for each month. Please select an area to monitor by selecting from the list or on the map and manage the trigger and the preplanned anticipatory actions of each area.", diff --git a/services/API-service/migration/1710755059526-RemoveActiveTrigger.ts b/services/API-service/migration/1710755059526-RemoveActiveTrigger.ts new file mode 100644 index 000000000..e41a06f90 --- /dev/null +++ b/services/API-service/migration/1710755059526-RemoveActiveTrigger.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveActiveTrigger1710755059526 implements MigrationInterface { + name = 'RemoveActiveTrigger1710755059526'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "IBF-app"."event-place-code" DROP COLUMN "activeTrigger"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "IBF-app"."event-place-code" ADD "activeTrigger" boolean NOT NULL DEFAULT true`, + ); + } +} diff --git a/services/API-service/src/api/admin-area/services/event-area.service.ts b/services/API-service/src/api/admin-area/services/event-area.service.ts index 7eec292eb..42f6f65ec 100644 --- a/services/API-service/src/api/admin-area/services/event-area.service.ts +++ b/services/API-service/src/api/admin-area/services/event-area.service.ts @@ -72,7 +72,7 @@ export class EventAreaService { countryCodeISO3, disaster.disasterType, ); - for await (const event of events.filter((e) => e.activeTrigger)) { + for await (const event of events) { const eventArea = await this.eventAreaRepository .createQueryBuilder('area') .where({ @@ -142,7 +142,7 @@ export class EventAreaService { ); const aggregateRecords = []; - for await (const event of events.filter((e) => e.activeTrigger)) { + for await (const event of events) { const aggregateValues = await this.getEventAreaAggregatesPerIndicator( disasterType, lastTriggeredDate, @@ -171,7 +171,7 @@ export class EventAreaService { ); const records = []; - for await (const event of events.filter((e) => e.activeTrigger)) { + for await (const event of events) { const aggregateValues = await this.getEventAreaAggregatesPerIndicator( disasterType, lastTriggeredDate, diff --git a/services/API-service/src/api/event/event-place-code.entity.ts b/services/API-service/src/api/event/event-place-code.entity.ts index 7ece43abc..69fb297d3 100644 --- a/services/API-service/src/api/event/event-place-code.entity.ts +++ b/services/API-service/src/api/event/event-place-code.entity.ts @@ -53,9 +53,6 @@ export class EventPlaceCodeEntity { @Column({ type: 'timestamp', nullable: true }) public manualStoppedDate: Date; - @Column({ default: true }) - public activeTrigger: boolean; - @Column({ default: false }) public stopped: boolean; diff --git a/services/API-service/src/api/event/event.service.ts b/services/API-service/src/api/event/event.service.ts index 7a3ba0e18..b51f18b2f 100644 --- a/services/API-service/src/api/event/event.service.ts +++ b/services/API-service/src/api/event/event.service.ts @@ -18,10 +18,7 @@ import { } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; -import { - LeadTime, - LeadTimeUnit, -} from '../admin-area-dynamic-data/enum/lead-time.enum'; +import { LeadTime } from '../admin-area-dynamic-data/enum/lead-time.enum'; import { UploadTriggerPerLeadTimeDto } from './dto/upload-trigger-per-leadtime.dto'; import { TriggerPerLeadTime } from './trigger-per-lead-time.entity'; import { EventSummaryCountry, TriggeredArea } from '../../shared/data.model'; @@ -76,23 +73,18 @@ export class EventService { .addSelect([ 'to_char(MIN("startDate") , \'yyyy-mm-dd\') AS "startDate"', 'to_char(MAX("endDate") , \'yyyy-mm-dd\') AS "endDate"', - 'MAX(event."activeTrigger"::int)::boolean AS "activeTrigger"', + // TODO: for now keep this, remove need in front-end when possible + 'MAX(1)::boolean AS "activeTrigger"', 'MAX(event."thresholdReached"::int)::boolean AS "thresholdReached"', ]) - .where('closed = :closed', { + .where({ closed: false, + endDate: MoreThanOrEqual(recentDate.date), + disasterType: disasterType, }) - .andWhere('"endDate" >= :date', { date: recentDate.date }) - .andWhere( - // for typhoon/flash-floods filter also on activeTrigger = true, thereby disabling old-event scenario - `(event."disasterType" NOT IN ('typhoon','flash-floods') OR (event."disasterType" IN ('typhoon','flash-floods') AND event."activeTrigger" = true))`, - ) .andWhere('area."countryCodeISO3" = :countryCodeISO3', { countryCodeISO3: countryCodeISO3, }) - .andWhere('event."disasterType" = :disasterType', { - disasterType: disasterType, - }) .getRawMany(); for await (const event of eventSummary) { @@ -299,7 +291,6 @@ export class EventService { 'event."actionsValue"', 'event."triggerValue"', 'event."eventPlaceCodeId"', - 'event."activeTrigger"', 'event."stopped"', 'event."startDate"', 'event."manualStoppedDate" AS "stoppedDate"', @@ -327,11 +318,7 @@ export class EventService { const triggeredAreas = await triggeredAreasQuery.getRawMany(); for (const area of triggeredAreas) { - if ( - triggeredPlaceCodes.length === 0 && - disasterType === DisasterType.Typhoon - ) { - // Exception to speed up typhoon performance. Works because old-event is switched off for typhoon. Should be refactored better. + if (triggeredPlaceCodes.length === 0) { area.eapActions = []; } else if (area.triggerValue < 1) { // Do not show actions for below-trigger events/areas @@ -612,10 +599,7 @@ export class EventService { date, ); - // Then set all events to inactive - await this.setAllEventsToInactive(countryCodeISO3, disasterType); - - // update active ones to true + update population and end_date + // update existing event areas + update population and end_date await this.updateExistingEventAreas( countryCodeISO3, disasterType, @@ -623,7 +607,7 @@ export class EventService { eventName, ); - // add new ones + // add new event areas await this.addNewEventAreas( countryCodeISO3, disasterType, @@ -631,62 +615,10 @@ export class EventService { eventName, ); - // close old events + // close old event areas await this.closeEventsAutomatic(countryCodeISO3, disasterType, eventName); } - private async setAllEventsToInactive( - countryCodeISO3: string, - disasterType: DisasterType, - ) { - const countryAdminAreaIds = await this.getCountryAdminAreaIds( - countryCodeISO3, - ); - // only set records that are not updated yet in this sequence of pipeline runs (e.g. multiple events in 1 day) - // after the 1st event this means everything is updated .. - // .. and from the 2nd event onwards if will not be set to activeTrigger=false again .. - const recentDate = await this.helperService.getRecentDate( - countryCodeISO3, - disasterType, - ); - const cutoffDate = this.helperService.getUploadCutoffMoment( - disasterType, - recentDate.timestamp, - ); - const endDate = await this.getEndDate(disasterType, cutoffDate); - - // .. but only check on endDate if eventName is not null > I cannot remember why.. - const eventAreas = await this.eventPlaceCodeRepo.find({ - where: [ - { - adminArea: { id: In(countryAdminAreaIds) }, - disasterType: disasterType, - activeTrigger: true, - endDate: LessThan(endDate), - }, - { - adminArea: { id: In(countryAdminAreaIds) }, - disasterType: disasterType, - activeTrigger: true, - eventName: IsNull(), - }, - ], - }); - - if (eventAreas.length) { - await this.eventPlaceCodeRepo - .createQueryBuilder() - .update() - .set({ - activeTrigger: false, - }) - .where({ - eventPlaceCodeId: In(eventAreas.map((area) => area.eventPlaceCodeId)), - }) - .execute(); - } - } - private async getAffectedAreas( countryCodeISO3: string, disasterType: DisasterType, @@ -797,14 +729,12 @@ export class EventService { const idsToUpdateAboveThreshold = []; const idsToUpdateBelowThreshold = []; const uploadDate = await this.getRecentDate(countryCodeISO3, disasterType); - const endDate = await this.getEndDate(disasterType, uploadDate.timestamp); unclosedEventAreas.forEach((eventArea) => { const affectedArea = affectedAreas.find( (area) => area.placeCode === eventArea.adminArea.placeCode, ); if (affectedArea) { - eventArea.activeTrigger = true; - eventArea.endDate = endDate; + eventArea.endDate = uploadDate.timestamp; if (affectedArea.triggerValue === 1) { eventArea.thresholdReached = true; idsToUpdateAboveThreshold.push(eventArea.eventPlaceCodeId); @@ -815,10 +745,18 @@ export class EventService { } }); // .. first fire one query to update all rows that need thresholdReached = true - await this.updateEvents(idsToUpdateAboveThreshold, true, endDate); + await this.updateEvents( + idsToUpdateAboveThreshold, + true, + uploadDate.timestamp, + ); // .. then fire one query to update all rows that need thresholdReached = false - await this.updateEvents(idsToUpdateBelowThreshold, false, endDate); + await this.updateEvents( + idsToUpdateBelowThreshold, + false, + uploadDate.timestamp, + ); // .. lastly we update those records where actionsValue or triggerValue changed await this.updateValues(unclosedEventAreas, affectedAreas); @@ -834,7 +772,6 @@ export class EventService { .createQueryBuilder() .update() .set({ - activeTrigger: true, thresholdReached: aboveThreshold, endDate: endDate, }) @@ -921,11 +858,7 @@ export class EventService { eventArea.triggerValue = area.triggerValue; eventArea.actionsValue = +area.actionsValue; eventArea.startDate = startDate.timestamp; - eventArea.endDate = await this.getEndDate( - disasterType, - startDate.timestamp, - ); - eventArea.activeTrigger = true; + eventArea.endDate = startDate.timestamp; eventArea.stopped = false; eventArea.manualStoppedDate = null; eventArea.disasterType = disasterType; @@ -948,7 +881,7 @@ export class EventService { disasterType, ); const whereFilters = { - endDate: LessThan(uploadDate.timestamp), + endDate: LessThan(uploadDate.timestamp), // If the area was not prolongued earlier, then the endDate is not updated and is therefore less than the uploadDate adminArea: In(countryAdminAreaIds), disasterType: disasterType, closed: false, @@ -976,20 +909,6 @@ export class EventService { await this.eventPlaceCodeRepo.save(aboveThresholdEvents); } - private async getEndDate( - disasterType: DisasterType, - passedDate: Date, - ): Promise { - const today = new Date(JSON.parse(JSON.stringify(passedDate))); - - const disasterTypeEntity = await this.disasterTypeRepository.findOne({ - where: { disasterType: disasterType }, - }); - return disasterTypeEntity.leadTimeUnit === LeadTimeUnit.month - ? new Date(today.getFullYear(), today.getMonth() + 1, 0, 23, 59, 59) - : new Date(today.setDate(today.getDate() + 7)); - } - public async postEventMapImage( countryCodeISO3: string, disasterType: DisasterType, diff --git a/services/API-service/src/api/notification/notification.service.ts b/services/API-service/src/api/notification/notification.service.ts index ae8c0b790..7d0858fa8 100644 --- a/services/API-service/src/api/notification/notification.service.ts +++ b/services/API-service/src/api/notification/notification.service.ts @@ -97,9 +97,8 @@ export class NotificationService { const date = uploadDate ? new Date(uploadDate) : new Date(); const yesterdayActiveDate = new Date(date.setDate(date.getDate() + 6)); // determine yesterday still active events by endDate lying (7 - 1) days in the future if ( - !event.activeTrigger && new Date(event.endDate) >= - new Date(yesterdayActiveDate.setHours(0, 0, 0, 0)) + new Date(yesterdayActiveDate.setHours(0, 0, 0, 0)) ) { return true; } @@ -112,7 +111,7 @@ export class NotificationService { disasterType: DisasterType, countryCodeISO3: string, ): Promise { - let send = event.activeTrigger; + let send = true; if (disasterType === DisasterType.Typhoon) { send = await this.typhoonTrackService.shouldSendNotification( countryCodeISO3, diff --git a/services/API-service/src/api/notification/whatsapp/whatsapp.service.ts b/services/API-service/src/api/notification/whatsapp/whatsapp.service.ts index 31ee97bed..2fac18d72 100644 --- a/services/API-service/src/api/notification/whatsapp/whatsapp.service.ts +++ b/services/API-service/src/api/notification/whatsapp/whatsapp.service.ts @@ -246,9 +246,9 @@ export class WhatsappService { country.countryCodeISO3, disasterType.disasterType, ); - const activeEvents = events - .filter((event) => event.activeTrigger) - .sort((a, b) => (a.firstLeadTime > b.firstLeadTime ? 1 : -1)); + const activeEvents = events.sort((a, b) => + a.firstLeadTime > b.firstLeadTime ? 1 : -1, + ); if (activeEvents.length === 0) { const noTriggerMessage = this.configureNoTriggerMessage( country, From c64e58241d109837992764ea5ee854980161d8d2 Mon Sep 17 00:00:00 2001 From: jannisvisser Date: Mon, 18 Mar 2024 12:02:54 +0100 Subject: [PATCH 2/8] refactor: rm 'activeTrigger' from frontend AB#26940 --- .../about-btn/about-btn.component.ts | 2 +- .../admin-level/admin-level.component.ts | 2 +- .../aggregates/aggregates.component.ts | 2 +- .../areas-of-focus-summary.component.ts | 2 +- .../app/components/chat/chat.component.html | 8 ++-- .../src/app/components/chat/chat.component.ts | 6 +-- .../event-speech-bubble.component.html | 6 +-- .../event-switcher.component.ts | 5 +- .../export-view/export-view.component.ts | 2 +- .../ibf-guide-button.component.ts | 2 +- .../src/app/components/map/map.component.ts | 4 +- .../app/components/matrix/matrix.component.ts | 4 +- .../components/timeline/timeline.component.ts | 2 +- .../user-state/user-state.component.ts | 2 +- .../src/app/services/aggregates.service.ts | 2 +- .../src/app/services/event.service.ts | 21 ++------- .../src/app/services/map-legend.service.ts | 7 ++- .../src/app/services/map.service.ts | 18 ++++--- .../src/app/services/point-marker.service.ts | 2 +- .../src/app/services/timeline.service.ts | 47 ++++++++++--------- .../src/app/types/event-state.ts | 1 - .../src/app/types/triggered-area.ts | 1 - .../src/api/event/event.service.ts | 2 - 23 files changed, 63 insertions(+), 87 deletions(-) diff --git a/interfaces/IBF-dashboard/src/app/components/about-btn/about-btn.component.ts b/interfaces/IBF-dashboard/src/app/components/about-btn/about-btn.component.ts index fa367fb12..fea71e958 100644 --- a/interfaces/IBF-dashboard/src/app/components/about-btn/about-btn.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/about-btn/about-btn.component.ts @@ -64,7 +64,7 @@ export class AboutBtnComponent implements OnDestroy { public btnAction() { this.analyticsService.logEvent(AnalyticsEvent.aboutTrigger, { page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); diff --git a/interfaces/IBF-dashboard/src/app/components/admin-level/admin-level.component.ts b/interfaces/IBF-dashboard/src/app/components/admin-level/admin-level.component.ts index 3090196f0..0fbffff03 100644 --- a/interfaces/IBF-dashboard/src/app/components/admin-level/admin-level.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/admin-level/admin-level.component.ts @@ -146,7 +146,7 @@ export class AdminLevelComponent implements OnInit, OnDestroy { adminLevel, adminLevelState: layer.active, page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); } else { diff --git a/interfaces/IBF-dashboard/src/app/components/aggregates/aggregates.component.ts b/interfaces/IBF-dashboard/src/app/components/aggregates/aggregates.component.ts index 112e45742..63e7381f7 100644 --- a/interfaces/IBF-dashboard/src/app/components/aggregates/aggregates.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/aggregates/aggregates.component.ts @@ -211,7 +211,7 @@ export class AggregatesComponent implements OnInit, OnDestroy { this.analyticsService.logEvent(AnalyticsEvent.aggregateInformation, { indicator: indicator.name, page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); diff --git a/interfaces/IBF-dashboard/src/app/components/areas-of-focus-summary/areas-of-focus-summary.component.ts b/interfaces/IBF-dashboard/src/app/components/areas-of-focus-summary/areas-of-focus-summary.component.ts index 601a27b76..560c0df66 100644 --- a/interfaces/IBF-dashboard/src/app/components/areas-of-focus-summary/areas-of-focus-summary.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/areas-of-focus-summary/areas-of-focus-summary.component.ts @@ -182,7 +182,7 @@ export class AreasOfFocusSummaryComponent implements OnInit, OnDestroy { this.analyticsService.logEvent(AnalyticsEvent.aggregateInformation, { indicator: id, page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); diff --git a/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html b/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html index 713f142c2..81456b4a8 100644 --- a/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html +++ b/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html @@ -44,7 +44,7 @@ - + +

0, component: this.constructor.name, }); @@ -330,7 +330,7 @@ export class ChatComponent implements OnInit, OnDestroy { this.analyticsService.logEvent(AnalyticsEvent.eapSubmit, { placeCode, page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); @@ -434,7 +434,7 @@ export class ChatComponent implements OnInit, OnDestroy { ): void { this.analyticsService.logEvent(AnalyticsEvent.stopTrigger, { page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, placeCode, }); this.apiService.toggleTrigger(eventPlaceCodeId).subscribe({ diff --git a/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html b/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html index 4d6ebf509..c5176203b 100644 --- a/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html +++ b/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html @@ -6,7 +6,7 @@ [isSelected]="eventBubbleIsSelected(event.eventName)" [isConnected]="true" > - + - +

e.activeTrigger) - .length > 1 - ); + return this.eventState?.events.length > 1; } private onDisasterTypeChange = (disasterType: DisasterType) => { diff --git a/interfaces/IBF-dashboard/src/app/components/export-view/export-view.component.ts b/interfaces/IBF-dashboard/src/app/components/export-view/export-view.component.ts index 8a826a30e..4d96608a4 100644 --- a/interfaces/IBF-dashboard/src/app/components/export-view/export-view.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/export-view/export-view.component.ts @@ -33,7 +33,7 @@ export class ExportViewComponent { this.analyticsService.logEvent(AnalyticsEvent.exportView, { page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); diff --git a/interfaces/IBF-dashboard/src/app/components/ibf-guide-button/ibf-guide-button.component.ts b/interfaces/IBF-dashboard/src/app/components/ibf-guide-button/ibf-guide-button.component.ts index 01c7c29e6..83b578256 100644 --- a/interfaces/IBF-dashboard/src/app/components/ibf-guide-button/ibf-guide-button.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/ibf-guide-button/ibf-guide-button.component.ts @@ -60,7 +60,7 @@ export class IbfGuideButtonComponent implements OnDestroy { this.analyticsService.logEvent(AnalyticsEvent.watchIbfGuide, { page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); diff --git a/interfaces/IBF-dashboard/src/app/components/map/map.component.ts b/interfaces/IBF-dashboard/src/app/components/map/map.component.ts index 7a4db3e8c..9286e8b03 100644 --- a/interfaces/IBF-dashboard/src/app/components/map/map.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/map/map.component.ts @@ -385,7 +385,7 @@ export class MapComponent implements AfterViewInit, OnDestroy { IbfLayerName.schools, ].includes(layerName) && this.disasterType.disasterType === DisasterTypeKey.flashFloods && - this.eventState.activeTrigger + this.eventState.events?.length > 0 ); } @@ -638,7 +638,7 @@ export class MapComponent implements AfterViewInit, OnDestroy { this.analyticsService.logEvent(AnalyticsEvent.mapPlaceSelect, { placeCode, page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); diff --git a/interfaces/IBF-dashboard/src/app/components/matrix/matrix.component.ts b/interfaces/IBF-dashboard/src/app/components/matrix/matrix.component.ts index 437d053d3..5458dac44 100644 --- a/interfaces/IBF-dashboard/src/app/components/matrix/matrix.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/matrix/matrix.component.ts @@ -84,7 +84,7 @@ export class MatrixComponent implements OnDestroy { mapLayerName: layer.name, mapLayerStatus: layer.active, page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); @@ -96,7 +96,7 @@ export class MatrixComponent implements OnDestroy { mapLayerName: layer.name, mapLayerStatus: !layer.active, page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); diff --git a/interfaces/IBF-dashboard/src/app/components/timeline/timeline.component.ts b/interfaces/IBF-dashboard/src/app/components/timeline/timeline.component.ts index 063415838..2e3a6f893 100644 --- a/interfaces/IBF-dashboard/src/app/components/timeline/timeline.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/timeline/timeline.component.ts @@ -54,7 +54,7 @@ export class TimelineComponent implements OnInit, OnDestroy { this.analyticsService.logEvent(AnalyticsEvent.leadTime, { page: AnalyticsPage.dashboard, leadTime, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); diff --git a/interfaces/IBF-dashboard/src/app/components/user-state/user-state.component.ts b/interfaces/IBF-dashboard/src/app/components/user-state/user-state.component.ts index 33f49eca9..202a60327 100644 --- a/interfaces/IBF-dashboard/src/app/components/user-state/user-state.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/user-state/user-state.component.ts @@ -89,7 +89,7 @@ export class UserStateComponent implements OnInit { public doLogout() { this.analyticsService.logEvent(AnalyticsEvent.logOut, { page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); diff --git a/interfaces/IBF-dashboard/src/app/services/aggregates.service.ts b/interfaces/IBF-dashboard/src/app/services/aggregates.service.ts index 7c5e12283..3297e7d32 100644 --- a/interfaces/IBF-dashboard/src/app/services/aggregates.service.ts +++ b/interfaces/IBF-dashboard/src/app/services/aggregates.service.ts @@ -176,7 +176,7 @@ export class AggregatesService { : aggregate[IbfLayerName.alertThreshold] > 0 ? AreaStatus.TriggeredOrWarned : aggregate[this.disasterType.actionsUnit] > 0 && - this.eventState.activeTrigger + this.eventState.events?.length > 0 ? AreaStatus.TriggeredOrWarned : AreaStatus.NonTriggeredOrWarnd; }; diff --git a/interfaces/IBF-dashboard/src/app/services/event.service.ts b/interfaces/IBF-dashboard/src/app/services/event.service.ts index 4e01e7ea9..7fd0758a1 100644 --- a/interfaces/IBF-dashboard/src/app/services/event.service.ts +++ b/interfaces/IBF-dashboard/src/app/services/event.service.ts @@ -19,7 +19,6 @@ export class EventSummary { endDate: string; endDateLabel: string; thresholdReached: boolean; - activeTrigger: boolean; eventName: string; firstLeadTime?: string; firstLeadTimeLabel?: string; @@ -49,7 +48,6 @@ export class EventService { events: null, event: null, thresholdReached: null, - activeTrigger: null, }; public state = this.nullState; @@ -91,9 +89,7 @@ export class EventService { }; public switchEvent(eventName: string) { - const event = this.state.activeTrigger - ? this.state.events.find((e) => e.eventName === eventName) - : this.state.event; + const event = this.state.events.find((e) => e.eventName === eventName); // Trigger a different 'event' subject in this case .. // .. so that timelineService can distinguish between initial event switch and manual event switch this.setEventManually(event); @@ -105,7 +101,6 @@ export class EventService { private setEventInitially(event: EventSummary) { this.state.event = event; - this.state.activeTrigger = this.setOverallActiveTrigger(); this.state.thresholdReached = this.setOverallThresholdReached(); this.initialEventStateSubject.next(this.state); this.setAlertState(); @@ -113,7 +108,6 @@ export class EventService { private setEventManually(event: EventSummary) { this.state.event = event; - this.state.activeTrigger = this.setOverallActiveTrigger(); this.state.thresholdReached = this.setOverallThresholdReached(); this.manualEventStateSubject.next(this.state); this.setAlertState(); @@ -159,8 +153,8 @@ export class EventService { events, ) => { disasterType.activeTrigger = - events.filter((e: EventSummary) => e.activeTrigger && e.thresholdReached) - .length > 0 || false; + events.filter((e: EventSummary) => e.thresholdReached).length > 0 || + false; callback(disasterType); }; @@ -313,7 +307,7 @@ export class EventService { private setAlertState = () => { const dashboardElement = document.getElementById('ibf-dashboard-interface'); if (dashboardElement) { - if (this.state.activeTrigger && this.state.thresholdReached) { + if (this.state.thresholdReached) { dashboardElement.classList.remove('no-alert'); dashboardElement.classList.add('trigger-alert'); } else { @@ -348,13 +342,6 @@ export class EventService { } } - private setOverallActiveTrigger() { - return this.state.event - ? this.state.event.activeTrigger - : this.state.events?.filter((e: EventSummary) => e.activeTrigger).length > - 0; - } - private setOverallThresholdReached() { return this.state.event ? this.state.event.thresholdReached diff --git a/interfaces/IBF-dashboard/src/app/services/map-legend.service.ts b/interfaces/IBF-dashboard/src/app/services/map-legend.service.ts index 0bb4aa7ba..3207063bb 100644 --- a/interfaces/IBF-dashboard/src/app/services/map-legend.service.ts +++ b/interfaces/IBF-dashboard/src/app/services/map-legend.service.ts @@ -127,10 +127,9 @@ export class MapLegendService { ); } - const colors = - this.eventState?.activeTrigger && this.eventState?.thresholdReached - ? this.mapService.state.colorGradientTriggered - : this.mapService.state.colorGradient; + const colors = this.eventState?.thresholdReached + ? this.mapService.state.colorGradientTriggered + : this.mapService.state.colorGradient; const getColor = this.getFeatureColorByColorsAndColorThresholds( colors, diff --git a/interfaces/IBF-dashboard/src/app/services/map.service.ts b/interfaces/IBF-dashboard/src/app/services/map.service.ts index f572bf4e1..5a89de2c8 100644 --- a/interfaces/IBF-dashboard/src/app/services/map.service.ts +++ b/interfaces/IBF-dashboard/src/app/services/map.service.ts @@ -407,7 +407,7 @@ export class MapService { return indicatorOrLayer.active === LayerActivation.yes ? true : indicatorOrLayer.active === LayerActivation.ifTrigger && - this.eventState?.activeTrigger + this.eventState?.events?.length > 0 ? true : false; } @@ -429,10 +429,9 @@ export class MapService { colorProperty: indicator.name, colorBreaks: indicator.colorBreaks, numberFormatMap: indicator.numberFormatMap, - legendColor: - this.eventState.activeTrigger && this.eventState.thresholdReached - ? this.state.colorGradientTriggered[2] - : this.state.colorGradient[2], + legendColor: this.eventState.thresholdReached + ? this.state.colorGradientTriggered[2] + : this.state.colorGradient[2], group: indicator.name === IbfLayerName.alertThreshold ? IbfLayerGroup.outline @@ -517,7 +516,7 @@ export class MapService { ): boolean => { if ( layer.group === IbfLayerGroup.outline && - this.eventState?.activeTrigger + this.eventState?.events?.length > 0 ) { return true; } @@ -761,10 +760,9 @@ export class MapService { placeCodeParent: string, ): string => { let adminRegionFillColor = this.state.defaultFillColor; - const currentColorGradient = - this.eventState.activeTrigger && this.eventState.thresholdReached - ? this.state.colorGradientTriggered - : this.state.colorGradient; + const currentColorGradient = this.eventState.thresholdReached + ? this.state.colorGradientTriggered + : this.state.colorGradient; const area = this.getAreaByPlaceCode(placeCode, placeCodeParent); switch (true) { diff --git a/interfaces/IBF-dashboard/src/app/services/point-marker.service.ts b/interfaces/IBF-dashboard/src/app/services/point-marker.service.ts index e589ec76c..93f03894b 100644 --- a/interfaces/IBF-dashboard/src/app/services/point-marker.service.ts +++ b/interfaces/IBF-dashboard/src/app/services/point-marker.service.ts @@ -110,7 +110,7 @@ export class PointMarkerService { private onMapMarkerClick = (analyticsEvent) => (): void => { this.analyticsService.logEvent(analyticsEvent, { page: AnalyticsPage.dashboard, - isActiveTrigger: this.eventService.state.activeTrigger, + isActiveTrigger: this.eventService.state.events?.length > 0, component: this.constructor.name, }); }; diff --git a/interfaces/IBF-dashboard/src/app/services/timeline.service.ts b/interfaces/IBF-dashboard/src/app/services/timeline.service.ts index 6fc907d58..601c228f8 100644 --- a/interfaces/IBF-dashboard/src/app/services/timeline.service.ts +++ b/interfaces/IBF-dashboard/src/app/services/timeline.service.ts @@ -186,7 +186,7 @@ export class TimelineService { this.eventState.event ? ((this.eventState.event.firstTriggerLeadTime || this.eventState.event.firstLeadTime) as LeadTime) - : this.eventState.activeTrigger + : this.eventState.events?.length > 0 ? null : this.getFallbackNoTriggerLeadTime(this.disasterType.disasterType), null, @@ -231,17 +231,13 @@ export class TimelineService { const events = this.eventState?.events; if (events?.length) { for (const event of events) { - if (event.activeTrigger) { - this.apiService - .getTriggerPerLeadTime( - this.country.countryCodeISO3, - this.disasterType.disasterType, - event?.eventName, - ) - .subscribe(this.onTriggerPerLeadTime); - } else { - this.onTriggerPerLeadTime(null); - } + this.apiService + .getTriggerPerLeadTime( + this.country.countryCodeISO3, + this.disasterType.disasterType, + event?.eventName, + ) + .subscribe(this.onTriggerPerLeadTime); } } if (!events || !events.length) { @@ -526,26 +522,31 @@ export class TimelineService { } } else if (disasterType.disasterType === DisasterTypeKey.typhoon) { const events = this.eventState?.events; - const relevantLeadTimes = this.eventState?.activeTrigger - ? events - .filter((e) => !e.disasterSpecificProperties?.typhoonNoLandfallYet) - .map((e) => e.firstLeadTime) - : []; + const relevantLeadTimes = + this.eventState?.events?.length > 0 + ? events + .filter( + (e) => !e.disasterSpecificProperties?.typhoonNoLandfallYet, + ) + .map((e) => e.firstLeadTime) + : []; return relevantLeadTimes.includes(leadTime); } else if (disasterType.disasterType === DisasterTypeKey.flashFloods) { const events = this.eventState?.events; if (!events.length) { return leadTime === LeadTime.hour1; } - const relevantLeadTimes = this.eventState?.activeTrigger - ? events.map((e) => e.firstLeadTime) - : []; + const relevantLeadTimes = + this.eventState?.events?.length > 0 + ? events.map((e) => e.firstLeadTime) + : []; return relevantLeadTimes.includes(leadTime); } else if (disasterType.disasterType === DisasterTypeKey.floods) { const events = this.eventState?.events; - const relevantLeadTimes = this.eventState?.activeTrigger - ? events.map((e) => e.firstLeadTime) - : []; + const relevantLeadTimes = + this.eventState?.events?.length > 0 + ? events.map((e) => e.firstLeadTime) + : []; return relevantLeadTimes.includes(leadTime); } else { return true; diff --git a/interfaces/IBF-dashboard/src/app/types/event-state.ts b/interfaces/IBF-dashboard/src/app/types/event-state.ts index 412fdc75e..166a7a27c 100644 --- a/interfaces/IBF-dashboard/src/app/types/event-state.ts +++ b/interfaces/IBF-dashboard/src/app/types/event-state.ts @@ -3,6 +3,5 @@ import { EventSummary } from '../services/event.service'; export class EventState { events: EventSummary[]; event: EventSummary; - activeTrigger: boolean; thresholdReached: boolean; } diff --git a/interfaces/IBF-dashboard/src/app/types/triggered-area.ts b/interfaces/IBF-dashboard/src/app/types/triggered-area.ts index 8302b2299..4a118e17d 100644 --- a/interfaces/IBF-dashboard/src/app/types/triggered-area.ts +++ b/interfaces/IBF-dashboard/src/app/types/triggered-area.ts @@ -4,7 +4,6 @@ import { EapAction } from './eap-action'; export class TriggeredArea { actionsValue: number; triggerValue: number; - activeTrigger: boolean; displayName: string; eapActions: EapAction[]; eventPlaceCodeId: string; diff --git a/services/API-service/src/api/event/event.service.ts b/services/API-service/src/api/event/event.service.ts index b51f18b2f..8c9fa2521 100644 --- a/services/API-service/src/api/event/event.service.ts +++ b/services/API-service/src/api/event/event.service.ts @@ -73,8 +73,6 @@ export class EventService { .addSelect([ 'to_char(MIN("startDate") , \'yyyy-mm-dd\') AS "startDate"', 'to_char(MAX("endDate") , \'yyyy-mm-dd\') AS "endDate"', - // TODO: for now keep this, remove need in front-end when possible - 'MAX(1)::boolean AS "activeTrigger"', 'MAX(event."thresholdReached"::int)::boolean AS "thresholdReached"', ]) .where({ From 5fb3127a2f0234f1e0dc372d554d133de85c528c Mon Sep 17 00:00:00 2001 From: jannisvisser Date: Mon, 18 Mar 2024 13:38:21 +0100 Subject: [PATCH 3/8] fix: call get trigger-per-lead-time only once AB#26940 --- .../src/app/services/timeline.service.ts | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/interfaces/IBF-dashboard/src/app/services/timeline.service.ts b/interfaces/IBF-dashboard/src/app/services/timeline.service.ts index 601c228f8..6ba3f62dc 100644 --- a/interfaces/IBF-dashboard/src/app/services/timeline.service.ts +++ b/interfaces/IBF-dashboard/src/app/services/timeline.service.ts @@ -148,9 +148,8 @@ export class TimelineService { ); } - private onTriggerPerLeadTime = (triggers) => { - // TODO: test regression effects here better + nothing happens with input anymore now? + use this somehow to highlight relevant leadTimes per event? - // this.triggersAllEvents = { ...this.triggersAllEvents, ...triggers }; + private onTriggerPerLeadTime = (triggersPerLeadTime: CountryTriggers) => { + this.triggersAllEvents = triggersPerLeadTime; this.state.timeStepButtons = []; const visibleLeadTimes = this.getVisibleLeadTimes(); @@ -224,25 +223,7 @@ export class TimelineService { this.disasterType.disasterType, null, ) - .subscribe((response) => { - this.triggersAllEvents = response; - }); - - const events = this.eventState?.events; - if (events?.length) { - for (const event of events) { - this.apiService - .getTriggerPerLeadTime( - this.country.countryCodeISO3, - this.disasterType.disasterType, - event?.eventName, - ) - .subscribe(this.onTriggerPerLeadTime); - } - } - if (!events || !events.length) { - this.onTriggerPerLeadTime(null); - } + .subscribe(this.onTriggerPerLeadTime); }; public loadTimeStepButtons(): void { From d5372ae9f49545f8d01a8ce71f5d90fbaade6efb Mon Sep 17 00:00:00 2001 From: jannisvisser Date: Mon, 18 Mar 2024 14:00:01 +0100 Subject: [PATCH 4/8] fix: ongoing header malaria AB#26935 --- interfaces/IBF-dashboard/src/assets/i18n/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/interfaces/IBF-dashboard/src/assets/i18n/en.json b/interfaces/IBF-dashboard/src/assets/i18n/en.json index 4e0b26c3f..9366645bf 100644 --- a/interfaces/IBF-dashboard/src/assets/i18n/en.json +++ b/interfaces/IBF-dashboard/src/assets/i18n/en.json @@ -335,6 +335,7 @@ }, "active-event-active-trigger": { "header": "Alert: {{ disasterTypeLabel }}", + "header-ongoing": "Ongoing alert: {{ disasterTypeLabel }}", "welcome": "An alert for {{ disasterTypeLabel }} was issued on {{ startDate }}. The alert is issued for {{ firstLeadTimeDate }}." }, "active-event": { From 5158c4966ffbc04e8179132d820d0f1a3b1549df Mon Sep 17 00:00:00 2001 From: Ruben Date: Mon, 18 Mar 2024 14:45:07 +0100 Subject: [PATCH 5/8] AB#26925 Sync stores and layers to geoserver --- docker-compose.override.yml | 1 + .../1710512991479-rename-mock-rasters.ts | 38 ++++ services/API-service/package.json | 3 +- .../admin-area-dynamic-data.service.ts | 14 +- services/API-service/src/config.ts | 2 + .../disaster-type-geoserver-file.mapper.ts | 73 +++++++ .../src/scripts/geoserver-sync.service.ts | 206 ++++++++++++++++++ .../src/scripts/json/countries.json | 2 +- .../src/scripts/scripts.controller.ts | 26 +++ .../API-service/src/scripts/scripts.module.ts | 4 + .../src/scripts/scripts.service.ts | 122 ++++++++--- 11 files changed, 449 insertions(+), 42 deletions(-) create mode 100644 services/API-service/migration/1710512991479-rename-mock-rasters.ts create mode 100644 services/API-service/src/scripts/disaster-type-geoserver-file.mapper.ts create mode 100644 services/API-service/src/scripts/geoserver-sync.service.ts diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 6b01a8fbf..8e8fa7ac2 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -6,6 +6,7 @@ services: environment: - NODE_ENV=development - LOCAL_PORT_IBF_SERVICE=${LOCAL_PORT_IBF_SERVICE} + - GEOSERVER_ADMIN_PASSWORD=${GEOSERVER_ADMIN_PASSWORD} ports: - ${LOCAL_PORT_IBF_SERVICE}:3000 depends_on: diff --git a/services/API-service/migration/1710512991479-rename-mock-rasters.ts b/services/API-service/migration/1710512991479-rename-mock-rasters.ts new file mode 100644 index 000000000..4ce5f8a52 --- /dev/null +++ b/services/API-service/migration/1710512991479-rename-mock-rasters.ts @@ -0,0 +1,38 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import * as fs from 'fs'; +import * as path from 'path'; + +export class RenameMockRasters1710512991479 implements MigrationInterface { + public async up(_queryRunner: QueryRunner): Promise { + const directoryPath = './geoserver-volume/raster-files/mock-output/'; + const ugandaFloodsBasicPath = `${directoryPath}/flood_extent_day_UGA.tif`; + if (fs.existsSync(ugandaFloodsBasicPath)) { + // Do not run the migration another time if it has already been run on test servers + return; + } + + if (fs.existsSync(directoryPath)) { + const files = fs.readdirSync(directoryPath); + console.log('🚀 ~ RenameMockRasters1710512991479 ~ up ~ files:', files); + + files.forEach((file) => { + if (!file.includes('hour_MWI')) { + const newFilename = file.replace( + /_[0-9]+-(hour|day|month)_/g, + '_$1_', + ); + fs.renameSync( + path.join(directoryPath, file), + path.join(directoryPath, newFilename), + ); + } + }); + } else { + console.log(`Directory ${directoryPath} does not exist`); + } + } + + public async down(_queryRunner: QueryRunner): Promise { + // If you want to revert the renaming, you would need to implement the logic here + } +} diff --git a/services/API-service/package.json b/services/API-service/package.json index d1b535818..cbacf60be 100644 --- a/services/API-service/package.json +++ b/services/API-service/package.json @@ -19,7 +19,8 @@ "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js", "migration:generate": "npm run typeorm migration:generate -- -d ./appdatasource.ts", "migration:run": "npm run typeorm migration:run -- -d ./appdatasource.ts", - "migration:revert": "npm run typeorm migration:revert -- -d ./appdatasource.ts" + "migration:revert": "npm run typeorm migration:revert -- -d ./appdatasource.ts", + "migration:create": "npm run typeorm migration:create -- " }, "private": true, "dependencies": { diff --git a/services/API-service/src/api/admin-area-dynamic-data/admin-area-dynamic-data.service.ts b/services/API-service/src/api/admin-area-dynamic-data/admin-area-dynamic-data.service.ts index 5b8033ca5..768569e08 100644 --- a/services/API-service/src/api/admin-area-dynamic-data/admin-area-dynamic-data.service.ts +++ b/services/API-service/src/api/admin-area-dynamic-data/admin-area-dynamic-data.service.ts @@ -15,6 +15,7 @@ import fs from 'fs'; import { CountryEntity } from '../country/country.entity'; import { HelperService } from '../../shared/helper.service'; import { EventAreaService } from '../admin-area/services/event-area.service'; +import { DisasterTypeGeoServerMapper } from '../../scripts/disaster-type-geoserver-file.mapper'; @Injectable() export class AdminAreaDynamicDataService { @@ -231,16 +232,9 @@ export class AdminAreaDynamicDataService { data: any, disasterType: DisasterType, ): Promise { - let subfolder: string; - if ( - [DisasterType.Floods, DisasterType.FlashFloods].includes(disasterType) - ) { - subfolder = 'flood_extents'; - } else if ( - [DisasterType.HeavyRain, DisasterType.Drought].includes(disasterType) - ) { - subfolder = 'rainfall_extents'; - } else { + const subfolder = + DisasterTypeGeoServerMapper.getSubfolderForDisasterType(disasterType); + if (subfolder === '') { throw new HttpException( 'Disaster Type not allowed', HttpStatus.BAD_REQUEST, diff --git a/services/API-service/src/config.ts b/services/API-service/src/config.ts index 97f9e8d7b..fac93bbce 100644 --- a/services/API-service/src/config.ts +++ b/services/API-service/src/config.ts @@ -14,6 +14,8 @@ const rootUrl = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.LOCAL_PORT_IBF_SERVICE}/` : process.env.EXTERNAL_API_SERVICE_URL; +export const INTERNAL_GEOSERVER_API_URL = + 'http://ibf-geoserver:8080/geoserver/rest'; export const EXTERNAL_API = { root: rootUrl, whatsAppStatus: baseApiUrl + API_PATHS.whatsAppStatus, diff --git a/services/API-service/src/scripts/disaster-type-geoserver-file.mapper.ts b/services/API-service/src/scripts/disaster-type-geoserver-file.mapper.ts new file mode 100644 index 000000000..144b13be3 --- /dev/null +++ b/services/API-service/src/scripts/disaster-type-geoserver-file.mapper.ts @@ -0,0 +1,73 @@ +import { DisasterType } from '../api/disaster/disaster-type.enum'; + +export class DisasterTypeGeoServerMapper { + static getLayerStorePrefixForDisasterType( + disasterType: DisasterType + ): string { + if (disasterType === DisasterType.Floods) { + return 'flood_extent'; + } else if (disasterType === DisasterType.HeavyRain) { + return 'rainfall_extent'; + } else if (disasterType === DisasterType.Drought) { + return 'rainfall_forecast'; + } else if (disasterType === DisasterType.FlashFloods) { + return 'flood_extent'; + } + return ''; + } + + static getDestFilePrefixForDisasterType( + disasterType: DisasterType, + countryCode: string, + ): string { + if (disasterType === DisasterType.Floods) { + return 'flood_extent'; + } else if (disasterType === DisasterType.HeavyRain) { + if (countryCode === 'EGY') { + return 'rain_rp'; + } else if (countryCode === 'UGA') { + return 'rainfall_extent'; + } + } else if (disasterType === DisasterType.Drought) { + return 'rain_rp'; + } else if (disasterType === DisasterType.FlashFloods) { + return 'flood_extent'; + } + return ''; + } + + static getSubfolderForDisasterType(disasterType: DisasterType): string { + let subfolder = ''; + if ( + [DisasterType.Floods, DisasterType.FlashFloods].includes(disasterType) + ) { + subfolder = 'flood_extents'; + } else if ( + [DisasterType.HeavyRain, DisasterType.Drought].includes(disasterType) + ) { + subfolder = 'rainfall_extents'; + } + return subfolder; + } + + // DOES not work for heavy rain as it will be phased out + static getStyleForCountryAndDisasterType( + countryCode: string, + disasterType: DisasterType, + ): string { + if (disasterType === DisasterType.Drought) { + return 'rainfall_extent_drought'; + } else if (disasterType === DisasterType.FlashFloods) { + return 'flood_extent_MWI_flash-floods'; + } else if (disasterType === DisasterType.Floods) { + if (countryCode === 'PHL') { + return 'flood_extent_PHL'; + } else { + return 'flood_extent'; + } + } + throw new Error( + `No style found for disaster type' ${disasterType} and country code ${countryCode}`, + ); + } +} diff --git a/services/API-service/src/scripts/geoserver-sync.service.ts b/services/API-service/src/scripts/geoserver-sync.service.ts new file mode 100644 index 000000000..732a17484 --- /dev/null +++ b/services/API-service/src/scripts/geoserver-sync.service.ts @@ -0,0 +1,206 @@ +import { HttpService } from '@nestjs/axios'; +import { Injectable } from '@nestjs/common'; +import { firstValueFrom, map } from 'rxjs'; +import { INTERNAL_GEOSERVER_API_URL } from '../config'; +import countries from './json/countries.json'; +import { DisasterType } from '../api/disaster/disaster-type.enum'; +import { DisasterTypeGeoServerMapper } from './disaster-type-geoserver-file.mapper'; +import * as fs from 'fs'; + +const workspaceName = 'ibf-system'; + +class RecourceNameObject { + resourceName: string; + disasterType: DisasterType; + countryCodeISO3: string; +} + +@Injectable() +export class GeoseverSyncService { + constructor(private httpService: HttpService) {} + + public async sync( + countryCodeISO3?: string, + disasterType?: DisasterType, + ): Promise { + const filteredCountries = countries.filter((country: any) => { + return countryCodeISO3 + ? country.countryCodeISO3 === countryCodeISO3 + : true; + }); + // also filter by disaster type + for (const country of filteredCountries) { + const disasterSettings = country.countryDisasterSettings.filter( + (disasterSetting: any) => { + return disasterType + ? disasterSetting.disasterType === disasterType + : true; + }, + ); + country.countryDisasterSettings = disasterSettings; + } + const geoserverResourceNameObjects = + this.generateGeoserverResourceNames(filteredCountries); + await this.syncStores(geoserverResourceNameObjects); + await this.syncLayers(geoserverResourceNameObjects); + } + + private async syncStores(expectedStoreNameObjects: RecourceNameObject[]) { + const foundStoreNames = await this.getStoreNamesFromGeoserver( + workspaceName, + ); + const missingStoreNames = expectedStoreNameObjects.filter( + (o) => !foundStoreNames.includes(o.resourceName), + ); + await this.postStoreNamesToGeoserver(missingStoreNames); + } + + private generateGeoserverResourceNames( + filteredCountries: any[], + ): RecourceNameObject[] { + const resourceNameObjects = []; + for (const country of filteredCountries) { + resourceNameObjects.push(...this.generateStoreNameForCountry(country)); + } + return resourceNameObjects; + } + + private generateStoreNameForCountry(country: any): RecourceNameObject[] { + const resourceNameObjects = []; + const countryCode = country.countryCodeISO3; + for (const disasterSetting of country.countryDisasterSettings) { + if (disasterSetting.disasterType == DisasterType.Floods) { + for (const leadTime of disasterSetting.activeLeadTimes) { + const disasterTypeStorePrefix = + DisasterTypeGeoServerMapper.getLayerStorePrefixForDisasterType( + disasterSetting.disasterType + ); + const resourceName = `${disasterTypeStorePrefix}_${leadTime}_${countryCode}`; + resourceNameObjects.push({ + resourceName: resourceName, + disasterType: disasterSetting.disasterType, + countryCodeISO3: countryCode, + }); + } + } + } + return resourceNameObjects; + } + + private async getStoreNamesFromGeoserver(workspaceName: string) { + const data = await this.get(`workspaces/${workspaceName}/coveragestores`); + const storeNames = data.coverageStores.coverageStore.map( + (store: any) => store.name, + ); + return storeNames; + } + + private async postStoreNamesToGeoserver( + resourceNameObjects: RecourceNameObject[], + ) { + for (const resourceNameObject of resourceNameObjects) { + const subfolder = DisasterTypeGeoServerMapper.getSubfolderForDisasterType( + resourceNameObject.disasterType, + ); + const url = `workspaces/${workspaceName}/coveragestores`; // replace with the correct API endpoint + const body = { + coverageStore: { + name: resourceNameObject.resourceName, + workspace: workspaceName, + enabled: true, + type: 'GeoTIFF', + url: `file:workspaces/ibf-system/ibf-pipeline/output/${subfolder}/${resourceNameObject.resourceName}.tif`, + }, + }; + const result = await this.post(url, body); + console.log( + 'Updated geoserver with ', + result, + 'please commit the resulting config changes of geoserver to git.', + ); + } + } + + public async syncLayers(expectedLayerNames: RecourceNameObject[]) { + const foundLayerNames = await this.getLayerNamesFromGeoserver( + workspaceName, + ); + const missingLayerNames = expectedLayerNames.filter( + (o) => !foundLayerNames.includes(o.resourceName), + ); + await this.postLayerNamesToGeoserver(missingLayerNames); + } + + private async getLayerNamesFromGeoserver(workspaceName: string) { + const data = await this.get(`workspaces/${workspaceName}/layers`); + const layerNames = data.layers.layer.map((layer: any) => layer.name); + return layerNames; + } + + private async postLayerNamesToGeoserver( + resourceNameObjects: RecourceNameObject[], + ) { + for (const resourceNameObject of resourceNameObjects) { + const publishLayerUrl = `workspaces/${workspaceName}/coveragestores/${resourceNameObject.resourceName}/coverages`; + const publishLayerBody = { + coverage: { + name: resourceNameObject.resourceName, + title: resourceNameObject.resourceName, + nativeName: resourceNameObject.resourceName, + }, + }; + await this.post(publishLayerUrl, publishLayerBody); + // Set the default style for the layer + const styleName = + DisasterTypeGeoServerMapper.getStyleForCountryAndDisasterType( + resourceNameObject.countryCodeISO3, + resourceNameObject.disasterType, + ); + const styleUrl = `layers/${resourceNameObject.resourceName}`; + const body = { + layer: { + defaultStyle: { + name: `${workspaceName}:${styleName}`, + }, + }, + }; + await this.put(styleUrl, body); + } + } + + private async post(path: string, body: any) { + const url = `${INTERNAL_GEOSERVER_API_URL}/${path}`; + const headers = this.getHeaders(); + const result = await firstValueFrom( + this.httpService.post(url, body, { headers }), + ); + return result.data; + } + + private async put(path: string, body: any) { + const url = `${INTERNAL_GEOSERVER_API_URL}/${path}`; + const headers = this.getHeaders(); + const result = await firstValueFrom( + this.httpService.put(url, body, { headers }), + ); + return result.data; + } + + private async get(path: string) { + const url = `${INTERNAL_GEOSERVER_API_URL}/${path}`; + const headers = this.getHeaders(); + const result = await firstValueFrom(this.httpService.get(url, { headers })); + return result.data; + } + + private getHeaders() { + const username = 'admin'; + return { + Authorization: + 'Basic ' + + Buffer.from( + username + ':' + process.env.GEOSERVER_ADMIN_PASSWORD, + ).toString('base64'), + }; + } +} diff --git a/services/API-service/src/scripts/json/countries.json b/services/API-service/src/scripts/json/countries.json index 57b1d2b33..2996b00d6 100644 --- a/services/API-service/src/scripts/json/countries.json +++ b/services/API-service/src/scripts/json/countries.json @@ -9,7 +9,7 @@ "disasterType": "floods", "adminLevels": [2, 3, 4], "defaultAdminLevel": 2, - "activeLeadTimes": ["5-day"], + "activeLeadTimes": ["1-day", "2-day", "3-day", "4-day", "5-day", "6-day", "7-day"], "eapLink": "https://docs.google.com/document/d/1z4KfTIF1aJKgx-te8gPY6Scr2FcYR51x/edit#bookmark=id.j5clfgl1ywrd", "eapAlertClasses": { "no": { diff --git a/services/API-service/src/scripts/scripts.controller.ts b/services/API-service/src/scripts/scripts.controller.ts index c44540d54..66ee499d6 100644 --- a/services/API-service/src/scripts/scripts.controller.ts +++ b/services/API-service/src/scripts/scripts.controller.ts @@ -5,6 +5,8 @@ import { Res, HttpStatus, UseGuards, + Patch, + HttpException, } from '@nestjs/common'; import { ApiBearerAuth, @@ -26,6 +28,7 @@ import { RolesGuard } from '../roles.guard'; import { DisasterType } from '../api/disaster/disaster-type.enum'; import { Roles } from '../roles.decorator'; import { UserRole } from '../api/user/user-role.enum'; +import { GeoseverSyncService } from './geoserver-sync.service'; class ResetDto { @ApiProperty({ example: 'fill_in_secret' }) @@ -122,6 +125,7 @@ export class ScriptsController { public constructor( private scriptsService: ScriptsService, private seedInit: SeedInit, + private geoseverSyncService: GeoseverSyncService, ) {} @Roles(UserRole.Admin) @@ -207,4 +211,26 @@ export class ScriptsController { return res.status(HttpStatus.ACCEPTED).send(result); } + + @Roles(UserRole.Admin) + @ApiOperation({ + summary: + 'Syncs the geoserver with countries.json, this also run the mock all script', + }) + @ApiResponse({ + status: 201, + description: 'Geoserver synced with countries.json', + }) + @Patch('/sync-geoserver') + public async syncGeoserver(@Body() body: ResetDto): Promise { + if (body.secret !== process.env.RESET_SECRET) { + throw new HttpException('Not allowed', HttpStatus.FORBIDDEN); + } + await this.scriptsService.mockAll({ + secret: body.secret, + triggered: true, + date: new Date(), + }); + return await this.geoseverSyncService.sync(); + } } diff --git a/services/API-service/src/scripts/scripts.module.ts b/services/API-service/src/scripts/scripts.module.ts index 940d56203..ffa3238e6 100644 --- a/services/API-service/src/scripts/scripts.module.ts +++ b/services/API-service/src/scripts/scripts.module.ts @@ -29,6 +29,8 @@ import { AdminAreaDynamicDataEntity } from '../api/admin-area-dynamic-data/admin import { LinesDataModule } from '../api/lines-data/lines-data.module'; import SeedLineData from './seed-line-data'; import { ORMConfig } from '../../ormconfig'; +import { GeoseverSyncService } from './geoserver-sync.service'; +import { HttpModule } from '@nestjs/axios'; @Module({ imports: [ @@ -53,6 +55,7 @@ import { ORMConfig } from '../../ormconfig'; PointDataModule, LinesDataModule, AdminAreaDataModule, + HttpModule, ], providers: [ SeedInit, @@ -63,6 +66,7 @@ import { ORMConfig } from '../../ormconfig'; SeedPointData, SeedLineData, SeedRainfallData, + GeoseverSyncService, ], controllers: [ScriptsController], }) diff --git a/services/API-service/src/scripts/scripts.service.ts b/services/API-service/src/scripts/scripts.service.ts index 42d350f89..04454faf6 100644 --- a/services/API-service/src/scripts/scripts.service.ts +++ b/services/API-service/src/scripts/scripts.service.ts @@ -30,6 +30,8 @@ import { LinesDataEnum } from '../api/lines-data/lines-data.entity'; import { PointDataEnum } from '../api/point-data/point-data.entity'; import { UploadDynamicPointDataDto } from '../api/point-data/dto/upload-asset-exposure-status.dto'; import { PointDataService } from '../api/point-data/point-data.service'; +import { DisasterTypeGeoServerMapper } from './disaster-type-geoserver-file.mapper'; +import { GeoseverSyncService as GeoServerSyncService } from './geoserver-sync.service'; @Injectable() export class ScriptsService { @@ -57,6 +59,7 @@ export class ScriptsService { private metadataService: MetadataService, private linesDataService: LinesDataService, private pointDataService: PointDataService, + private geoServerSyncService: GeoServerSyncService, ) {} public async mockAll(mockAllInput: MockAll) { @@ -1056,42 +1059,27 @@ export class ScriptsService { disasterType: DisasterType, triggered: boolean, ) { + await this.geoServerSyncService.sync( + selectedCountry.countryCodeISO3, + disasterType, + ); for await (const leadTime of selectedCountry.countryDisasterSettings.find( (s) => s.disasterType === disasterType, ).activeLeadTimes) { console.log( `Seeding disaster extent raster file for country: ${selectedCountry.countryCodeISO3} for leadtime: ${leadTime}`, ); - - let sourceFileName, destFileName; - if (disasterType === DisasterType.Floods) { - sourceFileName = `flood_extent_${leadTime}_${selectedCountry.countryCodeISO3}.tif`; - destFileName = sourceFileName; - } else if (disasterType === DisasterType.HeavyRain) { - // Use 3-day mock for every lead-time - sourceFileName = `rainfall_extent_3-day_${ - selectedCountry.countryCodeISO3 - }${triggered ? '-triggered' : ''}.tif`; - if (selectedCountry.countryCodeISO3 === 'EGY') { - destFileName = `rain_rp_${leadTime}_${selectedCountry.countryCodeISO3}.tif`; - } else if (selectedCountry.countryCodeISO3 === 'UGA') { - destFileName = `rainfall_extent_${leadTime}_${selectedCountry.countryCodeISO3}.tif`; - } - } else if (disasterType === DisasterType.Drought) { - // Use 0-month mock for every lead-time - sourceFileName = `rainfall_forecast_0-month_${ - selectedCountry.countryCodeISO3 - }${triggered ? '-triggered' : ''}.tif`; - destFileName = `rain_rp_${leadTime}_${selectedCountry.countryCodeISO3}.tif`; - } else if (disasterType === DisasterType.FlashFloods) { - if (leadTime === LeadTime.hour24 || leadTime === LeadTime.hour6) { - sourceFileName = `flood_extent_${leadTime}_${selectedCountry.countryCodeISO3}.tif`; - } else { - continue; - } - destFileName = sourceFileName; - } - + const sourceFileName = this.getMockRasterSourceFileName( + disasterType, + selectedCountry.countryCodeISO3, + leadTime, + triggered, + ); + const destFileName = this.getMockRasterDestFileName( + disasterType, + leadTime, + selectedCountry.countryCodeISO3, + ); let file; try { file = fs.readFileSync( @@ -1113,6 +1101,80 @@ export class ScriptsService { } } + private getMockRasterSourceFileName( + disasterType: DisasterType, + countryCodeISO3: string, + leadTime: string, + triggered?: boolean, + ) { + const directoryPath = './geoserver-volume/raster-files/mock-output/'; + const leadTimeUnit = leadTime.replace(/\d+-/, ''); + + const files = fs.readdirSync(directoryPath); + + const layerStorePrefix = + DisasterTypeGeoServerMapper.getLayerStorePrefixForDisasterType( + disasterType + ); + + // Only for HeavyRain and Drought we have triggered and non-triggered files + const suffix = + triggered && + [DisasterType.HeavyRain, DisasterType.Drought].includes(disasterType) + ? '-triggered.tif' + : '.tif'; + const filename = `${layerStorePrefix}_${leadTimeUnit}_${countryCodeISO3}${suffix}`; + const fileExists = files.includes(filename); + if (fileExists) { + return filename; + } else { + // new code + const leadTimeNumber = parseInt(leadTime.split('-')[0]); + const closestFiles = files.filter( + (file) => + file.startsWith(layerStorePrefix) && + file.endsWith(`${leadTimeUnit}_${countryCodeISO3}${suffix}`), + ); + // if no files are found, return null + if (closestFiles.length === 0) { + console.log( + 'No closest files found for the given lead time', + layerStorePrefix, + leadTimeUnit, + countryCodeISO3, + suffix, + ); + return null; + } + const numbersFromClosestFiles = closestFiles.map((file) => + parseInt(file.split('_')[2]), + ); + // from the numbers, find the closest number to the leadTimeNumber + let closestNumber = numbersFromClosestFiles[0]; + for (let i = 1; i < numbersFromClosestFiles.length; i++) { + if ( + Math.abs(numbersFromClosestFiles[i] - leadTimeNumber) < + Math.abs(closestNumber - leadTimeNumber) + ) { + closestNumber = numbersFromClosestFiles[i]; + } + } + return null; + } + } + + private getMockRasterDestFileName( + disasterType: DisasterType, + leadTime: string, + countryCode: string, + ): string { + const prefix = DisasterTypeGeoServerMapper.getDestFilePrefixForDisasterType( + disasterType, + countryCode, + ); + return `${prefix}_${leadTime}_${countryCode}.tif`; + } + private async mockMapImageFile( countryCodeISO3: string, disasterType: DisasterType, From d9d72b4d0336f71205187b0c8c3b31945f954378 Mon Sep 17 00:00:00 2001 From: jannisvisser Date: Mon, 18 Mar 2024 15:45:27 +0100 Subject: [PATCH 6/8] fix: breadcrumb behavior AB#27047 --- .../admin-level/admin-level.component.ts | 1 + .../src/scripts/json/countries.json | 27 ++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/interfaces/IBF-dashboard/src/app/components/admin-level/admin-level.component.ts b/interfaces/IBF-dashboard/src/app/components/admin-level/admin-level.component.ts index 0fbffff03..5a3fea737 100644 --- a/interfaces/IBF-dashboard/src/app/components/admin-level/admin-level.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/admin-level/admin-level.component.ts @@ -225,6 +225,7 @@ export class AdminLevelComponent implements OnInit, OnDestroy { } if (breadCrumb === MapView.event) { + this.adminLevelService.zoomToDefaultAdminLevel(); this.placeCodeService?.clearPlaceCode(); return; } diff --git a/services/API-service/src/scripts/json/countries.json b/services/API-service/src/scripts/json/countries.json index 5bd82fc42..3c29c6833 100644 --- a/services/API-service/src/scripts/json/countries.json +++ b/services/API-service/src/scripts/json/countries.json @@ -41,7 +41,8 @@ "color": "ibf-glofas-trigger", "value": 1 } - } + }, + "isEventBased": true }, { "disasterType": "drought", @@ -295,7 +296,8 @@ "color": "ibf-glofas-trigger", "value": 1 } - } + }, + "isEventBased": true }, { "disasterType": "drought", @@ -392,7 +394,8 @@ "color": "ibf-glofas-trigger", "value": 1 } - } + }, + "isEventBased": true }, { "disasterType": "flash-floods", @@ -478,7 +481,8 @@ "color": "ibf-glofas-trigger", "value": 1 } - } + }, + "isEventBased": true } ], "adminRegionLabels": { @@ -534,7 +538,8 @@ "color": "ibf-glofas-trigger", "value": 1 } - } + }, + "isEventBased": true }, { "disasterType": "drought", @@ -641,14 +646,16 @@ "color": "ibf-glofas-trigger", "value": 1 } - } + }, + "isEventBased": true }, { "disasterType": "malaria", "adminLevels": [3], "defaultAdminLevel": 3, "activeLeadTimes": ["0-month", "1-month", "2-month"], - "eapLink": "https://docs.google.com/document/d/1v7FNVaYIS1_5zTn-KDWe3gZmjvRxljsXR9EutAF0wuk/edit?usp=sharing" + "eapLink": "https://docs.google.com/document/d/1v7FNVaYIS1_5zTn-KDWe3gZmjvRxljsXR9EutAF0wuk/edit?usp=sharing", + "isEventBased": true }, { "disasterType": "drought", @@ -898,7 +905,8 @@ "adminLevels": [2], "defaultAdminLevel": 2, "activeLeadTimes": ["0-month", "1-month", "2-month"], - "eapLink": "https://rodekruis.sharepoint.com/sites/510-CRAVK-510/_layouts/15/guestaccess.aspx?docid=0f9249b35da4d4c0a985b33393fc388df&authkey=AQSLnnafGF-M2t1CGIg0hdA&expiration=2022-06-29T22%3A00%3A00.000Z&e=2asHJm" + "eapLink": "https://rodekruis.sharepoint.com/sites/510-CRAVK-510/_layouts/15/guestaccess.aspx?docid=0f9249b35da4d4c0a985b33393fc388df&authkey=AQSLnnafGF-M2t1CGIg0hdA&expiration=2022-06-29T22%3A00%3A00.000Z&e=2asHJm", + "isEventBased": true }, { "disasterType": "typhoon", @@ -1095,7 +1103,8 @@ "color": "ibf-glofas-trigger", "value": 1 } - } + }, + "isEventBased": true } ], "adminRegionLabels": { From 1c8ee328c250e052220e15f0cce31e8a62abe875 Mon Sep 17 00:00:00 2001 From: Ruben Date: Mon, 18 Mar 2024 15:53:09 +0100 Subject: [PATCH 7/8] Only sync in DEBUG AB#26925 --- .../src/scripts/geoserver-sync.service.ts | 8 +++++-- .../src/scripts/scripts.controller.ts | 22 ------------------- .../src/scripts/scripts.service.ts | 13 +++++++---- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/services/API-service/src/scripts/geoserver-sync.service.ts b/services/API-service/src/scripts/geoserver-sync.service.ts index 732a17484..47f8707bd 100644 --- a/services/API-service/src/scripts/geoserver-sync.service.ts +++ b/services/API-service/src/scripts/geoserver-sync.service.ts @@ -23,7 +23,8 @@ export class GeoseverSyncService { countryCodeISO3?: string, disasterType?: DisasterType, ): Promise { - const filteredCountries = countries.filter((country: any) => { + const countriesCopy = JSON.parse(JSON.stringify(countries)); + const filteredCountries = countriesCopy.filter((country: any) => { return countryCodeISO3 ? country.countryCodeISO3 === countryCodeISO3 : true; @@ -40,7 +41,8 @@ export class GeoseverSyncService { country.countryDisasterSettings = disasterSettings; } const geoserverResourceNameObjects = - this.generateGeoserverResourceNames(filteredCountries); + this.generateGeoserverResourceNames(filteredCountries); + console.log("🚀 ~ GeoseverSyncService ~ geoserverResourceNameObjects:", geoserverResourceNameObjects) await this.syncStores(geoserverResourceNameObjects); await this.syncLayers(geoserverResourceNameObjects); } @@ -49,9 +51,11 @@ export class GeoseverSyncService { const foundStoreNames = await this.getStoreNamesFromGeoserver( workspaceName, ); + console.log("🚀 ~ GeoseverSyncService ~ syncStores ~ foundStoreNames:", foundStoreNames) const missingStoreNames = expectedStoreNameObjects.filter( (o) => !foundStoreNames.includes(o.resourceName), ); + console.log("🚀 ~ GeoseverSyncService ~ syncStores ~ missingStoreNames:", missingStoreNames) await this.postStoreNamesToGeoserver(missingStoreNames); } diff --git a/services/API-service/src/scripts/scripts.controller.ts b/services/API-service/src/scripts/scripts.controller.ts index 66ee499d6..e0ee88bb8 100644 --- a/services/API-service/src/scripts/scripts.controller.ts +++ b/services/API-service/src/scripts/scripts.controller.ts @@ -211,26 +211,4 @@ export class ScriptsController { return res.status(HttpStatus.ACCEPTED).send(result); } - - @Roles(UserRole.Admin) - @ApiOperation({ - summary: - 'Syncs the geoserver with countries.json, this also run the mock all script', - }) - @ApiResponse({ - status: 201, - description: 'Geoserver synced with countries.json', - }) - @Patch('/sync-geoserver') - public async syncGeoserver(@Body() body: ResetDto): Promise { - if (body.secret !== process.env.RESET_SECRET) { - throw new HttpException('Not allowed', HttpStatus.FORBIDDEN); - } - await this.scriptsService.mockAll({ - secret: body.secret, - triggered: true, - date: new Date(), - }); - return await this.geoseverSyncService.sync(); - } } diff --git a/services/API-service/src/scripts/scripts.service.ts b/services/API-service/src/scripts/scripts.service.ts index 04454faf6..2aba71148 100644 --- a/services/API-service/src/scripts/scripts.service.ts +++ b/services/API-service/src/scripts/scripts.service.ts @@ -32,6 +32,7 @@ import { UploadDynamicPointDataDto } from '../api/point-data/dto/upload-asset-ex import { PointDataService } from '../api/point-data/point-data.service'; import { DisasterTypeGeoServerMapper } from './disaster-type-geoserver-file.mapper'; import { GeoseverSyncService as GeoServerSyncService } from './geoserver-sync.service'; +import { DEBUG } from '../config'; @Injectable() export class ScriptsService { @@ -1059,10 +1060,14 @@ export class ScriptsService { disasterType: DisasterType, triggered: boolean, ) { - await this.geoServerSyncService.sync( - selectedCountry.countryCodeISO3, - disasterType, - ); + // Add the needed stores and layers to geoserver, only do this in debug mode + // The resulting XML files should be commited to git and will end up on the servers that way + if (DEBUG) { + await this.geoServerSyncService.sync( + selectedCountry.countryCodeISO3, + disasterType, + ); + } for await (const leadTime of selectedCountry.countryDisasterSettings.find( (s) => s.disasterType === disasterType, ).activeLeadTimes) { From 54c14626fb43c02e590e06c52660ca3e6f950a4b Mon Sep 17 00:00:00 2001 From: Ruben Date: Mon, 18 Mar 2024 15:53:47 +0100 Subject: [PATCH 8/8] AB#26925 Generated xml config for geoserver UGA floods --- .../flood_extent_1-day_UGA/coveragestore.xml | 12 ++ .../flood_extent_1-day_UGA/coverage.xml | 129 ++++++++++++++++++ .../flood_extent_1-day_UGA/layer.xml | 19 +++ .../flood_extent_2-day_UGA/coveragestore.xml | 12 ++ .../flood_extent_2-day_UGA/coverage.xml | 129 ++++++++++++++++++ .../flood_extent_2-day_UGA/layer.xml | 19 +++ .../flood_extent_3-day_UGA/coveragestore.xml | 12 ++ .../flood_extent_3-day_UGA/coverage.xml | 129 ++++++++++++++++++ .../flood_extent_3-day_UGA/layer.xml | 19 +++ .../flood_extent_4-day_UGA/coveragestore.xml | 12 ++ .../flood_extent_4-day_UGA/coverage.xml | 129 ++++++++++++++++++ .../flood_extent_4-day_UGA/layer.xml | 19 +++ .../flood_extent_6-day_UGA/coveragestore.xml | 12 ++ .../flood_extent_6-day_UGA/coverage.xml | 129 ++++++++++++++++++ .../flood_extent_6-day_UGA/layer.xml | 19 +++ .../flood_extent_7-day_UGA/coveragestore.xml | 12 ++ .../flood_extent_7-day_UGA/coverage.xml | 129 ++++++++++++++++++ .../flood_extent_7-day_UGA/layer.xml | 19 +++ 18 files changed, 960 insertions(+) create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/coveragestore.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/flood_extent_1-day_UGA/coverage.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/flood_extent_1-day_UGA/layer.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/coveragestore.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/flood_extent_2-day_UGA/coverage.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/flood_extent_2-day_UGA/layer.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/coveragestore.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/flood_extent_3-day_UGA/coverage.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/flood_extent_3-day_UGA/layer.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/coveragestore.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/flood_extent_4-day_UGA/coverage.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/flood_extent_4-day_UGA/layer.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/coveragestore.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/flood_extent_6-day_UGA/coverage.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/flood_extent_6-day_UGA/layer.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/coveragestore.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/flood_extent_7-day_UGA/coverage.xml create mode 100644 services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/flood_extent_7-day_UGA/layer.xml diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/coveragestore.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/coveragestore.xml new file mode 100644 index 000000000..5c17e1385 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/coveragestore.xml @@ -0,0 +1,12 @@ + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ffc + flood_extent_1-day_UGA + GeoTIFF + true + + WorkspaceInfoImpl-48cab08a:175ace47603:-7ffb + + <__default>false + 2024-03-18 14:47:29.131 UTC + file:workspaces/ibf-system/ibf-pipeline/output/flood_extents/flood_extent_1-day_UGA.tif + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/flood_extent_1-day_UGA/coverage.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/flood_extent_1-day_UGA/coverage.xml new file mode 100644 index 000000000..1bf718a64 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/flood_extent_1-day_UGA/coverage.xml @@ -0,0 +1,129 @@ + + CoverageInfoImpl--18a5c506:18e5207e95d:-7ff6 + flood_extent_1-day_UGA + flood_extent_1-day_UGA + + NamespaceInfoImpl-48cab08a:175ace47603:-7ffa + + flood_extent_1-day_UGA + Generated from GeoTIFF + + flood_extent_1-day_UGA + WCS + GeoTIFF + + GEOGCS["WGS 84", + DATUM["World Geodetic System 1984", + SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], + AUTHORITY["EPSG","6326"]], + PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], + UNIT["degree", 0.017453292519943295], + AXIS["Geodetic longitude", EAST], + AXIS["Geodetic latitude", NORTH], + AUTHORITY["EPSG","4326"]] + EPSG:4326 + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + REPROJECT_TO_DECLARED + true + + flood_extent_1-day_UGA_flood_extent_1-day_UGA + + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ffc + + false + false + GeoTIFF + + + 0 0 + 6514 6854 + + + 8.333332528400369E-4 + -8.333333332360665E-4 + 0.0 + 0.0 + 29.57291544462642 + 4.230416666333381 + + EPSG:4326 + + + ImagePyramid + RST + GIF + PNG + JPEG + TIFF + GEOTIFF + NITF + ArcGrid + DTED + AIG + GeoPackage (mosaic) + ImageMosaic + SRP + ERDASImg + VRT + ENVIHdr + RPFTOC + EHdr + + + nearest neighbor + bilinear + bicubic + + nearest neighbor + + + GRAY_INDEX + GridSampleDimension[-Infinity,Infinity] + + -inf + inf + + + 3.4028234663852886E38 + + + REAL_32BITS + + + + + EPSG:4326 + + + EPSG:4326 + + + + InputTransparentColor + + + + SUGGESTED_TILE_SIZE + 512,512 + + + RescalePixels + true + + + flood_extent_1-day_UGA + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/flood_extent_1-day_UGA/layer.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/flood_extent_1-day_UGA/layer.xml new file mode 100644 index 000000000..2a7f15ba6 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_1-day_UGA/flood_extent_1-day_UGA/layer.xml @@ -0,0 +1,19 @@ + + flood_extent_1-day_UGA + LayerInfoImpl--18a5c506:18e5207e95d:-7ff5 + RASTER + + StyleInfoImpl-48cab08a:175ace47603:-7ff7 + + + CoverageInfoImpl--18a5c506:18e5207e95d:-7ff6 + + true + false + + 0 + 0 + + 2024-03-18 14:47:30.361 UTC + 2024-03-18 14:47:30.453 UTC + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/coveragestore.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/coveragestore.xml new file mode 100644 index 000000000..49696c722 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/coveragestore.xml @@ -0,0 +1,12 @@ + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ffb + flood_extent_2-day_UGA + GeoTIFF + true + + WorkspaceInfoImpl-48cab08a:175ace47603:-7ffb + + <__default>false + 2024-03-18 14:47:29.267 UTC + file:workspaces/ibf-system/ibf-pipeline/output/flood_extents/flood_extent_2-day_UGA.tif + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/flood_extent_2-day_UGA/coverage.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/flood_extent_2-day_UGA/coverage.xml new file mode 100644 index 000000000..935a0f0d7 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/flood_extent_2-day_UGA/coverage.xml @@ -0,0 +1,129 @@ + + CoverageInfoImpl--18a5c506:18e5207e95d:-7ff4 + flood_extent_2-day_UGA + flood_extent_2-day_UGA + + NamespaceInfoImpl-48cab08a:175ace47603:-7ffa + + flood_extent_2-day_UGA + Generated from GeoTIFF + + flood_extent_2-day_UGA + WCS + GeoTIFF + + GEOGCS["WGS 84", + DATUM["World Geodetic System 1984", + SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], + AUTHORITY["EPSG","6326"]], + PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], + UNIT["degree", 0.017453292519943295], + AXIS["Geodetic longitude", EAST], + AXIS["Geodetic latitude", NORTH], + AUTHORITY["EPSG","4326"]] + EPSG:4326 + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + REPROJECT_TO_DECLARED + true + + flood_extent_2-day_UGA_flood_extent_2-day_UGA + + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ffb + + false + false + GeoTIFF + + + 0 0 + 6514 6854 + + + 8.333332528400369E-4 + -8.333333332360665E-4 + 0.0 + 0.0 + 29.57291544462642 + 4.230416666333381 + + EPSG:4326 + + + ImagePyramid + RST + GIF + PNG + JPEG + TIFF + GEOTIFF + NITF + ArcGrid + DTED + AIG + GeoPackage (mosaic) + ImageMosaic + SRP + ERDASImg + VRT + ENVIHdr + RPFTOC + EHdr + + + nearest neighbor + bilinear + bicubic + + nearest neighbor + + + GRAY_INDEX + GridSampleDimension[-Infinity,Infinity] + + -inf + inf + + + 3.4028234663852886E38 + + + REAL_32BITS + + + + + EPSG:4326 + + + EPSG:4326 + + + + InputTransparentColor + + + + SUGGESTED_TILE_SIZE + 512,512 + + + RescalePixels + true + + + flood_extent_2-day_UGA + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/flood_extent_2-day_UGA/layer.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/flood_extent_2-day_UGA/layer.xml new file mode 100644 index 000000000..b6136d44f --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_2-day_UGA/flood_extent_2-day_UGA/layer.xml @@ -0,0 +1,19 @@ + + flood_extent_2-day_UGA + LayerInfoImpl--18a5c506:18e5207e95d:-7ff3 + RASTER + + StyleInfoImpl-48cab08a:175ace47603:-7ff7 + + + CoverageInfoImpl--18a5c506:18e5207e95d:-7ff4 + + true + false + + 0 + 0 + + 2024-03-18 14:47:30.734 UTC + 2024-03-18 14:47:30.811 UTC + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/coveragestore.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/coveragestore.xml new file mode 100644 index 000000000..d72f77ab2 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/coveragestore.xml @@ -0,0 +1,12 @@ + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ffa + flood_extent_3-day_UGA + GeoTIFF + true + + WorkspaceInfoImpl-48cab08a:175ace47603:-7ffb + + <__default>false + 2024-03-18 14:47:29.337 UTC + file:workspaces/ibf-system/ibf-pipeline/output/flood_extents/flood_extent_3-day_UGA.tif + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/flood_extent_3-day_UGA/coverage.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/flood_extent_3-day_UGA/coverage.xml new file mode 100644 index 000000000..6c7dc1842 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/flood_extent_3-day_UGA/coverage.xml @@ -0,0 +1,129 @@ + + CoverageInfoImpl--18a5c506:18e5207e95d:-7ff2 + flood_extent_3-day_UGA + flood_extent_3-day_UGA + + NamespaceInfoImpl-48cab08a:175ace47603:-7ffa + + flood_extent_3-day_UGA + Generated from GeoTIFF + + flood_extent_3-day_UGA + WCS + GeoTIFF + + GEOGCS["WGS 84", + DATUM["World Geodetic System 1984", + SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], + AUTHORITY["EPSG","6326"]], + PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], + UNIT["degree", 0.017453292519943295], + AXIS["Geodetic longitude", EAST], + AXIS["Geodetic latitude", NORTH], + AUTHORITY["EPSG","4326"]] + EPSG:4326 + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + REPROJECT_TO_DECLARED + true + + flood_extent_3-day_UGA_flood_extent_3-day_UGA + + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ffa + + false + false + GeoTIFF + + + 0 0 + 6514 6854 + + + 8.333332528400369E-4 + -8.333333332360665E-4 + 0.0 + 0.0 + 29.57291544462642 + 4.230416666333381 + + EPSG:4326 + + + ImagePyramid + RST + GIF + PNG + JPEG + TIFF + GEOTIFF + NITF + ArcGrid + DTED + AIG + GeoPackage (mosaic) + ImageMosaic + SRP + ERDASImg + VRT + ENVIHdr + RPFTOC + EHdr + + + nearest neighbor + bilinear + bicubic + + nearest neighbor + + + GRAY_INDEX + GridSampleDimension[-Infinity,Infinity] + + -inf + inf + + + 3.4028234663852886E38 + + + REAL_32BITS + + + + + EPSG:4326 + + + EPSG:4326 + + + + InputTransparentColor + + + + SUGGESTED_TILE_SIZE + 512,512 + + + RescalePixels + true + + + flood_extent_3-day_UGA + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/flood_extent_3-day_UGA/layer.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/flood_extent_3-day_UGA/layer.xml new file mode 100644 index 000000000..b79c5192a --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_3-day_UGA/flood_extent_3-day_UGA/layer.xml @@ -0,0 +1,19 @@ + + flood_extent_3-day_UGA + LayerInfoImpl--18a5c506:18e5207e95d:-7ff1 + RASTER + + StyleInfoImpl-48cab08a:175ace47603:-7ff7 + + + CoverageInfoImpl--18a5c506:18e5207e95d:-7ff2 + + true + false + + 0 + 0 + + 2024-03-18 14:47:30.958 UTC + 2024-03-18 14:47:31.0 UTC + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/coveragestore.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/coveragestore.xml new file mode 100644 index 000000000..6e226ad6d --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/coveragestore.xml @@ -0,0 +1,12 @@ + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ff9 + flood_extent_4-day_UGA + GeoTIFF + true + + WorkspaceInfoImpl-48cab08a:175ace47603:-7ffb + + <__default>false + 2024-03-18 14:47:29.372 UTC + file:workspaces/ibf-system/ibf-pipeline/output/flood_extents/flood_extent_4-day_UGA.tif + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/flood_extent_4-day_UGA/coverage.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/flood_extent_4-day_UGA/coverage.xml new file mode 100644 index 000000000..f76fc3be3 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/flood_extent_4-day_UGA/coverage.xml @@ -0,0 +1,129 @@ + + CoverageInfoImpl--18a5c506:18e5207e95d:-7ff0 + flood_extent_4-day_UGA + flood_extent_4-day_UGA + + NamespaceInfoImpl-48cab08a:175ace47603:-7ffa + + flood_extent_4-day_UGA + Generated from GeoTIFF + + flood_extent_4-day_UGA + WCS + GeoTIFF + + GEOGCS["WGS 84", + DATUM["World Geodetic System 1984", + SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], + AUTHORITY["EPSG","6326"]], + PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], + UNIT["degree", 0.017453292519943295], + AXIS["Geodetic longitude", EAST], + AXIS["Geodetic latitude", NORTH], + AUTHORITY["EPSG","4326"]] + EPSG:4326 + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + REPROJECT_TO_DECLARED + true + + flood_extent_4-day_UGA_flood_extent_4-day_UGA + + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ff9 + + false + false + GeoTIFF + + + 0 0 + 6514 6854 + + + 8.333332528400369E-4 + -8.333333332360665E-4 + 0.0 + 0.0 + 29.57291544462642 + 4.230416666333381 + + EPSG:4326 + + + ImagePyramid + RST + GIF + PNG + JPEG + TIFF + GEOTIFF + NITF + ArcGrid + DTED + AIG + GeoPackage (mosaic) + ImageMosaic + SRP + ERDASImg + VRT + ENVIHdr + RPFTOC + EHdr + + + nearest neighbor + bilinear + bicubic + + nearest neighbor + + + GRAY_INDEX + GridSampleDimension[-Infinity,Infinity] + + -inf + inf + + + 3.4028234663852886E38 + + + REAL_32BITS + + + + + EPSG:4326 + + + EPSG:4326 + + + + InputTransparentColor + + + + SUGGESTED_TILE_SIZE + 512,512 + + + RescalePixels + true + + + flood_extent_4-day_UGA + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/flood_extent_4-day_UGA/layer.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/flood_extent_4-day_UGA/layer.xml new file mode 100644 index 000000000..de36e269f --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_4-day_UGA/flood_extent_4-day_UGA/layer.xml @@ -0,0 +1,19 @@ + + flood_extent_4-day_UGA + LayerInfoImpl--18a5c506:18e5207e95d:-7fef + RASTER + + StyleInfoImpl-48cab08a:175ace47603:-7ff7 + + + CoverageInfoImpl--18a5c506:18e5207e95d:-7ff0 + + true + false + + 0 + 0 + + 2024-03-18 14:47:31.111 UTC + 2024-03-18 14:47:31.146 UTC + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/coveragestore.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/coveragestore.xml new file mode 100644 index 000000000..843bb996f --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/coveragestore.xml @@ -0,0 +1,12 @@ + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ff8 + flood_extent_6-day_UGA + GeoTIFF + true + + WorkspaceInfoImpl-48cab08a:175ace47603:-7ffb + + <__default>false + 2024-03-18 14:47:29.405 UTC + file:workspaces/ibf-system/ibf-pipeline/output/flood_extents/flood_extent_6-day_UGA.tif + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/flood_extent_6-day_UGA/coverage.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/flood_extent_6-day_UGA/coverage.xml new file mode 100644 index 000000000..a9e147a89 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/flood_extent_6-day_UGA/coverage.xml @@ -0,0 +1,129 @@ + + CoverageInfoImpl--18a5c506:18e5207e95d:-7fee + flood_extent_6-day_UGA + flood_extent_6-day_UGA + + NamespaceInfoImpl-48cab08a:175ace47603:-7ffa + + flood_extent_6-day_UGA + Generated from GeoTIFF + + flood_extent_6-day_UGA + WCS + GeoTIFF + + GEOGCS["WGS 84", + DATUM["World Geodetic System 1984", + SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], + AUTHORITY["EPSG","6326"]], + PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], + UNIT["degree", 0.017453292519943295], + AXIS["Geodetic longitude", EAST], + AXIS["Geodetic latitude", NORTH], + AUTHORITY["EPSG","4326"]] + EPSG:4326 + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + REPROJECT_TO_DECLARED + true + + flood_extent_6-day_UGA_flood_extent_6-day_UGA + + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ff8 + + false + false + GeoTIFF + + + 0 0 + 6514 6854 + + + 8.333332528400369E-4 + -8.333333332360665E-4 + 0.0 + 0.0 + 29.57291544462642 + 4.230416666333381 + + EPSG:4326 + + + ImagePyramid + RST + GIF + PNG + JPEG + TIFF + GEOTIFF + NITF + ArcGrid + DTED + AIG + GeoPackage (mosaic) + ImageMosaic + SRP + ERDASImg + VRT + ENVIHdr + RPFTOC + EHdr + + + nearest neighbor + bilinear + bicubic + + nearest neighbor + + + GRAY_INDEX + GridSampleDimension[-Infinity,Infinity] + + -inf + inf + + + 3.4028234663852886E38 + + + REAL_32BITS + + + + + EPSG:4326 + + + EPSG:4326 + + + + InputTransparentColor + + + + SUGGESTED_TILE_SIZE + 512,512 + + + RescalePixels + true + + + flood_extent_6-day_UGA + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/flood_extent_6-day_UGA/layer.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/flood_extent_6-day_UGA/layer.xml new file mode 100644 index 000000000..5e3f16472 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_6-day_UGA/flood_extent_6-day_UGA/layer.xml @@ -0,0 +1,19 @@ + + flood_extent_6-day_UGA + LayerInfoImpl--18a5c506:18e5207e95d:-7fed + RASTER + + StyleInfoImpl-48cab08a:175ace47603:-7ff7 + + + CoverageInfoImpl--18a5c506:18e5207e95d:-7fee + + true + false + + 0 + 0 + + 2024-03-18 14:47:31.252 UTC + 2024-03-18 14:47:31.287 UTC + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/coveragestore.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/coveragestore.xml new file mode 100644 index 000000000..a2619fac5 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/coveragestore.xml @@ -0,0 +1,12 @@ + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ff7 + flood_extent_7-day_UGA + GeoTIFF + true + + WorkspaceInfoImpl-48cab08a:175ace47603:-7ffb + + <__default>false + 2024-03-18 14:47:29.457 UTC + file:workspaces/ibf-system/ibf-pipeline/output/flood_extents/flood_extent_7-day_UGA.tif + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/flood_extent_7-day_UGA/coverage.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/flood_extent_7-day_UGA/coverage.xml new file mode 100644 index 000000000..108b2cf5d --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/flood_extent_7-day_UGA/coverage.xml @@ -0,0 +1,129 @@ + + CoverageInfoImpl--18a5c506:18e5207e95d:-7fec + flood_extent_7-day_UGA + flood_extent_7-day_UGA + + NamespaceInfoImpl-48cab08a:175ace47603:-7ffa + + flood_extent_7-day_UGA + Generated from GeoTIFF + + flood_extent_7-day_UGA + WCS + GeoTIFF + + GEOGCS["WGS 84", + DATUM["World Geodetic System 1984", + SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], + AUTHORITY["EPSG","6326"]], + PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], + UNIT["degree", 0.017453292519943295], + AXIS["Geodetic longitude", EAST], + AXIS["Geodetic latitude", NORTH], + AUTHORITY["EPSG","4326"]] + EPSG:4326 + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + + 29.572498778 + 35.000831587 + -1.4808333329999996 + 4.230833333 + EPSG:4326 + + REPROJECT_TO_DECLARED + true + + flood_extent_7-day_UGA_flood_extent_7-day_UGA + + + CoverageStoreInfoImpl--18a5c506:18e5207e95d:-7ff7 + + false + false + GeoTIFF + + + 0 0 + 6514 6854 + + + 8.333332528400369E-4 + -8.333333332360665E-4 + 0.0 + 0.0 + 29.57291544462642 + 4.230416666333381 + + EPSG:4326 + + + ImagePyramid + RST + GIF + PNG + JPEG + TIFF + GEOTIFF + NITF + ArcGrid + DTED + AIG + GeoPackage (mosaic) + ImageMosaic + SRP + ERDASImg + VRT + ENVIHdr + RPFTOC + EHdr + + + nearest neighbor + bilinear + bicubic + + nearest neighbor + + + GRAY_INDEX + GridSampleDimension[-Infinity,Infinity] + + -inf + inf + + + 3.4028234663852886E38 + + + REAL_32BITS + + + + + EPSG:4326 + + + EPSG:4326 + + + + InputTransparentColor + + + + SUGGESTED_TILE_SIZE + 512,512 + + + RescalePixels + true + + + flood_extent_7-day_UGA + \ No newline at end of file diff --git a/services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/flood_extent_7-day_UGA/layer.xml b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/flood_extent_7-day_UGA/layer.xml new file mode 100644 index 000000000..2e8565775 --- /dev/null +++ b/services/API-service/geoserver-volume/geoserver-layers/flood_extent_7-day_UGA/flood_extent_7-day_UGA/layer.xml @@ -0,0 +1,19 @@ + + flood_extent_7-day_UGA + LayerInfoImpl--18a5c506:18e5207e95d:-7feb + RASTER + + StyleInfoImpl-48cab08a:175ace47603:-7ff7 + + + CoverageInfoImpl--18a5c506:18e5207e95d:-7fec + + true + false + + 0 + 0 + + 2024-03-18 14:47:31.402 UTC + 2024-03-18 14:47:31.428 UTC + \ No newline at end of file