From d25af7bb56ea5dc29c0e56aa1678098fb713b828 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 19 Mar 2024 19:10:01 -0600 Subject: [PATCH] Shared scheduling MVP (#1149) * split scheduling into metadata and definition * added AssociationForm and DefinitionEditor components * order analyses by descending id * fix editors not loading in preselected models on page load * filter out scheduling items that are private * make constraints/scheduling columns 50/50 --------- Co-authored-by: Aaron Plave --- e2e-tests/fixtures/Constraints.ts | 4 +- e2e-tests/fixtures/Plan.ts | 21 +- e2e-tests/fixtures/SchedulingConditions.ts | 16 +- e2e-tests/fixtures/SchedulingGoals.ts | 15 +- e2e-tests/tests/scheduling.test.ts | 1 + .../constraints/ConstraintForm.svelte | 659 +++----------- src/components/constraints/Constraints.svelte | 95 +- ...ManagePlanSchedulingConditionsModal.svelte | 351 ++++++++ .../ManagePlanSchedulingGoalsModal.svelte | 341 +++++++ src/components/scheduling/Scheduling.svelte | 98 +- .../SchedulingConditionsPanel.svelte | 148 +++- .../scheduling/SchedulingEditor.svelte | 53 -- .../scheduling/SchedulingGoalsPanel.svelte | 164 ++-- .../conditions/SchedulingCondition.svelte | 124 ++- .../SchedulingConditionEditor.svelte | 53 -- .../conditions/SchedulingConditionForm.svelte | 482 +++++----- .../conditions/SchedulingConditions.svelte | 214 +++-- .../scheduling/goals/SchedulingGoal.svelte | 163 ++-- .../goals/SchedulingGoalForm.svelte | 562 ++++-------- .../goals/SchedulingGoalForm.svelte.test.ts | 45 +- .../scheduling/goals/SchedulingGoals.svelte | 251 +++--- .../sequencing/SequenceEditor.svelte | 65 +- .../ui/Association/AssociationForm.svelte | 574 ++++++++++++ .../Association/DefinitionEditor.svelte} | 53 +- src/components/ui/MonacoEditor.svelte | 15 +- src/routes/constraints/edit/[id]/+page.svelte | 2 +- src/routes/constraints/new/+page.svelte | 13 +- src/routes/plans/[id]/+page.svelte | 2 +- src/routes/plans/[id]/+page.ts | 2 +- src/routes/scheduling/+page.svelte | 2 +- src/routes/scheduling/+page.ts | 5 - .../conditions/edit/[id]/+page.svelte | 99 ++- .../scheduling/conditions/edit/[id]/+page.ts | 19 +- .../scheduling/conditions/new/+page.svelte | 26 +- src/routes/scheduling/conditions/new/+page.ts | 16 +- .../scheduling/goals/edit/[id]/+page.svelte | 100 ++- .../scheduling/goals/edit/[id]/+page.ts | 22 +- src/routes/scheduling/goals/new/+page.svelte | 26 +- src/routes/scheduling/goals/new/+page.ts | 16 +- src/stores/constraints.ts | 3 +- src/stores/scheduling.ts | 228 ++--- src/types/constraint.ts | 36 +- src/types/metadata.ts | 34 + src/types/plan.ts | 4 +- src/types/scheduling.ts | 197 ++-- src/types/tags.ts | 26 + src/utilities/effects.ts | 838 ++++++++++++------ src/utilities/gql.ts | 683 +++++++++----- src/utilities/modal.ts | 72 ++ src/utilities/permissions.ts | 235 +++-- src/utilities/scheduling.ts | 45 + 51 files changed, 4518 insertions(+), 2800 deletions(-) create mode 100644 src/components/modals/ManagePlanSchedulingConditionsModal.svelte create mode 100644 src/components/modals/ManagePlanSchedulingGoalsModal.svelte delete mode 100644 src/components/scheduling/SchedulingEditor.svelte delete mode 100644 src/components/scheduling/conditions/SchedulingConditionEditor.svelte create mode 100644 src/components/ui/Association/AssociationForm.svelte rename src/components/{constraints/ConstraintEditor.svelte => ui/Association/DefinitionEditor.svelte} (63%) create mode 100644 src/types/metadata.ts create mode 100644 src/utilities/scheduling.ts diff --git a/e2e-tests/fixtures/Constraints.ts b/e2e-tests/fixtures/Constraints.ts index 76672893ee..196e9cdde0 100644 --- a/e2e-tests/fixtures/Constraints.ts +++ b/e2e-tests/fixtures/Constraints.ts @@ -85,9 +85,9 @@ export class Constraints { this.confirmModal = page.locator(`.modal:has-text("Delete Constraint")`); this.confirmModalDeleteButton = page.locator(`.modal:has-text("Delete Constraint") >> button:has-text("Delete")`); this.inputConstraintDefinition = page.locator('.monaco-editor >> textarea.inputarea'); - this.inputConstraintDescription = page.locator('textarea[name="constraint-description"]'); + this.inputConstraintDescription = page.locator('textarea[name="metadata-description"]'); this.inputConstraintModel = page.locator(this.inputConstraintModelSelector); - this.inputConstraintName = page.locator('input[name="constraint-name"]'); + this.inputConstraintName = page.locator('input[name="metadata-name"]'); this.page = page; this.saveButton = page.locator(`button:has-text("Save")`); this.tableRow = page.locator(`.ag-row:has-text("${this.constraintName}")`); diff --git a/e2e-tests/fixtures/Plan.ts b/e2e-tests/fixtures/Plan.ts index 694a38b60d..7eb1cc18d6 100644 --- a/e2e-tests/fixtures/Plan.ts +++ b/e2e-tests/fixtures/Plan.ts @@ -41,12 +41,14 @@ export class Plan { scheduleButton: Locator; schedulingConditionEnabledCheckbox: Locator; schedulingConditionListItemSelector: string; + schedulingConditionManageButton: Locator; schedulingConditionNewButton: Locator; schedulingGoal: Locator; schedulingGoalDifferenceBadge: Locator; schedulingGoalEnabledCheckboxSelector: (goalName: string) => Locator; schedulingGoalExpand: Locator; schedulingGoalListItemSelector: (goalName: string) => string; + schedulingGoalManageButton: Locator; schedulingGoalNewButton: Locator; schedulingSatisfiedActivity: Locator; schedulingStatusSelector: (status: string) => string; @@ -91,28 +93,34 @@ export class Plan { } async createSchedulingCondition(baseURL: string | undefined) { + await this.schedulingConditionManageButton.click(); const [newSchedulingConditionPage] = await Promise.all([ this.page.waitForEvent('popup'), this.schedulingConditionNewButton.click(), ]); this.schedulingConditions.updatePage(newSchedulingConditionPage); - await newSchedulingConditionPage.waitForURL(`${baseURL}/scheduling/conditions/new?modelId=*&&specId=*`); + await newSchedulingConditionPage.waitForURL(`${baseURL}/scheduling/conditions/new?modelId=*`); await this.schedulingConditions.createSchedulingCondition(baseURL); await newSchedulingConditionPage.close(); this.schedulingConditions.updatePage(this.page); + await this.page.getByRole('row', { name: this.schedulingConditions.conditionName }).getByRole('checkbox').click(); + await this.page.getByRole('button', { name: 'Update' }).click(); await this.page.waitForSelector(this.schedulingConditionListItemSelector, { state: 'visible', strict: true }); } async createSchedulingGoal(baseURL: string | undefined, goalName: string) { + await this.schedulingGoalManageButton.click(); const [newSchedulingGoalPage] = await Promise.all([ this.page.waitForEvent('popup'), this.schedulingGoalNewButton.click(), ]); this.schedulingGoals.updatePage(newSchedulingGoalPage); - await newSchedulingGoalPage.waitForURL(`${baseURL}/scheduling/goals/new?modelId=*&&specId=*`); + await newSchedulingGoalPage.waitForURL(`${baseURL}/scheduling/goals/new?modelId=*`); await this.schedulingGoals.createSchedulingGoal(baseURL, goalName); await newSchedulingGoalPage.close(); this.schedulingGoals.updatePage(this.page); + await this.page.getByRole('row', { name: goalName }).getByRole('checkbox').click(); + await this.page.getByRole('button', { name: 'Update' }).click(); await this.page.waitForSelector(this.schedulingGoalListItemSelector(goalName), { state: 'visible', strict: true }); } @@ -165,6 +173,13 @@ export class Plan { await this.page.locator(this.constraintListItemSelector).waitFor({ state: 'detached' }); } + async removeSchedulingGoal(goalName: string) { + await this.schedulingGoalManageButton.click(); + await this.page.getByRole('row', { name: goalName }).getByRole('checkbox').click(); + await this.page.getByRole('button', { name: 'Update' }).click(); + await this.page.locator(this.schedulingGoalListItemSelector(goalName)).waitFor({ state: 'detached' }); + } + async runAnalysis() { await this.analyzeButton.click(); await this.waitForSchedulingStatus('Incomplete'); @@ -330,6 +345,8 @@ export class Plan { this.roleSelector = page.locator(`.nav select`); this.scheduleButton = page.locator('.header-actions button[aria-label="Schedule"]'); this.analyzeButton = page.locator('.header-actions button[aria-label="Analyze"]'); + this.schedulingGoalManageButton = page.locator(`button[name="manage-goals"]`); + this.schedulingConditionManageButton = page.locator(`button[name="manage-conditions"]`); this.schedulingGoal = page.locator('.scheduling-goal').first(); this.schedulingGoalDifferenceBadge = this.schedulingGoal.locator('.difference-badge'); this.schedulingGoalEnabledCheckboxSelector = (goalName: string) => diff --git a/e2e-tests/fixtures/SchedulingConditions.ts b/e2e-tests/fixtures/SchedulingConditions.ts index 646216bed8..5bea4003d7 100644 --- a/e2e-tests/fixtures/SchedulingConditions.ts +++ b/e2e-tests/fixtures/SchedulingConditions.ts @@ -1,7 +1,6 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { adjectives, animals, colors, uniqueNamesGenerator } from 'unique-names-generator'; import { fillEditorText } from '../utilities/editor.js'; -import { getOptionValueFromText } from '../utilities/selectors.js'; import { Models } from './Models.js'; export class SchedulingConditions { @@ -28,14 +27,12 @@ export class SchedulingConditions { async createSchedulingCondition(baseURL: string | undefined) { await expect(this.saveButton).toBeDisabled(); - await this.selectModel(); await this.fillConditionName(); await this.fillConditionDescription(); await this.fillConditionDefinition(); await expect(this.saveButton).not.toBeDisabled(); await this.saveButton.click(); await this.page.waitForURL(`${baseURL}/scheduling/conditions/edit/*`); - await expect(this.saveButton).not.toBeDisabled(); await expect(this.closeButton).not.toBeDisabled(); await this.closeButton.click(); await this.page.waitForURL(`${baseURL}/scheduling`); @@ -45,7 +42,6 @@ export class SchedulingConditions { 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' }); @@ -86,14 +82,6 @@ export class SchedulingConditions { await this.page.waitForSelector(`input[placeholder="Filter conditions"]`, { state: 'attached' }); } - async selectModel() { - await this.page.waitForSelector(`option:has-text("${this.models.modelName}")`, { state: 'attached' }); - const value = await getOptionValueFromText(this.page, this.inputConditionModelSelector, this.models.modelName); - await this.inputConditionModel.focus(); - await this.inputConditionModel.selectOption(value); - await this.inputConditionModel.evaluate(e => e.blur()); - } - updatePage(page: Page): void { this.closeButton = page.locator(`button:has-text("Close")`); this.confirmModal = page.locator(`.modal:has-text("Delete Scheduling Condition")`); @@ -101,9 +89,9 @@ export class SchedulingConditions { `.modal:has-text("Delete Scheduling Condition") >> button:has-text("Delete")`, ); this.inputConditionDefinition = page.locator('.monaco-editor >> textarea.inputarea'); - this.inputConditionDescription = page.locator('textarea[name="condition-description"]'); + this.inputConditionDescription = page.locator('textarea[name="metadata-description"]'); this.inputConditionModel = page.locator(this.inputConditionModelSelector); - this.inputConditionName = page.locator(`input[name="condition-name"]`); + this.inputConditionName = page.locator(`input[name="metadata-name"]`); this.newButton = page.locator(`button:has-text("New")`); this.page = page; this.saveButton = page.locator(`button:has-text("Save")`); diff --git a/e2e-tests/fixtures/SchedulingGoals.ts b/e2e-tests/fixtures/SchedulingGoals.ts index 98967b1bcb..9c111cbd68 100644 --- a/e2e-tests/fixtures/SchedulingGoals.ts +++ b/e2e-tests/fixtures/SchedulingGoals.ts @@ -1,6 +1,5 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { fillEditorText } from '../utilities/editor.js'; -import { getOptionValueFromText } from '../utilities/selectors.js'; import { Models } from './Models.js'; export class SchedulingGoals { @@ -25,14 +24,12 @@ export class SchedulingGoals { async createSchedulingGoal(baseURL: string | undefined, goalName: string) { await expect(this.saveButton).toBeDisabled(); - await this.selectModel(); await this.fillGoalName(goalName); await this.fillGoalDescription(); await this.fillGoalDefinition(); await expect(this.saveButton).not.toBeDisabled(); await this.saveButton.click(); await this.page.waitForURL(`${baseURL}/scheduling/goals/edit/*`); - await expect(this.saveButton).not.toBeDisabled(); await expect(this.closeButton).not.toBeDisabled(); await this.closeButton.click(); await this.page.waitForURL(`${baseURL}/scheduling`); @@ -83,14 +80,6 @@ export class SchedulingGoals { await this.page.waitForSelector(`input[placeholder="Filter goals"]`, { state: 'attached' }); } - async selectModel() { - await this.page.waitForSelector(`option:has-text("${this.models.modelName}")`, { state: 'attached' }); - const value = await getOptionValueFromText(this.page, this.inputGoalModelSelector, this.models.modelName); - await this.inputGoalModel.focus(); - await this.inputGoalModel.selectOption(value); - await this.inputGoalModel.evaluate(e => e.blur()); - } - updatePage(page: Page): void { this.closeButton = page.locator(`button:has-text("Close")`); this.confirmModal = page.locator(`.modal:has-text("Delete Scheduling Goal")`); @@ -98,9 +87,9 @@ export class SchedulingGoals { `.modal:has-text("Delete Scheduling Goal") >> button:has-text("Delete")`, ); this.inputGoalDefinition = page.locator('.monaco-editor >> textarea.inputarea'); - this.inputGoalDescription = page.locator('textarea[name="goal-description"]'); + this.inputGoalDescription = page.locator('textarea[name="metadata-description"]'); this.inputGoalModel = page.locator(this.inputGoalModelSelector); - this.inputGoalName = page.locator(`input[name="goal-name"]`); + this.inputGoalName = page.locator(`input[name="metadata-name"]`); this.newButton = page.locator(`button:has-text("New")`); this.page = page; this.saveButton = page.locator(`button:has-text("Save")`); diff --git a/e2e-tests/tests/scheduling.test.ts b/e2e-tests/tests/scheduling.test.ts index 55f3a19bf3..0dd43d941f 100644 --- a/e2e-tests/tests/scheduling.test.ts +++ b/e2e-tests/tests/scheduling.test.ts @@ -117,6 +117,7 @@ test.describe.serial('Scheduling', () => { }); test('Delete scheduling goal', async () => { + await plan.removeSchedulingGoal(goalName1); await schedulingGoals.deleteSchedulingGoal(goalName1); }); }); diff --git a/src/components/constraints/ConstraintForm.svelte b/src/components/constraints/ConstraintForm.svelte index 16acd07383..1edf51f94f 100644 --- a/src/components/constraints/ConstraintForm.svelte +++ b/src/components/constraints/ConstraintForm.svelte @@ -3,36 +3,16 @@ - - - - {mode === 'create' ? 'New Constraint' : 'Edit Constraint'} - -
- - {#if mode === 'edit' && saveButtonEnabled} - - {/if} - -
-
- - -
- - -
{constraintNameError}
-
- -
- - -
- -
- -