Skip to content

Commit

Permalink
Created an endpoint selection modal popup for users to save, add impr…
Browse files Browse the repository at this point in the history
…ovements on endpoint selection (#630)

* A minor and temporary fix for auth issue with the globus endpoint search popup. This will be addressed more elegantly in the future.

* Created some persistence related functions which will store variables in a local scope object and keep them loaded/synchronized with session storage when redirects occur. Did a rewrite of the transfer function which uses goals to keep track of what the user wanted to do between redirects. Uses local storage to save goals, as it is faster and more reliable than session storage. Consolidated and simplifed the globus transfer related data types and atoms. Globus transfers seem to be failing when testing locally, and that issue will still need to be addressed. Removed the default endpoint in favor of allowing users to save their endpoints and prefered paths, and that is then saved in session storage for when they would log in later.

* Moved the persistent storage functions to a separate file so that they can be used by other components. Re-organized the globus download steps function so that sign in tokens aren't required when setting the path of a saved endpoint. Also simplified the flow of the function to follow based on user goals rather than page state.
  • Loading branch information
downiec authored Jul 15, 2024
1 parent e82dd76 commit ec5ec89
Show file tree
Hide file tree
Showing 13 changed files with 665 additions and 575 deletions.
3 changes: 3 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ docker compose -p metagrid_backend_dev build --no-cache
# Run pyTest (May need to rebuild container before tests)
docker compose -p metagrid_backend_dev run --rm django pytest

# Run pyTest for a specific test (example)
docker compose -p metagrid_backend_dev run --rm django pytest metagrid/api_proxy/tests/test_views.py::TestProxyViewSet::test_do_globus_auth

# Run manage.py function
docker compose -p metagrid_backend_dev run --rm django python manage.py <function>

Expand Down
2 changes: 1 addition & 1 deletion backend/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": STATICFILES_DIRS,
"APP_DIRS": True,
"APP_DIRS": False,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
Expand Down
4 changes: 3 additions & 1 deletion backend/metagrid/api_proxy/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@ def do_globus_get_endpoint(request):
@permission_classes([])
def do_globus_search_endpoints(request):
search_text = request.GET.get("search_text", None)

if request.user.is_authenticated:
tc = load_transfer_client(request.user) # pragma: no cover
else:
client = globus_sdk.ConfidentialAppAuthClient(
settings.SOCIAL_AUTH_GLOBUS_KEY, settings.SOCIAL_AUTH_GLOBUS_SECRET
settings.SOCIAL_AUTH_GLOBUS_KEY,
settings.SOCIAL_AUTH_GLOBUS_SECRET,
)
token_response = client.oauth2_client_credentials_tokens()
globus_transfer_data = token_response.by_resource_server[
Expand Down
18 changes: 9 additions & 9 deletions frontend/.envs/.react
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# =====================FRONTEND CONFIG====================

PUBLIC_URL=

# Redirect the frontend to home page when old subdirectory is used (optional)
REACT_APP_PREVIOUS_URL=metagrid

# MetaGrid API
# https://github.com/aims-group/metagrid/tree/master/backend
REACT_APP_METAGRID_API_URL=

# Authentication Method
REACT_APP_AUTHENTICATION_METHOD=globus

# Globus
# ------------------------------------------------------------------------------
Expand All @@ -24,8 +27,6 @@ REACT_APP_WGET_API_URL=https://esgf-node.llnl.gov/esg-search/wget
# ESGF Search API
# https://esgf.github.io/esg-search/ESGF_Search_RESTful_API.html
REACT_APP_SEARCH_URL=https://esgf-node.llnl.gov/esg-search/search

# https://esgf.github.io/esg-search/ESGF_Search_RESTful_API.html
REACT_APP_ESGF_SOLR_URL=https://esgf-fedtest.llnl.gov/solr

# ESGF Node Status API
Expand All @@ -38,6 +39,10 @@ REACT_APP_KEYCLOAK_REALM=esgf
REACT_APP_KEYCLOAK_URL=https://esgf-login.ceda.ac.uk/
REACT_APP_KEYCLOAK_CLIENT_ID=metagrid-localhost

# Django Auth URLs
REACT_APP_DJANGO_LOGIN_URL=http://localhost:8000/login/globus/
REACT_APP_DJANGO_LOGOUT_URL=http://localhost:8000/proxy/globus-logout/

# react-hotjar
# https://github.com/abdalla/react-hotjar
REACT_APP_HOTJAR_ID=
Expand All @@ -52,9 +57,4 @@ REACT_APP_GOOGLE_ANALYTICS_TRACKING_ID=
# https://github.com/react-keycloak/react-keycloak/issues/176
GENERATE_SOURCEMAP=false

# Django Auth URLs
REACT_APP_DJANGO_LOGIN_URL=http://localhost:8000/login/globus/
REACT_APP_DJANGO_LOGOUT_URL=http://localhost:8000/proxy/globus-logout/

# Authentication Method
REACT_APP_AUTHENTICATION_METHOD=globus
2 changes: 1 addition & 1 deletion frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ export const saveSessionValue = async <T>(
dataKey: key,
dataValue: 'None',
};
if (value !== null) {
if (value !== null && value !== undefined) {
data = { ...data, dataValue: value };
}
return axios
Expand Down
15 changes: 1 addition & 14 deletions frontend/src/api/mock/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ import {
RawProject,
RawProjects,
} from '../../components/Facets/types';
import {
GlobusEndpointData,
GlobusTokenResponse,
} from '../../components/Globus/types';
import { GlobusTokenResponse } from '../../components/Globus/types';
import {
NodeStatusArray,
RawNodeStatus,
Expand Down Expand Up @@ -342,13 +339,3 @@ export const globusEnabledDatasetFixture = (): RawSearchResult[] => {
},
];
};

export const globusEndpointFixture = (): GlobusEndpointData => {
return {
endpoint: 'globus endpoint',
label: 'Globus Test Endpoint',
path: 'test/path',
globfs: 'test/data',
endpointId: '1234567',
};
};
115 changes: 115 additions & 0 deletions frontend/src/common/persistData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { SetterOrUpdater } from 'recoil';
import { loadSessionValue, saveSessionValue } from '../api';

export type PersistentVar<T> = {
loader: () => Promise<T>;
saver: () => Promise<void>;
getter: () => T;
setter: (value: T) => void;
value: T;
};

const PERSISTENT_VARS: {
[key: string]: PersistentVar<unknown>;
} = {};

export function addPersistVar<T>(
varKey: string,
defaultVal: T,
setterFunc: SetterOrUpdater<T>,
loaderFunc?: () => Promise<T>
): void {
if (Object.hasOwn(PERSISTENT_VARS, varKey)) {
return;
}

const loader = async (): Promise<T> => {
let val: T | null = null;
if (loaderFunc) {
val = await loaderFunc();
} else {
val = await loadSessionValue<T>(varKey);
}

if (PERSISTENT_VARS[varKey]) {
PERSISTENT_VARS[varKey].value = val || defaultVal;
setterFunc(val || defaultVal);
}
return val || defaultVal;
};

const saver = async (): Promise<void> => {
if (PERSISTENT_VARS[varKey]) {
await saveSessionValue<T>(varKey, PERSISTENT_VARS[varKey].value as T);
} else {
await saveSessionValue<T>(varKey, defaultVal);
}
};

const setter = (val: T): void => {
if (PERSISTENT_VARS[varKey]) {
PERSISTENT_VARS[varKey].value = val;
setterFunc(val);
}
};

const getter = (): T => {
if (PERSISTENT_VARS[varKey]) {
return PERSISTENT_VARS[varKey].value as T;
}
return defaultVal;
};

const newVar = { loader, saver, getter, setter, value: defaultVal };
PERSISTENT_VARS[varKey] = newVar as PersistentVar<unknown>;
}

export function getPersistVal<T>(varKey: string): T | null {
if (PERSISTENT_VARS[varKey]) {
return PERSISTENT_VARS[varKey].value as T;
}
return null;
}

export async function loadPersistVal<T>(varKey: string): Promise<T | null> {
if (PERSISTENT_VARS[varKey]) {
return PERSISTENT_VARS[varKey].loader() as Promise<T>;
}
return null;
}

export async function setPersistVal<T>(
varKey: string,
value: T,
save: boolean
): Promise<void> {
if (PERSISTENT_VARS[varKey]) {
PERSISTENT_VARS[varKey].setter(value);

if (save) {
await PERSISTENT_VARS[varKey].saver();
}
}
}

export async function loadAllPersistVals(): Promise<void> {
const loadFuncs: Promise<unknown>[] = [];
Object.values(PERSISTENT_VARS).forEach((persistVar) => {
if (persistVar && persistVar.loader) {
loadFuncs.push(persistVar.loader());
}
});

await Promise.all(loadFuncs);
}

export async function saveAllPersistVals(): Promise<void> {
const saveFuncs: Promise<void>[] = [];
Object.values(PERSISTENT_VARS).forEach((persistVar) => {
if (persistVar && persistVar.saver) {
saveFuncs.push(persistVar.saver());
}
});

await Promise.all(saveFuncs);
}
14 changes: 10 additions & 4 deletions frontend/src/components/Cart/Items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import Button from '../General/Button';
import Table from '../Search/Table';
import { RawSearchResults } from '../Search/types';
import DatasetDownload from '../Globus/DatasetDownload';
import { saveSessionValue } from '../../api';
import CartStateKeys, { cartItemSelections } from './recoil/atoms';
import { NodeStatusArray } from '../NodeStatus/types';
import { addPersistVar, setPersistVal } from '../../common/persistData';

const styles: CSSinJS = {
summary: {
Expand Down Expand Up @@ -44,10 +44,16 @@ const Items: React.FC<React.PropsWithChildren<Props>> = ({
const [itemSelections, setItemSelections] = useRecoilState<RawSearchResults>(
cartItemSelections
);
addPersistVar<RawSearchResults>(
CartStateKeys.cartItemSelections,
[],
setItemSelections
);

const handleRowSelect = (selectedRows: RawSearchResults | []): void => {
saveSessionValue(CartStateKeys.cartItemSelections, selectedRows);
setItemSelections(selectedRows);
const handleRowSelect = async (
selectedRows: RawSearchResults | []
): Promise<void> => {
await setPersistVal(CartStateKeys.cartItemSelections, selectedRows, true);
};

return (
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/Cart/Summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { RawSearchResult, RawSearchResults } from '../Search/types';
import { UserCart } from './types';
import { GlobusTaskItem } from '../Globus/types';
import GlobusStateKeys, { globusTaskItems } from '../Globus/recoil/atom';
import { saveSessionValue } from '../../api';
import { addPersistVar, setPersistVal } from '../../common/persistData';

const styles: CSSinJS = {
headerContainer: { display: 'flex', justifyContent: 'center' },
Expand All @@ -35,6 +35,7 @@ const Summary: React.FC<React.PropsWithChildren<Props>> = ({ userCart }) => {
const [taskItems, setTaskItems] = useRecoilState<GlobusTaskItem[]>(
globusTaskItems
);
addPersistVar(GlobusStateKeys.globusTaskItems, [], setTaskItems);

let numFiles = 0;
let totalDataSize = '0';
Expand All @@ -52,9 +53,8 @@ const Summary: React.FC<React.PropsWithChildren<Props>> = ({ userCart }) => {
totalDataSize = formatBytes(rawDataSize);
}

const clearAllTasks = (): void => {
setTaskItems([]);
saveSessionValue(GlobusStateKeys.globusTaskItems, []);
const clearAllTasks = async (): Promise<void> => {
await setPersistVal(GlobusStateKeys.globusTaskItems, [], true);
};

return (
Expand Down
Loading

0 comments on commit ec5ec89

Please sign in to comment.