Skip to content

Commit

Permalink
Add Constraint Compile Icon (#1022)
Browse files Browse the repository at this point in the history
* Add constraintID to the response.

We will need this to filter out or 3 types of constraints.

* Tweak how we store the constraint response data

* Use the constraintResponseMap now.

* Add warning icon for compile error

* Updated code to use because constraintResults have been changed
  • Loading branch information
goetzrrGit authored and JosephVolosin committed Oct 21, 2024
1 parent a972fd5 commit 73d5d13
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 68 deletions.
27 changes: 21 additions & 6 deletions src/components/constraints/ConstraintListItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import FilterIcon from '@nasa-jpl/stellar/icons/filter.svg?component';
import VisibleHideIcon from '@nasa-jpl/stellar/icons/visible_hide.svg?component';
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 type { User } from '../../types/app';
import type { Constraint, ConstraintResult } from '../../types/constraint';
import type { Constraint, ConstraintResponse } from '../../types/constraint';
import type { Plan } from '../../types/plan';
import effects from '../../utilities/effects';
import { permissionHandler } from '../../utilities/permissionHandler';
Expand All @@ -19,7 +20,7 @@
import ConstraintViolationButton from './ConstraintViolationButton.svelte';
export let constraint: Constraint;
export let constraintResult: ConstraintResult;
export let constraintResponse: ConstraintResponse;
export let hasDeletePermission: boolean = false;
export let hasEditPermission: boolean = false;
export let plan: Plan | null;
Expand All @@ -29,7 +30,8 @@
const dispatch = createEventDispatcher();
$: violationCount = constraintResult?.violations?.length;
$: violationCount = constraintResponse?.results?.violations?.length;
$: success = constraintResponse?.success;
</script>

<div class="constraint-list-item">
Expand All @@ -47,7 +49,11 @@
{violationCount}
{/if}
</div>
{:else}
{:else if constraintResponse && !success}
<div class="violations-error" use:tooltip={{ content: 'Compile Errors', placement: 'top' }}>
<WarningIcon />
</div>
{:else if constraintResponse && success}
<div class="no-violations" use:tooltip={{ content: 'No Violations', placement: 'top' }}>
<CheckmarkIcon />
</div>
Expand Down Expand Up @@ -77,9 +83,9 @@
</Collapse>

<Collapse title="Violations" defaultExpanded={false}>
{#if constraintResult?.violations?.length}
{#if constraintResponse?.results?.violations?.length}
<div class="violations">
{#each constraintResult.violations as violation}
{#each constraintResponse?.results?.violations as violation}
{#each violation.windows as window}
<ConstraintViolationButton {window} />
{/each}
Expand Down Expand Up @@ -155,4 +161,13 @@
justify-content: center;
width: 20px;
}
.violations-error {
align-items: center;
color: var(--st-error-red);
display: flex;
flex-shrink: 0;
justify-content: center;
width: 20px;
}
</style>
57 changes: 33 additions & 24 deletions src/components/constraints/ConstraintsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import PlanRightArrow from '@nasa-jpl/stellar/icons/plan_with_right_arrow.svg?component';
import VisibleHideIcon from '@nasa-jpl/stellar/icons/visible_hide.svg?component';
import VisibleShowIcon from '@nasa-jpl/stellar/icons/visible_show.svg?component';
import { isEmpty } from 'lodash-es';
import { PlanStatusMessages } from '../../enums/planStatusMessages';
import {
checkConstraintsStatus,
constraintResultMap,
constraintResults,
constraintResponseMap,
constraintVisibilityMap,
constraints,
setAllConstraintsVisible,
Expand All @@ -21,7 +21,7 @@
import { field } from '../../stores/form';
import { plan, planReadOnly, viewTimeRange } from '../../stores/plan';
import type { User } from '../../types/app';
import type { Constraint, ConstraintResult } from '../../types/constraint';
import type { Constraint, ConstraintResponse } from '../../types/constraint';
import type { FieldStore } from '../../types/form';
import type { ViewGridSection } from '../../types/view';
import effects from '../../utilities/effects';
Expand Down Expand Up @@ -51,7 +51,7 @@
let startTimeDoyField: FieldStore<string>;
let showFilters: boolean = false;
let showConstraintsWithNoViolations: boolean = true;
let filteredConstraintResultMap: Record<Constraint['id'], ConstraintResult> = {};
let filteredConstraintResponseMap: Record<Constraint['id'], ConstraintResponse> = {};
$: startTimeDoy = $plan?.start_time_doy || '';
$: startTimeDoyField = field<string>(startTimeDoy, [required, timestamp]);
Expand All @@ -60,33 +60,42 @@
$: startTimeMs = getUnixEpochTime(startTimeDoy);
$: endTimeMs = getUnixEpochTime(endTimeDoy);
$: if ($constraints && $constraintResults && startTimeMs && endTimeMs) {
filteredConstraintResultMap = {};
$: if ($constraints && $constraintResponseMap && startTimeMs && endTimeMs) {
filteredConstraintResponseMap = {};
$constraints.forEach(constraint => {
const constraintResult = $constraintResultMap[constraint.id];
if (constraintResult) {
const constraintResponse = $constraintResponseMap[constraint.id];
if (constraintResponse) {
// Filter violations/windows by time bounds
const filteredViolations = constraintResult.violations.map(violation => ({
...violation,
windows: violation.windows.filter(window => window.end >= startTimeMs && window.start <= endTimeMs),
}));
filteredConstraintResultMap[constraint.id] = { ...constraintResult, violations: filteredViolations };
if (constraintResponse.results && !isEmpty(constraintResponse.results)) {
constraintResponse.results.violations = constraintResponse.results.violations.map(violation => ({
...violation,
windows: violation.windows.filter(window => window.end >= startTimeMs && window.start <= endTimeMs),
}));
}
filteredConstraintResponseMap[constraint.id] = {
constraintId: constraintResponse.constraintId,
constraintName: constraintResponse.constraintName,
errors: constraintResponse.errors,
results: constraintResponse.results,
success: constraintResponse.success,
type: constraintResponse.type,
};
}
});
}
$: filteredConstraints = filterConstraints(
$constraints,
filteredConstraintResultMap,
filteredConstraintResponseMap,
filterText,
showConstraintsWithNoViolations,
);
$: totalViolationCount = getViolationCount($constraintResults);
$: filteredViolationCount = getViolationCount(Object.values(filteredConstraintResultMap));
$: totalViolationCount = getViolationCount(Object.values($constraintResponseMap));
$: filteredViolationCount = getViolationCount(Object.values(filteredConstraintResponseMap));
function filterConstraints(
constraints: Constraint[],
filteredConstraintResultMap: Record<Constraint['id'], ConstraintResult>,
filteredConstraintResponseMap: Record<Constraint['id'], ConstraintResponse>,
filterText: string,
showConstraintsWithNoViolations: boolean,
) {
Expand All @@ -97,19 +106,19 @@
return false;
}
const constraintResult = filteredConstraintResultMap[constraint.id];
const constraintResponse = filteredConstraintResponseMap[constraint.id];
// Always show constraints with no violations
if (!constraintResult?.violations?.length) {
if (!constraintResponse?.results.violations?.length) {
return showConstraintsWithNoViolations;
}
return true;
});
}
function getViolationCount(constraintResults: ConstraintResult[]) {
return constraintResults.reduce((count, constraintResult) => {
return count + constraintResult.violations.length;
function getViolationCount(constraintResponse: ConstraintResponse[]) {
return constraintResponse.reduce((count, constraintResponse) => {
return constraintResponse.results.violations ? constraintResponse.results.violations.length + count : 0;
}, 0);
}
Expand Down Expand Up @@ -278,11 +287,11 @@
{#each filteredConstraints as constraint}
<ConstraintListItem
{constraint}
constraintResult={filteredConstraintResultMap[constraint.id]}
constraintResponse={filteredConstraintResponseMap[constraint.id]}
hasDeletePermission={$plan ? featurePermissions.constraints.canDelete(user, $plan) : false}
hasEditPermission={$plan ? featurePermissions.constraints.canUpdate(user, $plan) : false}
plan={$plan}
totalViolationCount={$constraintResultMap[constraint.id]?.violations?.length || 0}
totalViolationCount={$constraintResponseMap[constraint.id]?.results.violations?.length || 0}
{user}
visible={$constraintVisibilityMap[constraint.id]}
on:toggleVisibility={toggleVisibility}
Expand Down
29 changes: 16 additions & 13 deletions src/components/timeline/TimelineHistogram.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import type { ScaleTime } from 'd3-scale';
import { select, type Selection } from 'd3-selection';
import { zoom as d3Zoom, zoomIdentity, type D3ZoomEvent, type ZoomBehavior, type ZoomTransform } from 'd3-zoom';
import { isEmpty } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import type { ActivityDirective } from '../../types/activity';
import type { ConstraintResult } from '../../types/constraint';
Expand Down Expand Up @@ -186,21 +187,23 @@
// Compute constraint violations histogram
constraintViolationsToRender = [];
constraintResults.forEach(constraintResult => {
constraintResult.violations.forEach(violation => {
violation.windows.forEach(window => {
if (xScaleMax !== null) {
const xStart = xScaleMax(window.start);
const xEnd = xScaleMax(window.end);
const clampedStart = xStart < 0 ? 0 : xStart;
const clampedEnd = xEnd > drawWidth ? drawWidth : xEnd;
const width = clampedEnd - clampedStart;
const clampedWidth = width <= 0 ? 5 : width;
constraintViolationsToRender.push({ width: clampedWidth, x: clampedStart });
}
constraintResults
.filter(result => !isEmpty(result))
.forEach(constraintResult => {
constraintResult.violations.forEach(violation => {
violation.windows.forEach(window => {
if (xScaleMax !== null) {
const xStart = xScaleMax(window.start);
const xEnd = xScaleMax(window.end);
const clampedStart = xStart < 0 ? 0 : xStart;
const clampedEnd = xEnd > drawWidth ? drawWidth : xEnd;
const width = clampedEnd - clampedStart;
const clampedWidth = width <= 0 ? 5 : width;
constraintViolationsToRender.push({ width: clampedWidth, x: clampedStart });
}
});
});
});
});
}
$: if (histogramContainer && drawWidth) {
Expand Down
18 changes: 13 additions & 5 deletions src/components/timeline/Tooltip.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

<script lang="ts">
import { select } from 'd3-selection';
import { find } from 'lodash-es';
import { constraintResponseMap } from '../../stores/constraints';
import type { ActivityDirective } from '../../types/activity';
import type { ConstraintResult } from '../../types/constraint';
import type { ConstraintResponse, ConstraintResult } from '../../types/constraint';
import type { Span } from '../../types/simulation';
import type { LinePoint, MouseOver, Point, XRangePoint } from '../../types/timeline';
import { getDoyTime } from '../../utilities/time';
Expand Down Expand Up @@ -186,14 +188,20 @@
}
function textForConstraintViolation(constraintViolation: ConstraintResult): string {
const { constraintName } = constraintViolation;
return `
const matchResponse: ConstraintResponse = find(
Object.values(constraintResponseMap),
(response: ConstraintResponse) => response.results === constraintViolation,
);
return matchResponse
? `
<div>
Constraint Violation
<br>
Name: ${constraintName}
Name: ${matchResponse.constraintName}
</div>
`;
`
: '';
}
function textForLinePoint(point: LinePoint): string {
Expand Down
32 changes: 18 additions & 14 deletions src/stores/constraints.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { keyBy } from 'lodash-es';
import { isEmpty, keyBy } from 'lodash-es';
import { derived, get, writable, type Readable, type Writable } from 'svelte/store';
import type { Constraint, ConstraintResponse, ConstraintResult } from '../types/constraint';
import gql from '../utilities/gql';
Expand Down Expand Up @@ -50,28 +50,32 @@ export const constraintResults: Readable<ConstraintResult[]> = derived(
.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,
})),
})),
violations: !isEmpty(constraintResult)
? constraintResult.violations.map(violation => ({
...violation,
windows: violation.windows.map(({ end, start }) => ({
end: $planStartTimeMs + end / 1000,
start: $planStartTimeMs + start / 1000,
})),
}))
: [],
});

return list;
}, []),
);

export const visibleConstraintResults: Readable<ConstraintResult[]> = derived(
[constraintResults, constraintVisibilityMap],
([$constraintResults, $constraintVisibilityMap]) =>
$constraintResults.filter(constraintResult => $constraintVisibilityMap[constraintResult.constraintId]),
[constraintResponse, constraintVisibilityMap],
([$constraintResponse, $constraintVisibilityMap]) =>
$constraintResponse
.filter(response => $constraintVisibilityMap[response.constraintId])
.map(response => response.results),
);

export const constraintResultMap: Readable<Record<Constraint['id'], ConstraintResult>> = derived(
[constraintResults],
([$constraintResults]) => keyBy($constraintResults, 'constraintId'),
export const constraintResponseMap: Readable<Record<Constraint['id'], ConstraintResponse>> = derived(
[constraintResponse],
([$constraintResponse]) => keyBy($constraintResponse, 'constraintId'),
);

/* Helper Functions. */
Expand Down
6 changes: 3 additions & 3 deletions src/types/constraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ export type ConstraintViolation = {
};

export type ConstraintResult = {
constraintId: Constraint['id'];
constraintName: Constraint['name'];
gaps: TimeRange[];
resourceIds: string[];
type: ConstraintType;
violations: ConstraintViolation[];
};

export type ConstraintResponse = {
constraintId: Constraint['id'];
constraintName: Constraint['name'];
errors: UserCodeError[];
results: ConstraintResult;
success: boolean;
type: ConstraintType;
};

export type UserCodeError = {
Expand Down
6 changes: 3 additions & 3 deletions src/utilities/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ const gql = {
query CheckConstraints($planId: Int!) {
constraintResponses: constraintViolations(planId: $planId) {
success
constraintId
constraintName
type
results {
constraintId
constraintName
resourceIds
type
gaps {
end
start
Expand Down

0 comments on commit 73d5d13

Please sign in to comment.