-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
ref(seer): Persist Seer Explorer input draft per run #115919
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e7774cd
feat(seer): Persist Seer Explorer input draft per run
aliu39 846fce7
test(seer): Cover input draft persistence in explorer drawer
aliu39 d3da33c
tweak test
aliu39 6b96351
feat(seer): Skip draft persistence when runId is null
aliu39 158e036
refactor to global util
aliu39 79167e6
support null in original
aliu39 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import {useCallback, useEffect, useRef, useState} from 'react'; | ||
|
|
||
| import {sessionStorageWrapper} from 'sentry/utils/sessionStorage'; | ||
|
|
||
| function readValue(key: string | null): string { | ||
| if (key === null) { | ||
| return ''; | ||
| } | ||
| return sessionStorageWrapper.getItem(key) ?? ''; | ||
| } | ||
|
|
||
| function writeValue(key: string | null, value: string): void { | ||
| if (key === null) { | ||
| return; | ||
| } | ||
| try { | ||
| sessionStorageWrapper.setItem(key, value); | ||
| } catch { | ||
| // best effort | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Persists a string value (e.g. a textarea draft) to sessionStorage, scoped by | ||
| * `scopeId`. Unlike `useSessionStorage`, writes are deferred — storage is only | ||
| * touched on: | ||
| * - scope change (flush old key, load new key) | ||
| * - unmount (flush current key) | ||
| * - explicit `clear()` (drop the persisted value and reset state to '') | ||
| * | ||
| * When `scopeId` is null the value lives in React state only — no | ||
| * sessionStorage reads or writes happen for that scope. | ||
| */ | ||
| export function usePersistedValue( | ||
| scope: string, | ||
| scopeId: string | number | null | undefined | ||
| ) { | ||
| const key = scopeId === null ? null : `${scope}:${scopeId}`; | ||
| const [value, setValue] = useState(() => readValue(key)); | ||
|
|
||
| const valueRef = useRef(value); | ||
| valueRef.current = value; | ||
|
|
||
| // Flush to storage and update value on key change | ||
| const keyRef = useRef(key); | ||
| useEffect(() => { | ||
| if (keyRef.current !== key) { | ||
| writeValue(keyRef.current, valueRef.current); | ||
| setValue(readValue(key)); | ||
| keyRef.current = key; | ||
| } | ||
| }, [key]); | ||
|
|
||
| // Flush to storage on unmount | ||
| useEffect(() => { | ||
| return () => { | ||
| writeValue(keyRef.current, valueRef.current); | ||
| }; | ||
| }, []); | ||
|
|
||
| const clear = useCallback(() => { | ||
| setValue(''); | ||
| if (keyRef.current !== null) { | ||
| sessionStorageWrapper.removeItem(keyRef.current); | ||
| } | ||
| }, []); | ||
|
|
||
| return {value, setValue, clear}; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks similar to useLocalStorageState. you could use that and pass in the same kind of key
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of our existing
use*Storagehooks support deferred writes like this hook, which avoids hammering storage on every keystroke in a textarea.There's certainly an argument to be made that we should add an option for one of those hooks to support it, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd aim to layer a throttle or something, instead of baking things in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ryan953 do you mean to throttle the number of updates to local/session storage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have strong enough opinions to block this PR. But I'll think it all through (rant) since we're here...
I was sorta alerted because i know the llms love to make new files instead of finding existing ones, and i think that kind of duplication was already a problem and it'll get much worse over time.
So we've got the base useLocalStorageState, which is the place that does serialization as well as the setState call (2 jobs). I'd argue that those two jobs could be split up a little better; the benefit could be that we don't need to have
readValue()andwriteValue()re-implemented.I also found
NoteInputWithStoragewhich is another implementation of the persist-unsaved-content pattern. It's gotdebounce()to wrap the save call, evidence that its a great call to have here too 👍 . It would be ideal if that component were composable, so we could just use it directly. Instead its coupled to NoteInput.This kind of circles back to the idea that there are multiple similar implementations all over and they're hard to find. So if we keep this file i'd move it into something like static/app/utils/storage. The NoteInput isn't obvious to find, and who wants to share by importing from components/activity/note/
So yeah, not trying to block this. brain dump for y'all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks for the thoughts! I can move to app/utils so its easier to find, and see if we can reuse read/write