Skip to content

Commit

Permalink
Shared Constraints MVP (#1118)
Browse files Browse the repository at this point in the history
* split constraints into metadata and definitions
* add reference model selection to constraint editor
* add radio button component
* update ag-grid to take advantage of cell type feature
  • Loading branch information
duranb authored Feb 29, 2024
1 parent 1b33869 commit 87319d9
Show file tree
Hide file tree
Showing 48 changed files with 2,161 additions and 894 deletions.
12 changes: 0 additions & 12 deletions e2e-tests/fixtures/Constraints.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { adjectives, animals, colors, uniqueNamesGenerator } from 'unique-names-generator';
import { getOptionValueFromText } from '../utilities/selectors.js';
import { Models } from './Models.js';

export class Constraints {
Expand All @@ -26,14 +25,12 @@ export class Constraints {

async createConstraint(baseURL: string | undefined) {
await expect(this.saveButton).toBeDisabled();
await this.selectModel();
await this.fillConstraintName();
await this.fillConstraintDescription();
await this.fillConstraintDefinition();
await expect(this.saveButton).not.toBeDisabled();
await this.saveButton.click();
await this.page.waitForURL(`${baseURL}/constraints/edit/*`);
await expect(this.saveButton).not.toBeDisabled();
await expect(this.closeButton).not.toBeDisabled();
await this.closeButton.click();
await this.page.waitForURL(`${baseURL}/constraints`);
Expand All @@ -43,7 +40,6 @@ export class Constraints {
await this.goto();
await expect(this.tableRow).toBeVisible();
await expect(this.tableRowDeleteButton).not.toBeVisible();

await this.tableRow.hover();
await this.tableRowDeleteButton.waitFor({ state: 'attached' });
await this.tableRowDeleteButton.waitFor({ state: 'visible' });
Expand Down Expand Up @@ -85,14 +81,6 @@ export class Constraints {
await this.page.waitForTimeout(250);
}

async selectModel() {
await this.page.waitForSelector(`option:has-text("${this.models.modelName}")`, { state: 'attached' });
const value = await getOptionValueFromText(this.page, this.inputConstraintModelSelector, this.models.modelName);
await this.inputConstraintModel.focus();
await this.inputConstraintModel.selectOption(value);
await this.inputConstraintModel.evaluate(e => e.blur());
}

updatePage(page: Page): void {
this.closeButton = page.locator(`button:has-text("Close")`);
this.confirmModal = page.locator(`.modal:has-text("Delete Constraint")`);
Expand Down
19 changes: 17 additions & 2 deletions e2e-tests/fixtures/Plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class Plan {
analyzeButton: Locator;
appError: Locator;
constraintListItemSelector: string;
constraintManageButton: Locator;
constraintNewButton: Locator;
gridMenu: Locator;
gridMenuButton: Locator;
Expand Down Expand Up @@ -74,12 +75,18 @@ export class Plan {
}

async createConstraint(baseURL: string | undefined) {
const [newConstraintPage] = await Promise.all([this.page.waitForEvent('popup'), this.constraintNewButton.click()]);
await this.constraintManageButton.click();
const [newConstraintPage] = await Promise.all([
this.page.waitForEvent('popup'),
await this.constraintNewButton.click(),
]);
this.constraints.updatePage(newConstraintPage);
await newConstraintPage.waitForURL(`${baseURL}/constraints/new`);
await newConstraintPage.waitForURL(`${baseURL}/constraints/new?modelId=*`);
await this.constraints.createConstraint(baseURL);
await newConstraintPage.close();
this.constraints.updatePage(this.page);
await this.page.getByRole('row', { name: this.constraints.constraintName }).getByRole('checkbox').click();
await this.page.getByRole('button', { name: 'Update' }).click();
await this.page.waitForSelector(this.constraintListItemSelector, { state: 'visible', strict: true });
}

Expand Down Expand Up @@ -148,6 +155,13 @@ export class Plan {
await this.page.waitForTimeout(250);
}

async removeConstraint() {
await this.constraintManageButton.click();
await this.page.getByRole('row', { name: this.constraints.constraintName }).getByRole('checkbox').click();

Check failure on line 160 in e2e-tests/fixtures/Plan.ts

View workflow job for this annotation

GitHub Actions / test

[e2e tests] › tests/constraints.test.ts:54:3 › Constraints › Delete constraint

1) [e2e tests] › tests/constraints.test.ts:54:3 › Constraints › Delete constraint ──────────────── Error: locator.click: Target closed =========================== logs =========================== waiting for getByRole('row', { name: 'zesty_pink_chicken' }).getByRole('checkbox') ============================================================ at fixtures/Plan.ts:160 158 | async removeConstraint() { 159 | await this.constraintManageButton.click(); > 160 | await this.page.getByRole('row', { name: this.constraints.constraintName }).getByRole('checkbox').click(); | ^ 161 | await this.page.getByRole('button', { name: 'Update' }).click(); 162 | await this.page.locator(this.constraintListItemSelector).waitFor({ state: 'detached' }); 163 | } at Plan.removeConstraint (/home/runner/work/***-ui/***-ui/e2e-tests/fixtures/Plan.ts:160:103) at /home/runner/work/***-ui/***-ui/e2e-tests/tests/constraints.test.ts:55:5
await this.page.getByRole('button', { name: 'Update' }).click();
await this.page.locator(this.constraintListItemSelector).waitFor({ state: 'detached' });
}

async runAnalysis() {
await this.analyzeButton.click();
await this.page.waitForSelector(this.schedulingStatusSelector('Incomplete'), { state: 'attached', strict: true });
Expand Down Expand Up @@ -287,6 +301,7 @@ export class Plan {
this.activitiesTableFirstRow = page
.locator(`div.ag-theme-stellar.table .ag-center-cols-container > .ag-row`)
.nth(0);
this.constraintManageButton = page.locator(`button[name="manage-constraints"]`);
this.constraintNewButton = page.locator(`button[name="new-constraint"]`);
this.gridMenu = page.locator('.grid-menu > .menu > .menu-slot');
this.gridMenuButton = page.locator('.grid-menu');
Expand Down
1 change: 1 addition & 0 deletions e2e-tests/tests/constraints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ test.describe.serial('Constraints', () => {
});

test('Delete constraint', async () => {
await plan.removeConstraint();
await constraints.deleteConstraint();
});
});
14 changes: 7 additions & 7 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@nasa-jpl/stellar": "^1.1.18",
"@sveltejs/adapter-node": "1.2.4",
"@sveltejs/kit": "1.20.5",
"ag-grid-community": "29.3.3",
"ag-grid-community": "30.2.0",
"ajv": "^8.12.0",
"bootstrap": "^5.3.0",
"bootstrap-icons": "^1.11.0",
Expand Down
5 changes: 1 addition & 4 deletions src/components/activity/ActivityDirectivesTablePanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@
export let user: User | null;
type ActivityDirectiveColumns = keyof ActivityDirective | 'derived_start_time';
interface ActivityDirectiveColDef extends ColDef<ActivityDirective> {
field: ActivityDirectiveColumns;
}
type ActivityDirectiveColDef = ColDef<ActivityDirective>;
let activityDirectivesTable: ViewTable | undefined;
let autoSizeColumns: AutoSizeColumns | undefined;
Expand Down Expand Up @@ -103,7 +101,6 @@
sortable: true,
},
derived_start_time: {
field: 'derived_start_time',
filter: 'text',
headerName: 'Absolute Start Time (UTC)',
hide: true,
Expand Down
6 changes: 1 addition & 5 deletions src/components/activity/ActivitySpansTablePanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@
export let gridSection: ViewGridSection;
type SpanColumns = keyof Span | 'derived_start_time' | 'derived_end_time';
interface SpanColDef extends ColDef<Span> {
field: SpanColumns;
}
type SpanColDef = ColDef<Span>;
let activitySpansTable: ViewTable | undefined;
let autoSizeColumns: AutoSizeColumns | undefined;
Expand All @@ -48,7 +46,6 @@
sortable: true,
},
derived_start_time: {
field: 'derived_start_time',
filter: 'text',
headerName: 'Absolute Start Time (UTC)',
hide: true,
Expand All @@ -62,7 +59,6 @@
},
},
derived_end_time: {
field: 'derived_end_time',
filter: 'text',
headerName: 'Absolute End Time (UTC)',
hide: true,
Expand Down
3 changes: 1 addition & 2 deletions src/components/app/Nav.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts">
import { invalidateAll } from '$app/navigation';
import AppMenu from '../../components/menus/AppMenu.svelte';
import type { User, UserRole } from '../../types/app';
import { getTarget } from '../../utilities/generic';
Expand All @@ -15,7 +14,7 @@
const { value } = getTarget(event);
if (value) {
await changeUserRole(value as string);
await invalidateAll();
window.location.reload();
}
}
</script>
Expand Down
1 change: 0 additions & 1 deletion src/components/console/views/ActivityErrors.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
width: 60,
},
{
field: 'fields',
filter: 'number',
headerName: '# fields',
resizable: true,
Expand Down
54 changes: 48 additions & 6 deletions src/components/constraints/ConstraintEditor.svelte
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
<svelte:options immutable={true} />

<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { models } from '../../stores/plan';
import type { User } from '../../types/app';
import type { DropdownOptions, SelectedDropdownOptionValue } from '../../types/dropdown';
import type { Monaco, TypeScriptFile } from '../../types/monaco';
import effects from '../../utilities/effects';
import MonacoEditor from '../ui/MonacoEditor.svelte';
import Panel from '../ui/Panel.svelte';
import SearchableDropdown from '../ui/SearchableDropdown.svelte';
import SectionTitle from '../ui/SectionTitle.svelte';
export let constraintDefinition: string = '';
export let constraintModelId: number | null = null;
export let constraintPlanId: number | null = null;
export let referenceModelId: number | null = null;
export let readOnly: boolean = false;
export let title: string = 'Constraint - Definition Editor';
export let user: User | null;
const dispatch = createEventDispatcher();
let constraintsTsFiles: TypeScriptFile[];
let modelOptions: DropdownOptions = [];
let monaco: Monaco;
$: if (constraintModelId !== null && constraintPlanId !== null) {
effects
.getTsFilesConstraints(constraintModelId, constraintPlanId, user)
.then(tsFiles => (constraintsTsFiles = tsFiles));
$: modelOptions = $models.map(({ id, name, version }) => ({
display: `${name} (Version: ${version})`,
hasSelectPermission: true,
value: id,
}));
$: if (referenceModelId !== null) {
effects.getTsFilesConstraints(referenceModelId, user).then(tsFiles => (constraintsTsFiles = tsFiles));
} else {
constraintsTsFiles = [];
}
$: if (monaco !== undefined && constraintsTsFiles !== undefined) {
Expand All @@ -33,11 +45,28 @@
typescriptDefaults.setCompilerOptions({ ...options, lib: ['esnext'], strictNullChecks: true });
typescriptDefaults.setExtraLibs(constraintsTsFiles);
}
function onSelectReferenceModel(event: CustomEvent<SelectedDropdownOptionValue>) {
const { detail: modelId } = event;
dispatch('selectReferenceModel', modelId);
}
</script>

<Panel overflowYBody="hidden">
<svelte:fragment slot="header">
<SectionTitle>{title}</SectionTitle>
<div class="dropdown-select">
{#if !readOnly}
<label for="models">Reference Model:</label>
<SearchableDropdown
selectedOptionValue={referenceModelId}
placeholder="No Model"
name="models"
options={modelOptions}
on:selectOption={onSelectReferenceModel}
/>
{/if}
</div>
</svelte:fragment>

<svelte:fragment slot="body">
Expand All @@ -56,3 +85,16 @@
/>
</svelte:fragment>
</Panel>

<style>
.dropdown-select {
align-items: center;
column-gap: 0.5rem;
display: grid;
grid-template-columns: min-content auto;
}
.dropdown-select label {
white-space: nowrap;
}
</style>
Loading

0 comments on commit 87319d9

Please sign in to comment.