feat: add bulk file actions (#7827)

* fixed styling

* Enabled header checkbox selection

* Changed styling of selection

* Implemented bulk downloading and deleting

* Added delete bulk hook

* Added download bulk hook

* Fix backend to send extension in download single file

* Fix hook to download single file directly

* Added header and selection handling

* Added delete confirmation

* [autofix.ci] apply automated fixes

* Fixed selection with shift

* Show disabled files

* Show disabled files as not clickable

* Changed color of icon when disabled

* Implemented pressed shift handling

* Fixed shift selection and disabled text selection when holding shift

* Created test for bulk selection on files modal

* add test of disabled components in file component

* Fixed files page test to include bulk editing test

* removed ring on focus visible

* Changed delete files having the right select

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Lucas Oliveira 2025-05-13 18:59:10 -03:00 • committed by GitHub
commit a72995c408
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1244 additions and 235 deletions

View file

@ -318,3 +318,148 @@ test(
}
},
);
test(
"should handle bulk actions for multiple files",
{ tag: ["@release", "@files"] },
async ({ page }) => {
const fileNames = {
txt: generateRandomFilename(),
json: generateRandomFilename(),
py: generateRandomFilename(),
};
const testFiles = [
path.join(__dirname, "../../assets/test-file.txt"),
path.join(__dirname, "../../assets/test-file.json"),
path.join(__dirname, "../../assets/test-file.py"),
];
const fileContents = testFiles.map((file) => fs.readFileSync(file));
await awaitBootstrapTest(page, { skipModal: true });
const firstRunLangflow = await page
.getByTestId("empty-project-description")
.count();
if (firstRunLangflow > 0) {
await addFlowToTestOnEmptyLangflow(page);
}
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
await page.getByText("My Files").first().click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("upload-file-btn").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles([
{
name: `${fileNames.txt}.txt`,
mimeType: "text/plain",
buffer: fileContents[0],
},
{
name: `${fileNames.json}.json`,
mimeType: "application/json",
buffer: fileContents[1],
},
{
name: `${fileNames.py}.py`,
mimeType: "text/x-python",
buffer: fileContents[2],
},
]);
// Wait for upload success message
const successMessage = await page.getByText("Files uploaded successfully");
expect(successMessage).toBeTruthy();
// Verify all files appear in the list
for (const name of Object.values(fileNames)) {
const file = await page.getByText(name).last();
await expect(file).toBeVisible({
timeout: 1000,
});
}
// Select files with shift (checkbox on the grid)
await page.keyboard.down("Shift");
await page.locator('input[data-ref="eInput"]').nth(5).click();
await page.locator('input[data-ref="eInput"]').nth(7).click();
await page.keyboard.up("Shift");
expect(
await page.locator('input[data-ref="eInput"]').nth(5).isChecked(),
).toBe(true);
expect(
await page.locator('input[data-ref="eInput"]').nth(6).isChecked(),
).toBe(true);
expect(
await page.locator('input[data-ref="eInput"]').nth(7).isChecked(),
).toBe(true);
// Check if the bulk actions toolbar appears
const selectedCountText = await page.getByText("3 selected");
await expect(selectedCountText).toBeVisible();
// Check if download button is visible
const downloadButton = await page.getByTestId("bulk-download-btn");
await expect(downloadButton).toBeVisible();
// Set up download listener
const downloadPromise = page.waitForEvent("download");
// Click download button
await downloadButton.click();
// Wait for download to start
const download = await downloadPromise;
// Verify the download was initiated
expect(download).toBeTruthy();
// Check for success message
const downloadSuccessMessage = await page.getByText(
/Files? downloaded successfully/,
);
expect(downloadSuccessMessage).toBeTruthy();
// Select both files (checkbox on the grid)
await page.locator('input[data-ref="eInput"]').nth(7).click();
// Check if the bulk actions toolbar appears
const selectedCountTextDelete = await page.getByText("2 selected");
await expect(selectedCountTextDelete).toBeVisible();
const deleteButton = await page.getByTestId("bulk-delete-btn");
await expect(deleteButton).toBeVisible();
// Test delete functionality
await deleteButton.click();
// Confirm the delete in the modal
const confirmDeleteButton = await page.getByRole("button", {
name: "Delete",
});
await confirmDeleteButton.click();
// Check for success message
const deleteSuccessMessage = await page.getByText(
"Files deleted successfully",
);
expect(deleteSuccessMessage).toBeTruthy();
// Verify the deleted files are no longer visible
const remainingFileCount =
(await page.getByText(fileNames.py + ".py").count()) +
(await page.getByText(fileNames.txt + ".txt").count()) +
(await page.getByText(fileNames.json + ".json").count());
expect(remainingFileCount).toBe(1);
},
);