diff --git a/.dockerignore b/.dockerignore
index 4d895b56..cea94d7d 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,2 +1,9 @@
-node_modules/
.git
+**/node_modules
+**/build
+
+# Angular jwt file
+**/jwt.ts
+
+# React project secrets
+**/secret_local.ts
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 33e11662..02ecfc9a 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -64,7 +64,7 @@ jobs:
- name: Setup Node.js for use with actions
uses: actions/setup-node@v1
with:
- node-version: 16.x
+ node-version: 20.10.x
- uses: actions/cache@v1
with:
@@ -80,6 +80,7 @@ jobs:
- name: Unit Tests
run: |
cd react
+ cp src/secret_local.example.ts src/secret_local.ts
npm run test
React-Linting:
runs-on: ubuntu-latest
@@ -88,7 +89,7 @@ jobs:
- name: Setup Node.js for use with actions
uses: actions/setup-node@v1
with:
- node-version: 16.x
+ node-version: 20.10.x
- uses: actions/cache@v1
with:
@@ -112,7 +113,7 @@ jobs:
- name: Setup Node.js for use with actions
uses: actions/setup-node@v1
with:
- node-version: 16.x
+ node-version: 20.10.x
- uses: actions/cache@v1
with:
@@ -128,5 +129,5 @@ jobs:
- name: Build
run: |
cd react
- cp .env.example .env
+ cp src/secret_local.example.ts src/secret_local.ts
npm run build
diff --git a/.gitignore b/.gitignore
index 12b4a3cd..d9613dd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,20 +1,14 @@
-# See http://help.github.com/ignore-files/ for more about ignoring files.
+**/node_modules
+**/build
+**/dist
+**/tmp
+**/out-tsc
-# compiled output
-/dist
-src/environments/jwt.ts
-/tmp
-/out-tsc
+# Angular jwt file
+**/jwt.ts
-angular/dist
-angular/src/environments/jwt.ts
-angular/tmp
-angular/out-tsc
-# Only exists if Bazel was run
-/bazel-out
-__pycache__
-# dependencies
-node_modules
+# React project secrets
+**/secret_local.ts
# profiling files
chrome-profiler-events.json
diff --git a/Makefile b/Makefile
index 3ec5b55f..f7118e25 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,20 @@
TAG := $(shell git log --format=%h -1)
-IMAGE ?= taccaci/hazmapper:$(TAG)
+
+.PHONY: build-angular
+build-angular:
+ docker build -t taccaci/hazmapper:$(TAG) -f angular/Dockerfile .
+ docker tag taccaci/hazmapper:$(TAG) taccaci/hazmapper:latest
+
+.PHONY: build-react
+build-react:
+ docker build -t taccaci/hazmapper-react:$(TAG) -f react/Dockerfile .
+ docker tag taccaci/hazmapper-react:$(TAG) taccaci/hazmapper-react:latest
.PHONY: build
build:
- docker build -t $(IMAGE) -f angular/Dockerfile .
- docker tag taccaci/hazmapper:${TAG} taccaci/hazmapper:latest
+ make build-angular && make build-react
-.PHONY: deploy
-deploy:
- docker push $(IMAGE)
+.PHONY: publish
+publish:
+ docker push taccaci/hazmapper:$(TAG)
+ docker push taccaci/hazmapper-react:$(TAG)
diff --git a/README.md b/README.md
index a583a0e0..87faf3c7 100644
--- a/README.md
+++ b/README.md
@@ -13,40 +13,18 @@ See https://github.com/TACC-Cloud/geoapi which is an associated restful API.
## Local React Development (work-in-progress)
-`react/` has the react client
+`react/` has the React client
-
-#### Environments
-
-Environments are handled by vite via `vite.config.ts` and the `.env` files.
-
-
-The `TARGET` specified in `.env` prefix determines which environment we are running/building in (this must be set inside the directory of each deployment).
+To get started, create a local secret file for local development:
```
-TARGET="TARGET ENVIRONMENT"
+cp react/src/secret_local.example.ts react/src/secret_local.ts
```
-Possible target environments are `development`, `staging`, and `production`.
-
-Then, for local development, the `GEOAPI_BACKEND` specified in `.env` will allow testing different backends.
+Add the jwt retrieved from [Getting started](###getting-started) to `react/src/secret_local.ts`.
+The `geoapiBackend` in ( see [react/src/secret_local.example.ts](react/src/secret_local.example.ts) ) can be used to select which backend `geoapi` is used by Hazmapper during local development (e.g. `EnvironmentType.Production`, `EnvironmentType.Staging`, `EnvironmentType.Dev`, * `EnvironmentType.Local`
-First, to set the target backend for local development, edit the `.env` file and add the target backend.
-Possible target backends are `development`, `staging`, and `production`.
-```
-GEOAPI_BACKEND="TARGET BACKEND"
-```
-
-
-Then add the jwt retrieved from [Getting started](###getting-started) to `.env`.
-
-```
-JWT="YOUR JWT FROM ABOVE"
-```
-
-
-Furthermore, additional environments defined in `vite.config.ts` will be accessible in the react codebase via `process.env.*`. (e.g. `process.env.apiUrl`)
-
+See https://github.com/TACC-Cloud/geoapi for more details on running geoapi locally.
#### Run
@@ -55,11 +33,12 @@ npm ci
npm run dev
```
+Navigate to `http://localhost:4200/` or `http://hazmapper.local:4200/`. (Note that `hazmapper.local` needs to be added to your `/etc/hosts`)
+
#### Running unit tests
Run `npm run test`
-
#### Running linters
Run `npm run lint` to run linter
@@ -78,7 +57,7 @@ The app will automatically reload if you change any of the source files.
### Configuring which geoapi-backend is used
-The `backend` in [src/environments/environment.ts](src/environments/environment.ts) can be used to select which backend `geoapi` is used by the app:
+The `backend` in [angular/src/environments/environment.ts](angular/src/environments/environment.ts) can be used to select which backend `geoapi` is used by the app:
* `EnvironmentType.Production`
* `EnvironmentType.Staging`
@@ -103,8 +82,3 @@ Run `npm run lint:css -- --fix` to fix css files.
### Code scaffolding
Run `ng generate component components/component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
-
-## Kubernetes (Production/Staging environments)
-
-Information on Kubernetes configuration for production and staging environments can be found in the [kube/README.md](kube/README.md) including information
-on kube commands and Jenkins deployment workflows.
diff --git a/angular/src/app/services/env.service.ts b/angular/src/app/services/env.service.ts
index 1ae02ef2..88a0e6a4 100644
--- a/angular/src/app/services/env.service.ts
+++ b/angular/src/app/services/env.service.ts
@@ -155,7 +155,7 @@ export class EnvService {
} else if (/^hazmapper.tacc.utexas.edu/.test(hostname) && pathname.startsWith('/dev')) {
this._env = EnvironmentType.Dev;
this._apiUrl = this.getApiUrl(this.env);
- this._taggitUrl = origin + '/taggit-dev'; /* doesn't yet exist */
+ this._taggitUrl = origin + '/taggit-dev';
this._portalUrl = this.getPortalUrl(this.env);
this._clientId = 'oEuGsl7xi015wnrEpxIeUmvzc6Qa';
this._baseHref = '/dev/';
diff --git a/react/.env.example b/react/.env.example
deleted file mode 100644
index e7a0a9f4..00000000
--- a/react/.env.example
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copy this to .env
-# TARGET can be production, staging, or development
-TARGET=development
-# GEOAPI_BACKEND can be production, staging, or development
-GEOAPI_BACKEND=development
-# Not in use yet
-DESIGNSAFE_PORTAL=
-# Replace with jwt in local environment (refer to README.md)
-JWT=
diff --git a/react/.gitignore b/react/.gitignore
index 860f7c01..ed835a8e 100644
--- a/react/.gitignore
+++ b/react/.gitignore
@@ -25,3 +25,4 @@ dist-ssr
# Environment
.env
+secret_local.ts
diff --git a/react/Dockerfile b/react/Dockerfile
new file mode 100644
index 00000000..a9798261
--- /dev/null
+++ b/react/Dockerfile
@@ -0,0 +1,16 @@
+FROM node:20.10.0-alpine3.18 as node
+
+RUN mkdir /www
+COPY react /www
+WORKDIR /www
+
+# Creating dummy local file as it is not dynamically loaded; not actually used in prod TODO_REACT
+COPY react/src/secret_local.example.ts src/secret_local.ts
+
+RUN npm ci
+RUN npm run build
+
+FROM nginx:1.24-alpine
+WORKDIR /
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+COPY --from=node /www/dist/ /usr/share/nginx/html
diff --git a/react/index.html b/react/index.html
index bb734a44..bd61da09 100644
--- a/react/index.html
+++ b/react/index.html
@@ -5,7 +5,28 @@
Hazmapper
-
+
+
diff --git a/react/jest.config.cjs b/react/jest.config.cjs
index d26a0805..9e7d0d42 100644
--- a/react/jest.config.cjs
+++ b/react/jest.config.cjs
@@ -28,9 +28,9 @@ module.exports = {
coverageDirectory: 'coverage',
// An array of regexp pattern strings used to skip coverage collection
- // coveragePathIgnorePatterns: [
- // "\\\\node_modules\\\\"
- // ],
+ coveragePathIgnorePatterns: [
+ "src/core-components/"
+ ],
// Indicates which provider should be used to instrument code for coverage
// coverageProvider: "babel",
diff --git a/react/package.json b/react/package.json
index 18079751..78a4efa3 100644
--- a/react/package.json
+++ b/react/package.json
@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
- "build": "tsc && vite build",
+ "build": "tsc && vite build --base=./",
"preview": "vite preview",
"test": "jest --testPathIgnorePatterns='/src/core-components/'",
"lint": "npm run lint:js && npm run prettier:check",
diff --git a/react/src/AppRouter.tsx b/react/src/AppRouter.tsx
index 9fcc5eed..ca1a0c74 100644
--- a/react/src/AppRouter.tsx
+++ b/react/src/AppRouter.tsx
@@ -16,6 +16,7 @@ import Callback from './pages/Callback/Callback';
import StreetviewCallback from './pages/StreetviewCallback/StreetviewCallback';
import { RootState } from './redux/store';
import { isTokenValid } from './utils/authUtils';
+import { useBasePath } from './hooks/environment';
interface ProtectedRouteProps {
isAuthenticated: boolean;
@@ -41,8 +42,10 @@ function AppRouter() {
isTokenValid(state.auth.token)
);
+ const basePath = useBasePath();
+
return (
-
+ {
+ const basePath = useBasePath();
+
+ const appConfiguration = useMemo(() => {
+ const hostname = window && window.location && window.location.hostname;
+ const pathname = window && window.location && window.location.pathname;
+
+ const mapillaryConfig: MapillaryConfiguration = {
+ authUrl: 'https://www.mapillary.com/connect',
+ tokenUrl: 'https://graph.mapillary.com/token',
+ apiUrl: 'https://graph.mapillary.com/',
+ tileUrl: 'https://tiles.mapillary.com/',
+ scope:
+ 'user:email+user:read+user:write+public:write+public:upload+private:read+private:write+private:upload',
+ clientSecret: '',
+ clientId: '',
+ clientToken: '',
+ };
+
+ if (/^localhost/.test(hostname) || /^hazmapper.local/.test(hostname)) {
+ // Check if jwt has been set properly if we are using local geoapi
+ if (
+ localDevelopmentConfiguration.geoapiBackend ===
+ GeoapiBackendEnvironment.Local
+ ) {
+ if (
+ localDevelopmentConfiguration.jwt.startsWith('INSERT YOUR JWT HERE')
+ ) {
+ console.error(
+ 'JWT has not been added to secret_local.ts; see README'
+ );
+ throw new Error('JWT has not been added to secret_local.ts');
+ }
+ }
+
+ // local devevelopers can use localhost or hazmapper.local but
+ // hazmapper.local has been preferred in the past as TAPIS only supported it as a frame ancestor
+ // then (i.e. it allows for point cloud iframe preview)
+ const clientId = /^localhost/.test(hostname)
+ ? 'XgCBlhfAaqfv7jTu3NRc4IJDGdwa'
+ : 'Eb9NCCtWkZ83c01UbIAITFvhD9ka';
+
+ const appConfig: AppConfiguration = {
+ basePath: basePath,
+ clientId: clientId,
+ geoapiBackend: localDevelopmentConfiguration.geoapiBackend,
+ geoapiUrl: getGeoapiUrl(localDevelopmentConfiguration.geoapiBackend),
+ designSafeUrl: 'https://agave.designsafe-ci.org/',
+ designsafePortalUrl: getDesignsafePortalUrl(
+ DesignSafePortalEnvironment.Dev
+ ),
+ mapillary: mapillaryConfig,
+ taggitUrl: origin + '/taggit-staging',
+ jwt: localDevelopmentConfiguration.jwt,
+ };
+ appConfig.mapillary.clientId = '5156692464392931';
+ appConfig.mapillary.clientSecret =
+ 'MLY|5156692464392931|6be48c9f4074f4d486e0c42a012b349f';
+ appConfig.mapillary.clientToken =
+ 'MLY|5156692464392931|4f1118aa1b06f051a44217cb56bedf79';
+ return appConfig;
+ } else if (
+ /^hazmapper.tacc.utexas.edu/.test(hostname) &&
+ pathname.startsWith('/staging')
+ ) {
+ const clientId = basePath.includes('react')
+ ? 'AhV_h3Ilvrfs1S2Cj10yj82G0Uoa' // "staging-react" client
+ : 'foitdqFcimPzKZuMhbQ1oyh3Anka'; // "staging client" client
+ const appConfig: AppConfiguration = {
+ basePath: basePath,
+ clientId: clientId,
+ geoapiBackend: GeoapiBackendEnvironment.Staging,
+ geoapiUrl: getGeoapiUrl(GeoapiBackendEnvironment.Staging),
+ designSafeUrl: 'https://agave.designsafe-ci.org/',
+ designsafePortalUrl: getDesignsafePortalUrl(
+ DesignSafePortalEnvironment.Dev
+ ),
+ mapillary: mapillaryConfig,
+ taggitUrl: origin + '/taggit-staging',
+ };
+
+ appConfig.mapillary.clientId = '4936281379826603';
+ appConfig.mapillary.clientSecret =
+ 'MLY|4936281379826603|cafd014ccd8cfc983e47c69c16082c7b';
+ appConfig.mapillary.clientToken =
+ 'MLY|4936281379826603|f8c4732d3c9d96582b86158feb1c1a7a';
+ return appConfig;
+ } else if (
+ /^hazmapper.tacc.utexas.edu/.test(hostname) &&
+ pathname.startsWith('/dev')
+ ) {
+ const clientId = basePath.includes('react')
+ ? '9rWjQLiJb0XPXHicmUh1RUq6rOEa' // "react-dev" client
+ : 'oEuGsl7xi015wnrEpxIeUmvzc6Qa'; // "dev" client
+ const appConfig: AppConfiguration = {
+ basePath: basePath,
+ clientId: clientId,
+ geoapiBackend: GeoapiBackendEnvironment.Dev,
+ geoapiUrl: getGeoapiUrl(GeoapiBackendEnvironment.Dev),
+ designSafeUrl: 'https://agave.designsafe-ci.org/',
+ designsafePortalUrl: getDesignsafePortalUrl(
+ DesignSafePortalEnvironment.Dev
+ ),
+ mapillary: mapillaryConfig,
+ taggitUrl: origin + '/taggit-dev',
+ };
+
+ // TODO_REACT mapillary config is currently copy from /staging and not correct for /dev
+ appConfig.mapillary.clientId = '4936281379826603';
+ appConfig.mapillary.clientSecret =
+ 'MLY|4936281379826603|cafd014ccd8cfc983e47c69c16082c7b';
+ appConfig.mapillary.clientToken =
+ 'MLY|4936281379826603|f8c4732d3c9d96582b86158feb1c1a7a';
+ return appConfig;
+ } else if (/^hazmapper.tacc.utexas.edu/.test(hostname)) {
+ const clientId = basePath.includes('react')
+ ? 'XEMnINR8b8hA6kFxE69HVTyoNCga' // "hazmapper-react" client
+ : 'tMvAiRdcsZ52S_89lCkO4x3d6VMa'; // "hazmapper" client
+ const appConfig: AppConfiguration = {
+ basePath: basePath,
+ clientId: clientId,
+ geoapiBackend: GeoapiBackendEnvironment.Production,
+ geoapiUrl: getGeoapiUrl(GeoapiBackendEnvironment.Production),
+ designSafeUrl: 'https://agave.designsafe-ci.org/',
+ designsafePortalUrl: getDesignsafePortalUrl(
+ DesignSafePortalEnvironment.Production
+ ),
+ mapillary: mapillaryConfig,
+ taggitUrl: origin + '/taggit',
+ };
+
+ appConfig.mapillary.clientId = '5156692464392931';
+ appConfig.mapillary.clientSecret =
+ 'MLY|5156692464392931|6be48c9f4074f4d486e0c42a012b349f';
+ appConfig.mapillary.clientToken =
+ 'MLY|5156692464392931|4f1118aa1b06f051a44217cb56bedf79';
+ return appConfig;
+ } else {
+ console.error('Cannot find environment for host name ${hostname}');
+ throw new Error('Cannot find environment for host name ${hostname}');
+ }
+ }, []);
+ return appConfiguration;
+};
+
+export default useAppConfiguration;
diff --git a/react/src/hooks/environment/useBasePath.ts b/react/src/hooks/environment/useBasePath.ts
new file mode 100644
index 00000000..72a7bafe
--- /dev/null
+++ b/react/src/hooks/environment/useBasePath.ts
@@ -0,0 +1,28 @@
+import { useMemo } from 'react';
+
+/**
+ * Computes the base path for the application based on the current URL.
+ */
+const useBasePath = (): string => {
+ const basePath = useMemo(() => {
+ // note that path order matters
+ // as we use startsWith to find a match
+ const paths: string[] = [
+ '/hazmapper-react',
+ '/staging-react',
+ '/dev-react',
+ '/hazmapper',
+ '/staging',
+ '/dev',
+ ];
+ const currentPath: string = window.location.pathname;
+ const base: string | undefined = paths.find((path) =>
+ currentPath.startsWith(path)
+ );
+ return base || '/';
+ }, []);
+
+ return basePath;
+};
+
+export default useBasePath;
diff --git a/react/src/hooks/index.ts b/react/src/hooks/index.ts
index 28bd8ba1..c42c5d60 100644
--- a/react/src/hooks/index.ts
+++ b/react/src/hooks/index.ts
@@ -1,2 +1,3 @@
export { default as useProjects } from './projects/useProjects';
export { default as useSystems } from './systems/useSystems';
+export * from './environment';
diff --git a/react/src/hooks/projects/useProjects.ts b/react/src/hooks/projects/useProjects.ts
index 2e9a8715..1caa7b49 100644
--- a/react/src/hooks/projects/useProjects.ts
+++ b/react/src/hooks/projects/useProjects.ts
@@ -6,7 +6,6 @@ const useProjects = (): UseQueryResult => {
const query = useGet({
endpoint: '/projects/',
key: ['projects'],
- baseUrl: 'https://agave.designsafe-ci.org/geo/v2',
});
return query;
};
diff --git a/react/src/pages/Login/Login.tsx b/react/src/pages/Login/Login.tsx
index 7b56a8b3..1b6819c1 100644
--- a/react/src/pages/Login/Login.tsx
+++ b/react/src/pages/Login/Login.tsx
@@ -3,6 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { RootState } from '../../redux/store';
import { isTokenValid } from '../../utils/authUtils';
+import { useAppConfiguration } from '../../hooks';
function Login() {
const location = useLocation();
@@ -10,6 +11,7 @@ function Login() {
const isAuthenticated = useSelector((state: RootState) =>
isTokenValid(state.auth.token)
);
+ const configuration = useAppConfiguration();
useEffect(() => {
const queryParams = new URLSearchParams(location.search);
@@ -23,14 +25,13 @@ function Login() {
localStorage.setItem('authState', state);
localStorage.setItem('toParam', toParam);
- // TODO check for staging/prod
- const callbackUrl = `${window.location.origin}/callback`;
-
- // TODO make auth/server configurable
- const client_id = 'RMCJHgW9CwJ6mKjhLTDnUYBo9Hka';
-
+ const callbackUrl = (
+ window.location.origin +
+ configuration.basePath +
+ '/callback'
+ ).replace(/([^:])(\/{2,})/g, '$1/');
// Construct the authentication URL with the client_id, redirect_uri, scope, response_type, and state parameters
- const authUrl = `https://agave.designsafe-ci.org/authorize?client_id=${client_id}&redirect_uri=${callbackUrl}&scope=openid&response_type=token&state=${state}`;
+ const authUrl = `https://agave.designsafe-ci.org/authorize?client_id=${configuration.clientId}&redirect_uri=${callbackUrl}&scope=openid&response_type=token&state=${state}`;
window.location.replace(authUrl);
}
diff --git a/react/src/redux/api/geoapi.ts b/react/src/redux/api/geoapi.ts
index fa3fa531..8e1737d2 100644
--- a/react/src/redux/api/geoapi.ts
+++ b/react/src/redux/api/geoapi.ts
@@ -1,6 +1,7 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/dist/query/react';
import type { RootState } from '../store';
+// TODO_REACT: REMOVE AS NOT USED
// TODO: make configurable so can be https://agave.designsafe-ci.org/geo-staging/v2 or https://agave.designsafe-ci.org/geo/v2
// See https://tacc-main.atlassian.net/browse/WG-196
const BASE_URL = 'https://agave.designsafe-ci.org/geo/v2';
diff --git a/react/src/redux/projectsSlice.ts b/react/src/redux/projectsSlice.ts
index 49d34e2e..5fbb62b9 100644
--- a/react/src/redux/projectsSlice.ts
+++ b/react/src/redux/projectsSlice.ts
@@ -1,6 +1,7 @@
import { createSlice } from '@reduxjs/toolkit';
import { geoapi } from './api/geoapi';
+// TODO_REACT: REMOVE AS NOT USED
const slice = createSlice({
name: 'projects',
initialState: { projects: [] },
diff --git a/react/src/requests.test.ts b/react/src/requests.test.ts
new file mode 100644
index 00000000..bf62ef37
--- /dev/null
+++ b/react/src/requests.test.ts
@@ -0,0 +1,47 @@
+import { getHeaders } from './requests';
+import { ApiService, GeoapiBackendEnvironment } from './types';
+import {
+ authenticatedUser,
+ unauthenticatedUser,
+} from './__fixtures__/authStateFixtures';
+import { localDevConfiguration } from './__fixtures__/appConfigurationFixture';
+
+describe('getHeaders', () => {
+ it('returns JWT header when using local Geoapi', () => {
+ const headers = getHeaders(
+ ApiService.Geoapi,
+ {
+ ...localDevConfiguration,
+ geoapiBackend: GeoapiBackendEnvironment.Local,
+ },
+ authenticatedUser
+ );
+
+ expect(headers).toEqual({
+ 'X-JWT-Assertion-designsafe': localDevConfiguration.jwt,
+ });
+ });
+
+ it('returns Authorization header for non-local Geoapi', () => {
+ const headers = getHeaders(
+ ApiService.Geoapi,
+ {
+ ...localDevConfiguration,
+ geoapiBackend: GeoapiBackendEnvironment.Production, // Or any other non-local environment
+ },
+ authenticatedUser
+ );
+ expect(headers).toEqual({
+ Authorization: `Bearer ${authenticatedUser.token?.token}`,
+ });
+ });
+
+ it('returns no auth-related headers for unauthenticatedUser', () => {
+ const headers = getHeaders(
+ ApiService.Geoapi,
+ localDevConfiguration,
+ unauthenticatedUser
+ );
+ expect(headers).toEqual({});
+ });
+});
diff --git a/react/src/requests.ts b/react/src/requests.ts
index 7ad90c64..a46fc42b 100644
--- a/react/src/requests.ts
+++ b/react/src/requests.ts
@@ -2,6 +2,50 @@ import axios from 'axios';
import store from './redux/store';
import { AxiosError } from 'axios';
import { useQuery, UseQueryOptions, QueryKey } from 'react-query';
+import { useAppConfiguration } from './hooks';
+import {
+ ApiService,
+ AppConfiguration,
+ AuthState,
+ GeoapiBackendEnvironment,
+} from './types';
+
+function getBaseApiUrl(
+ apiService: ApiService,
+ configuration: AppConfiguration
+): string {
+ switch (apiService) {
+ case ApiService.Geoapi:
+ return configuration.geoapiUrl;
+ case ApiService.DesignSafe:
+ return configuration.designSafeUrl;
+ case ApiService.Tapis:
+ // Tapis and DesignSafe are currently the same
+ return configuration.designSafeUrl;
+ default:
+ throw new Error('Unsupported api service Type.');
+ }
+}
+
+export function getHeaders(
+ apiService: ApiService,
+ configuration: AppConfiguration,
+ auth: AuthState
+) {
+ // TODO_REACT add mapillary support
+ if (auth.token && apiService !== ApiService.Mapillary) {
+ //Add auth information in header for DesignSafe, Tapis, Geoapi for logged in users
+ if (
+ apiService === ApiService.Geoapi &&
+ configuration.geoapiBackend === GeoapiBackendEnvironment.Local
+ ) {
+ // Use JWT in request header because local geoapi API is not behind ws02
+ return { 'X-JWT-Assertion-designsafe': configuration.jwt };
+ }
+ return { Authorization: `Bearer ${auth.token.token}` };
+ }
+ return {};
+}
type UseGetParams = {
endpoint: string;
@@ -10,25 +54,31 @@ type UseGetParams = {
UseQueryOptions,
'queryKey' | 'queryFn'
>;
- baseUrl: string;
+ apiService?: ApiService;
};
export function useGet({
endpoint,
key,
options = {},
- baseUrl,
+ apiService = ApiService.Geoapi,
}: UseGetParams) {
const client = axios;
const state = store.getState();
- const token = state.auth.token?.token;
- // change to prod const { baseUrl } = useConfig();
+ const configuration = useAppConfiguration();
+
+ const baseUrl = getBaseApiUrl(apiService, configuration);
+ const headers = getHeaders(apiService, configuration, state.auth);
+
+ /* TODO_REACT Send analytics-related params to projects endpoint only (until we use headers
+ again in https://tacc-main.atlassian.net/browse/WG-192) */
+
const getUtil = async () => {
const request = await client.get(
`${baseUrl}${endpoint}`,
{
- headers: { Authorization: `Bearer ${token}` },
+ headers: headers,
}
);
return request.data;
diff --git a/react/src/secret_local.example.ts b/react/src/secret_local.example.ts
new file mode 100644
index 00000000..396ba952
--- /dev/null
+++ b/react/src/secret_local.example.ts
@@ -0,0 +1,9 @@
+import { GeoapiBackendEnvironment, LocalAppConfiguration } from './types';
+
+// prettier-ignore
+const jwt = 'INSERT YOUR JWT HERE; See README ';
+
+export const localDevelopmentConfiguration: LocalAppConfiguration = {
+ jwt: jwt,
+ geoapiBackend: GeoapiBackendEnvironment.Production,
+};
diff --git a/react/src/types/environment.ts b/react/src/types/environment.ts
new file mode 100644
index 00000000..d1ae074a
--- /dev/null
+++ b/react/src/types/environment.ts
@@ -0,0 +1,95 @@
+/**
+ * Environment for Geoapi Backend
+ */
+export enum GeoapiBackendEnvironment {
+ Production = 'production',
+ Staging = 'staging',
+ Dev = 'dev',
+ Local = 'local',
+}
+
+/**
+ * Environment for Geoapi Backend
+ */
+export enum DesignSafePortalEnvironment {
+ Production = 'production',
+ Dev = 'dev' /* DesignSafe has 2 deployed environments: prod and dev. This dev is comparable to Geoapi's staging */,
+}
+
+/**
+ * Known Apis
+ */
+export enum ApiService {
+ /* Geoapi api */
+ Geoapi = 'geoapi',
+
+ /* DesignSafe api - for project listings */
+ DesignSafe = 'designsafe',
+
+ /* Tapis api - for system listings and file operations */
+ Tapis = 'tapis',
+
+ /* Mapillary */
+ Mapillary = 'mapillary',
+}
+
+/**
+ * Configuration settings for local development.
+ *
+ * These can be configured by developer in secret_local.ts (see README)
+ *
+ */
+export interface LocalAppConfiguration {
+ /* Developer's JWT token used for authentication during local development. */
+ jwt: string;
+
+ /* The type of backend environment (production, staging, development, or local) */
+ geoapiBackend: GeoapiBackendEnvironment;
+}
+
+/**
+ * Mapillary configuration
+ */
+export interface MapillaryConfiguration {
+ authUrl: string;
+ tokenUrl: string;
+ apiUrl: string;
+ tileUrl: string;
+ scope: string;
+ clientSecret: string;
+ clientId: string;
+ clientToken: string;
+}
+
+/**
+ * Configuration for the application
+ * related to Geoapi backend, auth and other services
+ */
+export interface AppConfiguration {
+ /** Base URL path for the application. */
+ basePath: string;
+
+ /** Client ID used for Tapis authentication. */
+ clientId: string;
+
+ /* The type of backend environment */
+ geoapiBackend: GeoapiBackendEnvironment;
+
+ /** URL for the GeoAPI service. */
+ geoapiUrl: string;
+
+ /** URL for the DesignSafe/tapis API. */
+ designSafeUrl: string;
+
+ /** URL for the DesignSafe portal. */
+ designsafePortalUrl: string;
+
+ /** Mapillary related configuration */
+ mapillary: MapillaryConfiguration;
+
+ /** URL for taggit */
+ taggitUrl: string;
+
+ /** Optional JWT token used for development with local geoapi service. */
+ jwt?: string;
+}
diff --git a/react/src/types/index.ts b/react/src/types/index.ts
index c793bf6d..2b894f76 100644
--- a/react/src/types/index.ts
+++ b/react/src/types/index.ts
@@ -8,3 +8,4 @@ export type {
export type { Project } from './projects';
export type { AuthState, AuthenticatedUser, AuthToken } from './auth';
export type { System } from './systems';
+export * from './environment';
diff --git a/react/vite.config.ts b/react/vite.config.ts
index edb68196..98196463 100644
--- a/react/vite.config.ts
+++ b/react/vite.config.ts
@@ -1,99 +1,13 @@
-import { defineConfig, loadEnv } from 'vite';
+import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
-// Retrieves the target back.
-// Used for getting a dynamic backend for local development.
-function getGeoapiUrl(backend: string): string {
- if (backend === 'development') {
- return 'http://localhost:8888';
- } else if (backend === 'staging') {
- return 'https://agave.designsafe-ci.org/geo-staging/v2';
- } else if (backend === 'production') {
- return 'https://agave.designsafe-ci.org/geo/v2';
- } else {
- throw new Error(
- 'Unsupported TARGET/GEOAPI_BACKEND Type. Please check the .env file.'
- );
- }
-}
-
-function getDesignsafePortalUrl(backend: string): string {
- if (backend === 'production') {
- return 'https://www.designsafe-ci.org/';
- } else {
- return 'https://designsafeci-dev.tacc.utexas.edu/';
- }
-}
-
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => { // eslint-disable-line
- const envFile = loadEnv(mode, process.cwd(), '');
- const targetEnvironment = envFile.TARGET;
- const env = {
- designSafeUrl: 'https://agave.designsafe-ci.org/',
- backend: envFile.GEOAPI_BACKEND,
- geoapiUrl: '',
- designsafePortalUrl: '',
- clientId: '',
- host: '',
- baseHref: '',
- jwt: '',
- mapillaryAuthUrl: 'https://www.mapillary.com/connect',
- mapillaryTokenUrl: 'https://graph.mapillary.com/token',
- mapillaryApiUrl: 'https://graph.mapillary.com/',
- mapillaryTileUrl: 'https://tiles.mapillary.com/',
- mapillaryScope:
- 'user:email+user:read+user:write+public:write+public:upload+private:read+private:write+private:upload',
- mapillaryClientSecret: '',
- mapillaryClientId: '',
- mapillaryClientToken: '',
- };
-
- if (targetEnvironment === 'production') {
- env.geoapiUrl = getGeoapiUrl(targetEnvironment);
- env.designsafePortalUrl = getDesignsafePortalUrl(targetEnvironment);
- env.clientId = 'tMvAiRdcsZ52S_89lCkO4x3d6VMa';
- env.host = 'hazmapper.utexas.edu/hazmapper/';
- env.baseHref = '/hazmapper/';
- env.mapillaryClientId = '5156692464392931';
- env.mapillaryClientSecret =
- 'MLY|5156692464392931|6be48c9f4074f4d486e0c42a012b349f';
- env.mapillaryClientToken =
- 'MLY|5156692464392931|4f1118aa1b06f051a44217cb56bedf79';
- } else if (targetEnvironment === 'staging') {
- env.geoapiUrl = getGeoapiUrl(targetEnvironment);
- env.designsafePortalUrl = getDesignsafePortalUrl(targetEnvironment);
- env.clientId = 'foitdqFcimPzKZuMhbQ1oyh3Anka';
- env.host = 'hazmapper.utexas.edu/staging/';
- env.baseHref = '/staging/';
- env.mapillaryClientSecret =
- 'MLY|4936281379826603|cafd014ccd8cfc983e47c69c16082c7b';
- env.mapillaryClientId = '4936281379826603';
- env.mapillaryClientToken =
- 'MLY|4936281379826603|f8c4732d3c9d96582b86158feb1c1a7a';
- } else {
- env.geoapiUrl = getGeoapiUrl(envFile.GEOAPI_BACKEND);
- env.designsafePortalUrl = getDesignsafePortalUrl(envFile.GEOAPI_BACKEND);
- env.clientId = 'Eb9NCCtWkZ83c01UbIAITFvhD9ka';
- env.host = 'hazmapper.local';
- env.baseHref = '/';
- env.mapillaryClientSecret =
- 'MLY|5156692464392931|6be48c9f4074f4d486e0c42a012b349f';
- env.mapillaryClientId = '5156692464392931';
- env.mapillaryClientToken =
- 'MLY|5156692464392931|4f1118aa1b06f051a44217cb56bedf79';
- env.jwt = envFile.BACKEND === 'development' && envFile.JWT;
- }
-
return {
plugins: [react()],
server: {
port: 4200,
- base: env.baseHref,
- host: env.host,
- },
- define: {
- 'process.env': env,
+ host: 'localhost',
},
};
});