Skip to content

Fixes: error handling on New Form when underlying file is changed.#1491

Open
sadiqkhoja wants to merge 1 commit intogetodk:masterfrom
sadiqkhoja:fixes/form-upload-file-changing
Open

Fixes: error handling on New Form when underlying file is changed.#1491
sadiqkhoja wants to merge 1 commit intogetodk:masterfrom
sadiqkhoja:fixes/form-upload-file-changing

Conversation

@sadiqkhoja
Copy link
Contributor

@sadiqkhoja sadiqkhoja commented Mar 6, 2026

Closes getodk/central#1291 and getodk/central#1558

What has been done to verify that this works as intended?

Added e2e tests and manual verification

Why is this the best possible solution? Were any other approaches considered?

I'd prefer to use File System API and upload the latest file but it is not fully supported in Firefox. With File System API, iterating on Form Design would be really easy. In the absence of that, I went with the following approach:

  • For the error case: resetting the file control seems to be best option since we know that user would need to modify the file and select the file again. Only situation where this approach might feel agressive if error is thrown because of server/network/infra issue and the file is perfectly fine. I think that would be rare occurrence and won't be that annoying to the users.
  • For warning case: I am using FileReader to read the file descriptor and if it is not readable then it means that file is modified/deleted hence can't be re-uploaded.

How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?

None

Does this change require updates to user documentation? If so, please file an issue here and include the link below.

No

Before submitting this PR, please make sure you have:

  • run npm run test and npm run lint and confirmed all checks still pass OR confirm CircleCI build passes
  • verified that any code or assets from external sources are properly credited in comments or that everything is internally sourced

@sadiqkhoja sadiqkhoja requested a review from matthew-white March 6, 2026 18:44
await expect(page.locator('.modal-warnings')).not.toBeVisible();
} finally {
// Clean up temp directory
// fs.rmSync(tempDir, { recursive: true, force: true });
Copy link
Member

Choose a reason for hiding this comment

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

Should this be commented back in? Or if it's not needed, could the whole try/finally be removed?

}

// Assumption: If file is changed/deleted on the disk then it won't be readable
if (ignoreWarnings) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't totally understand yet why this if is needed. I feel like it'd be helpful in all cases to read the file and check for errors.

Case 1: File modification between file selection and initial upload

  1. Open the modal.
  2. Select a file.
  3. You realize you forgot to save the file — now you do so.
  4. Click the upload button.
    • Error due to file modification

Case 2: File modification after warnings receipt

  1. Open the modal.
  2. Select a file.
  3. Click the upload button. A warning is returned.
  4. Fix the file and save it.
  5. Click the upload button.
    • Error

Proposal

  • Always read the file and check for error before sending the request.
  • Don't reset this.file after a request error. In that case, we know the error isn't due to file modification.

I'm guessing I don't understand some part of this, so I just wanted to share my current thinking. Does this approach not work because the file can only be read once?

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; Case 1 is intermittent failure for me (at least on Firefox). Last time when I was working on this, browser was able to read the changed file before upload button click.

I like your proposal let me try that.

];
const devServer = {
port: 8989,
host: true,
Copy link
Member

Choose a reason for hiding this comment

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

Could you say more about why this change is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it is needed to run e2e tests locally otherwise playwright is unable to resolve the host.

but this change shouldn't be part of this PR.

Community member has also faced related issue: #1274

Comment on lines +209 to +210
this.file = null;
this.warnings = null;
Copy link
Member

Choose a reason for hiding this comment

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

This pattern of clearing both this.file and this.warnings also appears in the state watcher. I wonder if it'd make things clearer to add a method to do so (named maybe clear or reset). Just a thought / no strong feelings.


deleteForm = async (xmlFormId) => {
const request = await this.#getRequest();
const response = await request.delete(`/v1/projects/${projectId}/forms/${xmlFormId}`);
Copy link
Member

Choose a reason for hiding this comment

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

Is it worth encoding xmlFormId in the URL? Or is that never a problem in e2e tests?

await backendClient.dispose();
});

const login = async (page) => {
Copy link
Member

Choose a reason for hiding this comment

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

Since this function is used in other e2e tests, what do you think about making it reusable somehow? Like creating a util file?

};

test.describe('Form Upload', () => {
test('clears file input when server returns an error', async ({ page }) => {
Copy link
Member

Choose a reason for hiding this comment

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

I understand why the next e2e test is useful: it's hard to mock file modification in component tests. But this one I don't feel as sure about. I feel like this one could be tested in component tests easily enough (or maybe already is?). We frequently mock server errors in component tests.

Copy link
Member

Choose a reason for hiding this comment

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

getodk/central#1291 (comment) makes the point that this issue comes up in other cases as well:

this issue affects file uploads generally, not just forms. I also encounter the issue on the form attachments page

Given that, I feel like this change doesn't fully close out #1291.

Maybe one option is to file a follow-up issue about form attachments. This could probably come up for entity upload as well. Though it could be that those are rare cases. Certainly form upload seems like the most important case. 👍

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.

Uploading file after modification

2 participants