Skip to content

Commit

Permalink
feat: Style API Portal and add templating mechanism (#2965)
Browse files Browse the repository at this point in the history
* Include Brian changes for the templating (#2956)

* new styles initial commit

* swagger cont

* css changes

* spacing

* fix html

* update token

---------

Co-authored-by: Brian Lee <[email protected]>

* revert back

Signed-off-by: at670475 <[email protected]>

* resolve conflicts

Signed-off-by: at670475 <[email protected]>

* flag the css for zowe and portal

Signed-off-by: at670475 <[email protected]>

* add backend parameters mapping

Signed-off-by: at670475 <[email protected]>

* cleanup

Signed-off-by: at670475 <[email protected]>

* fix conditional flag check

Signed-off-by: at670475 <[email protected]>

* wip - fix css

Signed-off-by: at670475 <[email protected]>

* wip

Signed-off-by: at670475 <[email protected]>

* adjust css

Signed-off-by: at670475 <[email protected]>

* wip

Signed-off-by: at670475 <[email protected]>

* add links

Signed-off-by: at670475 <[email protected]>

* define branding function

Signed-off-by: at670475 <[email protected]>

* footer links

Signed-off-by: at670475 <[email protected]>

* count number of footer content

Signed-off-by: at670475 <[email protected]>

* fix issues and add move footer

Signed-off-by: at670475 <[email protected]>

* fix css

Signed-off-by: at670475 <[email protected]>

* flag the css for zowe and portal

Signed-off-by: at670475 <[email protected]>

* fix test

Signed-off-by: at670475 <[email protected]>

* small fix

Signed-off-by: at670475 <[email protected]>

* fix bug with version dropdown menu empty

Signed-off-by: at670475 <[email protected]>

* open link on new tab

Signed-off-by: at670475 <[email protected]>

* small fix

Signed-off-by: at670475 <[email protected]>

* make rsponsiveness

Signed-off-by: at670475 <[email protected]>

* responsiveness

Signed-off-by: at670475 <[email protected]>

* responsive

Signed-off-by: at670475 <[email protected]>

* fix css

Signed-off-by: at670475 <[email protected]>

* put back toastify config

Signed-off-by: at670475 <[email protected]>

* fix status

Signed-off-by: at670475 <[email protected]>

* update schema, some test fix for locar run

Signed-off-by: Pablo Hernán Carle <[email protected]>

* fix tests

Signed-off-by: at670475 <[email protected]>

* add media icons

Signed-off-by: at670475 <[email protected]>

* wip

Signed-off-by: at670475 <[email protected]>

* add templating for logo and controller to retrieve it

Signed-off-by: at670475 <[email protected]>

* add doc link

Signed-off-by: at670475 <[email protected]>

* Add skeleton for the footer

Signed-off-by: at670475 <[email protected]>

* Add tests

Signed-off-by: at670475 <[email protected]>

* fix code smells

Signed-off-by: at670475 <[email protected]>

* add test

Signed-off-by: at670475 <[email protected]>

* Update schema and start.sh

Signed-off-by: at670475 <[email protected]>

* delete yarn and set specific version for sass

Signed-off-by: at670475 <[email protected]>

* fix bug

Signed-off-by: at670475 <[email protected]>

* Add tests

Signed-off-by: at670475 <[email protected]>

* improve tests

Signed-off-by: at670475 <[email protected]>

* define mock custom config in tests

Signed-off-by: at670475 <[email protected]>

* increase coverage

Signed-off-by: at670475 <[email protected]>

* fix

Signed-off-by: at670475 <[email protected]>

* address pr comments

Signed-off-by: at670475 <[email protected]>

* revert back

Signed-off-by: at670475 <[email protected]>

* fix tests

Signed-off-by: at670475 <[email protected]>

* fix e2e test

Signed-off-by: at670475 <[email protected]>

* fix css

Signed-off-by: at670475 <[email protected]>

* Address pr comment

Signed-off-by: at670475 <[email protected]>

* npm script standalone

Signed-off-by: Pablo Hernán Carle <[email protected]>

* fix font family config

Signed-off-by: at670475 <[email protected]>

* address PR comments

Signed-off-by: at670475 <[email protected]>

* remove return from arrow function

Signed-off-by: at670475 <[email protected]>

---------

Signed-off-by: at670475 <[email protected]>
Signed-off-by: Pablo Hernán Carle <[email protected]>
Co-authored-by: Brian Lee <[email protected]>
Co-authored-by: Pablo Hernán Carle <[email protected]>
  • Loading branch information
3 people authored Jul 11, 2023
1 parent a5a93d2 commit b286cef
Show file tree
Hide file tree
Showing 103 changed files with 4,364 additions and 4,732 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ gateway-service/jwt*.tmp
# zowe-api-dev
.api-dev
.zowe-api-dev
user-zowe-api.json
user-zowe-api*.json
lastJob.json
invalidated*.data
invalidated*.index
Expand Down
7 changes: 7 additions & 0 deletions api-catalog-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${CATALOG_CODE} java \
-Dapiml.security.authorization.resourceClass=${ZWE_components_gateway_apiml_security_authorization_resourceClass:-ZOWE} \
-Dapiml.security.auth.cookieProperties.cookieName=${cookieName:-apimlAuthenticationToken} \
-Dapiml.catalog.hide.serviceInfo=${ZWE_configs_apiml_catalog_hide_serviceInfo:-false} \
-Dapiml.catalog.customStyle.logo=${ZWE_configs_apiml_catalog_customStyle_logo:-} \
-Dapiml.catalog.customStyle.fontFamily=${ZWE_configs_apiml_catalog_customStyle_fontFamily:-} \
-Dapiml.catalog.customStyle.backgroundColor=${ZWE_configs_apiml_catalog_customStyle_backgroundColor:-} \
-Dapiml.catalog.customStyle.titlesColor=${ZWE_configs_apiml_catalog_customStyle_titlesColor:-} \
-Dapiml.catalog.customStyle.headerColor=${ZWE_configs_apiml_catalog_customStyle_headerColor:-} \
-Dapiml.catalog.customStyle.textColor=${ZWE_configs_apiml_catalog_customStyle_textColor:-} \
-Dapiml.catalog.customStyle.docLink=${ZWE_configs_apiml_catalog_customStyle_docLink:-} \
-Dapiml.httpclient.ssl.enabled-protocols=${ZWE_components_gateway_apiml_httpclient_ssl_enabled_protocols:-"TLSv1.2"} \
-Dspring.profiles.include=$LOG_LEVEL \
-Dserver.address=0.0.0.0 \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.apicatalog.controllers.api;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

@RestController
@RequestMapping("/")
public class ImageController {

@Value("${apiml.catalog.customStyle.logo:}")
private String image;

@GetMapping(value = "/custom-logo")
@HystrixCommand()
@ResponseBody
public ResponseEntity<InputStreamResource> downloadImage() {
try {
File imageFile = new File(image);
String extension = image.substring(image.lastIndexOf(".") + 1);
MediaType mediaType;
InputStream imageStream = new FileInputStream(imageFile);
switch (extension.toLowerCase()) {
case "png":
mediaType = MediaType.IMAGE_PNG;
break;
case "jpg":
case "jpeg":
mediaType = MediaType.IMAGE_JPEG;
break;
case "svg":
mediaType = MediaType.valueOf("image/svg+xml");
break;
default:
mediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(mediaType);
return ResponseEntity.ok()
.headers(headers)
.body(new InputStreamResource(imageStream));
} catch (IOException e) {
return ResponseEntity.notFound().build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public class APIContainer implements Serializable {
@Schema(description = "Control whether the service's information should be shown")
private boolean hideServiceInfo;

@Schema(description = "Control selected style properties")
private CustomStyleConfig customStyleConfig;

public APIContainer() {
this.lastUpdatedTimestamp = Calendar.getInstance();
this.createdTimestamp = this.lastUpdatedTimestamp;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.apicatalog.model;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.io.Serializable;

@Data
@Configuration
@ConfigurationProperties(prefix = "apiml.catalog.custom-style", ignoreInvalidFields = true)
@JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.PUBLIC_ONLY, setterVisibility = Visibility.PUBLIC_ONLY)
public class CustomStyleConfig implements Serializable {

private String logo = "";
private String fontFamily = "";
private String titlesColor = "";
private String headerColor = "";
private String backgroundColor = "";
private String textColor = "";
private String docLink = "";

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.springframework.stereotype.Service;
import org.zowe.apiml.apicatalog.model.APIContainer;
import org.zowe.apiml.apicatalog.model.APIService;
import org.zowe.apiml.apicatalog.model.CustomStyleConfig;
import org.zowe.apiml.auth.Authentication;
import org.zowe.apiml.auth.AuthenticationSchemes;
import org.zowe.apiml.config.ApiInfo;
Expand All @@ -30,11 +31,22 @@
import org.zowe.apiml.product.routing.transform.TransformService;
import org.zowe.apiml.product.routing.transform.URLTransformationException;

import java.util.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import static java.util.stream.Collectors.toList;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.*;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.AUTHENTICATION_SCHEME;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.AUTHENTICATION_SSO;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.CATALOG_DESCRIPTION;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.CATALOG_TITLE;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.CATALOG_VERSION;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_DESCRIPTION;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_TITLE;

/**
* Caching service for eureka services
Expand All @@ -57,17 +69,20 @@ public class CachedProductFamilyService {
private final Map<String, APIContainer> products = new HashMap<>();

private final AuthenticationSchemes schemes = new AuthenticationSchemes();
private final CustomStyleConfig customStyleConfig;

@Value("${apiml.catalog.hide.serviceInfo:false}")
private boolean hideServiceInfo;

public CachedProductFamilyService(CachedServicesService cachedServicesService,
TransformService transformService,
@Value("${apiml.service-registry.cacheRefreshUpdateThresholdInMillis}")
Integer cacheRefreshUpdateThresholdInMillis) {
Integer cacheRefreshUpdateThresholdInMillis,
CustomStyleConfig customStyleConfig) {
this.cachedServicesService = cachedServicesService;
this.transformService = transformService;
this.cacheRefreshUpdateThresholdInMillis = cacheRefreshUpdateThresholdInMillis;
this.customStyleConfig = customStyleConfig;
}

/**
Expand Down Expand Up @@ -232,6 +247,21 @@ public void calculateContainerServiceValues(APIContainer apiContainer) {
setStatus(apiContainer, servicesCount, activeServicesCount);
apiContainer.setSso(isSso);
apiContainer.setHideServiceInfo(hideServiceInfo);

// set metadata to customize the UI
if (customStyleConfig != null) {
setCustomUiConfig(apiContainer);
}

}

/**
* Map the configuration to customize the Catalog UI to the container
*
* @param apiContainer
*/
private void setCustomUiConfig(APIContainer apiContainer) {
apiContainer.setCustomStyleConfig(customStyleConfig);
}

/**
Expand Down Expand Up @@ -404,6 +434,7 @@ private void setStatus(APIContainer apiContainer, int servicesCount, int activeS
} else {
apiContainer.setStatus("WARNING");
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.models.security.SecurityScheme;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

@Slf4j

public class SecuritySchemeSerializer extends JsonSerializer<SecurityScheme> {


Expand Down
9 changes: 9 additions & 0 deletions api-catalog-services/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ apiml:
title: API Mediation Layer API
description: The API Mediation Layer for z/OS internal API services. The API Mediation Layer provides a single point of access to mainframe REST APIs and offers enterprise cloud-like features such as high-availability, scalability, dynamic API discovery, and documentation.
version: 1.0.0
# Configuration to customize the style of Catalog UI
customStyle:
logo:
fontFamily:
titlesColor:
headerColor:
backgroundColor:
textColor:
docLink:

service-registry:
serviceFetchDelayInMillis: 20000
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.apicatalog.controllers.api;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

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.status;

@ExtendWith(MockitoExtension.class)
class ImageControllerTest {

private MockMvc mockMvc;

@InjectMocks
private ImageController imageController;

@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(imageController).build();
}

@Nested
class GivenImageEndpointRequest {
@Nested
class WhenPngFormat {
@Test
void thenDownloadImage() throws Exception {
ReflectionTestUtils.setField(imageController, "image", "src/test/resources/api-catalog.png");

mockMvc.perform(get("/custom-logo"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.IMAGE_PNG))
.andReturn();
}
}

@Nested
class WhenJpegFormat {
@Test
void thenDownloadImage() throws Exception {
ReflectionTestUtils.setField(imageController, "image", "src/test/resources/api-catalog.jpeg");

mockMvc.perform(get("/custom-logo"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.IMAGE_JPEG))
.andReturn();
ReflectionTestUtils.setField(imageController, "image", "src/test/resources/api-catalog.jpg");

mockMvc.perform(get("/custom-logo"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.IMAGE_JPEG))
.andReturn();
}
}

@Nested
class WhenSvgFormat {
@Test
void thenDownloadImage() throws Exception {
ReflectionTestUtils.setField(imageController, "image", "src/test/resources/api-catalog.svg");

mockMvc.perform(get("/custom-logo"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.valueOf("image/svg+xml")))
.andReturn();
}
}

@Test
void thenReturnFileNotFound() throws Exception {
ReflectionTestUtils.setField(imageController, "image", "wrong/path/img.png");

mockMvc.perform(get("/custom-logo"))
.andExpect(status().isNotFound());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.zowe.apiml.apicatalog.standalone.ExampleService;

Expand All @@ -33,12 +33,12 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(SpringExtension.class)
@WebMvcTest(
controllers = { MockController.class },
excludeAutoConfiguration = { SecurityAutoConfiguration.class}
)
@ContextConfiguration(classes = MockControllerTest.Context.class)
@ActiveProfiles("test")
class MockControllerTest {

@Autowired
Expand Down Expand Up @@ -71,6 +71,7 @@ void whenPostRequest() throws Exception {
}

@Configuration
@Profile("test")
@SpyBean(ExampleService.class)
static class Context {

Expand All @@ -81,4 +82,4 @@ public MockController mockController(ExampleService exampleService) {

}

}
}
Loading

0 comments on commit b286cef

Please sign in to comment.