From e5a6c38bed1d62b3146491db3f756952f2bb6b99 Mon Sep 17 00:00:00 2001 From: Ryan Goetz Date: Wed, 25 Oct 2023 11:16:25 -1000 Subject: [PATCH 1/2] Update GQL to match what the server will return for constraints and compilation errors. --- src/types/constraint.ts | 17 +++++++++++++++++ src/utilities/gql.ts | 31 +++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/types/constraint.ts b/src/types/constraint.ts index c225eb4e57..cff363543c 100644 --- a/src/types/constraint.ts +++ b/src/types/constraint.ts @@ -36,3 +36,20 @@ export type ConstraintResult = { type: ConstraintType; violations: ConstraintViolation[]; }; + +export type ConstraintResponse = { + errors: UserCodeError[]; + results: ConstraintResult; + success: boolean; +}; + +export type UserCodeError = { + location: CodeLocation; + message: string; + stack: string; +}; + +export type CodeLocation = { + column: number; + line: number; +}; diff --git a/src/utilities/gql.ts b/src/utilities/gql.ts index b2efbe70eb..76e3d26012 100644 --- a/src/utilities/gql.ts +++ b/src/utilities/gql.ts @@ -26,17 +26,32 @@ const gql = { CHECK_CONSTRAINTS: `#graphql query CheckConstraints($planId: Int!) { - constraintResults: constraintViolations(planId: $planId) { - constraintId - constraintName - resourceIds - type - violations { - activityInstanceIds - windows { + constraintResponses: constraintViolations(planId: $planId) { + success + results { + constraintId + constraintName + resourceIds + type + gaps { end start } + violations { + activityInstanceIds + windows { + end + start + } + } + } + errors { + message + stack + location { + column + line + } } } } From f388c7933a02d84dfed44d5f9e7c875d9d3d1776 Mon Sep 17 00:00:00 2001 From: Ryan Goetz Date: Wed, 25 Oct 2023 11:19:51 -1000 Subject: [PATCH 2/2] Update the UI to parse the ConstraintViolation response differently. This will return an object containing all constraints processed by the server, including whether they have compilation errors or ran successfully. --- src/stores/constraints.ts | 37 +++++++++++++++++++---------------- src/utilities/effects.ts | 41 +++++++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/stores/constraints.ts b/src/stores/constraints.ts index 0cc5dc4353..927b157ed5 100644 --- a/src/stores/constraints.ts +++ b/src/stores/constraints.ts @@ -1,6 +1,6 @@ import { keyBy } from 'lodash-es'; import { derived, get, writable, type Readable, type Writable } from 'svelte/store'; -import type { Constraint, ConstraintResult } from '../types/constraint'; +import type { Constraint, ConstraintResponse, ConstraintResult } from '../types/constraint'; import gql from '../utilities/gql'; import type { Status } from '../utilities/status'; import { modelId, planId, planStartTimeMs } from './plan'; @@ -34,7 +34,7 @@ export const constraintVisibilityMap: Readable export const checkConstraintsStatus: Writable = writable(null); -export const constraintResultsResponse: Writable = writable([]); +export const constraintResponse: Writable = writable([]); export const constraintsColumns: Writable = writable('2fr 3px 1fr'); export const constraintsFormColumns: Writable = writable('1fr 3px 2fr'); @@ -42,22 +42,25 @@ export const constraintsFormColumns: Writable = writable('1fr 3px 2fr'); /* Derived. */ export const constraintResults: Readable = derived( - [constraintResultsResponse, planStartTimeMs], - ([$constraintResultsResponse, $planStartTimeMs]) => - $constraintResultsResponse.reduce((list: ConstraintResult[], constraintResult) => { - list.push({ - ...constraintResult, - violations: constraintResult.violations.map(violation => ({ - ...violation, - windows: violation.windows.map(({ end, start }) => ({ - end: $planStartTimeMs + end / 1000, - start: $planStartTimeMs + start / 1000, + [constraintResponse, planStartTimeMs], + ([$constraintResponse, $planStartTimeMs]) => + $constraintResponse + .filter(response => response.success) + .map(successfulResponse => successfulResponse.results) + .reduce((list: ConstraintResult[], constraintResult) => { + list.push({ + ...constraintResult, + violations: constraintResult.violations.map(violation => ({ + ...violation, + windows: violation.windows.map(({ end, start }) => ({ + end: $planStartTimeMs + end / 1000, + start: $planStartTimeMs + start / 1000, + })), })), - })), - }); + }); - return list; - }, []), + return list; + }, []), ); export const visibleConstraintResults: Readable = derived( @@ -88,5 +91,5 @@ export function setAllConstraintsVisible(visible: boolean) { export function resetConstraintStores(): void { checkConstraintsStatus.set(null); - constraintResultsResponse.set([]); + constraintResponse.set([]); } diff --git a/src/utilities/effects.ts b/src/utilities/effects.ts index c3197ceec2..e8a2e69f81 100644 --- a/src/utilities/effects.ts +++ b/src/utilities/effects.ts @@ -4,7 +4,7 @@ import type { CommandDictionary as AmpcsCommandDictionary } from '@nasa-jpl/aeri import { get } from 'svelte/store'; import { SearchParameters } from '../enums/searchParameters'; import { activityDirectives, activityDirectivesMap, selectedActivityDirectiveId } from '../stores/activities'; -import { checkConstraintsStatus, constraintResultsResponse } from '../stores/constraints'; +import { checkConstraintsStatus, constraintResponse } from '../stores/constraints'; import { catchError, catchSchedulingError } from '../stores/errors'; import { createExpansionRuleError, @@ -37,7 +37,7 @@ import type { import type { ActivityMetadata } from '../types/activity-metadata'; import type { BaseUser, User, UserId } from '../types/app'; import type { ReqAuthResponse, ReqSessionResponse } from '../types/auth'; -import type { Constraint, ConstraintInsertInput, ConstraintResult } from '../types/constraint'; +import type { Constraint, ConstraintInsertInput, ConstraintResponse, ConstraintResult } from '../types/constraint'; import type { ExpansionRule, ExpansionRuleInsertInput, @@ -285,19 +285,44 @@ const effects = { checkConstraintsStatus.set(Status.Incomplete); if (plan !== null) { const { id: planId } = plan; - const data = await reqHasura( + const data = await reqHasura( gql.CHECK_CONSTRAINTS, { planId, }, user, ); + const { constraintResponses } = data; + if (constraintResponses) { + constraintResponse.set(constraintResponses); - const { constraintResults } = data; - if (constraintResults != null) { - constraintResultsResponse.set(constraintResults); - checkConstraintsStatus.set(Status.Complete); - showSuccessToast('Check Constraints Complete'); + // find only the constraints compiled. + const successfulConstraintResults: ConstraintResult[] = constraintResponses + .filter(constraintResponse => constraintResponse.success) + .map(constraintResponse => constraintResponse.results); + + const failedConstraintResponses = constraintResponses.filter( + constraintResponse => !constraintResponse.success, + ); + + if (successfulConstraintResults.length === 0 && constraintResponses.length > 0) { + showFailureToast('All Constraints Failed'); + checkConstraintsStatus.set(Status.Failed); + } else if (successfulConstraintResults.length !== constraintResponses.length) { + showFailureToast('Partial Constraints Checked'); + checkConstraintsStatus.set(successfulConstraintResults.length !== 0 ? Status.Incomplete : Status.Failed); + } else { + showSuccessToast('All Constraints Checked'); + checkConstraintsStatus.set(Status.Complete); + } + + if (failedConstraintResponses.length > 0) { + failedConstraintResponses.forEach(failedConstraint => { + failedConstraint.errors.forEach(error => { + catchError(`${error.message}`, error.stack); + }); + }); + } } else { throw Error(`Unable to check constraints for plan with ID: "${plan.id}"`); }