Skip to content

Commit

Permalink
Did some refactoring to move some functions to other locations that m…
Browse files Browse the repository at this point in the history
…ake more sense. Updated antd package. Changed persist function into a singleton class that we can set the datastore for. It should hopefully make testing it easier.
  • Loading branch information
downiec committed Jul 19, 2024
1 parent 8904f71 commit fe03413
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 107 deletions.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@babel/plugin-syntax-flow": "7.23.3",
"@babel/plugin-transform-react-jsx": "7.23.4",
"@react-keycloak/web": "3.4.0",
"antd": "5.15.2",
"antd": "5.19.2",
"autoprefixer": "10.4.14",
"axios": "0.26.1",
"dayjs": "1.11.10",
Expand Down
32 changes: 31 additions & 1 deletion frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'setimmediate'; // Added because in Jest 27, setImmediate is not defined,
import humps from 'humps';
import queryString from 'query-string';
import { AxiosResponse, AxiosError } from 'axios';
import PKCE from 'js-pkce';
import axios from '../lib/axios';
import {
RawUserCart,
Expand All @@ -27,9 +28,14 @@ import {
TextInputs,
} from '../components/Search/types';
import { RawUserAuth, RawUserInfo } from '../contexts/types';
import { metagridApiURL } from '../env';
import { globusClientID, globusRedirectUrl, metagridApiURL } from '../env';
import apiRoutes, { ApiRoute, HTTPCodeType } from './routes';
import { GlobusEndpointSearchResults } from '../components/Globus/types';
import GlobusStateKeys from '../components/Globus/recoil/atom';

// Reference: https://github.com/bpedroza/js-pkce
export const REQUESTED_SCOPES =
'openid profile email urn:globus:auth:scope:transfer.api.globus.org:all';

export interface ResponseError extends Error {
status?: number;
Expand Down Expand Up @@ -671,6 +677,30 @@ export const saveSessionValue = async <T>(
);
};

export const saveSessionValues = async (
data: { key: string; value: unknown }[]
): Promise<void> => {
const saveFuncs: Promise<AxiosResponse>[] = [];
data.forEach((value) => {
saveFuncs.push(saveSessionValue(value.key, value.value));
});

await Promise.all(saveFuncs);
};

// Creates an auth object using desired authentication scope
export async function createGlobusAuthObject(): Promise<PKCE> {
const authScope = await loadSessionValue<string>(GlobusStateKeys.globusAuth);

return new PKCE({
client_id: globusClientID, // Update this using your native client ID
redirect_uri: globusRedirectUrl, // Update this if you are deploying this anywhere else (Globus Auth will redirect back here once you have logged in)
authorization_endpoint: 'https://auth.globus.org/v2/oauth2/authorize', // No changes needed
token_endpoint: 'https://auth.globus.org/v2/oauth2/token', // No changes needed
requested_scopes: authScope || REQUESTED_SCOPES, // Update with any scopes you would need, e.g. transfer
});
}

/**
* Performs validation against the globus API to ensure a 200 response.
*
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/common/DataPersister.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { DataPersister } from './DataPersister';

describe('Test DataPersister Class', () => {
it('returns the DataPersistor singleton instance', () => {
const persistor = DataPersister.Instance;

expect(persistor).toBeInstanceOf(DataPersister);
});
});
136 changes: 136 additions & 0 deletions frontend/src/common/DataPersister.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* eslint-disable no-underscore-dangle */
import { SetterOrUpdater } from 'recoil';
import { loadSessionValue, saveSessionValue } from '../api';

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

export class DataPersister {
private static _instance: DataPersister;

private _PERSISTENT_STORE: {
[key: string]: PersistData<unknown>;
} = {};

private constructor() {
this._PERSISTENT_STORE = {};
}

public static get Instance(): DataPersister {
if (!this._instance) {
this._instance = new this();
}
return this._instance;
}

initializeDataStore(dataStore: {
[key: string]: PersistData<unknown>;
}): void {
this._PERSISTENT_STORE = dataStore;
}

addNewVar<T>(
varKey: string,
defaultVal: T,
setterFunc: SetterOrUpdater<T>,
loaderFunc?: () => Promise<T>
): void {
if (Object.hasOwn(this._PERSISTENT_STORE, varKey)) {
return;
}

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

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

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

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

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

const newVar = { loader, saver, getter, setter, value: defaultVal };
this._PERSISTENT_STORE[varKey] = newVar as PersistData<unknown>;
}

getValue<T>(varKey: string): T | null {
if (this._PERSISTENT_STORE[varKey]) {
return this._PERSISTENT_STORE[varKey].value as T;
}
return null;
}

async loadValue<T>(varKey: string): Promise<T | null> {
if (this._PERSISTENT_STORE[varKey]) {
return this._PERSISTENT_STORE[varKey].loader() as Promise<T>;
}
return null;
}

async setValue<T>(varKey: string, value: T, save: boolean): Promise<void> {
if (this._PERSISTENT_STORE[varKey]) {
this._PERSISTENT_STORE[varKey].setter(value);

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

async loadAllValues(): Promise<void> {
const loadFuncs: Promise<unknown>[] = [];
Object.values(this._PERSISTENT_STORE).forEach((persistVar) => {
if (persistVar && persistVar.loader) {
loadFuncs.push(persistVar.loader());
}
});

await Promise.all(loadFuncs);
}

async saveAllValues(): Promise<void> {
const saveFuncs: Promise<void>[] = [];
Object.values(this._PERSISTENT_STORE).forEach((persistVar) => {
if (persistVar && persistVar.saver) {
saveFuncs.push(persistVar.saver());
}
});

await Promise.all(saveFuncs);
}
}
8 changes: 5 additions & 3 deletions frontend/src/components/Cart/Items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { RawSearchResults } from '../Search/types';
import DatasetDownload from '../Globus/DatasetDownload';
import CartStateKeys, { cartItemSelections } from './recoil/atoms';
import { NodeStatusArray } from '../NodeStatus/types';
import { addPersistVar, setPersistVal } from '../../common/persistData';
import { DataPersister } from '../../common/DataPersister';

const styles: CSSinJS = {
summary: {
Expand All @@ -35,6 +35,8 @@ export type Props = {
nodeStatus?: NodeStatusArray;
};

const dp: DataPersister = DataPersister.Instance;

const Items: React.FC<React.PropsWithChildren<Props>> = ({
userCart,
onUpdateCart,
Expand All @@ -44,7 +46,7 @@ const Items: React.FC<React.PropsWithChildren<Props>> = ({
const [itemSelections, setItemSelections] = useRecoilState<RawSearchResults>(
cartItemSelections
);
addPersistVar<RawSearchResults>(
dp.addNewVar<RawSearchResults>(
CartStateKeys.cartItemSelections,
[],
setItemSelections
Expand All @@ -53,7 +55,7 @@ const Items: React.FC<React.PropsWithChildren<Props>> = ({
const handleRowSelect = async (
selectedRows: RawSearchResults | []
): Promise<void> => {
await setPersistVal(CartStateKeys.cartItemSelections, selectedRows, true);
await dp.setValue(CartStateKeys.cartItemSelections, selectedRows, true);
};

return (
Expand Down
8 changes: 5 additions & 3 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 { addPersistVar, setPersistVal } from '../../common/persistData';
import { DataPersister } from '../../common/DataPersister';

const styles: CSSinJS = {
headerContainer: { display: 'flex', justifyContent: 'center' },
Expand All @@ -31,11 +31,13 @@ export type Props = {
userCart: UserCart | [];
};

const dp: DataPersister = DataPersister.Instance;

const Summary: React.FC<React.PropsWithChildren<Props>> = ({ userCart }) => {
const [taskItems, setTaskItems] = useRecoilState<GlobusTaskItem[]>(
globusTaskItems
);
addPersistVar(GlobusStateKeys.globusTaskItems, [], setTaskItems);
dp.addNewVar(GlobusStateKeys.globusTaskItems, [], setTaskItems);

let numFiles = 0;
let totalDataSize = '0';
Expand All @@ -54,7 +56,7 @@ const Summary: React.FC<React.PropsWithChildren<Props>> = ({ userCart }) => {
}

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

return (
Expand Down
Loading

0 comments on commit fe03413

Please sign in to comment.