Created
February 21, 2025 15:22
-
-
Save dannon/5aeb7ad0e6a4ff06e1c5f3ee98efa9f7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/client/src/api/index.ts b/client/src/api/index.ts | |
index 6f9ce9d021..fdd0c736c6 100644 | |
--- a/client/src/api/index.ts | |
+++ b/client/src/api/index.ts | |
@@ -266,6 +266,7 @@ export function isRegisteredUser(user: AnyUser | UserModel): user is RegisteredU | |
} | |
export function isAnonymousUser(user: AnyUser | UserModel): user is AnonymousUser { | |
+ console.debug("CHECKING IS ANONYMOUS USER", user); | |
return user !== null && !isRegisteredUser(user); | |
} | |
diff --git a/client/src/components/Landing/WorkflowLanding.vue b/client/src/components/Landing/WorkflowLanding.vue | |
index 2677d0a09b..e83840089a 100644 | |
--- a/client/src/components/Landing/WorkflowLanding.vue | |
+++ b/client/src/components/Landing/WorkflowLanding.vue | |
@@ -1,12 +1,8 @@ | |
<script setup lang="ts"> | |
import { BAlert } from "bootstrap-vue"; | |
-import { storeToRefs } from "pinia"; | |
-import { ref, watch } from "vue"; | |
-import { useRouter } from "vue-router/composables"; | |
+import { onMounted, ref } from "vue"; | |
import { GalaxyApi } from "@/api"; | |
-import { useActivityStore } from "@/stores/activityStore"; | |
-import { useUserStore } from "@/stores/userStore"; | |
import { errorMessageAsString } from "@/utils/simple-error"; | |
import LoadingSpan from "@/components/LoadingSpan.vue"; | |
@@ -27,56 +23,40 @@ const workflowId = ref<string | null>(null); | |
const errorMessage = ref<string | null>(null); | |
const requestState = ref<Record<string, never> | null>(null); | |
const instance = ref<boolean>(false); | |
-const userStore = useUserStore(); | |
-const router = useRouter(); | |
-userStore.loadUser(false); | |
-const { isAnonymous, currentUser } = storeToRefs(userStore); | |
+onMounted(async () => { | |
+ let claim; | |
+ let claimError; | |
-const activityStore = useActivityStore("default"); | |
+ if (props.public) { | |
+ const { data, error } = await GalaxyApi().GET("/api/workflow_landings/{uuid}", { | |
+ params: { | |
+ path: { uuid: props.uuid }, | |
+ }, | |
+ }); | |
+ claim = data; | |
+ claimError = error; | |
+ } else { | |
+ const { data, error } = await GalaxyApi().POST("/api/workflow_landings/{uuid}/claim", { | |
+ params: { | |
+ path: { uuid: props.uuid }, | |
+ }, | |
+ body: { | |
+ client_secret: props.secret, | |
+ }, | |
+ }); | |
+ claim = data; | |
+ claimError = error; | |
+ } | |
-watch( | |
- currentUser, | |
- async () => { | |
- if (isAnonymous.value) { | |
- router.push( | |
- `/login/start?redirect=/workflow_landings/${props.uuid}?public=${props.public}&client_secret=${props.secret}` | |
- ); | |
- } else if (currentUser.value) { | |
- let claim; | |
- let claimError; | |
- activityStore.closeSideBar(); | |
- if (props.public) { | |
- const { data, error } = await GalaxyApi().GET("/api/workflow_landings/{uuid}", { | |
- params: { | |
- path: { uuid: props.uuid }, | |
- }, | |
- }); | |
- claim = data; | |
- claimError = error; | |
- } else { | |
- const { data, error } = await GalaxyApi().POST("/api/workflow_landings/{uuid}/claim", { | |
- params: { | |
- path: { uuid: props.uuid }, | |
- }, | |
- body: { | |
- client_secret: props.secret, | |
- }, | |
- }); | |
- claim = data; | |
- claimError = error; | |
- } | |
- if (claim) { | |
- workflowId.value = claim.workflow_id; | |
- instance.value = claim.workflow_target_type === "workflow"; | |
- requestState.value = claim.request_state; | |
- } else { | |
- errorMessage.value = errorMessageAsString(claimError); | |
- } | |
- } | |
- }, | |
- { immediate: true } | |
-); | |
+ if (claim) { | |
+ workflowId.value = claim.workflow_id; | |
+ instance.value = claim.workflow_target_type === "workflow"; | |
+ requestState.value = claim.request_state; | |
+ } else { | |
+ errorMessage.value = errorMessageAsString(claimError); | |
+ } | |
+}); | |
</script> | |
<template> | |
diff --git a/client/src/composables/hashedUserId.ts b/client/src/composables/hashedUserId.ts | |
index a7f651a3c6..a40529ee86 100644 | |
--- a/client/src/composables/hashedUserId.ts | |
+++ b/client/src/composables/hashedUserId.ts | |
@@ -59,6 +59,7 @@ export function useHashedUserId(user?: Ref<AnyUser>) { | |
); | |
async function hashUserId(id: string) { | |
+ console.debug("Hashing user id", id); | |
if (unhashedId !== id) { | |
unhashedId = id; | |
currentHash.value = null; | |
diff --git a/client/src/composables/userLocalStorage.ts b/client/src/composables/userLocalStorage.ts | |
index d81dfda55e..0ba1370245 100644 | |
--- a/client/src/composables/userLocalStorage.ts | |
+++ b/client/src/composables/userLocalStorage.ts | |
@@ -1,28 +1,87 @@ | |
+// userLocalStorage.ts | |
import { watchImmediate } from "@vueuse/core"; | |
-import { type Ref, ref } from "vue"; | |
+import { type Ref, ref, watch } from "vue"; | |
import { type AnyUser } from "@/api"; | |
import { useHashedUserId } from "./hashedUserId"; | |
import { syncRefToLocalStorage } from "./persistentRef"; | |
+interface PendingChange { | |
+ key: string; | |
+ value: any; // Use any here | |
+ type: "string" | "number" | "boolean" | "object"; | |
+} | |
+ | |
+const pendingChanges: PendingChange[] = []; | |
+ | |
/** | |
* Local storage composable specific to current user. | |
* @param key | |
* @param initialValue | |
*/ | |
-export function useUserLocalStorage<T>(key: string, initialValue: T, user?: Ref<AnyUser>) { | |
+export function useUserLocalStorage<T>(key: string, initialValue: T, user?: Ref<AnyUser>): Ref<T> { | |
+ //return Ref<T> | |
const { hashedUserId } = useHashedUserId(user); | |
- | |
- const refToSync = ref(initialValue); | |
+ const refToSync = ref(initialValue) as Ref<T>; | |
let hasSynced = false; | |
+ // Function to apply pending changes | |
+ const applyPendingChanges = (userId: string) => { | |
+ const changesToApply = pendingChanges.filter((change) => change.key === key); | |
+ if (changesToApply.length > 0) { | |
+ console.debug(`Applying ${changesToApply.length} pending changes for key "${key}"`); | |
+ changesToApply.forEach((change) => { | |
+ // No try-catch is needed when applying to memory (it's needed when parsing) | |
+ // No type checking or casting is needed here! | |
+ refToSync.value = change.value; | |
+ | |
+ //remove item from pending changes | |
+ const index = pendingChanges.indexOf(change); | |
+ if (index > -1) { | |
+ pendingChanges.splice(index, 1); | |
+ } | |
+ }); | |
+ | |
+ // Sync to local storage after applying in-memory changes. | |
+ syncRefToLocalStorage(`${key}-${userId}`, refToSync); | |
+ } | |
+ }; | |
+ | |
+ // Watch for changes to refToSync and store them if hashedUserId is not yet available. | |
+ watch( | |
+ refToSync, | |
+ (newValue) => { | |
+ if (!hashedUserId.value) { | |
+ console.debug(`Queueing change for key "${key}" until user ID is available.`); | |
+ // Check if a change for this key already exists, and update it, or add a new change | |
+ | |
+ const existingChangeIndex = pendingChanges.findIndex((change) => change.key === key); | |
+ const type = typeof newValue as "string" | "number" | "boolean" | "object"; | |
+ if (existingChangeIndex > -1) { | |
+ pendingChanges[existingChangeIndex] = { key, value: newValue, type }; | |
+ } else { | |
+ pendingChanges.push({ key, value: newValue, type }); | |
+ } | |
+ } else if (!hasSynced) { | |
+ //This case handles calls to setItem that might happen *after* hashedUserId is set, | |
+ // but *before* this particular watchImmediate has run. | |
+ syncRefToLocalStorage(`${key}-${hashedUserId.value}`, refToSync); | |
+ } | |
+ }, | |
+ { deep: true } | |
+ ); | |
+ | |
watchImmediate( | |
() => hashedUserId.value, | |
() => { | |
if (hashedUserId.value && !hasSynced) { | |
+ console.debug("SYNCING TO LOCALSTORAGE", key, hashedUserId.value); | |
+ applyPendingChanges(hashedUserId.value); // Apply pending changes first | |
syncRefToLocalStorage(`${key}-${hashedUserId.value}`, refToSync); | |
hasSynced = true; | |
+ } else { | |
+ console.debug("NO USER -- SKIPPING", key, hashedUserId.value); | |
} | |
} | |
); | |
diff --git a/client/src/entry/analysis/router.js b/client/src/entry/analysis/router.js | |
index ba7da642ad..bc59c2dc83 100644 | |
--- a/client/src/entry/analysis/router.js | |
+++ b/client/src/entry/analysis/router.js | |
@@ -54,6 +54,8 @@ import AdminRoutes from "entry/analysis/routes/admin-routes"; | |
import LibraryRoutes from "entry/analysis/routes/library-routes"; | |
import StorageDashboardRoutes from "entry/analysis/routes/storageDashboardRoutes"; | |
import { getAppRoot } from "onload/loadConfig"; | |
+import { useActivityStore } from "stores/activityStore"; | |
+import { useUserStore } from "stores/userStore"; | |
import Vue from "vue"; | |
import VueRouter from "vue-router"; | |
@@ -523,6 +525,39 @@ export function getRouter(Galaxy) { | |
public: route.query.public.toLowerCase() === "true", | |
secret: route.query.client_secret, | |
}), | |
+ beforeEnter: async (to, from, next) => { | |
+ const userStore = useUserStore(); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug(await userStore.loadUser(false)); | |
+ console.debug("CurrentUser after load", userStore.currentUser.email); | |
+ | |
+ if (userStore.isAnonymous) { | |
+ next({ | |
+ path: "/login/start", | |
+ query: { | |
+ redirect: `/workflow_landings/${to.params.uuid}?public=${to.query.public}&client_secret=${to.query.client_secret}`, | |
+ }, | |
+ }); | |
+ return; | |
+ } | |
+ console.debug( | |
+ "Current user is not anonymous, proceeding to workflow landing page", | |
+ userStore.currentUser | |
+ ); | |
+ console.debug(userStore); | |
+ const activityStore = useActivityStore("default"); | |
+ console.debug("ROUTING CLOSE SIDE BAR"); | |
+ activityStore.closeSideBar(); | |
+ next(); | |
+ }, | |
}, | |
{ | |
path: "user", | |
diff --git a/client/src/stores/activityStore.ts b/client/src/stores/activityStore.ts | |
index f7fad52718..b91904aa5c 100644 | |
--- a/client/src/stores/activityStore.ts | |
+++ b/client/src/stores/activityStore.ts | |
@@ -69,7 +69,10 @@ export const useActivityStore = defineScopedStore("activityStore", (scope) => { | |
} | |
function closeSideBar() { | |
+ console.debug("CLOSE SIDEBAR"); | |
toggledSideBar.value = "closed"; | |
+ console.debug("AFTER CLOSE SIDEBAR", isSideBarOpen.value); | |
+ sync(); | |
} | |
function overrideDefaultActivities(activities: Activity[]) { | |
@@ -95,6 +98,7 @@ export const useActivityStore = defineScopedStore("activityStore", (scope) => { | |
* to the user stored activities which are persisted in local cache. | |
*/ | |
const sync = useDebounceFn(() => { | |
+ console.trace("sync"); | |
// create a map of built-in activities | |
const activitiesMap: Record<string, Activity> = {}; | |
@@ -133,8 +137,8 @@ export const useActivityStore = defineScopedStore("activityStore", (scope) => { | |
}); | |
activities.value = newActivities; | |
- | |
// if toggled side-bar does not exist, choose the first option | |
+ console.debug("IN SYNC< SIDEBAR OPEN IS", isSideBarOpen.value, "value is ", toggledSideBar.value); | |
if (isSideBarOpen.value) { | |
const allSideBars = activities.value.flatMap((activity) => { | |
if (activity.panel) { | |
@@ -193,7 +197,8 @@ export const useActivityStore = defineScopedStore("activityStore", (scope) => { | |
watchImmediate( | |
() => hashedUserId.value, | |
- () => { | |
+ (newValue, oldValue) => { | |
+ console.debug("Sync from watchImmediate", oldValue, newValue); | |
sync(); | |
} | |
); | |
diff --git a/client/src/stores/userStore.ts b/client/src/stores/userStore.ts | |
index c92dc0bbdc..f7f385afd7 100644 | |
--- a/client/src/stores/userStore.ts | |
+++ b/client/src/stores/userStore.ts | |
@@ -68,32 +68,39 @@ export const useUserStore = defineStore("userStore", () => { | |
currentUser.value = user; | |
} | |
- function loadUser(includeHistories = true) { | |
+ function loadUser(includeHistories = true): Promise<void> { | |
if (!loadPromise) { | |
- loadPromise = getCurrentUser() | |
- .then(async (user) => { | |
- if (isRegisteredUser(user)) { | |
- currentUser.value = user; | |
- currentPreferences.value = processUserPreferences(user); | |
- } else if (isAnonymousUser(user)) { | |
- currentUser.value = user; | |
- } else if (user === null) { | |
- currentUser.value = null; | |
+ loadPromise = new Promise<void>((resolve, reject) => { | |
+ (async () => { | |
+ console.debug("Loading once"); | |
+ try { | |
+ const user = await getCurrentUser(); | |
+ | |
+ if (isRegisteredUser(user)) { | |
+ currentUser.value = user; | |
+ currentPreferences.value = processUserPreferences(user); | |
+ } else if (isAnonymousUser(user)) { | |
+ currentUser.value = user; | |
+ } else if (user === null) { | |
+ currentUser.value = null; | |
+ } | |
+ if (includeHistories) { | |
+ const historyStore = useHistoryStore(); | |
+ await historyStore.loadHistories(); | |
+ } | |
+ resolve(); // Resolve the promise after successful load | |
+ } catch (e) { | |
+ console.error("Failed to load user", e); | |
+ reject(e); // Reject the promise on error | |
+ } finally { | |
+ //Don't clear the loadPromise, we still want multiple callers to await. | |
+ //Instead we must clear it upon $reset | |
+ // loadPromise = null; | |
} | |
- | |
- if (includeHistories) { | |
- const historyStore = useHistoryStore(); | |
- // load first few histories for user to start pagination | |
- await historyStore.loadHistories(); | |
- } | |
- }) | |
- .catch((e) => { | |
- console.error("Failed to load user", e); | |
- }) | |
- .finally(() => { | |
- loadPromise = null; | |
- }); | |
+ })(); | |
+ }); | |
} | |
+ return loadPromise; // Return the shared promise | |
} | |
async function setCurrentTheme(theme: string) { |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment