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

Add Constraint Compile Icon #1022

Merged
merged 5 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading