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

Procedural Constraints UI #1537

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
33 changes: 29 additions & 4 deletions src/components/constraints/ConstraintForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { createEventDispatcher } from 'svelte';
import type { DefinitionType } from '../../enums/association';
import { DefinitionType } from '../../enums/association';
import { ConstraintDefinitionType } from '../../enums/constraint';
import { SearchParameters } from '../../enums/searchParameters';
import { constraints } from '../../stores/constraints';
import type { User, UserId } from '../../types/app';
Expand All @@ -16,12 +17,14 @@
import AssociationForm from '../ui/Association/AssociationForm.svelte';

export let initialConstraintDefinitionAuthor: UserId | undefined = undefined;
export let initialConstraintDefinitionCode: string | null = '';
export let initialConstraintDefinitionCode: string | null = null;
export let initialConstraintDefinitionFilename: string | null = null;
export let initialConstraintDescription: string = '';
export let initialConstraintId: number | null = null;
export let initialConstraintName: string = '';
export let initialConstraintPublic: boolean = true;
export let initialConstraintDefinitionTags: Tag[] = [];
export let initialConstraintDefinitionType: ConstraintDefinitionType = ConstraintDefinitionType.EDSL;
export let initialConstraintMetadataTags: Tag[] = [];
export let initialConstraintOwner: UserId = null;
export let initialConstraintRevision: number | null = null;
Expand Down Expand Up @@ -100,13 +103,24 @@
}>,
) {
const {
detail: { definitionCode, definitionTags, description, name, public: isPublic, tags: metadataTags },
detail: {
definitionCode,
definitionFile,
definitionTags,
definitionType,
description,
name,
public: isPublic,
tags: metadataTags,
},
} = event;
const newConstraintId = await effects.createConstraint(
name,
isPublic,
metadataTags.map(({ id }) => ({ tag_id: id })),
definitionType === DefinitionType.CODE ? ConstraintDefinitionType.EDSL : ConstraintDefinitionType.JAR,
definitionCode ?? '',
definitionFile ?? null,
definitionTags.map(({ id }) => ({ tag_id: id })),
user,
description,
Expand All @@ -130,12 +144,14 @@
}>,
) {
const {
detail: { definitionCode, definitionTags },
detail: { definitionCode, definitionFile, definitionTags, definitionType },
} = event;
if (initialConstraintId !== null) {
const definition = await effects.createConstraintDefinition(
initialConstraintId,
definitionType === DefinitionType.CODE ? ConstraintDefinitionType.EDSL : ConstraintDefinitionType.JAR,
definitionCode ?? '',
definitionFile ?? null,
definitionTags.map(({ id }) => ({ tag_id: id })),
user,
);
Expand Down Expand Up @@ -228,12 +244,20 @@
<AssociationForm
allMetadata={$constraints}
defaultDefinitionCode={`export default (): Constraint => {\n\n}\n`}
definitionTypeConfigurations={{
code: { label: 'EDSL' },
file: { accept: '.jar', label: 'JAR File' },
}}
displayName="Constraint"
{hasCreateDefinitionCodePermission}
{hasWriteMetadataPermission}
{hasWriteDefinitionTagsPermission}
initialDefinitionAuthor={initialConstraintDefinitionAuthor}
initialDefinitionType={initialConstraintDefinitionType === ConstraintDefinitionType.EDSL
? DefinitionType.CODE
: DefinitionType.FILE}
initialDefinitionCode={initialConstraintDefinitionCode}
initialDefinitionFileName={initialConstraintDefinitionFilename}
initialDescription={initialConstraintDescription}
initialId={initialConstraintId}
initialName={initialConstraintName}
Expand All @@ -245,6 +269,7 @@
{initialReferenceModelId}
{permissionError}
revisions={constraintRevisions}
showDefinitionTypeSelector={true}
{tags}
tsFiles={constraintsTsFiles}
{mode}
Expand Down
115 changes: 100 additions & 15 deletions src/components/constraints/ConstraintListItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,78 @@
import VisibleShowIcon from '@nasa-jpl/stellar/icons/visible_show.svg?component';
import WarningIcon from '@nasa-jpl/stellar/icons/warning.svg?component';
import { createEventDispatcher } from 'svelte';
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 deletePermissionError: string = 'You do not have permission to delete constraints for this plan.';
export let editPermissionError: string = 'You do not have permission to edit constraints for this plan.';
export let modelId: number | undefined;
export let hasReadPermission: boolean = false;
export let hasDeletePermission: boolean = false;
export let hasEditPermission: boolean = false;
export let readOnly: boolean = false;
export let hasReadPermission: boolean = false;
export let readPermissionError: string = 'You do not have permission to view this constraint.';
export let totalViolationCount: number = 0;
export let visible: boolean = true;

const dispatch = createEventDispatcher<{
toggleVisibility: { id: number; visible: boolean };
updateConstraintPlanSpec: ConstraintPlanSpec;
deleteConstraintInvocation: ConstraintPlanSpecification;
duplicateConstraintInvocation: ConstraintPlanSpecification;
toggleVisibility: { constraintId: number; invocationId: number; visible: boolean };
updateConstraintPlanSpec: ConstraintPlanSpecification;
}>();

let formParameters: FormParameter[] = [];
let revisions: number[] = [];
let version: Pick<ConstraintDefinition, 'type' | 'revision' | 'parameter_schema'> | 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);
Expand All @@ -57,6 +96,16 @@
constraint_revision: revision === '' ? null : parseInt(`${revision}`),
});
}

function onChangeFormParameters(event: CustomEvent<FormParameter>) {
const {
detail: { name, value },
} = event;
dispatch('updateConstraintPlanSpec', {
...constraintPlanSpec,
arguments: { ...constraintPlanSpec.arguments, [name]: value },
});
}
</script>

<div class="constraint-list-item">
Expand All @@ -70,9 +119,7 @@
on:click|stopPropagation
use:permissionHandler={{
hasPermission: hasEditPermission,
permissionError: readOnly
? PlanStatusMessages.READ_ONLY
: 'You do not have permission to edit plan constraints',
permissionError: editPermissionError,
}}
use:tooltip={{
content: `${constraintPlanSpec.enabled ? 'Disable constraint' : 'Enable constraint'} on plan`,
Expand Down Expand Up @@ -108,10 +155,16 @@
<StatusBadge status={Status.Unchecked} />
</span>
{/if}
{constraintPlanSpec.invocation_id}
<button
use:tooltip={{ content: visible ? 'Hide' : 'Show', placement: 'top' }}
class="st-button icon"
on:click|stopPropagation={() => dispatch('toggleVisibility', { id: constraint.id, visible: !visible })}
on:click|stopPropagation={() =>
dispatch('toggleVisibility', {
constraintId: constraintPlanSpec.constraint_id,
invocationId: constraintPlanSpec.invocation_id,
visible: !visible,
})}
>
{#if visible}
<VisibleShowIcon />
Expand All @@ -126,9 +179,7 @@
on:click|stopPropagation
use:permissionHandler={{
hasPermission: hasEditPermission,
permissionError: readOnly
? PlanStatusMessages.READ_ONLY
: 'You do not have permission to edit plan constraints',
permissionError: editPermissionError,
}}
>
<option value={null}>Always use latest</option>
Expand All @@ -139,6 +190,10 @@
</div>
</svelte:fragment>

<Collapse title="Parameters" className="constraint-parameters" defaultExpanded={true}>
<Parameters disabled={false} expanded={true} {formParameters} on:change={onChangeFormParameters} />
</Collapse>

<svelte:fragment slot="contextMenuContent">
<ContextMenuItem
on:click={() =>
Expand All @@ -155,13 +210,43 @@
permissionHandler,
{
hasPermission: hasReadPermission,
permissionError: 'You do not have permission to edit this constraint',
permissionError: readPermissionError,
},
],
]}
>
View Constraint
</ContextMenuItem>
{#if version?.type === 'JAR'}
<ContextMenuItem
on:click={onDuplicateConstraintInvocation}
use={[
[
permissionHandler,
{
hasPermission: hasEditPermission,
permissionError: editPermissionError,
},
],
]}
>
Duplicate Invocation
</ContextMenuItem>
<ContextMenuItem
on:click={onDeleteConstraintInvocation}
use={[
[
permissionHandler,
{
hasPermission: hasDeletePermission,
permissionError: deletePermissionError,
},
],
]}
>
Delete Invocation
</ContextMenuItem>
{/if}
</svelte:fragment>

<Collapse title="Description" defaultExpanded={false}>
Expand Down
Loading
Loading