From 92a11cc679658a72dd731465ee00dca79582090f Mon Sep 17 00:00:00 2001 From: bduran Date: Tue, 8 Oct 2024 15:20:30 -0700 Subject: [PATCH 01/14] add invocations and arguments to constraint plan specifications add `type` to constraint definition add ability to duplicate constraint invocations --- .../constraints/ConstraintListItem.svelte | 62 +++++++- .../constraints/ConstraintsPanel.svelte | 43 ++++-- .../modals/ManagePlanConstraintsModal.svelte | 15 +- .../scheduling/SchedulingGoalsPanel.svelte | 10 +- .../scheduling/goals/SchedulingGoal.svelte | 12 +- .../goals/SchedulingGoalForm.svelte | 12 +- src/enums/constraint.ts | 4 + src/enums/scheduling.ts | 2 +- src/routes/models/[id]/+page.svelte | 12 +- src/stores/constraints.ts | 10 +- src/stores/scheduling.ts | 8 +- src/types/constraint.ts | 21 ++- src/types/model.ts | 4 +- src/types/plan.ts | 4 +- src/types/scheduling.ts | 6 +- src/utilities/effects.ts | 118 +++++++++++----- src/utilities/gql.ts | 132 +++++++++++------- src/utilities/permissions.ts | 24 ++-- 18 files changed, 346 insertions(+), 153 deletions(-) create mode 100644 src/enums/constraint.ts diff --git a/src/components/constraints/ConstraintListItem.svelte b/src/components/constraints/ConstraintListItem.svelte index 804f864da6..5a2131dce4 100644 --- a/src/components/constraints/ConstraintListItem.svelte +++ b/src/components/constraints/ConstraintListItem.svelte @@ -11,18 +11,25 @@ import { PlanStatusMessages } from '../../enums/planStatusMessages'; import { SearchParameters } from '../../enums/searchParameters'; import { Status } from '../../enums/status'; - import type { ConstraintMetadata, ConstraintPlanSpec, ConstraintResponse } from '../../types/constraint'; + import type { + ConstraintDefinition, + ConstraintMetadata, + ConstraintPlanSpecification, + ConstraintResponse, + } from '../../types/constraint'; + import type { FormParameter } from '../../types/parameter'; import { getTarget } from '../../utilities/generic'; import { permissionHandler } from '../../utilities/permissionHandler'; import { pluralize } from '../../utilities/text'; import { tooltip } from '../../utilities/tooltip'; import Collapse from '../Collapse.svelte'; import ContextMenuItem from '../context-menu/ContextMenuItem.svelte'; + import Parameters from '../parameters/Parameters.svelte'; import StatusBadge from '../ui/StatusBadge.svelte'; import ConstraintViolationButton from './ConstraintViolationButton.svelte'; export let constraint: ConstraintMetadata; - export let constraintPlanSpec: ConstraintPlanSpec; + export let constraintPlanSpec: ConstraintPlanSpecification; export let constraintResponse: ConstraintResponse; export let modelId: number | undefined; export let hasReadPermission: boolean = false; @@ -32,15 +39,45 @@ export let visible: boolean = true; const dispatch = createEventDispatcher<{ + deleteConstraintInvocation: ConstraintPlanSpecification; + duplicateConstraintInvocation: ConstraintPlanSpecification; toggleVisibility: { id: number; visible: boolean }; - updateConstraintPlanSpec: ConstraintPlanSpec; + updateConstraintPlanSpec: ConstraintPlanSpecification; }>(); + let formParameters: FormParameter[] = []; let revisions: number[] = []; + let version: Pick | undefined = undefined; $: revisions = constraint.versions.map(({ revision }) => revision); $: violationCount = constraintResponse?.results?.violations?.length; $: success = constraintResponse?.success; + $: { + version = constraintPlanSpec.constraint_metadata?.versions[0]; // plan gql query already orders by version and limits 1 + const schema = version?.parameter_schema; + if (schema && schema.type === 'struct') { + formParameters = Object.entries(schema.items).map(([name, subschema], i) => ({ + errors: null, + name, + order: i, + required: true, + schema: subschema, + value: (constraintPlanSpec && constraintPlanSpec.arguments && constraintPlanSpec.arguments[name]) || '', + valueSource: 'none', + })); + } else { + formParameters = []; + } + } + + function onDuplicateConstraintInvocation() { + dispatch('duplicateConstraintInvocation', { + ...constraintPlanSpec, + }); + } + function onDeleteConstraintInvocation() { + dispatch('deleteConstraintInvocation', constraintPlanSpec); + } function onEnable(event: Event) { const { value: enabled } = getTarget(event); @@ -57,6 +94,16 @@ constraint_revision: revision === '' ? null : parseInt(`${revision}`), }); } + + function onChangeFormParameters(event: CustomEvent) { + const { + detail: { name, value }, + } = event; + dispatch('updateConstraintPlanSpec', { + ...constraintPlanSpec, + arguments: { ...constraintPlanSpec.arguments, [name]: value }, + }); + }
@@ -139,6 +186,10 @@
+ + + + @@ -164,6 +215,11 @@ + {#if version?.type === 'JAR'} + Duplicate Invocation + Delete Invocation + {/if} +
{#if constraint.description} diff --git a/src/components/constraints/ConstraintsPanel.svelte b/src/components/constraints/ConstraintsPanel.svelte index e7ba384e60..28f1b29eae 100644 --- a/src/components/constraints/ConstraintsPanel.svelte +++ b/src/components/constraints/ConstraintsPanel.svelte @@ -29,7 +29,7 @@ import type { ConstraintDefinition, ConstraintMetadata, - ConstraintPlanSpec, + ConstraintPlanSpecification, ConstraintResponse, } from '../../types/constraint'; import type { FieldStore } from '../../types/form'; @@ -54,7 +54,7 @@ let showAll: boolean = true; let filterText: string = ''; - let filteredConstraints: ConstraintPlanSpec[] = []; + let filteredConstraints: ConstraintPlanSpecification[] = []; let endTime: string; let endTimeField: FieldStore; let numOfPrivateConstraints: number = 0; @@ -119,7 +119,7 @@ $: filteredViolationCount = getViolationCount(Object.values(filteredConstraintResponses)); function filterConstraints( - planSpecs: ConstraintPlanSpec[], + planSpecs: ConstraintPlanSpecification[], constraintToConstraintResponseMap: Record, filterText: string, showConstraintsWithNoViolations: boolean, @@ -151,6 +151,29 @@ }, 0); } + async function onDuplicateConstraintInvocation(event: CustomEvent) { + const { + detail: { constraint_metadata, constraint_invocation_id, ...constraintPlanSpec }, + } = event; + if ($plan) { + await effects.createConstraintPlanSpecification(constraintPlanSpec, user); + } + } + + async function onDeleteConstraintInvocation(event: CustomEvent) { + const { + detail: { constraint_metadata, specification_id, ...constraintPlanSpec }, + } = event; + if ($plan) { + await effects.deleteConstraintInvocations( + $plan, + specification_id, + [constraintPlanSpec.constraint_invocation_id], + user, + ); + } + } + function onManageConstraints() { effects.managePlanConstraints(user); } @@ -167,7 +190,7 @@ } } - async function setTimeBoundsToView() { + async function onSetTimeBoundsToView() { await startTimeField.validateAndSet(formatDate(new Date($viewTimeRange.start), $plugins.time.primary.format)); await endTimeField.validateAndSet(formatDate(new Date($viewTimeRange.end), $plugins.time.primary.format)); onUpdateStartTime(); @@ -192,7 +215,7 @@ } } - async function onUpdateConstraint(event: CustomEvent) { + async function onUpdateConstraint(event: CustomEvent) { if ($plan) { const { detail: { constraint_metadata, ...constraintPlanSpec }, @@ -202,7 +225,7 @@ } } - function resetFilters() { + function onResetFilters() { onPlanStartTimeClick(); onPlanEndTimeClick(); filterText = ''; @@ -307,8 +330,8 @@
- - + + @@ -353,7 +376,7 @@ - {#each filteredConstraints as constraint} + {#each filteredConstraints as constraint (constraint.constraint_invocation_id)} {#if $constraintsMap[constraint.constraint_id]} {/if} diff --git a/src/components/modals/ManagePlanConstraintsModal.svelte b/src/components/modals/ManagePlanConstraintsModal.svelte index 5bf2422663..6587e4fe15 100644 --- a/src/components/modals/ManagePlanConstraintsModal.svelte +++ b/src/components/modals/ManagePlanConstraintsModal.svelte @@ -9,7 +9,11 @@ import { allowedConstraintPlanSpecMap, allowedConstraintSpecs, constraints } from '../../stores/constraints'; import { plan, planId, planReadOnly } from '../../stores/plan'; import type { User } from '../../types/app'; - import type { ConstraintMetadata, ConstraintPlanSpec, ConstraintPlanSpecInsertInput } from '../../types/constraint'; + import type { + ConstraintMetadata, + ConstraintPlanSpecification, + ConstraintPlanSpecInsertInput, + } from '../../types/constraint'; import type { DataGridColumnDef } from '../../types/data-grid'; import effects from '../../utilities/effects'; import { permissionHandler } from '../../utilities/permissionHandler'; @@ -106,7 +110,7 @@ return includesId || includesName; }); $: selectedConstraints = $allowedConstraintSpecs.reduce( - (prevBooleanMap: Record, constraintPlanSpec: ConstraintPlanSpec) => { + (prevBooleanMap: Record, constraintPlanSpec: ConstraintPlanSpecification) => { return { ...prevBooleanMap, [constraintPlanSpec.constraint_id]: true, @@ -205,9 +209,14 @@ ) => { const constraintId = parseInt(selectedConstraintId); const isSelected = selectedConstraints[constraintId]; + // if we find at least one constraint invocation with the selected constraint_id, we don't want to insert this constraint_id into the plan spec + // i.e. this constraint was already selected when we entered the modal, so we don't want to kick off an update, which would cause a duplicate invocation to appear + const constraintAlreadyExistsInPlanSpec = + $allowedConstraintSpecs.find(e => e.constraint_id === constraintId) !== undefined; + const constraintPlanSpec = $allowedConstraintPlanSpecMap[constraintId]; - if (isSelected) { + if (isSelected && !constraintAlreadyExistsInPlanSpec) { if (!constraintPlanSpec || constraintPlanSpec.constraint_metadata?.owner === user?.id) { return { ...prevConstraintPlanSpecUpdates, diff --git a/src/components/scheduling/SchedulingGoalsPanel.svelte b/src/components/scheduling/SchedulingGoalsPanel.svelte index 9ab01a7497..8eb043128a 100644 --- a/src/components/scheduling/SchedulingGoalsPanel.svelte +++ b/src/components/scheduling/SchedulingGoalsPanel.svelte @@ -94,7 +94,7 @@ } } - async function duplicateGoalInvocation(event: CustomEvent) { + async function onDuplicateGoalInvocation(event: CustomEvent) { const { detail: { goal_metadata, goal_invocation_id, priority, ...goalPlanSpec }, } = event; @@ -110,13 +110,13 @@ } } - async function deleteGoalInvocation(event: CustomEvent) { + async function onDeleteGoalInvocation(event: CustomEvent) { const { detail: { goal_metadata, specification_id, ...goalPlanSpec }, } = event; if ($plan) { - await effects.deleteSchedulingGoalInvocation($plan, specification_id, [goalPlanSpec.goal_invocation_id], user); + await effects.deleteSchedulingGoalInvocations($plan, specification_id, [goalPlanSpec.goal_invocation_id], user); } } @@ -233,8 +233,8 @@ ? PlanStatusMessages.READ_ONLY : 'You do not have permission to edit scheduling goals for this plan.'} on:updateGoalPlanSpec={onUpdateGoal} - on:duplicateGoalInvocation={duplicateGoalInvocation} - on:deleteGoalInvocation={deleteGoalInvocation} + on:duplicateGoalInvocation={onDuplicateGoalInvocation} + on:deleteGoalInvocation={onDeleteGoalInvocation} /> {/if} {/each} diff --git a/src/components/scheduling/goals/SchedulingGoal.svelte b/src/components/scheduling/goals/SchedulingGoal.svelte index 2b40b882fd..fff5d2697e 100644 --- a/src/components/scheduling/goals/SchedulingGoal.svelte +++ b/src/components/scheduling/goals/SchedulingGoal.svelte @@ -117,16 +117,14 @@ }); } - function duplicateGoalInvocation() { + function onDuplicateGoalInvocation() { dispatch('duplicateGoalInvocation', { ...goalPlanSpec, }); } - function deleteGoalInvocation() { - dispatch('deleteGoalInvocation', { - ...goalPlanSpec, - }); + function onDeleteGoalInvocation() { + dispatch('deleteGoalInvocation', goalPlanSpec); } function updatePriority(priority: number) { @@ -279,8 +277,8 @@ {#if version?.type === 'JAR'} - Duplicate Invocation - Delete Invocation + Duplicate Invocation + Delete Invocation {/if}
diff --git a/src/components/scheduling/goals/SchedulingGoalForm.svelte b/src/components/scheduling/goals/SchedulingGoalForm.svelte index 9ca0455536..27464b8a7c 100644 --- a/src/components/scheduling/goals/SchedulingGoalForm.svelte +++ b/src/components/scheduling/goals/SchedulingGoalForm.svelte @@ -5,7 +5,7 @@ import { base } from '$app/paths'; import { createEventDispatcher } from 'svelte'; import { DefinitionType } from '../../../enums/association'; - import { SchedulingType } from '../../../enums/scheduling'; + import { SchedulingDefinitionType } from '../../../enums/scheduling'; import { SearchParameters } from '../../../enums/searchParameters'; import { schedulingGoals } from '../../../stores/scheduling'; import type { User, UserId } from '../../../types/app'; @@ -27,7 +27,7 @@ export let initialGoalName: string = ''; export let initialGoalPublic: boolean = true; export let initialGoalDefinitionTags: Tag[] = []; - export let initialGoalDefinitionType: SchedulingType = SchedulingType.EDSL; + export let initialGoalDefinitionType: SchedulingDefinitionType = SchedulingDefinitionType.EDSL; export let initialGoalMetadataTags: Tag[] = []; export let initialGoalOwner: UserId = null; export let initialGoalRevision: number | null = null; @@ -120,7 +120,7 @@ name, isPublic, metadataTags.map(({ id }) => ({ tag_id: id })), - definitionType === DefinitionType.CODE ? SchedulingType.EDSL : SchedulingType.JAR, + definitionType === DefinitionType.CODE ? SchedulingDefinitionType.EDSL : SchedulingDefinitionType.JAR, definitionCode, definitionFile ?? null, definitionTags.map(({ id }) => ({ tag_id: id })), @@ -151,7 +151,7 @@ if (initialGoalId !== null) { const definition = await effects.createSchedulingGoalDefinition( initialGoalId, - definitionType === DefinitionType.CODE ? SchedulingType.EDSL : SchedulingType.JAR, + definitionType === DefinitionType.CODE ? SchedulingDefinitionType.EDSL : SchedulingDefinitionType.JAR, definitionCode, definitionFile ?? null, definitionTags.map(({ id }) => ({ tag_id: id })), @@ -245,7 +245,9 @@ {hasWriteDefinitionTagsPermission} {hasWriteMetadataPermission} initialDefinitionAuthor={initialGoalDefinitionAuthor} - initialDefinitionType={initialGoalDefinitionType === SchedulingType.EDSL ? DefinitionType.CODE : DefinitionType.FILE} + initialDefinitionType={initialGoalDefinitionType === SchedulingDefinitionType.EDSL + ? DefinitionType.CODE + : DefinitionType.FILE} initialDefinitionCode={initialGoalDefinitionCode} initialDefinitionFileName={initialGoalDefinitionFilename} initialDescription={initialGoalDescription} diff --git a/src/enums/constraint.ts b/src/enums/constraint.ts new file mode 100644 index 0000000000..ed07d599f2 --- /dev/null +++ b/src/enums/constraint.ts @@ -0,0 +1,4 @@ +export enum ConstraintDefinitionType { + JAR = 'JAR', + EDSL = 'EDSL', +} diff --git a/src/enums/scheduling.ts b/src/enums/scheduling.ts index 46b901bd60..11a4b414b6 100644 --- a/src/enums/scheduling.ts +++ b/src/enums/scheduling.ts @@ -1,4 +1,4 @@ -export enum SchedulingType { +export enum SchedulingDefinitionType { JAR = 'JAR', EDSL = 'EDSL', } diff --git a/src/routes/models/[id]/+page.svelte b/src/routes/models/[id]/+page.svelte index 4e0d77f9e2..0729629b89 100644 --- a/src/routes/models/[id]/+page.svelte +++ b/src/routes/models/[id]/+page.svelte @@ -2,13 +2,19 @@