Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
0b2c483
add workspace file metadata types, API methods, and fetching
AaronPlave Mar 26, 2026
32532b0
add workspace file metadata UI — table columns, metadata banner, and …
AaronPlave Mar 26, 2026
c1424bc
workspace panel refactor, metadata API fixes, and sidebar button fix
AaronPlave Mar 27, 2026
7c5fc63
extract PanelHeader component from repeated section title styling
AaronPlave Mar 27, 2026
3f9e3cd
add user metadata editor, open folder navigation, and file browser se…
AaronPlave Mar 27, 2026
b86f6a5
Column sizing fixes
AaronPlave Mar 27, 2026
b4359bf
Panel sizing
AaronPlave Mar 27, 2026
2749c63
Tweak workspace file browse column sizes for compactness
AaronPlave Apr 2, 2026
38234db
fix workspace metadata reactivity and type safety improvements
AaronPlave Apr 3, 2026
2729c63
Right sidebar icon swap for dictionary
AaronPlave Apr 3, 2026
7993683
Tweak user metadata input to use explicit edit, save, cancel buttons.…
AaronPlave Apr 3, 2026
ba06ac3
Tooltip improvements for workspace sidebar and icon tray
AaronPlave Apr 3, 2026
f4f70bd
Refactor
AaronPlave Apr 3, 2026
e1d37f6
Prettier
AaronPlave Apr 3, 2026
5fe549f
Test fixes
AaronPlave Apr 4, 2026
bac13c2
fix(sequencing): configure both readOnly and editable facets in editor
duranb Apr 9, 2026
56b7d60
refactor(workspace): extract panel toggle logic into reusable function
duranb Apr 9, 2026
604e1c4
refactor(workspace): improve variable declarations in WorkspaceRightP…
duranb Apr 10, 2026
3c9f59b
fix(workspace): clarify metadata message is for folders, not files
duranb Apr 10, 2026
7112339
fix(workspace): await file save and refresh contents after save
duranb Apr 10, 2026
3e040dc
refactor: remove version column and metadata from workspace UI
duranb Apr 10, 2026
9c18e52
Optimistically update the metadata user object so it doesn't flash on…
duranb Apr 10, 2026
2b04117
fix linting errors
duranb Apr 10, 2026
5b0fe84
fix: add overwrite merge behavior to workspace metadata POST requests
duranb Apr 10, 2026
5fa4413
feat(ui): add column resize support and centralize cookie management
duranb Apr 10, 2026
2c2f1cb
feat(workspace): add read-only file protection to context menu
duranb Apr 13, 2026
b92e9aa
fix test
duranb Apr 13, 2026
ce2b2af
refactor: simplify actions click handling and add open folder button
duranb Apr 14, 2026
ff1f050
refactor(workspace): reorganize component props and restructure layout
duranb Apr 14, 2026
84ce3dc
refactor(e2e): improve TypeScript imports and fix action selector
duranb Apr 14, 2026
2043f6d
refactor(workspace): simplify modals and add read-only file protection
duranb Apr 14, 2026
db7b092
fix(text-editor): configure editable state alongside readonly
duranb Apr 15, 2026
df3d13a
remove log
duranb Apr 15, 2026
f2c3a8c
fix: improve error handling in error display and bulk file operations
duranb Apr 15, 2026
ce66a76
fix(effects): improve error messaging for failed workspace item moves
duranb Apr 15, 2026
7e47501
fix(ui): add z-index to workspace content pane so tooltips don't go u…
duranb Apr 15, 2026
9ab7ec8
set minSize of left/right workspace panels to 20 to workaround issue …
dandelany Apr 15, 2026
cacb94b
suppressSizeToFit on workspace file browser to preserve users' saved …
dandelany Apr 15, 2026
5df2f19
update ConsoleLog to not wrap everything in pre tags, update action r…
dandelany Apr 16, 2026
97b47c5
linting
dandelany Apr 16, 2026
269d72e
fix action tab styling
AaronPlave Apr 16, 2026
4cfedc3
Action tab content switching fixes
AaronPlave Apr 16, 2026
0c16d5d
Style fix
AaronPlave Apr 16, 2026
920d364
fix: improve workspace file browser column sizing and add reset option
AaronPlave Apr 17, 2026
f73712b
Fix panel sizing after closing and re-opening
AaronPlave Apr 17, 2026
1578210
Fixes and cleanup
AaronPlave Apr 17, 2026
c744e6c
Preserve user-driven name column width changes. Add reset layout butt…
AaronPlave Apr 21, 2026
444fb47
Bug fix
AaronPlave Apr 21, 2026
be990d5
Refactor
AaronPlave Apr 21, 2026
355f61a
Revert change
AaronPlave Apr 21, 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
21 changes: 14 additions & 7 deletions e2e-tests/fixtures/Action.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { expect, Locator, Page } from '@playwright/test';
import { expect, type Locator, type Page } from '@playwright/test';
import { adjectives, animals, colors, uniqueNamesGenerator } from 'unique-names-generator';
import { setFileInputByFilepath } from '../utilities/helpers';

export class Action {
actionDescription: string = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] });
actionName: string = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] });
actionPath: string = 'e2e-tests/data/aerie-action-demo.js';
actionsSidebarTab: Locator;
createActionButton: Locator;
createModal: Locator;
actionsSidebarTab!: Locator;
createActionButton!: Locator;
createModal!: Locator;

constructor(
public page: Page,
Expand Down Expand Up @@ -85,15 +85,22 @@ export class Action {
// Verify we navigated to the run detail view
await expect(this.page.getByRole('heading', { name: /Run #\d+/ })).toBeVisible({ timeout: 15000 });
// Wait for a terminal status (Complete or Failed) in the main content area
const mainContent = this.page.getByRole('main');
await expect(mainContent.getByLabel('Complete').or(mainContent.getByLabel('Failed'))).toBeVisible({
await expect(
this.page
.getByRole('tabpanel')
.getByRole('button', { name: `Complete ${this.actionName}` })
.or(this.page.getByRole('tabpanel').getByRole('button', { name: `Failed ${this.actionName}` })),
).toBeVisible({
timeout: 30000,
});
}

async selectActionInSidebar(): Promise<void> {
// Click the action in the sidebar list (scoped to complementary to avoid matching other elements)
await this.page.getByRole('complementary').getByRole('button', { name: this.actionName }).click();
await this.page
.getByRole('tabpanel')
.getByRole('button', { name: `${this.actionName} Last run` })
.click();
await expect(this.page.getByRole('heading', { name: this.actionName })).toBeVisible();
}

Expand Down
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
Loading
Loading