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

Store Rubric in IndexedDB #9840

Merged
merged 16 commits into from
Feb 1, 2024

Conversation

thsparks
Copy link
Contributor

This change stores the rubric in the indexed db and loads the last-active rubric automatically when the page loads. Currently only one rubric is really stored at a time, but the schema can support multiple if we want to evolve this over time.

Indexed DB has two tables:

  1. Rubrics - where we store the rubrics, with the key set to the rubric name
  2. Metadata - internal settings we may want to store, currently just lastActiveRubricName, but I imagine we may want to use this later to preserve preferences like auto-save and auto-run.

A note on code reuse:
The indexed db code is borrowed from Kiosk. I considered moving some of it into a shared class, but noticed we already have IDBWrapper in BrowserUtils. Then I tried switching to that, but I ultimately needed more code to manage the error handling than I saved by re-using the wrapper code, so I moved away from that approach as well. (It also wasn't working cleanly with custom key fields.) I think the right long-term move here is to evolve the IDBWrapper class to work better for these scenarios, but I think it'd require quite a lot of testing outside of the teachertool, so I was hesitant to make that change now.

@thsparks thsparks requested a review from a team January 31, 2024 21:41
@@ -1,11 +1,14 @@
namespace pxt.blocks {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@srietkerk The changes in this file are responding to your comments in #9835. They're not related to the indexed db change, I actually forgot I'd made them in this branch, but I suppose here they are 😅

...teacherTool.rubric,
name,
};
dispatch(Actions.setRubric(newRubric));
Copy link
Contributor

Choose a reason for hiding this comment

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

May want to provide feedback here that the name was saved. Post a notification?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, a notification feels a little too heavy-weight here to me. Ideally, I think the user would just feel like the name is always saved once they type it, similarly to how criteria adding/removing works, no real need for a "saved" notification. Maybe I should just save the name whenever the text is changed and debounce it a little, rather than requiring them to hit enter and/or click away from the Input?

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably fine as is for now. Let's see what kind of feedback we get.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did end up adding a debounced input to handle this instead...but I can change it back if you want :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(also considered just wrapping the onChange call in pxt.Util.debounce but I got worried it wouldn't play nicely with React re-rendering things and possibly resetting fields)


return (
<div className="rubric-display">
<h3>{lf("Rubric")}</h3>
{teacherTool.selectedCriteria?.map(criteriaInstance => {
<Input
Copy link
Contributor

Choose a reason for hiding this comment

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

needs ariaLabel


const db = new TeacherToolDb();

let initializeAsync = async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This code actually doesn't work as intended. Due to the async, the function can be called multiple times before the reassignment happens. This is my bug :). I introduced it in Kiosk. One fix would be to have a global promise that does the db init and then resolves. getLastActiveRubricAsync, etc. would wait on that.

Copy link
Contributor

Choose a reason for hiding this comment

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

You could create a global promise that is the only source of the db instance. API calls have to await it to get the db instance. This would ensure no calls can be done on db before it's initialized.

}

export async function saveRubricAsync(rubric: Rubric) {
await db.saveRubric(rubric);
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to await the db init here.

placeholder={lf("Rubric Name")}
initialValue={inProgressName}
onEnterKey={handleConfirmName}
onBlur={handleConfirmName}
Copy link
Contributor

Choose a reason for hiding this comment

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

Normally you would need to include the preserveValueOnBlur={true} prop here, but you might be getting around it by setting initialValue={inProgressName}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, yeah. I think that was masking the issue. I'll still add it.

…ks/teacher_tool/store_rubric_in_indexedDb
… happens exactly once and all code that references the db must go through the promise to get it.
thsparks

This comment was marked as outdated.

Copy link
Contributor

@eanders-ms eanders-ms left a comment

Choose a reason for hiding this comment

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

Looks great.

…quiring the user to click away or click enter
clearTimeout(timerId.current);
}

timerId.current = setTimeout(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the expected behavior if the component unmounts while a timeout is pending?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm good question. I'll add some code to fire the onChange immediately if we're umounting and the timeout is pending.

import { Input, InputProps } from "react-common/components/controls/Input";

export interface DebouncedInputProps extends InputProps {
intervalMs?: number | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider assigning the default value here so it's less buried and available to intellisense.

    intervalMs = 500

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think I can set it here, since this is an interface, but I've moved it up to the DebouncedInput parameter definition and added a comment here instead.

…the component, move default interval value for better visibility)
@thsparks thsparks merged commit 1b92ccc into master Feb 1, 2024
6 checks passed
@thsparks thsparks deleted the thsparks/teacher_tool/store_rubric_in_indexedDb branch February 1, 2024 18:33
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.

2 participants