Skip to content

Commit

Permalink
feat: add ESRI WMS tile layer to react leaftlet (#343)
Browse files Browse the repository at this point in the history
Co-authored-by: Derek Roberts <[email protected]>
  • Loading branch information
Ricardo Campos and DerekRoberts authored Aug 23, 2024
1 parent f88aaa1 commit 9b24f02
Show file tree
Hide file tree
Showing 31 changed files with 6,531 additions and 8,145 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pr-open.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ jobs:
-p DB_POOL_MAX_SIZE=1
-p AWS_COGNITO_ISSUER_URI=https://cognito-idp.${{ vars.AWS_REGION }}.amazonaws.com/${{ vars.VITE_USER_POOLS_ID }}
-p DASHBOARD_JOB_IDIR_USERS=${{ vars.DASHBOARD_JOB_IDIR_USERS }}
-p WMS_LAYERS_WHITELIST_USERS=${{ vars.WMS_LAYERS_WHITELIST_USERS }}
- name: frontend
file: frontend/openshift.deploy.yml
parameters:
Expand Down
5 changes: 5 additions & 0 deletions backend/openshift.deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ parameters:
- name: ALLOWED_ORIGINS
description: Sets all the allowed request origins
value: "http://localhost:300*,https://*.apps.silver.devops.gov.bc.ca"
- name: WMS_LAYERS_WHITELIST_USERS
description: List of users that can see active layers on the map view
required: true
objects:
- apiVersion: apps/v1
kind: Deployment
Expand Down Expand Up @@ -132,6 +135,8 @@ objects:
value: ${DASHBOARD_JOB_IDIR_USERS}
- name: ALLOWED_ORIGINS
value: ${ALLOWED_ORIGINS}
- name: WMS_LAYERS_WHITELIST_USERS
value: ${WMS_LAYERS_WHITELIST_USERS}
- name: DATABASE_USER
valueFrom:
secretKeyRef:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ca.bc.gov.restapi.results.common.dto;

/** This record represents a user in the whitelist. */
public record WmsLayersWhitelistUserDto(String userName) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ca.bc.gov.restapi.results.common.endpoint;

import ca.bc.gov.restapi.results.common.service.RestService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/** This class holds resources for calling WFS. */
@RestController
@RequestMapping("/api/feature-service")
@AllArgsConstructor
@Tag(
name = "Feature Service to call WFS (Common)",
description = "Endpoints for handle WFS (Web Feature Service) within BC Geo Warehouse")
public class FeatureServiceEndpoint {

private final RestService restService;

/**
* Fetch Opening data from WFS.
*
* @param openingId The Opening identification (id)
* @return JSON object with response from WFS request
*/
@GetMapping("/polygon-and-props/{openingId}")
@Operation(
summary = "Fetch Opening data from WFS",
description =
"Fetch Opening data (polygon raster data and properties) from WFS. These are the props "
+ "being fetched: OPENING_ID, GEOMETRY, REGION_NAME, REGION_CODE, DISTRICT_NAME, "
+ "DISTRICT_CODE, CLIENT_NAME, CLIENT_NUMBER, and OPENING_WHEN_CREATED",
responses = {
@ApiResponse(
responseCode = "200",
description = "An object with the response from WFS request."),
@ApiResponse(
responseCode = "401",
description = "Access token is missing or invalid",
content = @Content(schema = @Schema(implementation = Void.class)))
})
public Object getOpeningPolygonAndProperties(
@Parameter(
name = "openingId",
in = ParameterIn.PATH,
description = "The opening Id.",
required = true)
@PathVariable
String openingId) {
return restService.getOpeningPolygonAndProperties(openingId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ca.bc.gov.restapi.results.common.endpoint;

import ca.bc.gov.restapi.results.common.dto.WmsLayersWhitelistUserDto;
import io.swagger.v3.oas.annotations.Hidden;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/** This class holds resources for getting secrets from the backend. */
@RestController
@RequestMapping("/api/secrets")
@Hidden
public class SecretsServiceEndpoint {

@Value("${nr.results.config.wms-layers.whitelist}")
private String[] wmsLayersWhitelistUsers;

/**
* Get all users allowed to see the WMS layers information.
*
* @return List of users.
*/
@GetMapping("/wms-layers-whitelist")
public List<WmsLayersWhitelistUserDto> getWmsLayersWhitelistUsers() {
return Stream.of(wmsLayersWhitelistUsers).map(WmsLayersWhitelistUserDto::new).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ca.bc.gov.restapi.results.common.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/** This service provides method for doing requests and GET calls. */
@Slf4j
@Service
public class RestService {

private final RestTemplate restTemplate;

public RestService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}

/**
* Get Opening polygons and properties from WFS given an opening id.
*
* @param openingId The Opening identification.
* @return An object with the response from WFS
*/
public Object getOpeningPolygonAndProperties(String openingId) {
StringBuilder sb = new StringBuilder();
sb.append("https://openmaps.gov.bc.ca/geo/ows");
sb.append("?service=WFS");
sb.append("&version=2.0.0");
sb.append("&request=GetFeature");
sb.append("&typeName=WHSE_FOREST_VEGETATION.RSLT_OPENING_SVW");
sb.append("&outputFormat=application/json");
sb.append("&SrsName=EPSG:4326");
sb.append("&PROPERTYNAME=");
sb.append("OPENING_ID,GEOMETRY,REGION_NAME,REGION_CODE,DISTRICT_NAME,DISTRICT_CODE,");
sb.append("CLIENT_NAME,CLIENT_NUMBER,OPENING_WHEN_CREATED");
sb.append("&CQL_FILTER=OPENING_ID=").append(openingId);

try {
ResponseEntity<Object> response = restTemplate.getForEntity(sb.toString(), Object.class);
Object responseBody = response.getBody();
log.info("Request to WFS finished with code {}", response.getStatusCode());
return responseBody;
} catch (Exception e) {
log.error("Exception when fetching from WFS {}", e.getMessage());
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ public PaginatedResult<OpeningSearchResponseDto> openingSearch(
name = "submittedToFrpa",
in = ParameterIn.QUERY,
description = "Submitted to FRPA Section 108, true or false.",
required = false,
example = "submittedToFrpa")
required = false)
Boolean submittedToFrpa,
@RequestParam(value = "disturbanceDateStart", required = false)
@Parameter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,11 @@ private List<OpeningSearchResponseDto> buildResultListDto(List<?> result) {

if (row.length > 17) {
BigDecimal silvaReliefAppId = getValue(BigDecimal.class, row[17], "submittedToFrpa108");
searchOpeningDto.setSubmittedToFrpa(silvaReliefAppId.compareTo(BigDecimal.ZERO) > 0);
searchOpeningDto.setSilvaReliefAppId(silvaReliefAppId.longValue());
boolean submittedApp = silvaReliefAppId.compareTo(BigDecimal.ZERO) > 0;
searchOpeningDto.setSubmittedToFrpa(submittedApp);
if (submittedApp) {
searchOpeningDto.setSilvaReliefAppId(silvaReliefAppId.longValue());
}
}

// fetch from forestClient API
Expand Down Expand Up @@ -370,8 +373,16 @@ private String createNativeSqlQuery(OpeningSearchFiltersDto filtersDto) {
}
// 5. Submitted to FRPA
if (filtersDto.hasValue(OpeningSearchFiltersDto.SUBMITTED_TO_FRPA)) {
log.info("Filter submitted to FRPA detected! submitted={}", filtersDto.getSubmittedToFrpa());
builder.append("AND sra.SILV_RELIEF_APPLICATION_ID IS NOT NULL ");
Boolean value = filtersDto.getSubmittedToFrpa();
if (Boolean.FALSE.equals(value)) {
log.info(
"Filter submitted to FRPA detected! submitted={}", filtersDto.getSubmittedToFrpa());
builder.append("AND sra.SILV_RELIEF_APPLICATION_ID IS NULL ");
} else {
log.info(
"Filter submitted to FRPA detected! submitted={}", filtersDto.getSubmittedToFrpa());
builder.append("AND sra.SILV_RELIEF_APPLICATION_ID IS NOT NULL ");
}
}
// 6. Disturbance start date
if (filtersDto.hasValue(OpeningSearchFiltersDto.DISTURBANCE_DATE_START)) {
Expand Down
10 changes: 4 additions & 6 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@ nr-results-ecs-version = 8.9
nr-results-backend-env-opensearch = ${RESULTS_ENV_OPENSEARCH:development}
nr-results-team-email-address = [email protected]

# Certificate for the Database
ca.bc.gov.nrs.oracle.keystore = ${ORACLEDB_KEYSTORE:jssecacerts-path}
ca.bc.gov.nrs.oracle.secret = ${ORACLEDB_SECRET:changeit}
ca.bc.gov.nrs.oracle.host = ${DATABASE_HOST}

# FAM
spring.security.oauth2.resourceserver.jwt.issuer-uri = ${AWS_COGNITO_ISSUER_URI:aws-cognito-any-url.com}
spring.security.oauth2.resourceserver.jwt.jwk-set-uri = ${AWS_COGNITO_ISSUER_URI:aws-cognito-any-url.com}/.well-known/jwks.json
Expand All @@ -74,4 +69,7 @@ spring.flyway.user = ${POSTGRES_USER:postgres}
spring.flyway.password = ${POSTGRES_PASSWORD:default}

# Users allowed to manually trigger the Dashboard extraction job
nr.results.dashboard-job-users = ${DASHBOARD_JOB_IDIR_USERS}
nr.results.dashboard-job-users = ${DASHBOARD_JOB_IDIR_USERS}

# Users allowed to see and download WMS layers information
nr.results.config.wms-layers.whitelist = ${WMS_LAYERS_WHITELIST_USERS:NONE}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package ca.bc.gov.restapi.results.common.endpoint;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import ca.bc.gov.restapi.results.common.service.RestService;
import java.util.Optional;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(FeatureServiceEndpoint.class)
@WithMockUser(roles = "user_read")
class FeatureServiceEndpointTest {

@Autowired private MockMvc mockMvc;

@MockBean RestService restService;

@Test
@DisplayName("Get opening polygon and properties happy path should succeed")
void getOpeningPolygonAndProperties_happyPath_shouldSucceed() throws Exception {
String openingId = "58993";

String response =
"""
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "WHSE_FOREST_VEGETATION.RSLT_OPENING_SVW.fid-6f119ee7_19129391292_7c7b",
"geometry_name": "GEOMETRY",
"properties": {
"OPENING_ID": 58993,
"OPENING_CATEGORY_CODE": "FTML",
"OPENING_STATUS_CODE": "FG",
"REGION_CODE": "ROM",
"REGION_NAME": "Omineca Natural Resource Region",
"DISTRICT_CODE": "DMK",
"DISTRICT_NAME": "Mackenzie Natural Resource District",
"CLIENT_NAME": "CONIFEX MACKENZIE FOREST PRODUCTS INC.",
"CLIENT_NUMBER": "00161229",
"OPENING_WHO_CREATED": "MLSIS",
"OPENING_WHEN_CREATED": "1999-08-26Z",
"OPENING_WHO_UPDATED": "IDIR\\\\\\\\SCAKERLE",
"OPENING_WHEN_UPDATED": "2023-11-14Z",
"OBJECTID": 3869732,
"bbox": [
-125.6056,
56.06894,
-125.59267,
56.07429
]
}
}
]
}
""";

when(restService.getOpeningPolygonAndProperties(openingId))
.thenReturn(ResponseEntity.of(Optional.of(response)));

mockMvc
.perform(
get("/api/feature-service/polygon-and-props/{openingId}", openingId)
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.type").value("FeatureCollection"))
.andExpect(jsonPath("$.features[0].type").value("Feature"))
.andExpect(
jsonPath("$.features[0].id")
.value("WHSE_FOREST_VEGETATION.RSLT_OPENING_SVW.fid-6f119ee7_19129391292_7c7b"))
.andExpect(jsonPath("$.features[0].geometry_name").value("GEOMETRY"))
.andExpect(jsonPath("$.features[0].properties.OPENING_ID").value(openingId))
.andExpect(jsonPath("$.features[0].properties.OPENING_CATEGORY_CODE").value("FTML"))
.andExpect(jsonPath("$.features[0].properties.OPENING_STATUS_CODE").value("FG"))
.andExpect(jsonPath("$.features[0].properties.REGION_CODE").value("ROM"))
.andExpect(
jsonPath("$.features[0].properties.REGION_NAME")
.value("Omineca Natural Resource Region"))
.andExpect(jsonPath("$.features[0].properties.DISTRICT_CODE").value("DMK"))
.andExpect(
jsonPath("$.features[0].properties.DISTRICT_NAME")
.value("Mackenzie Natural Resource District"))
.andExpect(
jsonPath("$.features[0].properties.CLIENT_NAME")
.value("CONIFEX MACKENZIE FOREST PRODUCTS INC."))
.andExpect(jsonPath("$.features[0].properties.CLIENT_NUMBER").value("00161229"))
.andExpect(jsonPath("$.features[0].properties.OPENING_WHO_CREATED").value("MLSIS"))
.andExpect(jsonPath("$.features[0].properties.OPENING_WHEN_CREATED").value("1999-08-26Z"))
.andExpect(
jsonPath("$.features[0].properties.OPENING_WHO_UPDATED").value("IDIR\\\\SCAKERLE"))
.andExpect(jsonPath("$.features[0].properties.OPENING_WHEN_UPDATED").value("2023-11-14Z"))
.andExpect(jsonPath("$.features[0].properties.OBJECTID").value("3869732"))
.andExpect(jsonPath("$.features[0].properties.bbox[0]").value("-125.6056"))
.andExpect(jsonPath("$.features[0].properties.bbox[1]").value("56.06894"))
.andExpect(jsonPath("$.features[0].properties.bbox[2]").value("-125.59267"))
.andExpect(jsonPath("$.features[0].properties.bbox[3]").value("56.07429"))
.andReturn();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ca.bc.gov.restapi.results.common.endpoint;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(SecretsServiceEndpoint.class)
@WithMockUser(roles = "user_read")
class SecretsServiceEndpointTest {

@Autowired private MockMvc mockMvc;

@Test
@DisplayName("Get WMS layers whitelist users happy path should succeed")
void getWmsLayersWhitelistUsers_happyPath_shouldSucceed() throws Exception {
mockMvc
.perform(
get("/api/secrets/wms-layers-whitelist")
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$[0].userName").value("NONE"))
.andReturn();
}
}
Loading

0 comments on commit 9b24f02

Please sign in to comment.