Skip to content

Commit

Permalink
Merge pull request #1519 from thehyve/FAIRSPC-50-icons-config
Browse files Browse the repository at this point in the history
Make the icons in application menu configurable (external services, metadata domains)
  • Loading branch information
ewelinagr authored May 1, 2024
2 parents 9e0d1d0 + 51e5cd1 commit 0de8142
Show file tree
Hide file tree
Showing 43 changed files with 821 additions and 161 deletions.
52 changes: 49 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1643,6 +1643,25 @@ curl -H 'Accept: application/json' 'http://localhost:8080/api/features/'
----
====

===== Icons (SVG)

|===
| ``GET /api/iconsvg/{icon_name}``

| Retrieve an SVG icon by name.
|===

Response contains an SVG icon (if configured).

.Example retrieving icon (curl)
[%collapsible]
====
[source, bash]
----
curl -H 'Accept: image/svg+xml' 'http://localhost:8080/api/iconsvg/{icon_name}'
----
====

===== Services

|===
Expand All @@ -1652,7 +1671,8 @@ curl -H 'Accept: application/json' 'http://localhost:8080/api/features/'
|===

Response contains list of external services linked to Fairspace,
e.g. JupyterHub, cBioPortal, etc.
e.g. JupyterHub, cBioPortal, etc with their configuration details: name, url and icon name that can be used to retrieve
the icon (using ``GET /api/iconsvg/{icon_name}``).

.Example listing services (curl)
[%collapsible]
Expand Down Expand Up @@ -1879,7 +1899,10 @@ Multiple external Fairspace metadata pages can be configured simultaneously. A l
| ``label``
| String to be used as a display name of the metadata source.
| ``url``
| Fairspace instance to connect to.
| Fairspace instance to connect to. If the url is not specified, the metadata source will be treated as the internal one.
| *Important!* There should only be a single configuration of internal metadata (only the first one will not be ignored).
| ``icon-name``
| Name of an icon configured in the "icons" section of values.yaml file. If the name is not specified, there will be a default icon used.
|===

Sample configuration of *external* metadata sources in YAML format:
Expand All @@ -1888,10 +1911,15 @@ Sample configuration of *external* metadata sources in YAML format:
fairspace:
...
metadata-sources:
internal:
name: internal
label: "Internal metadata"
icon-name: "icon-internal-metadata"
additionalMetaSource1:
name: metadataSource1
label: "Test metadata 1"
url: https://fairspace-test1/api/
icon-name: "icon-1"
metaSource2:
name: metadataSource2
label: "Test metadata 2"
Expand Down Expand Up @@ -2647,6 +2675,8 @@ ingress:
force: true
----

You can pass values files with ``-f`` or ``--values``.

===== Configure a Keycloak realm for Fairspace

* Navigate to ``https://keycloak.example.com`` and select _Administration Console_:
Expand Down Expand Up @@ -2715,8 +2745,14 @@ fairspace:
ingress:
domain: fairspace.example.com
features: []
icons:
jupyter: "/icons/jupyter.svg" # path to the icon svg file
extra-icon: "extra-icon.svg" # path to the custom svg file
services:
JupyterHub: https://jupyterhub.example.com/user/${username}/lab
jupyterhub:
name: "JupyterHub"
url: https://jupyterhub.example.com/user/${username}/lab
icon-name: "jupyter"
storages:
external:
name: external
Expand Down Expand Up @@ -2778,6 +2814,16 @@ postgres:
storageClass: expandable
----

Additionally, to include custom icons for `fairspace.icons` option, you need to pass paths to the icon svg files as `svgicons.<iconname>=<path/to/the/icon.svg` using `--set-file` option:

[source, shell]
----
~/bin/helm/helm install fairspace-new fairspace/fairspace --version 0.7.29 --namespace fairspace-new \
-f /path/to/values.yaml --set-file saturn.vocabulary=/path/to/vocabulary.ttl --set-file saturn.views=/path/to/views.yaml --set-file svgicons.extra-icon=/path/to/extra-icon.svg
----

It is possible to pass multiple icon files with the `--set-file` option. Each of the icons has to be included as child of the `svgicons` key.

===== Deploy Jupyter Hub
Create a new Kubernetes namespace:
[source, shell]
Expand Down
114 changes: 62 additions & 52 deletions charts/fairspace/templates/project/configmap-pluto.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,71 @@ data:
{{ toYaml .Values.fairspace.storages | indent 8 }}
metadata-sources:
{{ toYaml (index .Values.fairspace "metadata-sources") | indent 8 }}
management:
endpoint:
health:
probes:
enabled: true
services:
{{ toYaml (index .Values.fairspace.services ) | indent 8 }}
icons:
{{ toYaml (index .Values.fairspace.icons ) | indent 8 }}
management:
endpoint:
health:
probes:
enabled: true

logging:
level:
root: {{ .Values.pluto.logLevel }}
logging:
level:
root: {{ .Values.pluto.logLevel }}

server:
error:
whitelabel:
enabled: false
max-http-header-size: 65535
server:
error:
whitelabel:
enabled: false
max-http-header-size: 65535

security:
oidc:
redirect-after-logout-url: {{ template "fairspace.scheme" . }}://{{ .Values.fairspace.ingress.domain }}/login
clientId: {{ .Values.external.keycloak.clientId }}
clientSecret: '{{ .Values.external.keycloak.clientSecret }}'
security:
oidc:
redirect-after-logout-url: {{ template "fairspace.scheme" . }}://{{ .Values.fairspace.ingress.domain }}/login
clientId: {{ .Values.external.keycloak.clientId }}
clientSecret: '{{ .Values.external.keycloak.clientSecret }}'

spring:
web:
resources:
static-locations: classpath:static,file:/opt/mercury
cloud:
gateway:
routes:
spring:
web:
resources:
static-locations: classpath:static,file:/opt/mercury
cloud:
gateway:
routes:
{{ if .Values.pluto.backends.storageRoutes }}
{{ toYaml .Values.pluto.backends.storageRoutes | indent 10 }}
{{ toYaml .Values.pluto.backends.storageRoutes | indent 12 }}
{{ end }}
- id: saturn-post-request-size
uri: {{ .Values.pluto.backends.saturn | default "http://localhost:8090/api/" }}
predicates:
- Method=POST
- Path=/api/**
filters:
- name: RequestSize
args:
maxSize: {{ .Values.pluto.maxFileSize }}
- id: saturn
uri: {{ .Values.pluto.backends.saturn | default "http://localhost:8090"}}
predicates:
- Path=/api/**
default-filters:
- RemoveRequestHeader=Pragma X-Frame-Options X-Content-Type-Options X-XSS-Protection X-Permitted-Cross-Domain-Policies Origin
# Including the ORIGIN header would trigger CORS filtering downstream, but Pluto is already doing the filtering.
- name: Retry
args:
methods: GET,PUT,POST,DELETE
exceptions:
- reactor.netty.http.client.PrematureCloseException
httpclient:
connect-timeout: {{ .Values.pluto.connectTimeoutMillis }}
response-timeout: {{ .Values.pluto.responseTimeoutMillis }}
pool:
max-idle-time: 20s
- id: saturn-post-request-size
uri: {{ .Values.pluto.backends.saturn | default "http://localhost:8090/api/" }}
predicates:
- Method=POST
- Path=/api/**
filters:
- name: RequestSize
args:
maxSize: {{ .Values.pluto.maxFileSize }}
- id: saturn
uri: {{ .Values.pluto.backends.saturn | default "http://localhost:8090"}}
predicates:
- Path=/api/**
default-filters:
- RemoveRequestHeader=Pragma X-Frame-Options X-Content-Type-Options X-XSS-Protection X-Permitted-Cross-Domain-Policies Origin
# Including the ORIGIN header would trigger CORS filtering downstream, but Pluto is already doing the filtering.
- name: Retry
args:
methods: GET,PUT,POST,DELETE
exceptions:
- reactor.netty.http.client.PrematureCloseException
httpclient:
connect-timeout: {{ .Values.pluto.connectTimeoutMillis }}
response-timeout: {{ .Values.pluto.responseTimeoutMillis }}
pool:
max-idle-time: 20s

# Add external svg icons to the configmap data
{{- range $key, $value := .Values.svgicons }}
{{ (print $key ".svg") }}: |-
{{ $value | nindent 4 }}
{{- end }}
2 changes: 0 additions & 2 deletions charts/fairspace/templates/project/configmap-saturn.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ data:
blobStorePath: "/data/saturn/files/blobs"
viewDatabase:
enabled: true
services:
{{ toYaml .Values.fairspace.services | indent 6 }}
features:
{{ toYaml .Values.fairspace.features | indent 6 }}
{{ if has "ExtraStorage" .Values.fairspace.features }}
Expand Down
3 changes: 3 additions & 0 deletions charts/fairspace/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ fairspace:
services: {}
storages: {}
metadata-sources: {}
icons: {}

# Generic settings for tracing
tracing:
Expand Down Expand Up @@ -182,3 +183,5 @@ postgres:

# Pod annotations should remain empty. They are filled within the build process
podAnnotations: { }

svgicons: { }
2 changes: 1 addition & 1 deletion local-development/fairspace/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ popd
${helm_cmd} dependency update ../../charts/fairspace && \
${helm_cmd} package ../../charts/fairspace && \
${helm_cmd} upgrade fairspace-local --install --namespace fairspace-dev fairspace-0.0.0-RELEASEVERSION.tgz \
-f local-values.yaml
-f local-values.yaml --set-file svgicons.test=../icons/test.svg

popd
19 changes: 18 additions & 1 deletion local-development/fairspace/local-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,25 @@ fairspace:
enabled: false
features:
- ExtraStorage
icons:
test-icon: test.svg
jupyter: /icons/jupyter.svg
omics: /icons/omics.svg
metadata: /icons/metadata.svg
services:
JupyterHub: http://jupyterhub.local/user/${username}/lab
JupyterHub:
name: "JupyterHub"
url: http://jupyterhub.local/user/${username}/lab
icons-name: jupyter
metadata-sources:
internal:
label: "Metadata"
icon-name: metadata
test:
name: test
label: "External Metadata"
url: http://pluto.local/api/
icon-name: "test-icon"

# Specific settings for Saturn subchart
saturn:
Expand Down
3 changes: 3 additions & 0 deletions local-development/icons/test.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions projects/mercury/src/common/IconAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import axios from 'axios';

export type ObjectWithIconPath = {
iconPath: string
};

export type ObjectWithIconObjectUrl = ObjectWithIconPath & {
icon: string
};

export const getSvgIcon = (object: ObjectWithIconPath): Promise<ObjectWithIconObjectUrl> => {
if (object.iconPath === null) {
return Promise.resolve(object);
}
return axios.get(object.iconPath, {headers: {'Cache-Control': 'Private'}, responseType: 'blob'}).then(response => {
const iconUrl = URL.createObjectURL(response.data);
return {
...object,
icon: iconUrl
};
});
};

export const getSvgIcons = (objectList: ObjectWithIconPath[]): Promise<ObjectWithIconObjectUrl[]> => {
return Promise.all(objectList.map(object => getSvgIcon(object)));
};
14 changes: 12 additions & 2 deletions projects/mercury/src/common/contexts/ServicesContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@ import axios from 'axios';
import React from 'react';
import {extractJsonData, handleHttpError} from '../utils/httpUtils';
import useAsync from '../hooks/UseAsync';
import {getSvgIcons} from '../IconAPI';

export type Service = {
path: string,
name: string,
iconPath: string,
icon: string // URL to the crated svg `Blob` object
};

const ServicesContext = React.createContext({});

export const ServicesProvider = ({children}) => {
const {
data: services = [],
data: services = ([]: Service[]),
loading: servicesLoading,
error: servicesError
} = useAsync(() => axios.get('/api/services/').then(extractJsonData).catch(handleHttpError('Connection error.')));
} = useAsync(() =>
axios.get('/api/services/').then(extractJsonData).then(getSvgIcons).catch(handleHttpError('Connection error.'))
);

return (
<ServicesContext.Provider
Expand Down
2 changes: 1 addition & 1 deletion projects/mercury/src/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const APPLICATION_NAME = 'Fairspace';
export const METADATA_VIEW_MENU_LABEL = 'Metadata';
export const DEFAULT_METADATA_VIEW_MENU_LABEL = 'Metadata';
export const LOCAL_STORAGE_MENU_KEY = 'FAIRSPACE_MENU_EXPANDED';
export const CUT = 'CUT';
export const COPY = 'COPY';
Expand Down
10 changes: 7 additions & 3 deletions projects/mercury/src/dashboard/DashboardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import {Grid, Link, Paper, Typography} from '@mui/material';
import BreadCrumbs from '../common/components/BreadCrumbs';
import styles from './DashboardPage.styles';
import MetadataViewContext from '../metadata/views/MetadataViewContext';
import ExternalMetadataSourceContext from '../metadata/external-sources/ExternalMetadataSourceContext';
import ExternalMetadataSourceContext from '../metadata/metadata-sources/ExternalMetadataSourceContext';
import UserContext from '../users/UserContext';
import DomainInfo from './DomainInfo';
import {APPLICATION_NAME, METADATA_VIEW_MENU_LABEL} from '../constants';
import {APPLICATION_NAME} from '../constants';
import InternalMetadataSourceContext from '../metadata/metadata-sources/InternalMetadataSourceContext';

const DashboardPage = props => {
const {currentUser, classes} = props;
const {views} = useContext(MetadataViewContext);
const {externalMetadataSources} = useContext(ExternalMetadataSourceContext);
const {internalMetadataIcon, internalMetadataLabel} = useContext(InternalMetadataSourceContext);
const canViewMetadata = currentUser && currentUser.canViewPublicMetadata && views && views.length > 0;

return (
Expand Down Expand Up @@ -49,8 +51,9 @@ const DashboardPage = props => {
<Grid container justifyContent="center" spacing="5">
{canViewMetadata && (
<DomainInfo
domainName={METADATA_VIEW_MENU_LABEL}
domainName={internalMetadataLabel}
domainLink="/metadata-views"
domainIcon={internalMetadataIcon}
key="metadata-views"
/>
)}
Expand All @@ -59,6 +62,7 @@ const DashboardPage = props => {
<DomainInfo
domainName={source.label}
domainLink={'/metadata-sources/' + source.name}
domainIcon={source.icon}
key={source.name}
/>
))}
Expand Down
Loading

0 comments on commit 0de8142

Please sign in to comment.