diff --git a/e2e-tests/fixtures/Constraints.ts b/e2e-tests/fixtures/Constraints.ts index c2463f4dc6..5d15b85a7f 100644 --- a/e2e-tests/fixtures/Constraints.ts +++ b/e2e-tests/fixtures/Constraints.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 { Models } from './Models.js'; export class Constraints { closeButton: Locator; @@ -19,10 +18,7 @@ export class Constraints { tableRow: Locator; tableRowDeleteButton: Locator; - constructor( - public page: Page, - public models: Models, - ) { + constructor(public page: Page) { this.constraintName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] }); this.updatePage(page); } @@ -83,6 +79,10 @@ export class Constraints { await this.page.waitForTimeout(250); } + async gotoNew() { + await this.page.goto('/constraints/new', { waitUntil: 'networkidle' }); + } + updatePage(page: Page): void { this.closeButton = page.locator(`button:has-text("Close")`); this.confirmModal = page.locator(`.modal:has-text("Delete Constraint")`); diff --git a/e2e-tests/fixtures/Model.ts b/e2e-tests/fixtures/Model.ts new file mode 100644 index 0000000000..37ea77644a --- /dev/null +++ b/e2e-tests/fixtures/Model.ts @@ -0,0 +1,134 @@ +import { expect, type Locator, type Page } from '@playwright/test'; +import { Constraints } from './Constraints.js'; +import { Models } from './Models.js'; +import { SchedulingConditions } from './SchedulingConditions.js'; +import { SchedulingGoals } from './SchedulingGoals.js'; + +export class Model { + associationTable: Locator; + closeButton: Locator; + conditionRadioButton: Locator; + confirmModal: Locator; + confirmModalDeleteButton: Locator; + constraintRadioButton: Locator; + deleteButton: Locator; + descriptionInput: Locator; + goalRadioButton: Locator; + libraryRadioButton: Locator; + modelRadioButton: Locator; + nameInput: Locator; + newPlanButton: Locator; + saveButton: Locator; + versionInput: Locator; + + constructor( + public page: Page, + public models: Models, + public constraints: Constraints, + public schedulingGoals: SchedulingGoals, + public schedulingConditions: SchedulingConditions, + ) { + this.updatePage(page); + } + + async close() { + await this.closeButton.click(); + await expect(this.page).toHaveURL('.*/models$'); + } + + async deleteModel() { + await expect(this.confirmModal).not.toBeVisible(); + await this.deleteButton.click(); + await this.confirmModal.waitFor({ state: 'attached' }); + await this.confirmModal.waitFor({ state: 'visible' }); + await expect(this.confirmModal).toBeVisible(); + + await expect(this.confirmModalDeleteButton).toBeVisible(); + await this.confirmModalDeleteButton.click(); + } + + /** + * Wait for Hasura events to finish seeding the database after a model is created. + * If we do not wait then navigation to the plan will fail because the data is not there yet. + * If your tests fail then the timeout might be too short. + * Re-run the tests and increase the timeout if you get consistent failures. + */ + async goto() { + await this.page.waitForTimeout(1200); + await this.page.goto(`/models/${this.models.modelId}`, { waitUntil: 'networkidle' }); + await this.page.waitForTimeout(250); + } + + async saveModel() { + await expect(this.saveButton).toBeVisible(); + await this.saveButton.click(); + await expect(this.saveButton).toBeVisible(); + } + + async switchToConditions() { + await this.conditionRadioButton.click(); + this.updatePage(this.page); + await expect(this.page.getByText('Condition - Definition')).toBeVisible(); + } + + async switchToConstraints() { + await this.constraintRadioButton.click(); + this.updatePage(this.page); + await expect(this.page.getByText('Constraint - Definition')).toBeVisible(); + } + + async switchToGoals() { + await this.goalRadioButton.click(); + this.updatePage(this.page); + await expect(this.page.getByText('Goal - Definition')).toBeVisible(); + } + + async switchToLibraryView() { + await this.libraryRadioButton.click(); + this.updatePage(this.page); + await expect(this.associationTable).toBeVisible(); + } + + async switchToModelView() { + await this.modelRadioButton.click(); + this.updatePage(this.page); + await expect(this.associationTable).not.toBeVisible(); + } + + async updateDescription(modelDescription: string) { + await this.descriptionInput.click(); + await this.descriptionInput.fill(modelDescription); + await expect(this.descriptionInput).toHaveValue(modelDescription); + } + + async updateName(modelName: string) { + await this.nameInput.click(); + await this.nameInput.fill(modelName); + await expect(this.nameInput).toHaveValue(modelName); + } + + async updatePage(page: Page): Promise> { + this.closeButton = page.getByRole('button', { name: 'Close' }); + this.conditionRadioButton = page.getByRole('button', { name: 'Conditions' }); + this.constraintRadioButton = page.getByRole('button', { name: 'Constraints' }); + this.deleteButton = page.getByRole('button', { name: 'Delete model' }); + this.descriptionInput = page.locator('textarea[name="description"]'); + this.goalRadioButton = page.getByRole('button', { name: 'Goals' }); + this.goalRadioButton = page.getByRole('button', { name: 'Goals' }); + this.libraryRadioButton = page.getByRole('button', { name: 'Library' }); + this.modelRadioButton = page.getByRole('button', { exact: true, name: 'Model' }); + this.nameInput = page.locator('input[name="name"]'); + this.newPlanButton = page.getByRole('button', { name: 'New plan with model' }); + this.versionInput = page.locator('input[name="version"]'); + this.associationTable = page.getByRole('treegrid'); + this.saveButton = page.getByRole('button', { name: 'Save' }); + this.confirmModal = page.locator(`.modal:has-text("Delete Model")`); + this.confirmModalDeleteButton = page.locator(`.modal:has-text("Delete Model") >> button:has-text("Delete")`); + } + + async updateVersion(modelVersion: string) { + await this.nameInput.focus(); + await this.nameInput.fill(modelVersion); + await expect(this.nameInput).toHaveValue(modelVersion); + } +} diff --git a/e2e-tests/fixtures/Models.ts b/e2e-tests/fixtures/Models.ts index b81959edb8..811bc4aa7b 100644 --- a/e2e-tests/fixtures/Models.ts +++ b/e2e-tests/fixtures/Models.ts @@ -7,15 +7,18 @@ export class Models { confirmModal: Locator; confirmModalDeleteButton: Locator; createButton: Locator; + createPlanButton: Locator; creatingButton: Locator; inputFile: Locator; inputName: Locator; inputVersion: Locator; jarPath: string = 'e2e-tests/data/banananation-develop.jar'; // TODO: Pull .jar from aerie project. + modelId: string; modelName: string; modelVersion: string = '1.0.0'; tableRow: Locator; tableRowDeleteButton: Locator; + tableRowModelId: Locator; constructor(public page: Page) { this.modelName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] }); @@ -32,9 +35,16 @@ export class Models { await this.fillInputVersion(); await this.fillInputFile(); await this.createButton.click(); + await expect(this.page).toHaveURL(/.*\/models\/\d+/); + await this.goto(); await this.tableRow.waitFor({ state: 'attached' }); await this.tableRow.waitFor({ state: 'visible' }); await expect(this.tableRow).toBeVisible(); + await expect(this.tableRowModelId).toBeVisible(); + const el = await this.tableRowModelId.elementHandle(); + if (el) { + this.modelId = (await el.textContent()) as string; + } } async deleteModel() { @@ -88,6 +98,7 @@ export class Models { this.confirmModalDeleteButton = page.locator(`.modal:has-text("Delete Model") >> button:has-text("Delete")`); this.createButton = page.getByRole('button', { name: 'Create' }); this.creatingButton = page.getByRole('button', { name: 'Creating...' }); + this.createPlanButton = page.getByRole('button', { name: 'New plan with model' }); this.inputFile = page.locator('input[name="file"]'); this.inputName = page.locator('input[name="name"]'); this.inputVersion = page.locator('input[name="version"]'); @@ -96,5 +107,6 @@ export class Models { this.tableRowDeleteButton = page.locator( `.ag-row:has-text("${this.modelName}") >> button[aria-label="Delete Model"]`, ); + this.tableRowModelId = page.locator(`.ag-row:has-text("${this.modelName}") > div >> nth=0`); } } diff --git a/e2e-tests/fixtures/SchedulingConditions.ts b/e2e-tests/fixtures/SchedulingConditions.ts index f0b280d922..b1329b0b43 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 { Models } from './Models.js'; export class SchedulingConditions { closeButton: Locator; @@ -20,10 +19,7 @@ export class SchedulingConditions { tableRow: Locator; tableRowDeleteButton: Locator; - constructor( - public page: Page, - public models: Models, - ) { + constructor(public page: Page) { this.conditionName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] }); this.updatePage(page); } @@ -85,6 +81,10 @@ export class SchedulingConditions { await this.page.waitForSelector(`input[placeholder="Filter conditions"]`, { state: 'attached' }); } + async gotoNew() { + await this.page.goto('/scheduling/conditions/new', { waitUntil: 'networkidle' }); + } + updatePage(page: Page): void { this.closeButton = page.locator(`button:has-text("Close")`); this.confirmModal = page.locator(`.modal:has-text("Delete Scheduling Condition")`); diff --git a/e2e-tests/fixtures/SchedulingGoals.ts b/e2e-tests/fixtures/SchedulingGoals.ts index 77fba36c9d..9a6d85aa4f 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 { Models } from './Models.js'; export class SchedulingGoals { closeButton: Locator; @@ -18,10 +17,7 @@ export class SchedulingGoals { tableRowDeleteButtonSelector: (goalName: string) => Locator; tableRowSelector: (goalName: string) => Locator; - constructor( - public page: Page, - public models: Models, - ) { + constructor(public page: Page) { this.updatePage(page); } @@ -83,6 +79,10 @@ export class SchedulingGoals { await this.page.waitForSelector(`input[placeholder="Filter goals"]`, { state: 'attached' }); } + async gotoNew() { + await this.page.goto('/scheduling/goals/new', { waitUntil: 'networkidle' }); + } + updatePage(page: Page): void { this.closeButton = page.locator(`button:has-text("Close")`); this.confirmModal = page.locator(`.modal:has-text("Delete Scheduling Goal")`); diff --git a/e2e-tests/tests/constraints.test.ts b/e2e-tests/tests/constraints.test.ts index 5b2c242a08..eb4c654a8d 100644 --- a/e2e-tests/tests/constraints.test.ts +++ b/e2e-tests/tests/constraints.test.ts @@ -21,9 +21,9 @@ test.beforeAll(async ({ browser }) => { models = new Models(page); plans = new Plans(page, models); - constraints = new Constraints(page, models); - schedulingConditions = new SchedulingConditions(page, models); - schedulingGoals = new SchedulingGoals(page, models); + constraints = new Constraints(page); + schedulingConditions = new SchedulingConditions(page); + schedulingGoals = new SchedulingGoals(page); plan = new Plan(page, plans, constraints, schedulingGoals, schedulingConditions); await models.goto(); diff --git a/e2e-tests/tests/model.test.ts b/e2e-tests/tests/model.test.ts new file mode 100644 index 0000000000..422b6ed5b1 --- /dev/null +++ b/e2e-tests/tests/model.test.ts @@ -0,0 +1,120 @@ +import test, { expect, type BrowserContext, type Page } from '@playwright/test'; +import { adjectives, animals, colors, uniqueNamesGenerator } from 'unique-names-generator'; +import { Constraints } from '../fixtures/Constraints.js'; +import { Model } from '../fixtures/Model.js'; +import { Models } from '../fixtures/Models.js'; +import { SchedulingConditions } from '../fixtures/SchedulingConditions.js'; +import { SchedulingGoals } from '../fixtures/SchedulingGoals.js'; + +let constraints: Constraints; +let context: BrowserContext; +let models: Models; +let model: Model; +let page: Page; +let schedulingConditions: SchedulingConditions; +let schedulingGoals: SchedulingGoals; +let schedulingGoalName: string; + +const checkboxSelector = 'Press SPACE to toggle cell'; + +test.beforeAll(async ({ baseURL, browser }) => { + context = await browser.newContext(); + page = await context.newPage(); + + models = new Models(page); + constraints = new Constraints(page); + schedulingConditions = new SchedulingConditions(page); + schedulingGoals = new SchedulingGoals(page); + model = new Model(page, models, constraints, schedulingGoals, schedulingConditions); + schedulingGoalName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] }); + await constraints.gotoNew(); + await constraints.createConstraint(baseURL); + await schedulingConditions.gotoNew(); + await schedulingConditions.createSchedulingCondition(baseURL); + await schedulingGoals.gotoNew(); + await schedulingGoals.createSchedulingGoal(baseURL, schedulingGoalName); + await models.goto(); + await models.createModel(); + await model.goto(); +}); + +test.afterAll(async () => { + await model.deleteModel(); + await constraints.goto(); + await constraints.deleteConstraint(); + await schedulingConditions.goto(); + await schedulingConditions.deleteSchedulingCondition(); + await schedulingGoals.goto(); + await schedulingGoals.deleteSchedulingGoal(schedulingGoalName); + await page.close(); + await context.close(); +}); + +test.describe.serial('Model', () => { + test('Should be able to update the name of a model', async () => { + await model.updateName(uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] })); + }); + + test('Should be able to update the description of a model', async () => { + await model.updateDescription('Description of the model'); + }); + + test('Should be able to update the version of a model', async () => { + await model.updateVersion('2.0.0'); + }); + + test('Should be able to add a constraint to the model and specify a version', async () => { + await model.switchToConstraints(); + await model.switchToLibraryView(); + await model.switchToLibraryView(); + await model.associationTable + .getByRole('row', { name: model.constraints.constraintName }) + .getByLabel(checkboxSelector) + .check(); + await model.switchToModelView(); + await expect(page.getByRole('button', { name: model.constraints.constraintName })).toBeVisible(); + await expect( + page.getByRole('button', { name: model.constraints.constraintName }).getByRole('combobox'), + ).toHaveValue(''); + page.getByRole('button', { name: model.constraints.constraintName }).getByRole('combobox').selectOption('0'); + await expect( + page.getByRole('button', { name: model.constraints.constraintName }).getByRole('combobox'), + ).toHaveValue('0'); + }); + + test('Should be able to add a scheduling condition to the model and specify a version', async () => { + await model.switchToConditions(); + await model.switchToLibraryView(); + await model.associationTable + .getByRole('row', { name: model.schedulingConditions.conditionName }) + .getByLabel(checkboxSelector) + .check(); + await model.switchToModelView(); + await expect(page.getByRole('button', { name: model.schedulingConditions.conditionName })).toBeVisible(); + await expect( + page.getByRole('button', { name: model.schedulingConditions.conditionName }).getByRole('combobox'), + ).toHaveValue(''); + page + .getByRole('button', { name: model.schedulingConditions.conditionName }) + .getByRole('combobox') + .selectOption('0'); + await expect( + page.getByRole('button', { name: model.schedulingConditions.conditionName }).getByRole('combobox'), + ).toHaveValue('0'); + }); + + test('Should be able to add a scheduling goal to the model and specify a version', async () => { + await model.switchToGoals(); + await model.switchToLibraryView(); + await model.associationTable.getByRole('row', { name: schedulingGoalName }).getByLabel(checkboxSelector).check(); + await model.switchToModelView(); + await expect(page.getByRole('button', { name: schedulingGoalName })).toBeVisible(); + await expect(page.getByRole('button', { name: schedulingGoalName }).getByRole('combobox')).toHaveValue(''); + page.getByRole('button', { name: schedulingGoalName }).getByRole('combobox').selectOption('0'); + await expect(page.getByRole('button', { name: schedulingGoalName }).getByRole('combobox')).toHaveValue('0'); + }); + + test('Should successfully save the model changes', async () => { + await model.saveModel(); + }); +}); diff --git a/e2e-tests/tests/models.test.ts b/e2e-tests/tests/models.test.ts index 304cbfdcdb..d4993e8d91 100644 --- a/e2e-tests/tests/models.test.ts +++ b/e2e-tests/tests/models.test.ts @@ -10,7 +10,7 @@ test.beforeAll(async ({ browser }) => { context = await browser.newContext(); page = await context.newPage(); models = new Models(page); - constraints = new Constraints(page, models); + constraints = new Constraints(page); await models.goto(); }); @@ -54,10 +54,11 @@ test.describe.serial('Models', () => { }); test('Delete model', async () => { + await models.goto(); await models.deleteModel(); }); - test('Create button should be disabled after submitting once', async () => { + test('Successfully creating a model should navigate to the model edit page', async () => { // Setup the test await expect(models.tableRow).not.toBeVisible(); await models.fillInputName(); @@ -69,6 +70,10 @@ test.describe.serial('Models', () => { // Instead, the creating button should be present and disabled await expect(models.creatingButton).toBeVisible(); await expect(models.creatingButton).toBeDisabled(); + + // App now navigates away after model creation + await models.goto(); + await expect(models.createButton).toBeVisible(); await expect(models.inputFile).toBeEmpty(); diff --git a/e2e-tests/tests/plan.test.ts b/e2e-tests/tests/plan.test.ts index f37103f0c3..1ecdd537c6 100644 --- a/e2e-tests/tests/plan.test.ts +++ b/e2e-tests/tests/plan.test.ts @@ -21,9 +21,9 @@ test.beforeAll(async ({ browser }) => { models = new Models(page); plans = new Plans(page, models); - constraints = new Constraints(page, models); - schedulingConditions = new SchedulingConditions(page, models); - schedulingGoals = new SchedulingGoals(page, models); + constraints = new Constraints(page); + schedulingConditions = new SchedulingConditions(page); + schedulingGoals = new SchedulingGoals(page); plan = new Plan(page, plans, constraints, schedulingGoals, schedulingConditions); await models.goto(); @@ -97,7 +97,7 @@ test.describe.serial('Plan', () => { await expect(plan.panelTimelineEditor).toBeVisible(); }); - test(`Hovering on 'Activites' in the top navigation bar should show the activity checking menu`, async () => { + test(`Hovering on 'Activities' in the top navigation bar should show the activity checking menu`, async () => { await expect(plan.navButtonActivityCheckingMenu).not.toBeVisible(); plan.navButtonActivityChecking.hover(); await expect(plan.navButtonActivityCheckingMenu).toBeVisible(); diff --git a/e2e-tests/tests/plans.test.ts b/e2e-tests/tests/plans.test.ts index 9b774adac5..c3d8f6c9ad 100644 --- a/e2e-tests/tests/plans.test.ts +++ b/e2e-tests/tests/plans.test.ts @@ -34,11 +34,12 @@ test.describe.serial('Plans', () => { await expect(plans.createButton).toBeDisabled(); }); - test('Clicking on a model on the models page should route you to the plans page with that model selected', async ({ + test('Clicking on the "New plan with model" button should route you to the plans page with that model selected', async ({ baseURL, }) => { await models.goto(); await models.tableRow.click(); + await models.createPlanButton.click(); await expect(page).toHaveURL(`${baseURL}/plans`); const { text } = await plans.selectedModel(); expect(text).toEqual(`${models.modelName} (Version: ${models.modelVersion})`); diff --git a/src/components/console/Console.svelte b/src/components/console/Console.svelte index 507270662d..aa982b25a0 100644 --- a/src/components/console/Console.svelte +++ b/src/components/console/Console.svelte @@ -76,7 +76,7 @@
- +
{#if isOpen} diff --git a/src/components/constraints/ConstraintListItem.svelte b/src/components/constraints/ConstraintListItem.svelte index 0f165cbbb9..804f864da6 100644 --- a/src/components/constraints/ConstraintListItem.svelte +++ b/src/components/constraints/ConstraintListItem.svelte @@ -38,9 +38,7 @@ let revisions: number[] = []; - $: revisions = constraint.versions - .map(({ revision }) => revision) - .sort((revisionA, revisionB) => revisionB - revisionA); + $: revisions = constraint.versions.map(({ revision }) => revision); $: violationCount = constraintResponse?.results?.violations?.length; $: success = constraintResponse?.success; diff --git a/src/components/constraints/Constraints.svelte b/src/components/constraints/Constraints.svelte index 94f7d61cf5..b1ee29352f 100644 --- a/src/components/constraints/Constraints.svelte +++ b/src/components/constraints/Constraints.svelte @@ -68,7 +68,7 @@ suppressAutoSize: true, suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => { - return params?.data?.versions[params?.data?.versions.length - 1].revision; + return params?.data?.versions[0].revision; }, width: 125, }, @@ -174,11 +174,7 @@ function editConstraint({ id }: Pick) { const constraint = $constraints.find(c => c.id === id); - goto( - `${base}/constraints/edit/${id}?${SearchParameters.REVISION}=${ - constraint?.versions[constraint?.versions.length - 1].revision - }`, - ); + goto(`${base}/constraints/edit/${id}?${SearchParameters.REVISION}=${constraint?.versions[0].revision}`); } function editConstraintContext(event: CustomEvent) { @@ -256,9 +252,7 @@ diff --git a/src/components/constraints/ConstraintsPanel.svelte b/src/components/constraints/ConstraintsPanel.svelte index 765c3927a8..b57fbb12a3 100644 --- a/src/components/constraints/ConstraintsPanel.svelte +++ b/src/components/constraints/ConstraintsPanel.svelte @@ -211,7 +211,7 @@ permissionHandler, { hasPermission: $plan - ? featurePermissions.constraintPlanSpec.canCheck(user, $plan, $plan.model) && !$planReadOnly + ? featurePermissions.constraintsPlanSpec.canCheck(user, $plan, $plan.model) && !$planReadOnly : false, permissionError: $planReadOnly ? PlanStatusMessages.READ_ONLY @@ -337,7 +337,7 @@ constraintPlanSpec={$allowedConstraintPlanSpecMap[constraint.constraint_id]} constraintResponse={constraintToConstraintResponseMap[constraint.constraint_id]} hasReadPermission={featurePermissions.constraints.canRead(user)} - hasEditPermission={$plan ? featurePermissions.constraintPlanSpec.canUpdate(user, $plan) : false} + hasEditPermission={$plan ? featurePermissions.constraintsPlanSpec.canUpdate(user, $plan) : false} modelId={$plan?.model.id} totalViolationCount={$constraintResponseMap[constraint.constraint_id]?.results.violations?.length || 0} visible={$constraintVisibilityMap[constraint.constraint_id]} diff --git a/src/components/modals/ManagePlanConstraintsModal.svelte b/src/components/modals/ManagePlanConstraintsModal.svelte index 92c0941ccb..5bf2422663 100644 --- a/src/components/modals/ManagePlanConstraintsModal.svelte +++ b/src/components/modals/ManagePlanConstraintsModal.svelte @@ -71,7 +71,7 @@ suppressAutoSize: true, suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => { - return params?.data?.versions[params?.data?.versions.length - 1].revision; + return params?.data?.versions[0].revision; }, width: 80, }, @@ -115,7 +115,7 @@ {}, ); $: hasCreatePermission = featurePermissions.constraints.canCreate(user); - $: hasEditSpecPermission = $plan ? featurePermissions.constraintPlanSpec.canUpdate(user, $plan) : false; + $: hasEditSpecPermission = $plan ? featurePermissions.constraintsPlanSpec.canUpdate(user, $plan) : false; $: { columnDefs = [ ...baseColumnDefs, @@ -152,6 +152,7 @@ cellDataType: 'boolean', editable: hasEditSpecPermission, headerName: '', + resizable: false, suppressAutoSize: true, suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => { @@ -172,9 +173,7 @@ function viewConstraint({ id }: Pick) { const constraint = $constraints.find(c => c.id === id); window.open( - `${base}/constraints/edit/${constraint?.id}?${SearchParameters.REVISION}=${ - constraint?.versions[constraint?.versions.length - 1].revision - }&${SearchParameters.MODEL_ID}=${$plan?.model.id}`, + `${base}/constraints/edit/${constraint?.id}?${SearchParameters.REVISION}=${constraint?.versions[0].revision}&${SearchParameters.MODEL_ID}=${$plan?.model.id}`, ); } diff --git a/src/components/modals/ManagePlanSchedulingConditionsModal.svelte b/src/components/modals/ManagePlanSchedulingConditionsModal.svelte index 6bb671e492..7920208aeb 100644 --- a/src/components/modals/ManagePlanSchedulingConditionsModal.svelte +++ b/src/components/modals/ManagePlanSchedulingConditionsModal.svelte @@ -79,7 +79,7 @@ suppressAutoSize: true, suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => { - return params?.data?.versions[params?.data?.versions.length - 1].revision; + return params?.data?.versions[0].revision; }, width: 80, }, @@ -168,6 +168,7 @@ cellDataType: 'boolean', editable: hasEditSpecPermission, headerName: '', + resizable: false, suppressAutoSize: true, suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => { @@ -188,9 +189,7 @@ function viewCondition({ id }: Pick) { const condition = $schedulingConditions.find(c => c.id === id); window.open( - `${base}/scheduling/conditions/edit/${condition?.id}?${SearchParameters.REVISION}=${ - condition?.versions[condition?.versions.length - 1].revision - }&${SearchParameters.MODEL_ID}=${$plan?.model.id}`, + `${base}/scheduling/conditions/edit/${condition?.id}?${SearchParameters.REVISION}=${condition?.versions[0].revision}&${SearchParameters.MODEL_ID}=${$plan?.model.id}`, ); } diff --git a/src/components/modals/ManagePlanSchedulingGoalsModal.svelte b/src/components/modals/ManagePlanSchedulingGoalsModal.svelte index 8267eb5677..64d6b80716 100644 --- a/src/components/modals/ManagePlanSchedulingGoalsModal.svelte +++ b/src/components/modals/ManagePlanSchedulingGoalsModal.svelte @@ -75,7 +75,7 @@ suppressAutoSize: true, suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => { - return params?.data?.versions[params?.data?.versions.length - 1].revision; + return params?.data?.versions[0].revision; }, width: 80, }, @@ -164,6 +164,7 @@ cellDataType: 'boolean', editable: hasEditSpecPermission, headerName: '', + resizable: false, suppressAutoSize: true, suppressSizeToFit: true, valueGetter: (params: ValueGetterParams) => { @@ -184,9 +185,7 @@ function viewGoal({ id }: Pick) { const goal = $schedulingGoals.find(c => c.id === id); window.open( - `${base}/scheduling/goals/edit/${goal?.id}?${SearchParameters.REVISION}=${ - goal?.versions[goal?.versions.length - 1].revision - }&${SearchParameters.MODEL_ID}=${$plan?.model.id}`, + `${base}/scheduling/goals/edit/${goal?.id}?${SearchParameters.REVISION}=${goal?.versions[0].revision}&${SearchParameters.MODEL_ID}=${$plan?.model.id}`, ); } diff --git a/src/components/model/ModelAssociations.svelte b/src/components/model/ModelAssociations.svelte new file mode 100644 index 0000000000..a0d8f1bad8 --- /dev/null +++ b/src/components/model/ModelAssociations.svelte @@ -0,0 +1,318 @@ + + + + +
+
+
Associations
+ +
Constraints
+
Goals
+
Conditions
+
+
+ + +
+
+ +
+
+ +
Model
+
Library
+
+
+ {#if selectedViewId === 'library'} + + {:else} +
+ {#if model !== null && selectedSpecificationsList.length > 0} +
+ {#if numOfPrivateMetadata > 0} + {numOfPrivateMetadata} + {selectedAssociation}{numOfPrivateMetadata !== 1 ? 's' : ''} + {numOfPrivateMetadata > 1 ? 'are' : 'is'} private and not shown + {/if} +
+ {#each selectedSpecificationsList as spec} + {#if spec.selected && metadataMap[spec.id]} + {#if selectedAssociationId === 'goal'} + revision)} + selectedRevision={selectedSpecifications[spec.id].revision} + on:updatePriority={onUpdatePriority} + on:updateRevision={onUpdateRevision} + on:selectSpecification={onSelectSpecification} + /> + {:else} + revision)} + selectedRevision={selectedSpecifications[spec.id].revision} + on:updateRevision={onUpdateRevision} + on:selectSpecification={onSelectSpecification} + /> + {/if} + {/if} + {/each} + {:else} +
+ No {selectedAssociationTitle.toLowerCase()}s associated with this model yet. +
+ {/if} +
+ {/if} +
+ + + +
+
+ + diff --git a/src/components/model/ModelAssociationsListItem.svelte b/src/components/model/ModelAssociationsListItem.svelte new file mode 100644 index 0000000000..cc199d274d --- /dev/null +++ b/src/components/model/ModelAssociationsListItem.svelte @@ -0,0 +1,251 @@ + + + + +
+ +
+ {#if priority !== undefined} +
+ + + {#if hasEditPermission} +
+ + +
+ {/if} + +
+ {/if} + +
+
+ + diff --git a/src/components/model/ModelForm.svelte b/src/components/model/ModelForm.svelte new file mode 100644 index 0000000000..0a2b59b1da --- /dev/null +++ b/src/components/model/ModelForm.svelte @@ -0,0 +1,174 @@ + + + + +
+
+ + + + + + + + + + +