Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module unload and persistent module atoms #717

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

Anders2303
Copy link
Collaborator

  • Added a "onInstanceUnload" callback for modules, so you can perform any needed cleanup as a user removes an instance
  • Added a new atom utility: atomWithModuleInstanceStorage. This one can be used to persist module-atoms in localStorage, keeping them unique for each instance
  • Added examples of both of the above in MyModule2

@rubenthoms rubenthoms changed the title Module unload and persitent module atoms Module unload and persistent module atoms Sep 17, 2024
Copy link
Collaborator

@rubenthoms rubenthoms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, a good implementation 👍 Some remarks and comments.

As already discussed, this is very likely not the persistence mechanism we’re going to use in the long run as it lacks a layer of abstraction. The unload mechanism for module instances, however, might become relevant in other scenarios.

Comment on lines 61 to 111

/**
* Creates an writeable atom that persists it's value in localstorage, but keeps it unique per module instance. **Note: must only be used within a module instances own store**
* @param storageKey Prefix for the key. Should be unique among a module's atoms. Actual storage key will be: `<prefix>:<moduleInstanceId>`
* @param initialValue Initial atom value
* @returns A get/set atom
*/
export function atomWithModuleInstanceStorage<T>(storageKey: string, initialValue: T) {
// simple atom, needed to trigger new lookups
const instanceValueAtom = atom<T | null>(null);

return atom<T, [T], void>(
(get) => {
const instanceId = getIntanceId(get);
const fullStorageKey = makeInstanceStorageKey(instanceId, storageKey);
const instanceValue = get(instanceValueAtom);
const storedValue = localStorage.getItem(fullStorageKey);

if (instanceValue !== null) return instanceValue;
else if (storedValue !== null) return JSON.parse(storedValue);
else return initialValue;
},
(get, set, newVal: T) => {
const instanceId = getIntanceId(get);
const fullStorageKey = makeInstanceStorageKey(instanceId, storageKey);

localStorage.setItem(fullStorageKey, JSON.stringify(newVal));
set(instanceValueAtom, newVal);
}
);
}

export function clearModuleInstanceStorage(instanceId: string, key: string) {
const fullKey = makeInstanceStorageKey(instanceId, key);
localStorage.removeItem(fullKey);
}

function makeInstanceStorageKey(instanceId: string, key: string): string {
// ! Be mindful about changing this; existing stored values will dissapear!
return `${key}:${instanceId}`;
}

function getIntanceId(get: Getter): string {
const id = get(CurrentModuleInstanceIdAtom);

if (id === null) {
throw new Error("Module instance not set. Make sure this atom is only used within a module storage");
}

return id;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, this looks like a clean and nice implementation.
However, I am a bit concerned about this getting mixed up/confused with persistence in the longer run. Especially, since we have not decided yet to what extent we would like to persist modules. One scenario is to leave a dashboard and be able to get back to it exactly the way it was, no matter what machine/browser you were on. This would include overall states (both data and GUI) of the framework and also local module states (also both data and GUI). The other scenario is to just persist data-relevant states and not GUI states (such as which drawer/group is open etc.).

In the first scenario, this implementation would conflict with the other persistence implementation. In the latter, this could work as an addition to it (GUI states only per browser+user). However, the question is also if atoms should have their own persistence mechanism or not as they're usually not used by GUI components. Persistence of framework GUI elements is already managed by the GuiMessageBroker class. Other GUI elements could have a persistable state (usePersistedState). So where would atoms be in this setup?

Since all of this is not discussed yet, I would rather not want this implementation to leak out as I'm afraid others might start using it for their implementations making it harder to adjust to the persistence system we decide on in the longer run. I would prefer if the Well-log viewer module used a temporary internal way of storing its data.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats a fair concern. Im fine with closing down the PR and then we can come back to it later. Would it be okay to use this persistence solution privately inside the well-log viewer module still?

If we close the PR for now; since you noted that the onInstanceUnload would be nice, I could open a new PR that only introduces that logic by itself if wanted

frontend/src/framework/Module.tsx Outdated Show resolved Hide resolved
frontend/src/framework/Module.tsx Outdated Show resolved Hide resolved
frontend/src/framework/ModuleRegistry.ts Outdated Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: «experimental»

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could argue if «experimental» is the right term as this implies that this might become non-experimental at some point with a quite high certainty. Here, however, it might very well become obsolete due to the other persistence implementation. Another term could be «temporary» to highlight its temporary character.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, experimental is correct for something like this; I see "experimental" as implying something is equally likely to get added as it is to be removed, and therefore should be used with caution. "Temporary" is telling me that the thing 100% will be removed, which is not what my intention was, originally.

Even if it's unlikely we'll add it - as voiced in your earlier comment - we might decide to keep it, so I don't think "temporary" is correct.

@Anders2303 Anders2303 mentioned this pull request Oct 7, 2024
31 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants