Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3045d64
add workspace file metadata types, API methods, and fetching
AaronPlave Mar 26, 2026
95d155b
add workspace file metadata UI — table columns, metadata banner, and …
AaronPlave Mar 26, 2026
cea61cb
workspace panel refactor, metadata API fixes, and sidebar button fix
AaronPlave Mar 27, 2026
829ca80
extract PanelHeader component from repeated section title styling
AaronPlave Mar 27, 2026
6b431fd
add user metadata editor, open folder navigation, and file browser se…
AaronPlave Mar 27, 2026
fc3c149
Column sizing fixes
AaronPlave Mar 27, 2026
8a94fe8
Panel sizing
AaronPlave Mar 27, 2026
448fd49
Tweak workspace file browse column sizes for compactness
AaronPlave Apr 2, 2026
15dbbc1
fix workspace metadata reactivity and type safety improvements
AaronPlave Apr 3, 2026
8fa9f2e
Right sidebar icon swap for dictionary
AaronPlave Apr 3, 2026
2f287aa
Tweak user metadata input to use explicit edit, save, cancel buttons.…
AaronPlave Apr 3, 2026
5c140d9
Tooltip improvements for workspace sidebar and icon tray
AaronPlave Apr 3, 2026
98cb0c0
Refactor
AaronPlave Apr 3, 2026
0352b9c
Prettier
AaronPlave Apr 3, 2026
1100022
Test fixes
AaronPlave Apr 4, 2026
01586df
fix(sequencing): configure both readOnly and editable facets in editor
duranb Apr 9, 2026
463a7f3
refactor(workspace): extract panel toggle logic into reusable function
duranb Apr 9, 2026
5f1be65
refactor(workspace): improve variable declarations in WorkspaceRightP…
duranb Apr 10, 2026
5be4f2b
fix(workspace): clarify metadata message is for folders, not files
duranb Apr 10, 2026
999c151
fix(workspace): await file save and refresh contents after save
duranb Apr 10, 2026
f2956a4
refactor: remove version column and metadata from workspace UI
duranb Apr 10, 2026
dd11938
Optimistically update the metadata user object so it doesn't flash on…
duranb Apr 10, 2026
62f6325
fix linting errors
duranb Apr 10, 2026
8aab239
fix: add overwrite merge behavior to workspace metadata POST requests
duranb Apr 10, 2026
57a6fc7
feat(ui): add column resize support and centralize cookie management
duranb Apr 10, 2026
1ebd272
feat(workspace): add read-only file protection to context menu
duranb Apr 13, 2026
2ed5955
fix test
duranb Apr 13, 2026
be5e69b
test(e2e): update actions navigation selector to use tablist role
duranb Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 76 additions & 39 deletions e2e-tests/fixtures/Workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,34 @@ import { getWorkspacesUrl } from '../../src/utilities/routes';
import { generateRandomName, hoverRowAndWaitForButton, setFileInputByBuffer } from '../utilities/helpers';

export class Workspace {
editSequenceButton: Locator;
fileInput: Locator;
folderNameInput: Locator;
editSequenceButton!: Locator;
fileInput!: Locator;
folderNameInput!: Locator;
jsonPath: string = 'e2e-tests/data/ban00001.json';
navButtonSequences: Locator;
navButtonSequencesMenu: Locator;
pageLoadingLocatorWithData: Locator;
saveSequenceButton: Locator;
searchInput: Locator;
sequenceEditor: Locator;
sequenceNameInput: Locator;
textEditor: Locator;
workspaceCollaboratorInput: Locator;
workspaceContextMenuButton: Locator;
workspaceFileBrowserButton: Locator;
workspaceFileContextMenu: Locator;
workspaceFileGrid: Locator;
workspaceHeaderMenu: Locator;
workspaceSettingsButton: Locator;
workspaceSidebar: Locator;
metadataCancelButton!: Locator;
metadataEditButton!: Locator;
metadataPanel!: Locator;
metadataSaveButton!: Locator;
metadataTabButton!: Locator;
navButtonSequences!: Locator;
navButtonSequencesMenu!: Locator;
pageLoadingLocatorWithData!: Locator;
readOnlyCheckbox!: Locator;
rightPanelCollapseButton!: Locator;
saveSequenceButton!: Locator;
searchInput!: Locator;
sequenceEditor!: Locator;
sequenceNameInput!: Locator;
textEditor!: Locator;
userMetadataEditor!: Locator;
workspaceCollaboratorInput!: Locator;
workspaceContextMenuButton!: Locator;
workspaceFileBrowserButton!: Locator;
workspaceFileContextMenu!: Locator;
workspaceFileGrid!: Locator;
workspaceHeaderMenu!: Locator;
workspaceSettingsButton!: Locator;
workspaceSidebar!: Locator;

constructor(
public page: Page,
Expand Down Expand Up @@ -85,30 +93,23 @@ export class Workspace {
}

async deleteFile(fileName: string): Promise<void> {
const row = this.workspaceFileGrid.getByRole('row', { name: fileName });
const deleteButton = row.getByRole('button', { name: 'Delete' });
await hoverRowAndWaitForButton(this.page, row, deleteButton);
await deleteButton.click();
await this.openFileContextMenu(fileName);
await this.workspaceFileContextMenu.getByRole('menuitem', { name: 'Delete' }).click();
await this.page.locator('#modal-container').getByRole('button', { name: 'Delete' }).click();
await this.waitForToast('Workspace File Deleted Successfully');
}

async deleteFolder(folderName: string): Promise<void> {
const row = this.workspaceFileGrid.getByRole('row', { name: folderName });
const deleteButton = row.getByRole('button', { name: 'Delete' });
await hoverRowAndWaitForButton(this.page, row, deleteButton);
await deleteButton.click();
await this.openFileContextMenu(folderName);
await this.workspaceFileContextMenu.getByRole('menuitem', { name: 'Delete' }).click();
await this.page.locator('#modal-container').getByRole('button', { name: 'Delete' }).click();
await this.waitForToast('Workspace Folder Deleted Successfully');
}

async deleteSequence(sequenceName: string): Promise<void> {
const row = this.workspaceFileGrid.getByRole('row', { name: sequenceName });
const deleteButton = row.getByRole('button', { name: 'Delete' });
await hoverRowAndWaitForButton(this.page, row, deleteButton);
await deleteButton.click();
await this.openFileContextMenu(sequenceName);
await this.workspaceFileContextMenu.getByRole('menuitem', { name: 'Delete' }).click();
await this.page.locator('#modal-container').getByRole('button', { name: 'Delete' }).click();

await this.waitForToast('Workspace File Deleted Successfully');
}

Expand Down Expand Up @@ -158,6 +159,18 @@ export class Workspace {
await this.sequenceNameInput.blur();
}

/**
* Type content into the user metadata JSON editor (CodeMirror).
* Clears existing content first.
*/
async fillUserMetadata(content: string): Promise<void> {
// Focus the CodeMirror editor
await this.userMetadataEditor.click();
// Select all and replace
await this.page.keyboard.press('ControlOrMeta+a');
await this.page.keyboard.type(content);
}

getFileRow(name: string): Locator {
return this.workspaceFileGrid.getByRole('row', { name });
}
Expand Down Expand Up @@ -191,12 +204,32 @@ export class Workspace {

async openFileContextMenu(fileName: string): Promise<void> {
const row = this.workspaceFileGrid.getByRole('row', { name: fileName });
// AG Grid can briefly detach/reattach rows during re-renders (e.g., after a delete
// triggers a tree refetch). Wait for the row to stabilize before interacting.
await row.waitFor({ state: 'visible', timeout: 5000 });
try {
await row.scrollIntoViewIfNeeded();
} catch {
// Row was detached during AG Grid re-render; re-wait and retry
await row.waitFor({ state: 'visible', timeout: 5000 });
}
const moreActionsButton = row.getByLabel('More actions');
await hoverRowAndWaitForButton(this.page, row, moreActionsButton);
await moreActionsButton.click();
await this.workspaceFileContextMenu.waitFor({ state: 'visible' });
}

/**
* Open the right-side metadata panel by clicking the metadata tab icon.
* If the panel is already open on the metadata tab, this is a no-op.
*/
async openMetadataPanel(): Promise<void> {
// Click the Metadata tab button in the right icon rail
await this.metadataTabButton.click();
// Wait for the metadata panel content to appear
await this.page.getByText('User metadata', { exact: true }).waitFor({ state: 'visible', timeout: 5000 });
}

async openWorkspaceContextMenu(): Promise<void> {
await this.workspaceContextMenuButton.click();
await this.workspaceHeaderMenu.waitFor({ state: 'attached' });
Expand Down Expand Up @@ -241,24 +274,28 @@ export class Workspace {
// Use locator chain: find by aria-label within the modal
this.fileInput = page.locator('#modal-container input[type="file"][aria-label="File(s)"]');
this.folderNameInput = page.locator('#modal-container').getByRole('textbox', { name: 'Folder Name' });
this.metadataEditButton = page.getByRole('button', { name: 'Edit user metadata' });
this.metadataCancelButton = page.locator('.user-metadata-editor + div').getByRole('button', { name: 'Cancel' });
this.metadataPanel = page.locator('.user-metadata-editor').first();
this.metadataSaveButton = page.locator('.user-metadata-editor + div').getByRole('button', { name: 'Save' });
this.metadataTabButton = page.getByRole('button', { name: 'Metadata' });
this.navButtonSequences = page.locator('.nav-button:has-text("Sequences")');
this.navButtonSequencesMenu = this.navButtonSequences.getByRole('menu');
this.page = page;
this.pageLoadingLocatorWithData = page.getByRole('complementary').getByText('Loading workspace').first();
this.pageLoadingLocatorWithData = page.getByText('Loading workspace').first();
this.readOnlyCheckbox = page.locator('#read-only');
this.rightPanelCollapseButton = page.getByRole('button', { name: /Collapse panel|Expand panel/ }).last();
this.saveSequenceButton = page.getByRole('button', { name: 'Save' });
this.searchInput = page.getByPlaceholder('Search files and folders');
this.sequenceEditor = page.locator('.cm-activeLine').first();
this.sequenceNameInput = page.locator('#modal-container').getByRole('textbox', { name: 'File Name' });
this.textEditor = page.locator('.cm-activeLine').nth(2);
this.userMetadataEditor = page.locator('.user-metadata-editor .cm-content').first();
this.workspaceFileContextMenu = page.getByTestId('context-menu');
this.workspaceFileGrid = page.getByRole('treegrid');
this.workspaceHeaderMenu = page.getByTestId('workspace-header-menu');
this.workspaceSidebar = page.getByRole('complementary');
this.workspaceContextMenuButton = this.workspaceSidebar
.getByRole('button', {
name: 'New Workspace Item',
})
.first();
this.workspaceSidebar = page.locator('[data-slot="sidebar-wrapper"]').first();
this.workspaceContextMenuButton = page.getByRole('button', { name: 'New Workspace Item' });
this.workspaceSettingsButton = page.getByRole('button', { name: 'Settings' });
this.workspaceFileBrowserButton = page.getByRole('button', { name: 'Files' });
this.workspaceCollaboratorInput = page.getByPlaceholder('Search collaborators or workspaces');
Expand Down
2 changes: 1 addition & 1 deletion e2e-tests/tests/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test.afterAll(async () => {
test.describe.serial('Actions', () => {
test('Navigate to workspace actions from sidebar', async () => {
const newPagePromise = setup.context.waitForEvent('page');
await setup.page.getByRole('complementary').getByRole('button', { name: 'Actions' }).click();
await setup.page.getByRole('tablist').getByRole('button', { name: 'Actions' }).click();
const newTab = await newPagePromise;
await newTab.waitForLoadState();
await newTab.getByText('Loading...').first().waitFor({ state: 'hidden' });
Expand Down
Loading