Skip to content

Commit

Permalink
Plan Import/Export UI Improvements (#1415)
Browse files Browse the repository at this point in the history
* add export plan logic to plans table
* add export plan logic to plan nav menu
* add import validation error handling
* add `version` to plan transfer
* add json stream parsing
* change plans page to select plan before navigating
  • Loading branch information
duranb authored Aug 7, 2024
1 parent 0d552ac commit 0ee4f3c
Show file tree
Hide file tree
Showing 27 changed files with 1,695 additions and 583 deletions.
17 changes: 3 additions & 14 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
{
"label": "e2e Debug",
"type": "shell",
"dependsOn": ["Aerie UI"],
"command": "nvm use && npm run test:e2e:debug",
"detail": "Task to run the e2e tests in debug mode."
},
Expand All @@ -131,28 +132,16 @@
{
"label": "e2e Tests",
"type": "shell",
"dependsOn": ["Build UI"],
"dependsOn": ["Aerie UI"],
"command": "nvm use && npm run test:e2e",
"detail": "Task to run e2e tests"
},
{
"label": "e2e Rerun",
"type": "shell",
"command": "nvm use && npm run test:e2e",
"detail": "Task to rerun e2e tests without rebuilding the UI."
},
{
"label": "e2e Tests - With UI",
"type": "shell",
"dependsOn": ["Build UI"],
"dependsOn": ["Aerie UI"],
"command": "nvm use && npm run test:e2e:with-ui",
"detail": "Task to run e2e tests with Playwright UI."
},
{
"label": "e2e Tests - With UI Rerun",
"type": "shell",
"command": "nvm use && npm run test:e2e:with-ui",
"detail": "Task to run e2e tests with Playwright UI without rebuilding the UI."
}
]
}
4 changes: 3 additions & 1 deletion docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ This document describes the testing development workflow. End-to-end tests are r

## End-to-end

All end-to-end tests assume a production build of the project is available:
All end-to-end tests assume a production build of the project is available if run from CI:

```sh
npm run build
```

If you are running the tests locally, then the above step is not needed. Playwright will be using your local dev server rather than starting up its own node server that uses the `/build` directory.

All end-to-end tests also assume all Aerie services are running and available on `localhost`. See the example [docker-compose-test.yml](../docker-compose-test.yml) for an example of how to run the complete Aerie system. Notice we disable authentication for simplicity when running our end-to-end tests. You can reference the [Aerie deployment documentation](https://github.com/NASA-AMMOS/aerie/tree/develop/deployment) for more detailed deployment information.

To execute end-to-end tests normally (i.e. not in debug mode), use the following command:
Expand Down
11 changes: 11 additions & 0 deletions e2e-tests/data/banana-plan-export.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"activities": [],
"duration": "24:00:00",
"id": 59,
"model_id": 1,
"name": "Banana Plan",
"simulation_arguments": {},
"start_time": "2024-08-02T00:00:00+00:00",
"tags": [],
"version": "2"
}
5 changes: 5 additions & 0 deletions e2e-tests/fixtures/Constraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export class Constraints {

async createConstraint(baseURL: string | undefined) {
await expect(this.saveButton).toBeDisabled();

// TODO: Potentially fix this in component. The loading of monaco causes the page fields to reset
// so we need to wait until the page is fully loaded
await this.page.getByText('Loading Editor...').waitFor({ state: 'detached' });

await this.fillConstraintName();
await this.fillConstraintDescription();
await this.fillConstraintDefinition();
Expand Down
29 changes: 29 additions & 0 deletions e2e-tests/fixtures/Plans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export class Plans {
createButton: Locator;
durationDisplay: Locator;
endTime: string = '2022-006T00:00:00';
importButton: Locator;
importFilePath: string = 'e2e-tests/data/banana-plan-export.json';
inputEndTime: Locator;
inputFile: Locator;
inputModel: Locator;
inputModelSelector: string = 'select[name="model"]';
inputName: Locator;
Expand Down Expand Up @@ -86,6 +89,12 @@ export class Plans {
await this.inputEndTime.evaluate(e => e.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })));
}

async fillInputFile(importFilePath: string = this.importFilePath) {
await this.inputFile.focus();
await this.inputFile.setInputFiles(importFilePath);
await this.inputFile.evaluate(e => e.blur());
}

async fillInputName(planName = this.planName) {
await this.inputName.focus();
await this.inputName.fill(planName);
Expand Down Expand Up @@ -131,6 +140,24 @@ export class Plans {
await this.page.waitForTimeout(250);
}

async importPlan(planName = this.planName) {
await expect(this.tableRow(planName)).not.toBeVisible();
await this.importButton.click();
await this.selectInputModel();
await this.fillInputFile();
await this.fillInputName(planName);
await this.createButton.waitFor({ state: 'attached' });
await this.createButton.waitFor({ state: 'visible' });
await this.createButton.isEnabled({ timeout: 500 });
await this.createButton.click();
await this.filterTable(planName);
await this.tableRow(planName).waitFor({ state: 'attached' });
await this.tableRow(planName).waitFor({ state: 'visible' });
const planId = await this.getPlanId(planName);
this.planId = planId;
return planId;
}

async selectInputModel() {
const value = await getOptionValueFromText(this.page, this.inputModelSelector, this.models.modelName);
await this.inputModel.focus();
Expand All @@ -148,7 +175,9 @@ export class Plans {
this.confirmModalDeleteButton = this.confirmModal.getByRole('button', { name: 'Delete' });
this.createButton = page.getByRole('button', { name: 'Create' });
this.durationDisplay = page.locator('input[name="duration"]');
this.importButton = page.getByRole('button', { name: 'Import' });
this.inputEndTime = page.locator('input[name="end-time"]');
this.inputFile = page.locator('input[name="file"]');
this.inputModel = page.locator(this.inputModelSelector);
this.inputName = page.locator('input[name="name"]');
this.inputStartTime = page.locator('input[name="start-time"]');
Expand Down
5 changes: 5 additions & 0 deletions e2e-tests/fixtures/SchedulingConditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export class SchedulingConditions {

async createSchedulingCondition(baseURL: string | undefined) {
await expect(this.saveButton).toBeDisabled();

// TODO: Potentially fix this in component. The loading of monaco causes the page fields to reset
// so we need to wait until the page is fully loaded
await this.page.getByText('Loading Editor...').waitFor({ state: 'detached' });

await this.fillConditionName();
await this.fillConditionDescription();
await this.fillConditionDefinition();
Expand Down
5 changes: 5 additions & 0 deletions e2e-tests/fixtures/SchedulingGoals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export class SchedulingGoals {

async createSchedulingGoal(baseURL: string | undefined, goalName: string) {
await expect(this.saveButton).toBeDisabled();

// TODO: Potentially fix this in component. The loading of monaco causes the page fields to reset
// so we need to wait until the page is fully loaded
await this.page.getByText('Loading Editor...').waitFor({ state: 'detached' });

await this.fillGoalName(goalName);
await this.fillGoalDescription();
await this.fillGoalDefinition();
Expand Down
5 changes: 5 additions & 0 deletions e2e-tests/tests/plans.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,9 @@ test.describe.serial('Plans', () => {
test('Delete plan', async () => {
await plans.deletePlan();
});

test('Import plan', async () => {
await plans.importPlan();
await plans.deletePlan();
});
});
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@nasa-jpl/aerie-ampcs": "^1.0.5",
"@nasa-jpl/seq-json-schema": "^1.0.20",
"@nasa-jpl/stellar": "^1.1.18",
"@streamparser/json": "^0.0.17",
"@sveltejs/adapter-node": "5.0.1",
"@sveltejs/kit": "^2.5.4",
"ag-grid-community": "31.2.0",
Expand Down
1 change: 1 addition & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run preview',
port: 3000,
reuseExistingServer: !process.env.CI,
},
};

Expand Down
5 changes: 5 additions & 0 deletions src/assets/export.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/import.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 56 additions & 1 deletion src/components/menus/PlanMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
import { showPlanBranchesModal, showPlanMergeRequestsModal } from '../../utilities/modal';
import { permissionHandler } from '../../utilities/permissionHandler';
import { featurePermissions } from '../../utilities/permissions';
import { exportPlan } from '../../utilities/plan';
import Menu from '../menus/Menu.svelte';
import MenuItem from '../menus/MenuItem.svelte';
import ProgressRadial from '../ui/ProgressRadial.svelte';
import MenuDivider from './MenuDivider.svelte';
export let plan: Plan;
Expand All @@ -25,6 +27,8 @@
let hasCreatePlanBranchPermission: boolean = false;
let hasCreateSnapshotPermission: boolean = false;
let planMenu: Menu;
let planExportAbortController: AbortController | null = null;
let planExportProgress: number | null = null;
$: hasCreateMergeRequestPermission = plan.parent_plan
? featurePermissions.planBranch.canCreateRequest(
Expand All @@ -43,18 +47,50 @@
function createMergePlanBranchRequest() {
effects.createPlanBranchRequest(plan, 'merge', user);
planMenu.hide();
}
function createPlanBranch() {
effects.createPlanBranch(plan, user);
planMenu.hide();
}
function createPlanSnapshot() {
effects.createPlanSnapshot(plan, user);
planMenu.hide();
}
async function onExportPlan() {
if (planExportAbortController) {
planExportAbortController.abort();
}
planExportProgress = 0;
planExportAbortController = new AbortController();
if (planExportAbortController && !planExportAbortController.signal.aborted) {
await exportPlan(
plan,
user,
(progress: number) => {
planExportProgress = progress;
},
undefined,
planExportAbortController.signal,
);
}
planExportProgress = null;
}
function onCancelExportPlan() {
planExportAbortController?.abort();
planExportAbortController = null;
planExportProgress = null;
}
function viewSnapshotHistory() {
viewTogglePanel({ state: true, type: 'right', update: { rightComponentTop: 'PlanMetadataPanel' } });
planMenu.hide();
}
function showPlanBranches() {
Expand All @@ -63,6 +99,7 @@
function showPlanMergeRequests() {
showPlanMergeRequestsModal(user);
planMenu.hide();
}
</script>

Expand All @@ -76,7 +113,7 @@

<div class="plan-menu st-typography-medium" role="none" on:click|stopPropagation={() => planMenu.toggle()}>
<div class="plan-title">{plan.name}<ChevronDownIcon /></div>
<Menu bind:this={planMenu}>
<Menu hideAfterClick={false} bind:this={planMenu}>
<MenuItem
use={[
[
Expand Down Expand Up @@ -138,6 +175,17 @@
<MenuItem on:click={viewSnapshotHistory}>
<div class="column-name">View Snapshot History</div>
</MenuItem>
<MenuDivider />
<MenuItem on:click={planExportProgress === null ? onExportPlan : onCancelExportPlan}>
{#if planExportProgress === null}
Export plan as .json
{:else}
<div class="cancel-plan-export">
Cancel plan export
<ProgressRadial progress={planExportProgress} size={16} strokeWidth={1} />
</div>
{/if}
</MenuItem>
</Menu>
</div>
{#if plan.child_plans.length > 0}
Expand Down Expand Up @@ -193,4 +241,11 @@
cursor: pointer;
user-select: none;
}
.cancel-plan-export {
--progress-radial-background: var(--st-gray-20);
align-items: center;
column-gap: 0.25rem;
display: flex;
}
</style>
Loading

0 comments on commit 0ee4f3c

Please sign in to comment.