Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plan Import/Export UI Improvements #1415

Merged
merged 25 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e15d9b9
add export plan logic to plans table
duranb Jul 30, 2024
ddc0e78
add `version` to plan transfer
duranb Jul 30, 2024
5e3a6fc
add json stream parsing
duranb Jul 30, 2024
b083857
add import/export to plans page
duranb Aug 1, 2024
4f62211
fix more flaky tests
duranb Aug 2, 2024
189b99a
fix incorrect icons used
duranb Aug 2, 2024
2a3c5a5
add basic e2e test for import
duranb Aug 2, 2024
77fa55d
fix duration parsing
duranb Aug 2, 2024
f4091ac
fix lint
duranb Aug 2, 2024
518eb74
make plan import input field more strict
duranb Aug 2, 2024
6703c6e
add download progress to all places that export plans
duranb Aug 6, 2024
2b1016c
chunk plan downloading
duranb Aug 6, 2024
7570c30
align buttons
duranb Aug 6, 2024
346264f
fix test
duranb Aug 6, 2024
6228af1
update testing docs to reflect playwright config dev change
duranb Aug 6, 2024
7c7e613
add progress to plan menu export
duranb Aug 6, 2024
20fbab5
update export progress design
duranb Aug 6, 2024
8ca3d5d
clicking plan export in plan menu no longer dismisses it
duranb Aug 6, 2024
a7f5181
lighten import background
duranb Aug 6, 2024
ffb507c
add open button to plans table
duranb Aug 7, 2024
18b9838
add background to plan download progress radial
duranb Aug 7, 2024
3a881b5
set plan creation state for plan import
duranb Aug 7, 2024
80f2262
disable plan creation button during creation
duranb Aug 7, 2024
708fa64
properly reset plan form fields on plan creation
duranb Aug 7, 2024
c0e1501
hide plan import form with CSS instead
duranb Aug 7, 2024
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
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,
AaronPlave marked this conversation as resolved.
Show resolved Hide resolved
},
};

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
AaronPlave marked this conversation as resolved.
Show resolved Hide resolved
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 />
AaronPlave marked this conversation as resolved.
Show resolved Hide resolved
<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
Loading