From f16e80a0c6545aaf0fcf6bed4b896a66644ceef9 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Fri, 3 Jan 2025 14:43:28 -0800 Subject: [PATCH 01/37] Add loading text component --- src/components/Loading.svelte | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/components/Loading.svelte diff --git a/src/components/Loading.svelte b/src/components/Loading.svelte new file mode 100644 index 0000000000..cb5deed8de --- /dev/null +++ b/src/components/Loading.svelte @@ -0,0 +1,10 @@ +
Loading...
+ + From 735b930d8f2589cad7ab9d1aede9304a535b7260 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Fri, 3 Jan 2025 14:44:00 -0800 Subject: [PATCH 02/37] Add data grid loading skeleton and loading prop --- .../ui/DataGrid/BulkActionDataGrid.svelte | 4 + src/components/ui/DataGrid/DataGrid.svelte | 46 ++++++- .../ui/DataGrid/DataGridSkeleton.svelte | 122 ++++++++++++++++++ .../ui/DataGrid/SingleActionDataGrid.svelte | 4 + 4 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 src/components/ui/DataGrid/DataGridSkeleton.svelte diff --git a/src/components/ui/DataGrid/BulkActionDataGrid.svelte b/src/components/ui/DataGrid/BulkActionDataGrid.svelte index dbfd665edd..ca8058227c 100644 --- a/src/components/ui/DataGrid/BulkActionDataGrid.svelte +++ b/src/components/ui/DataGrid/BulkActionDataGrid.svelte @@ -32,12 +32,14 @@ export let hasDeletePermissionError: string = 'You do not have permission to delete.'; export let idKey: keyof RowData = 'id'; export let items: RowData[]; + export let loading: boolean = false; export let pluralItemDisplayText: string = ''; export let scrollToSelection: boolean = false; export let selectedItemId: RowId | null = null; export let selectedItemIds: RowId[] = []; export let showContextMenu: boolean = true; export let showCopyMenu: boolean = false; + export let showLoadingSkeleton: boolean = false; export let singleItemDisplayText: string = ''; export let suppressDragLeaveHidesColumns: boolean = true; export let suppressRowClickSelection: boolean = false; @@ -163,9 +165,11 @@ rowData={items} rowSelection="multiple" {scrollToSelection} + {showLoadingSkeleton} {suppressDragLeaveHidesColumns} {suppressRowClickSelection} {filterExpression} + {loading} on:blur={onBlur} on:cellEditingStarted on:cellEditingStopped diff --git a/src/components/ui/DataGrid/DataGrid.svelte b/src/components/ui/DataGrid/DataGrid.svelte index d7f7820d3e..742cfe74d3 100644 --- a/src/components/ui/DataGrid/DataGrid.svelte +++ b/src/components/ui/DataGrid/DataGrid.svelte @@ -58,6 +58,7 @@ import type { DataGridRowDoubleClick, DataGridRowSelection, RowId, TRowData } from '../../../types/data-grid'; import ContextMenu from '../../context-menu/ContextMenu.svelte'; import ColumnResizeContextMenu from './column-menu/ColumnResizeContextMenu.svelte'; + import DataGridSkeleton from './DataGridSkeleton.svelte'; export function autoSizeColumns(keys: (string | Column)[], skipHeader?: boolean) { gridApi?.autoSizeColumns(keys, skipHeader); @@ -99,15 +100,17 @@ export let highlightOnSelection: boolean = true; export let doesExternalFilterPass: ((node: IRowNode) => boolean) | undefined = undefined; export let idKey: keyof RowData = 'id'; + export let loading: boolean = false; export let maintainColumnOrder: boolean | undefined = undefined; export let isExternalFilterPresent: ((params: IsExternalFilterPresentParams) => boolean) | undefined = undefined; export let rowData: RowData[] = []; - export let rowHeight: number | undefined = undefined; + export let rowHeight: number | undefined = 33; export let rowSelection: 'single' | 'multiple' | undefined = undefined; export let scrollToSelection: boolean = false; export let selectedRowIds: RowId[] = []; export let shouldAutoGenerateId: boolean = false; + export let showLoadingSkeleton: boolean = false; export let suppressCellFocus: boolean = true; export let suppressDragLeaveHidesColumns: boolean = true; export let suppressRowClickSelection: boolean = false; @@ -132,6 +135,8 @@ let gridOptions: GridOptions; let gridApi: GridApi | undefined; let gridDiv: HTMLDivElement; + let loadingMessageTimeout: NodeJS.Timeout | null = null; + let mounted: boolean = false; let onColumnStateChangeDebounced = debounce(onColumnStateChange, 500); let onWindowResizedDebounced = debounce(sizeColumnsToFit, 50); let previousSelectedRowId: RowId | null = null; @@ -232,6 +237,23 @@ This has been seen to result in unintended and often glitchy behavior, which oft gridApi?.setGridOption('quickFilterText', filterExpression); } + $: if (loading) { + if (loadingMessageTimeout) { + clearTimeout(loadingMessageTimeout); + } + loadingMessageTimeout = setTimeout(() => { + gridApi?.setGridOption('loading', true); + }, 50); + } else { + if (loadingMessageTimeout) { + clearTimeout(loadingMessageTimeout); + } + if (gridApi?.getGridOption('loading')) { + gridApi?.setGridOption('loading', false); + } + // gridApi?.setGridOption('suppressNoRowsOverlay', false); + } + onDestroy(() => { resizeObserver?.disconnect(); }); @@ -411,6 +433,7 @@ This has been seen to result in unintended and often glitchy behavior, which oft rowSelection, suppressCellFocus, suppressDragLeaveHidesColumns, + suppressNoRowsOverlay: loading, suppressRowClickSelection, }; gridApi = createGrid(gridDiv, gridOptions); @@ -421,10 +444,19 @@ This has been seen to result in unintended and often glitchy behavior, which oft }); resizeObserver.observe(gridDiv); } + + mounted = true; }); -
+
+ {#if !mounted && showLoadingSkeleton} +
+ !c.hide).length} /> +
+ {/if} +
+
@@ -432,6 +464,16 @@ This has been seen to result in unintended and often glitchy behavior, which oft diff --git a/src/components/ui/DataGrid/SingleActionDataGrid.svelte b/src/components/ui/DataGrid/SingleActionDataGrid.svelte index 0201632ff6..17f1ac24e4 100644 --- a/src/components/ui/DataGrid/SingleActionDataGrid.svelte +++ b/src/components/ui/DataGrid/SingleActionDataGrid.svelte @@ -32,8 +32,10 @@ export let idKey: keyof RowData = 'id'; export let items: RowData[]; export let itemDisplayText: string; + export let loading: boolean = false; export let selectedItemId: RowId | null = null; export let scrollToSelection: boolean = false; + export let showLoadingSkeleton: boolean = false; export let user: User | null; export let getRowId: (data: RowData) => RowId = (data: RowData): RowId => parseInt(data[idKey]); @@ -119,6 +121,8 @@ rowData={items} rowSelection="single" {scrollToSelection} + {showLoadingSkeleton} + {loading} on:blur={onBlur} on:cellEditingStarted on:cellEditingStopped From 3265b6f9ea2e12a42a2e985830ce400cf169721b Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Fri, 3 Jan 2025 14:44:30 -0800 Subject: [PATCH 03/37] Improve loading indication for several stores and requests --- .../activity/ActivityDecomposition.svelte | 6 +- .../activity/ActivityDirectivesTable.svelte | 25 +++--- .../ActivityDirectivesTablePanel.svelte | 2 +- .../activity/ActivityFormPanel.svelte | 8 +- .../activity/ActivitySpanForm.svelte | 4 +- .../activity/ActivitySpansTable.svelte | 5 +- .../ExternalSourceManager.svelte | 2 +- .../modals/DeleteActivitiesModal.svelte | 6 +- .../modals/DeleteExternalSourceModal.svelte | 2 +- src/components/modals/SavedViewsModal.svelte | 8 +- src/components/model/Models.svelte | 35 ++++----- src/components/plan/PlanForm.svelte | 8 +- .../SchedulingGoalAnalysesActivities.svelte | 19 ++--- .../simulation/SimulationEventsPanel.svelte | 2 +- .../simulation/SimulationPanel.svelte | 78 +++++++++++-------- src/components/timeline/LayerDiscrete.svelte | 4 +- src/components/timeline/Row.svelte | 36 +++++---- src/components/timeline/Timeline.svelte | 18 +++-- .../timeline/TimelineViewControls.svelte | 6 +- src/components/view/ViewsTable.svelte | 5 +- src/css/ag-grid-stellar.css | 19 +++++ src/routes/models/+page.svelte | 2 +- src/routes/plans/+page.svelte | 40 +++++----- src/routes/plans/[id]/+page.svelte | 4 +- src/stores/activities.ts | 13 ++-- src/stores/errors.ts | 4 +- src/stores/planSnapshots.ts | 2 +- src/stores/plans.ts | 2 +- src/stores/scheduling.ts | 4 +- src/stores/simulation.ts | 22 +++--- src/stores/views.ts | 2 +- src/utilities/effects.ts | 21 +++-- src/utilities/gql.ts | 68 +++++++++++++++- 33 files changed, 301 insertions(+), 181 deletions(-) diff --git a/src/components/activity/ActivityDecomposition.svelte b/src/components/activity/ActivityDecomposition.svelte index 02acba0259..b4d993f4f0 100644 --- a/src/components/activity/ActivityDecomposition.svelte +++ b/src/components/activity/ActivityDecomposition.svelte @@ -12,7 +12,7 @@ export let expanded = true; export let rootSpanId: SpanId | null = null; export let selectedSpanId: SpanId | null = null; - export let spansMap: SpansMap = {}; + export let spansMap: SpansMap | null = {}; export let spanUtilityMaps: SpanUtilityMaps; export let childPageSize: number = 25; @@ -31,7 +31,7 @@ let buttonClass: string = ''; let childIdsInView: SpanId[] = []; - $: span = rootSpanId !== null ? spansMap[rootSpanId] : null; + $: span = rootSpanId !== null && spansMap !== null ? spansMap[rootSpanId] : null; $: isRoot = span ? !span.parent_id : true; $: type = span?.type || ''; $: childIds = span !== null ? spanUtilityMaps?.spanIdToChildIdsMap[span?.span_id] || [] : []; @@ -76,7 +76,7 @@
- {#if hasChildren && expanded} + {#if hasChildren && expanded && spansMap}
    {#each childIdsInView as childId}
  • diff --git a/src/components/activity/ActivityDirectivesTable.svelte b/src/components/activity/ActivityDirectivesTable.svelte index 23436e2341..4f5e4ea906 100644 --- a/src/components/activity/ActivityDirectivesTable.svelte +++ b/src/components/activity/ActivityDirectivesTable.svelte @@ -2,29 +2,29 @@ diff --git a/src/components/simulation/SimulationEventsPanel.svelte b/src/components/simulation/SimulationEventsPanel.svelte index a0e9684b91..34c44f28f1 100644 --- a/src/components/simulation/SimulationEventsPanel.svelte +++ b/src/components/simulation/SimulationEventsPanel.svelte @@ -106,7 +106,7 @@ resizable: true, sortable: true, valueGetter: params => { - if (params.data?.span_id) { + if ($spansMap && params.data?.span_id) { const span = $spansMap[params.data?.span_id] || null; if (span) { return JSON.stringify(span); diff --git a/src/components/simulation/SimulationPanel.svelte b/src/components/simulation/SimulationPanel.svelte index 7dcc3b3d65..398fc821a8 100644 --- a/src/components/simulation/SimulationPanel.svelte +++ b/src/components/simulation/SimulationPanel.svelte @@ -40,6 +40,7 @@ import { required, validateStartTime } from '../../utilities/validators'; import Collapse from '../Collapse.svelte'; import DatePickerField from '../form/DatePickerField.svelte'; + import Loading from '../Loading.svelte'; import GridMenu from '../menus/GridMenu.svelte'; import Parameters from '../parameters/Parameters.svelte'; import DatePickerActionButton from '../ui/DatePicker/DatePickerActionButton.svelte'; @@ -64,6 +65,7 @@ let hasRunPermission: boolean = false; let hasUpdatePermission: boolean = false; let isFilteredBySnapshot: boolean = false; + let loadingArguments: boolean = true; let numOfUserChanges: number = 0; let startTime: string; let startTimeField: FieldStore; @@ -120,6 +122,7 @@ $: if ($simulation && $plan) { // An empty object is provided in order to get only the default argument values to better distinguish overridden arguments effects.getEffectiveModelArguments($plan.model.id, {}, user).then(response => { + loadingArguments = false; if ($simulation !== null && response !== null) { const { arguments: defaultArguments } = response; // Displayed simulation arguments are either user input arguments, @@ -156,11 +159,11 @@ $: isFilteredBySnapshot = $planSnapshot !== null; $: if (isFilteredBySnapshot) { - filteredSimulationDatasets = $simulationDatasetsPlan.filter( + filteredSimulationDatasets = ($simulationDatasetsPlan || []).filter( simulationDataset => $planSnapshot === null || simulationDataset.plan_revision === $planSnapshot?.revision, ); } else { - filteredSimulationDatasets = $simulationDatasetsPlan; + filteredSimulationDatasets = $simulationDatasetsPlan || []; } $: enableReSimulation = @@ -425,36 +428,40 @@
    -
    - 0} - selectedSimulationTemplate={$simulation?.template} - plan={$plan} - {user} - on:applyTemplate={onApplySimulationTemplate} - on:deleteTemplate={onDeleteSimulationTemplate} - on:saveNewTemplate={onSaveNewSimulationTemplate} - on:saveTemplate={onSaveSimulationTemplate} - /> -
    - {#if formParameters.length} - + {#if loadingArguments} + {:else} -
    No simulation arguments found
    +
    + 0} + selectedSimulationTemplate={$simulation?.template} + plan={$plan} + {user} + on:applyTemplate={onApplySimulationTemplate} + on:deleteTemplate={onDeleteSimulationTemplate} + on:saveNewTemplate={onSaveNewSimulationTemplate} + on:saveTemplate={onSaveSimulationTemplate} + /> +
    + {#if formParameters.length} + + {:else} +
    No simulation arguments found
    + {/if} {/if}
    @@ -473,14 +480,18 @@ {/if}
    - {#if !filteredSimulationDatasets || !filteredSimulationDatasets.length} + {#if !$simulationDatasetsPlan} +
    + +
    + {:else if !filteredSimulationDatasets || !filteredSimulationDatasets.length}
    No Simulation Datasets
    {:else} {#each filteredSimulationDatasets as simDataset (simDataset.id)} = {}; let loadedResources: Resource[]; - let loadingErrors: string[]; + let resourceLoadingErrors: string[]; let anyResourcesLoading: boolean = true; let discreteTree: DiscreteTree = []; let filteredActivityDirectives: ActivityDirective[] = []; @@ -374,11 +374,12 @@ } }); loadedResources = newLoadedResources; - loadingErrors = newLoadingErrors; + resourceLoadingErrors = newLoadingErrors; // Consider row to be loading if the number of completed resource requests (loaded or error state) // is not equal to the total number of resource requests - anyResourcesLoading = loadedResources.length + loadingErrors.length !== Object.keys(resourceRequestMap).length; + anyResourcesLoading = + loadedResources.length + resourceLoadingErrors.length !== Object.keys(resourceRequestMap).length; } // Compute scale domains for axes since it is optionally defined in the view @@ -539,11 +540,18 @@ } } - $: if (hasActivityLayer && filterItemsByTime && filteredActivityDirectives && filteredSpans && viewTimeRange) { + $: if ( + spansMap && + hasActivityLayer && + filteredActivityDirectives && + filteredSpans && + viewTimeRange && + filterItemsByTime + ) { timeFilteredSpans = filteredSpans.filter(span => spanInView(span, viewTimeRange)); timeFilteredActivityDirectives = filteredActivityDirectives.filter(directive => { let inView = directiveInView(directive, viewTimeRange); - if (inView && showSpans) { + if (inView && showSpans && spansMap) { // Get max span bounds const rootSpanId = spanUtilityMaps.directiveIdToSpanIdMap[directive.id]; const rootSpan = spansMap[rootSpanId]; @@ -605,7 +613,7 @@ groupEventsByMethod, filterItemsByTime, spanUtilityMaps, - spansMap, + spansMap || {}, showSpans, showDirectives, viewTimeRange, @@ -947,17 +955,17 @@ - {#if hasResourceLayer && anyResourcesLoading} -
    Loading
    + {#if (hasResourceLayer && anyResourcesLoading) || (hasActivityLayer && (!activityDirectivesMap || !spansMap))} +
    Loading...
    {/if} {#if !layers.length}
    No layers added to this row
    {/if} - {#if hasResourceLayer && loadingErrors.length} + {#if hasResourceLayer && resourceLoadingErrors.length}
    - Failed to load profiles for {loadingErrors.length} layer{pluralize(loadingErrors.length)} + Failed to load profiles for {resourceLoadingErrors.length} layer{pluralize(resourceLoadingErrors.length)}
    {/if} @@ -1023,7 +1031,7 @@ {selectedSpanId} {selectedExternalEventId} {spanUtilityMaps} - {spansMap} + spansMap={spansMap || {}} {timelineInteractionMode} {timelineLockStatus} {user} diff --git a/src/components/timeline/Timeline.svelte b/src/components/timeline/Timeline.svelte index d468ae2777..2e25500e57 100644 --- a/src/components/timeline/Timeline.svelte +++ b/src/components/timeline/Timeline.svelte @@ -46,7 +46,7 @@ import Tooltip from './Tooltip.svelte'; import TimelineXAxis from './XAxis.svelte'; - export let activityDirectivesMap: ActivityDirectivesMap = {}; + export let activityDirectivesMap: ActivityDirectivesMap | null = null; export let externalEvents: ExternalEvent[] = []; export let constraintResults: ConstraintResultWithName[] = []; export let hasUpdateDirectivePermission: boolean = false; @@ -62,8 +62,8 @@ export let simulation: Simulation | null = null; export let simulationDataset: SimulationDataset | null = null; export let spanUtilityMaps: SpanUtilityMaps; - export let spansMap: SpansMap = {}; - export let spans: Span[] = []; + export let spansMap: SpansMap | null = {}; + export let spans: Span[] | null = []; export let timeline: Timeline | null = null; export let timelineInteractionMode: TimelineInteractionMode; export let timelineLockStatus: TimelineLockStatus; @@ -122,7 +122,7 @@ trailing: true, }); - $: activityDirectives = Object.values(activityDirectivesMap); + $: activityDirectives = activityDirectivesMap ? Object.values(activityDirectivesMap) : null; $: derivationGroups = $planDerivationGroupLinks .filter(link => link.plan_id === plan?.id) .map(link => link.derivation_group_name); @@ -393,8 +393,9 @@ /> {/if}
    + viewTimeRangeChanged(event.detail)} {simulation} {simulationDataset} - {spansMap} + spansMap={spansMap || {}} {spanUtilityMaps} {plan} {planStartTimeYmd} @@ -596,6 +597,7 @@ .timeline-padded-content { background: white; border-radius: 4px; + position: relative; } :global(#dnd-action-dragged-el .row-root) { diff --git a/src/components/timeline/TimelineViewControls.svelte b/src/components/timeline/TimelineViewControls.svelte index 65dd3c4a43..05c0a9be5c 100644 --- a/src/components/timeline/TimelineViewControls.svelte +++ b/src/components/timeline/TimelineViewControls.svelte @@ -22,8 +22,8 @@ selectedSpan, simulationDataset, simulationDatasetId, - spanUtilityMaps, spansMap, + spanUtilityMaps, } from '../../stores/simulation'; import { timelineInteractionMode, timelineLockStatus, viewIsModified } from '../../stores/views'; import type { TimeRange } from '../../types/timeline'; @@ -183,8 +183,8 @@ $selectedActivityDirective.id, $plan.start_time, $plan.end_time_doy, - $activityDirectivesMap, - $spansMap, + $activityDirectivesMap || {}, + $spansMap || {}, $spanUtilityMaps, ); } else if ($selectedSpan && $simulationDataset?.simulation_start_time) { diff --git a/src/components/view/ViewsTable.svelte b/src/components/view/ViewsTable.svelte index c09776dc9b..d5865743a5 100644 --- a/src/components/view/ViewsTable.svelte +++ b/src/components/view/ViewsTable.svelte @@ -10,7 +10,7 @@ import BulkActionDataGrid from '../ui/DataGrid/BulkActionDataGrid.svelte'; import DataGridActions from '../ui/DataGrid/DataGridActions.svelte'; - export let views: ViewSlim[] = []; + export let views: ViewSlim[] | null = []; export let user: User | null; type CellRendererParams = { @@ -136,7 +136,8 @@ rowData?.data?.owner !== 'system'} - items={views} + items={views || []} + loading={!views} pluralItemDisplayText="Views" singleItemDisplayText="View" {user} diff --git a/src/css/ag-grid-stellar.css b/src/css/ag-grid-stellar.css index 9b6dbaaf0b..e4caa0beef 100644 --- a/src/css/ag-grid-stellar.css +++ b/src/css/ag-grid-stellar.css @@ -532,3 +532,22 @@ opacity: var(--ag-icon-image-opacity-filter, 0.9); transition: opacity 0.1s linear; } + +.ag-overlay-loading-center { + border: none; + box-shadow: none; + color: var(--st-typography-label-color); + font-family: var(--st-typography-label-font-family); + font-size: var(--st-typography-label-font-size); + font-weight: var(--st-typography-label-font-weight); + letter-spacing: var(--st-typography-label-letter-spacing); + line-height: var(--st-typography-label-line-height); +} + +.ag-overlay-no-rows-center { + font-family: var(--st-typography-body-font-family); + font-size: var(--st-typography-body-font-size); + font-weight: var(--st-typography-body-font-weight); + letter-spacing: var(--st-typography-body-letter-spacing); + line-height: var(--st-typography-body-line-height); +} diff --git a/src/routes/models/+page.svelte b/src/routes/models/+page.svelte index 9f97840142..7c7dfeac1a 100644 --- a/src/routes/models/+page.svelte +++ b/src/routes/models/+page.svelte @@ -10,4 +10,4 @@ - + diff --git a/src/routes/plans/+page.svelte b/src/routes/plans/+page.svelte index 35ee8bdfef..fdc3f6a6e5 100644 --- a/src/routes/plans/+page.svelte +++ b/src/routes/plans/+page.svelte @@ -212,7 +212,7 @@ let nameField = field('', [ required, unique( - $plans.map(plan => plan.name), + ($plans || []).map(plan => plan.name), 'Plan name already exists', ), ]); @@ -250,6 +250,7 @@ selectedPlanModelName = $models.find(model => model.id === selectedPlan?.model_id)?.name ?? null; } } + $: plans.updateValue(() => data.plans); $: models.updateValue(() => data.models); // sort in descending ID order $: orderedModels = [...$models].sort(({ id: idA }, { id: idB }) => { @@ -340,6 +341,7 @@ ]; } $: createButtonEnabled = + $plans !== null && $endTimeField.dirtyAndValid && $modelIdField.dirtyAndValid && $nameField.dirtyAndValid && @@ -350,7 +352,7 @@ } else { createPlanButtonText = planUploadFiles ? 'Create from .json' : 'Create'; } - $: filteredPlans = $plans.filter(plan => { + $: filteredPlans = ($plans || []).filter(plan => { const filterTextLowerCase = filterText.toLowerCase(); return ( plan.end_time_doy.includes(filterTextLowerCase) || @@ -419,8 +421,8 @@ tag_id, })); newPlan.tags = planTags.map(tag => ({ tag })); - if (!$plans.find(({ id }) => newPlan.id === id)) { - plans.updateValue(storePlans => [...storePlans, newPlan]); + if (!($plans || []).find(({ id }) => newPlan.id === id)) { + plans.updateValue(storePlans => [...(storePlans || []), newPlan]); } await effects.createPlanTags(newPlanTags, newPlan, user); startTimeField.reset(''); @@ -434,7 +436,7 @@ const success = await effects.deletePlan(plan, user); if (success) { - plans.updateValue(storePlans => storePlans.filter(p => plan.id !== p.id)); + plans.updateValue(storePlans => (storePlans || []).filter(p => plan.id !== p.id)); } } @@ -960,21 +962,19 @@ - {#if filteredPlans && filteredPlans.length} - deletePlanContext(event, filteredPlans)} - on:rowClicked={({ detail }) => selectPlan(detail.data.id)} - on:rowDoubleClicked={({ detail }) => openPlan(detail.data.id)} - /> - {:else} - No Plans Found - {/if} + deletePlanContext(event, filteredPlans)} + on:rowClicked={({ detail }) => selectPlan(detail.data.id)} + on:rowDoubleClicked={({ detail }) => openPlan(detail.data.id)} + /> diff --git a/src/routes/plans/[id]/+page.svelte b/src/routes/plans/[id]/+page.svelte index 08397cf3b8..458694c5d9 100644 --- a/src/routes/plans/[id]/+page.svelte +++ b/src/routes/plans/[id]/+page.svelte @@ -367,7 +367,7 @@ .then(newEvents => ($simulationEvents = newEvents)); } else { simulationDataAbortController?.abort(); - $spans = []; + $spans = null; $simulationEvents = []; } @@ -662,7 +662,7 @@ {#if selectedSimulationStatus === Status.Pending && $simulationDatasetLatest}
    {formatSimulationQueuePosition( - getSimulationQueuePosition($simulationDatasetLatest, $simulationDatasetsAll), + getSimulationQueuePosition($simulationDatasetLatest, $simulationDatasetsAll || []), )}
    {:else} diff --git a/src/stores/activities.ts b/src/stores/activities.ts index b78fab27a4..fc823e0514 100644 --- a/src/stores/activities.ts +++ b/src/stores/activities.ts @@ -18,10 +18,10 @@ import { viewUpdateGrid } from './views'; /* Subscriptions. */ -export const activityDirectivesDB = gqlSubscribable( +export const activityDirectivesDB = gqlSubscribable( gql.SUB_ACTIVITY_DIRECTIVES, { planId }, - [], + null, null, ); @@ -75,11 +75,14 @@ export const activityDirectivesMap = derived( $spansMap, $spanUtilityMaps, ]) => { + if (!$activityDirectivesDB || !$spansMap) { + return null; + } if ($initialPlan === null) { return {}; } return computeActivityDirectivesMap( - $planSnapshotId !== null ? $planSnapshotActivityDirectives : $activityDirectivesDB, + $planSnapshotId !== null ? $planSnapshotActivityDirectives : $activityDirectivesDB || [], $initialPlan, $spansMap, $spanUtilityMaps, @@ -90,7 +93,7 @@ export const activityDirectivesMap = derived( export const selectedActivityDirective = derived( [activityDirectivesMap, selectedActivityDirectiveId], ([$activityDirectivesMap, $selectedActivityDirectiveId]) => { - if ($selectedActivityDirectiveId !== null) { + if ($activityDirectivesMap && $selectedActivityDirectiveId !== null) { return $activityDirectivesMap[$selectedActivityDirectiveId] || null; } return null; @@ -132,7 +135,7 @@ export function selectActivity( export function resetActivityStores() { activityMetadataDefinitions.updateValue(() => []); selectedActivityDirectiveId.set(null); - activityDirectivesDB.updateValue(() => []); + activityDirectivesDB.updateValue(() => null); anchorValidationStatuses.updateValue(() => []); activityMetadataDefinitions.updateValue(() => []); activityDirectiveValidationStatuses.updateValue(() => []); diff --git a/src/stores/errors.ts b/src/stores/errors.ts index e25f22837c..a4d6f752a6 100644 --- a/src/stores/errors.ts +++ b/src/stores/errors.ts @@ -61,7 +61,7 @@ export const activityValidationErrors: Readable = de activityId: directiveId, errors: [validations], status, - type: $activityDirectivesMap[directiveId]?.type, + type: ($activityDirectivesMap || {})[directiveId]?.type, // TODO maybe this whole thing should also be a nullable list? }; } else { activityValidationsErrorMap[directiveId].errors.push(validations); @@ -75,7 +75,7 @@ export const activityValidationErrors: Readable = de activityId: activityId, errors: [anchorValidationError], status: 'complete', - type: $activityDirectivesMap[activityId]?.type, + type: ($activityDirectivesMap || {})[activityId]?.type, }; } else { activityValidationsErrorMap[activityId].errors.push(anchorValidationError); diff --git a/src/stores/planSnapshots.ts b/src/stores/planSnapshots.ts index 7a91855e8d..1afc4115e1 100644 --- a/src/stores/planSnapshots.ts +++ b/src/stores/planSnapshots.ts @@ -33,7 +33,7 @@ export const planSnapshotsWithSimulations: Readable = derived( [planSnapshots, simulationDatasetsPlan], ([$planSnapshots, $simulationDatasetsPlan]) => { return $planSnapshots.map(planSnapshot => { - const latestPlanSnapshotSimulation = $simulationDatasetsPlan.find(simulation => { + const latestPlanSnapshotSimulation = ($simulationDatasetsPlan || []).find(simulation => { return simulation.plan_revision === planSnapshot?.revision; }); return { ...planSnapshot, simulation: latestPlanSnapshotSimulation || null }; diff --git a/src/stores/plans.ts b/src/stores/plans.ts index 04e97ccdf0..c22faaf199 100644 --- a/src/stores/plans.ts +++ b/src/stores/plans.ts @@ -5,7 +5,7 @@ import { gqlSubscribable } from './subscribable'; /* Subscriptions. */ -export const plans = gqlSubscribable(gql.SUB_PLANS, {}, [], null, plans => { +export const plans = gqlSubscribable(gql.SUB_PLANS, {}, null, null, plans => { return (plans as PlanSlim[]).map(plan => { return { ...plan, diff --git a/src/stores/scheduling.ts b/src/stores/scheduling.ts index 49e9ae772c..d7d7661c19 100644 --- a/src/stores/scheduling.ts +++ b/src/stores/scheduling.ts @@ -235,7 +235,9 @@ export const schedulingAnalysisStatus = derived( } else { let matchingSimDataset; if (typeof $latestSchedulingRequest.dataset_id === 'number') { - matchingSimDataset = $simulationDatasetsPlan.find(d => d.dataset_id === $latestSchedulingRequest.dataset_id); + matchingSimDataset = ($simulationDatasetsPlan || []).find( + d => d.dataset_id === $latestSchedulingRequest.dataset_id, + ); } /* diff --git a/src/stores/simulation.ts b/src/stores/simulation.ts index 01e60b29a6..097087d761 100644 --- a/src/stores/simulation.ts +++ b/src/stores/simulation.ts @@ -36,7 +36,7 @@ export const resourceTypes: Writable = writable([]); export const resourceTypesLoading: Writable = writable(true); -export const spans: Writable = writable([]); +export const spans: Writable = writable(null); export const yAxesWithScaleDomainsCache: Writable> = writable({}); @@ -72,18 +72,18 @@ export const simulationDatasetLatest = gqlSubscribable }, ); -export const simulationDatasetsPlan = gqlSubscribable( +export const simulationDatasetsPlan = gqlSubscribable( gql.SUB_SIMULATION_DATASETS, { planId }, - [], + null, null, v => v[0]?.simulation_datasets || [], ); -export const simulationDatasetsAll = gqlSubscribable( +export const simulationDatasetsAll = gqlSubscribable( gql.SUB_SIMULATION_DATASETS_ALL, null, - [], + null, null, ); @@ -109,10 +109,10 @@ export const allResourceTypes: Readable = derived( }, ); -export const spansMap: Readable = derived(spans, $spans => keyBy($spans, 'span_id')); +export const spansMap: Readable = derived(spans, $spans => (!spans ? null : keyBy($spans, 'span_id'))); export const spanUtilityMaps: Readable = derived(spans, $spans => { - return createSpanUtilityMaps($spans); + return createSpanUtilityMaps($spans || []); }); export const simulationStatus: Readable = derived( @@ -159,7 +159,7 @@ export const enableSimulation: Readable = derived(simulationStatus, $si }); export const selectedSpan = derived([spansMap, selectedSpanId], ([$spansMap, $selectedSpanId]) => { - if ($selectedSpanId !== null) { + if ($selectedSpanId !== null && $spansMap !== null) { return $spansMap[$selectedSpanId] || null; } @@ -184,8 +184,8 @@ export function resetSimulationStores() { simulationDatasetLatest.updateValue(() => null); simulationEvents.set([]); simulationTemplates.updateValue(() => []); - simulationDatasetsPlan.updateValue(() => []); - simulationDatasetsAll.updateValue(() => []); - spans.set([]); + simulationDatasetsPlan.updateValue(() => null); + simulationDatasetsAll.updateValue(() => null); + spans.set(null); resourceTypes.set([]); } diff --git a/src/stores/views.ts b/src/stores/views.ts index 621e3a9965..60fde6fd8d 100644 --- a/src/stores/views.ts +++ b/src/stores/views.ts @@ -35,7 +35,7 @@ import { gqlSubscribable } from './subscribable'; /* Subscriptions. */ -export const views = gqlSubscribable(gql.SUB_VIEWS, {}, [], null); +export const views = gqlSubscribable(gql.SUB_VIEWS, {}, null, null); /* Writeable. */ diff --git a/src/utilities/effects.ts b/src/utilities/effects.ts index 0b977b355c..8adcbaed65 100644 --- a/src/utilities/effects.ts +++ b/src/utilities/effects.ts @@ -575,7 +575,7 @@ const effects = { const { id } = newActivityDirective; activityDirectivesDB.updateValue(directives => { - return directives.map(directive => { + return (directives || []).map(directive => { if (directive.id === id) { return newActivityDirective; } @@ -2106,7 +2106,7 @@ const effects = { .map(({ affected_row: { id } }) => id); activityDirectivesDB.updateValue(directives => { - return directives.filter(directive => { + return (directives || []).filter(directive => { return deletedActivityIds.indexOf(directive.id) < 1; }); }); @@ -2146,7 +2146,7 @@ const effects = { .map(({ affected_row: { id } }) => id); activityDirectivesDB.updateValue(directives => { - return directives.filter(directive => { + return (directives || []).filter(directive => { return deletedActivityIds.indexOf(directive.id) < 1; }); }); @@ -2184,7 +2184,7 @@ const effects = { .map(({ affected_row: { id } }) => id); activityDirectivesDB.updateValue(directives => { - return directives.filter(directive => { + return (directives || []).filter(directive => { return deletedActivityIds.indexOf(directive.id) < 1; }); }); @@ -2211,7 +2211,7 @@ const effects = { if (response.deleteActivityDirectives) { const deletedActivityIds = response.deleteActivityDirectives.returning.map(({ id }) => id); activityDirectivesDB.updateValue(directives => { - return directives.filter(directive => { + return (directives || []).filter(directive => { return deletedActivityIds.indexOf(directive.id) < 1; }); }); @@ -3891,7 +3891,8 @@ const effects = { async getModels(user: User | null): Promise { try { - const data = await reqHasura(gql.GET_MODELS, {}, user); + const query = convertToQuery(gql.SUB_MODELS); + const data = await reqHasura(query, {}, user); const { models = [] } = data; if (models != null) { return models; @@ -5320,7 +5321,11 @@ const effects = { throwPermissionError('restore plan snapshot'); } - const { confirm, value } = await showRestorePlanSnapshotModal(snapshot, get(activityDirectivesDB).length, user); + const { confirm, value } = await showRestorePlanSnapshotModal( + snapshot, + (get(activityDirectivesDB) || []).length, + user, + ); if (confirm) { if (value && value.shouldCreateSnapshot) { @@ -5573,7 +5578,7 @@ const effects = { if (data.update_activity_directive_by_pk) { const { update_activity_directive_by_pk: updatedDirective } = data; activityDirectivesDB.updateValue(directives => { - return directives.map(directive => { + return (directives || []).map(directive => { if (directive.id === id) { return updatedDirective; } diff --git a/src/utilities/gql.ts b/src/utilities/gql.ts index cb5e1f7e71..e8e62a0477 100644 --- a/src/utilities/gql.ts +++ b/src/utilities/gql.ts @@ -1687,12 +1687,78 @@ const gql = { GET_PLANS_AND_MODELS: `#graphql query GetPlansAndModels { models: ${Queries.MISSION_MODELS}(order_by: { id: desc }) { - id + constraint_specification { + constraint_id + constraint_revision + constraint_definition { + definition + tags { + tag_id + } + } + constraint_metadata { + id + name + } + } + created_at + default_view_id + description jar_id + id + mission name + owner plans { id } + refresh_activity_type_logs(order_by: { created_at: desc }, limit: 1) { + error + error_message + pending + success + } + refresh_resource_type_logs(order_by: { created_at: desc }, limit: 1) { + error + error_message + pending + success + } + refresh_model_parameter_logs(order_by: { created_at: desc }, limit: 1) { + error + error_message + pending + success + } + scheduling_specification_conditions { + condition_id + condition_revision + condition_definition { + definition + tags { + tag_id + } + } + condition_metadata { + id + name + } + } + scheduling_specification_goals { + goal_id + goal_revision + goal_definition { + definition + tags { + tag_id + } + } + goal_metadata { + id + name + } + priority + } version } plans: ${Queries.PLANS}(order_by: { id: desc }) { From e083b5fef15ef037de5252339a6f192a94f29e5e Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 15 Jan 2025 09:46:25 -0800 Subject: [PATCH 04/37] Loading style fix --- src/components/activity/TimelineItemsPanel.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/activity/TimelineItemsPanel.svelte b/src/components/activity/TimelineItemsPanel.svelte index d8b773b8d1..31fd958322 100644 --- a/src/components/activity/TimelineItemsPanel.svelte +++ b/src/components/activity/TimelineItemsPanel.svelte @@ -73,7 +73,7 @@ } :global(button.timeline-items-tab.selected) { - background-color: white; + background-color: white !important; box-shadow: 1px 0px 0px inset var(--st-gray-20), -1px 0px 0px inset var(--st-gray-20); From 76138bfb95452195da074a33dfac0cd1f583036d Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 15 Jan 2025 09:48:46 -0800 Subject: [PATCH 05/37] Constraint runs loading improvements --- src/components/constraints/ConstraintsPanel.svelte | 8 +++++++- src/stores/constraints.ts | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/constraints/ConstraintsPanel.svelte b/src/components/constraints/ConstraintsPanel.svelte index e7ba384e60..8808350133 100644 --- a/src/components/constraints/ConstraintsPanel.svelte +++ b/src/components/constraints/ConstraintsPanel.svelte @@ -15,6 +15,7 @@ cachedConstraintsStatus, constraintPlanSpecs, constraintResponseMap, + constraintRuns, constraintVisibilityMap, constraintsMap, constraintsStatus, @@ -42,6 +43,7 @@ import { required } from '../../utilities/validators'; import CollapsibleListControls from '../CollapsibleListControls.svelte'; import DatePickerField from '../form/DatePickerField.svelte'; + import Loading from '../Loading.svelte'; import GridMenu from '../menus/GridMenu.svelte'; import DatePickerActionButton from '../ui/DatePicker/DatePickerActionButton.svelte'; import Panel from '../ui/Panel.svelte'; @@ -313,7 +315,11 @@
    - {#if !filteredConstraints.length} + {#if !$constraintRuns} +
    + +
    + {:else if !filteredConstraints.length}
    No constraints found
    diff --git a/src/stores/constraints.ts b/src/stores/constraints.ts index a941e3b752..c0a5948349 100644 --- a/src/stores/constraints.ts +++ b/src/stores/constraints.ts @@ -30,10 +30,10 @@ export const constraintsColumns: Writable = writable('1fr 3px 1fr'); export const constraints = gqlSubscribable(gql.SUB_CONSTRAINTS, {}, [], null); -export const constraintRuns = gqlSubscribable( +export const constraintRuns = gqlSubscribable( gql.SUB_CONSTRAINT_RUNS, { simulationDatasetId: simulationDatasetLatestId }, - [], + null, null, ); @@ -123,7 +123,7 @@ export const constraintResponseMap: Readable { const cachedResponseMap = keyBy( - $constraintRuns.map( + ($constraintRuns || []).map( run => ({ constraintId: run.constraint_id, @@ -185,7 +185,7 @@ export const uncheckedConstraintCount: Readable = derived( export const relevantConstraintRuns: Readable = derived( [constraintRuns, constraintPlanSpecsMap], ([$constraintRuns, $constraintPlanSpecsMap]) => { - return $constraintRuns.filter(constraintRun => { + return ($constraintRuns || []).filter(constraintRun => { const constraintPlanSpec = $constraintPlanSpecsMap[constraintRun.constraint_id]; let revision = -1; From 927ead545b3fbe1b5f22a59c66c4648f9444f85a Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 15 Jan 2025 09:48:59 -0800 Subject: [PATCH 06/37] Fix initial navbar jerkiness on page load --- src/routes/plans/[id]/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/plans/[id]/+page.svelte b/src/routes/plans/[id]/+page.svelte index 458694c5d9..649f47a8e6 100644 --- a/src/routes/plans/[id]/+page.svelte +++ b/src/routes/plans/[id]/+page.svelte @@ -179,7 +179,7 @@ let modelErrorCount: number = 0; let simulationExtent: string | null; let selectedSimulationStatus: Status | null; - let windowWidth = 0; + let windowWidth = 1600; let simulationDataAbortController: AbortController; let resourcesExternalAbortController: AbortController; let schedulingStatusText: string = ''; From 4b9fad1c3b4505864ca4906e878d0b9a23fbc293 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 15 Jan 2025 09:49:11 -0800 Subject: [PATCH 07/37] DataGrid loading improvements --- src/components/ui/DataGrid/DataGrid.svelte | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/ui/DataGrid/DataGrid.svelte b/src/components/ui/DataGrid/DataGrid.svelte index 742cfe74d3..4d523f6661 100644 --- a/src/components/ui/DataGrid/DataGrid.svelte +++ b/src/components/ui/DataGrid/DataGrid.svelte @@ -160,6 +160,9 @@ This has been seen to result in unintended and often glitchy behavior, which oft ); } }); + if (rowData.length < 1 && !isLoading()) { + gridApi?.setGridOption('suppressNoRowsOverlay', false); + } gridApi?.setGridOption('rowData', rowData); const previousSelectedRowIds: RowId[] = []; @@ -251,13 +254,16 @@ This has been seen to result in unintended and often glitchy behavior, which oft if (gridApi?.getGridOption('loading')) { gridApi?.setGridOption('loading', false); } - // gridApi?.setGridOption('suppressNoRowsOverlay', false); } onDestroy(() => { resizeObserver?.disconnect(); }); + function isLoading() { + return loading; + } + function onAutoSizeContent() { gridApi?.autoSizeAllColumns(); } @@ -299,6 +305,7 @@ This has been seen to result in unintended and often glitchy behavior, which oft ...(shouldAutoGenerateId ? {} : { getRowId: (params: { data: RowData }) => `${getRowId(params.data)}` }), isExternalFilterPresent, isRowSelectable, + loading, maintainColumnOrder, onCellContextMenu, onCellEditingStarted(event: CellEditingStartedEvent) { From 73633ab0f31a59cd3118fda7505399d1ddf66196 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 15 Jan 2025 09:49:24 -0800 Subject: [PATCH 08/37] Fix simulation dataset id usage --- src/stores/simulation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/simulation.ts b/src/stores/simulation.ts index 097087d761..b01683a7df 100644 --- a/src/stores/simulation.ts +++ b/src/stores/simulation.ts @@ -168,7 +168,7 @@ export const selectedSpan = derived([spansMap, selectedSpanId], ([$spansMap, $se export const simulationDatasetLatestId = derived( [simulationDatasetLatest], - ([$simulationDatasetLatest]) => $simulationDatasetLatest?.dataset_id ?? -1, + ([$simulationDatasetLatest]) => $simulationDatasetLatest?.id ?? -1, ); /* Helper Functions. */ From 5744eb455d4e26fd89938d9acac3cf211247be61 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 15 Jan 2025 16:52:28 -0800 Subject: [PATCH 09/37] Loading for constraints --- src/components/constraints/ConstraintForm.svelte | 2 +- src/components/constraints/Constraints.svelte | 12 +++++++----- .../modals/ManagePlanConstraintsModal.svelte | 7 ++++--- src/stores/constraints.ts | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/constraints/ConstraintForm.svelte b/src/components/constraints/ConstraintForm.svelte index 90a6064bb4..920a1e0265 100644 --- a/src/components/constraints/ConstraintForm.svelte +++ b/src/components/constraints/ConstraintForm.svelte @@ -226,7 +226,7 @@ {\n\n}\n`} displayName="Constraint" {hasCreateDefinitionCodePermission} diff --git a/src/components/constraints/Constraints.svelte b/src/components/constraints/Constraints.svelte index b1ee29352f..aace3e555f 100644 --- a/src/components/constraints/Constraints.svelte +++ b/src/components/constraints/Constraints.svelte @@ -94,7 +94,7 @@ let hasPermission: boolean = false; let selectedConstraint: ConstraintMetadata | null = null; - $: filteredConstraints = $constraints.filter(constraint => { + $: filteredConstraints = ($constraints || []).filter(constraint => { const filterTextLowerCase = filterText.toLowerCase(); const includesId = `${constraint.id}`.includes(filterTextLowerCase); const includesName = constraint.name.toLocaleLowerCase().includes(filterTextLowerCase); @@ -102,7 +102,7 @@ }); $: hasPermission = featurePermissions.constraints.canCreate(user); $: if (selectedConstraint !== null) { - const found = $constraints.findIndex(constraint => constraint.id === selectedConstraint?.id); + const found = ($constraints || []).findIndex(constraint => constraint.id === selectedConstraint?.id); if (found === -1) { selectedConstraint = null; } @@ -166,14 +166,14 @@ function deleteConstraintContext(event: CustomEvent) { const id = event.detail[0] as number; - const constraint = $constraints.find(c => c.id === id); + const constraint = ($constraints || []).find(c => c.id === id); if (constraint) { deleteConstraint(constraint); } } function editConstraint({ id }: Pick) { - const constraint = $constraints.find(c => c.id === id); + const constraint = ($constraints || []).find(c => c.id === id); goto(`${base}/constraints/edit/${id}?${SearchParameters.REVISION}=${constraint?.versions[0].revision}`); } @@ -230,8 +230,10 @@ - {#if filteredConstraints.length} + {#if !$constraints || filteredConstraints.length} = {}; - $: filteredConstraints = $constraints.filter(constraint => { + $: filteredConstraints = ($constraints || []).filter(constraint => { const filterTextLowerCase = filterText.toLowerCase(); const includesId = `${constraint.id}`.includes(filterTextLowerCase); const includesName = constraint.name.toLocaleLowerCase().includes(filterTextLowerCase); @@ -171,7 +171,7 @@ } function viewConstraint({ id }: Pick) { - const constraint = $constraints.find(c => c.id === id); + const constraint = ($constraints || []).find(c => c.id === id); window.open( `${base}/constraints/edit/${constraint?.id}?${SearchParameters.REVISION}=${constraint?.versions[0].revision}&${SearchParameters.MODEL_ID}=${$plan?.model.id}`, ); @@ -273,8 +273,9 @@

    - {#if filteredConstraints.length} + {#if !$constraints || filteredConstraints.length} = writable('1fr 3px 1fr'); /* Subscriptions. */ -export const constraints = gqlSubscribable(gql.SUB_CONSTRAINTS, {}, [], null); +export const constraints = gqlSubscribable(gql.SUB_CONSTRAINTS, {}, null, null); export const constraintRuns = gqlSubscribable( gql.SUB_CONSTRAINT_RUNS, From 5ac4814f7f26e2b4565955e5662ce9f7ac587606 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Thu, 16 Jan 2025 08:54:46 -0800 Subject: [PATCH 10/37] Allow customization of loading message --- src/components/Loading.svelte | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Loading.svelte b/src/components/Loading.svelte index cb5deed8de..040c6819d2 100644 --- a/src/components/Loading.svelte +++ b/src/components/Loading.svelte @@ -1,4 +1,10 @@ -
    Loading...
    +
    + {#if $$slots.default} + + {:else} + Loading... + {/if} +
    diff --git a/src/components/ui/Tags/TagsInput.svelte b/src/components/ui/Tags/TagsInput.svelte index d1d90b27d5..464e9d379a 100644 --- a/src/components/ui/Tags/TagsInput.svelte +++ b/src/components/ui/Tags/TagsInput.svelte @@ -33,6 +33,7 @@ export let minWidth: number = 82; export let name: string = ''; export let placeholder: string = 'Enter a tag...'; + export let showPlaceholderIfDisabled: boolean = false; export let selected: Tag[] = []; export let tagDisplayName: string = 'tag'; export let suggestionsLimit: number = 8; @@ -245,8 +246,8 @@ {id} {name} {disabled} - placeholder={disabled ? '' : placeholder} autocomplete="off" + placeholder={disabled && !showPlaceholderIfDisabled ? '' : placeholder} class="st-input" style:min-width={`${minWidth}px`} on:mouseup={openSuggestions} diff --git a/src/components/ui/Tags/UserInput.svelte b/src/components/ui/Tags/UserInput.svelte index d439b91c67..35ee1716bd 100644 --- a/src/components/ui/Tags/UserInput.svelte +++ b/src/components/ui/Tags/UserInput.svelte @@ -9,6 +9,7 @@ import UserInputRow from './UserInputRow.svelte'; export let allowMultiple: boolean = true; + export let disabled: boolean = false; export let placeholder: string = 'Search users'; export let selectedUsers: UserId[] = []; export let tagDisplayName = 'user'; @@ -133,6 +134,8 @@ bind:this={inputRef} {addTag} {allowMultiple} + {disabled} + showPlaceholderIfDisabled ignoreCase={false} {placeholder} creatable={false} diff --git a/src/routes/models/[id]/+page.svelte b/src/routes/models/[id]/+page.svelte index 4e0d77f9e2..f01e974e90 100644 --- a/src/routes/models/[id]/+page.svelte +++ b/src/routes/models/[id]/+page.svelte @@ -88,7 +88,12 @@ import { SearchParameters } from '../../../enums/searchParameters'; import { constraints } from '../../../stores/constraints'; import { initialModel, model, resetModelStores } from '../../../stores/model'; - import { schedulingConditions, schedulingGoals } from '../../../stores/scheduling'; + import { + schedulingConditionResponses, + schedulingConditions, + schedulingGoalResponses, + schedulingGoals, + } from '../../../stores/scheduling'; import { users } from '../../../stores/user'; import { views } from '../../../stores/views'; import type { User, UserId } from '../../../types/app'; @@ -117,6 +122,7 @@ let hasCreatePermission: boolean = false; let hasEditSpecPermission: boolean = false; let hasModelChanged: boolean = false; + let loading: boolean = true; let metadataList: Pick[] = []; let modelMetadata: { default_view_id: number | null; @@ -201,6 +207,7 @@ // goals require special logic because of priority management // goals must have consecutive priorities starting at 0 case 'goal': { + loading = !$schedulingGoalResponses; hasCreatePermission = featurePermissions.schedulingGoals.canCreate(user); hasEditSpecPermission = featurePermissions.schedulingGoalsModelSpec.canUpdate(user); metadataList = getMetadata($schedulingGoals, $model, 'scheduling_specification_goals').filter(goalMetadata => @@ -257,6 +264,7 @@ break; } case 'condition': + loading = !$schedulingConditionResponses; hasCreatePermission = featurePermissions.schedulingConditions.canCreate(user); hasEditSpecPermission = featurePermissions.schedulingConditionsModelSpec.canUpdate(user); metadataList = getMetadata($schedulingConditions, $model, 'scheduling_specification_conditions').filter( @@ -266,9 +274,10 @@ break; case 'constraint': default: + loading = !$constraints; hasCreatePermission = featurePermissions.constraints.canCreate(user); hasEditSpecPermission = featurePermissions.constraintsModelSpec.canUpdate(user); - metadataList = getMetadata($constraints, $model, 'constraint_specification'); + metadataList = getMetadata($constraints || [], $model, 'constraint_specification'); selectedSpecifications = selectedConstraintModelSpecifications; } $: hasModelChanged = @@ -680,8 +689,8 @@ modelId={$model?.id} createdAt={$model?.created_at} user={data.user} - users={$users ?? []} - views={$views ?? []} + users={$users} + views={$views} on:createPlan={onCreatePlanWithModel} on:deleteModel={onDeleteModel} on:hasModelChanged={onModelMetadataChange} @@ -695,6 +704,7 @@ {hasCreatePermission} {hasEditSpecPermission} {hasModelChanged} + {loading} {metadataList} model={$model} {selectedAssociation} diff --git a/src/stores/scheduling.ts b/src/stores/scheduling.ts index d7d7661c19..045ec4edd9 100644 --- a/src/stores/scheduling.ts +++ b/src/stores/scheduling.ts @@ -42,17 +42,17 @@ export const schedulingRequests = gqlSubscribable( null, ); -export const schedulingConditionResponses = gqlSubscribable( +export const schedulingConditionResponses = gqlSubscribable( gql.SUB_SCHEDULING_CONDITIONS, {}, - [], + null, null, ); -export const schedulingGoalResponses = gqlSubscribable( +export const schedulingGoalResponses = gqlSubscribable( gql.SUB_SCHEDULING_GOALS, {}, - [], + null, null, ); @@ -81,7 +81,7 @@ export const schedulingPlanSpecification = gqlSubscribable { - return $schedulingConditionResponses.map(schedulingConditionResponse => + return ($schedulingConditionResponses || []).map(schedulingConditionResponse => convertResponseToMetadata( schedulingConditionResponse, $tags, @@ -91,7 +91,7 @@ export const schedulingConditions = derivedDeeply( ); export const schedulingGoals = derivedDeeply([schedulingGoalResponses, tags], ([$schedulingGoalResponses, $tags]) => { - return $schedulingGoalResponses.map(schedulingGoalResponse => + return ($schedulingGoalResponses || []).map(schedulingGoalResponse => convertResponseToMetadata(schedulingGoalResponse, $tags), ); }); From 6a41ce15927f35e95f95a29788c1f2c06637edd3 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Fri, 17 Jan 2025 09:30:46 -0800 Subject: [PATCH 13/37] Initial span loading tracking and indication --- src/components/activity/ActivitySpansTable.svelte | 5 +++-- src/components/timeline/Timeline.svelte | 1 + src/components/timeline/TimelinePanel.svelte | 2 ++ src/routes/plans/[id]/+page.svelte | 15 +++++++++++++-- src/stores/simulation.ts | 3 +++ 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/components/activity/ActivitySpansTable.svelte b/src/components/activity/ActivitySpansTable.svelte index 427e4b46b3..1e5c57dc90 100644 --- a/src/components/activity/ActivitySpansTable.svelte +++ b/src/components/activity/ActivitySpansTable.svelte @@ -2,6 +2,7 @@ @@ -112,7 +119,12 @@
    - {#if !filteredSchedulingConditionSpecs.length} + + {#if !$allowedSchedulingConditionSpecs || !$schedulingConditionResponses} +
    + +
    + {:else if !filteredSchedulingConditionSpecs.length}
    No scheduling conditions found
    {#if numOfPrivateConditions > 0} diff --git a/src/components/scheduling/SchedulingGoalsPanel.svelte b/src/components/scheduling/SchedulingGoalsPanel.svelte index 9ab01a7497..a8d520d491 100644 --- a/src/components/scheduling/SchedulingGoalsPanel.svelte +++ b/src/components/scheduling/SchedulingGoalsPanel.svelte @@ -20,6 +20,7 @@ import { permissionHandler } from '../../utilities/permissionHandler'; import { featurePermissions, isAdminRole } from '../../utilities/permissions'; import CollapsibleListControls from '../CollapsibleListControls.svelte'; + import Loading from '../Loading.svelte'; import GridMenu from '../menus/GridMenu.svelte'; import Panel from '../ui/Panel.svelte'; import PanelHeaderActionButton from '../ui/PanelHeaderActionButton.svelte'; @@ -65,7 +66,7 @@ } return 0; }); - $: numOfPrivateGoals = $schedulingGoalSpecifications.length - visibleSchedulingGoalSpecs.length; + $: numOfPrivateGoals = ($schedulingGoalSpecifications || []).length - visibleSchedulingGoalSpecs.length; $: if ($plan) { hasAnalyzePermission = featurePermissions.schedulingGoalsPlanSpec.canAnalyze(user, $plan, $plan.model) && !$planReadOnly; @@ -207,7 +208,11 @@
    - {#if !filteredSchedulingGoalSpecs.length} + {#if !$schedulingGoalSpecifications} +
    + +
    + {:else if !filteredSchedulingGoalSpecs.length}
    No scheduling goals found
    {#if numOfPrivateGoals > 0} diff --git a/src/stores/scheduling.ts b/src/stores/scheduling.ts index 045ec4edd9..485a0b74f2 100644 --- a/src/stores/scheduling.ts +++ b/src/stores/scheduling.ts @@ -134,26 +134,28 @@ export const schedulingGoalsMap: Readable export const schedulingConditionSpecifications = derived( [schedulingPlanSpecification], - ([$schedulingPlanSpecification]) => $schedulingPlanSpecification?.conditions ?? [], + ([$schedulingPlanSpecification]) => $schedulingPlanSpecification?.conditions ?? null, ); export const schedulingGoalSpecifications = derived( [schedulingPlanSpecification], - ([$schedulingPlanSpecification]) => $schedulingPlanSpecification?.goals ?? [], + ([$schedulingPlanSpecification]) => $schedulingPlanSpecification?.goals ?? null, ); -export const allowedSchedulingConditionSpecs: Readable = derived( +export const allowedSchedulingConditionSpecs: Readable = derived( [schedulingConditionSpecifications], ([$schedulingConditionSpecifications]) => - $schedulingConditionSpecifications.filter( - ({ condition_metadata: conditionMetadata }) => conditionMetadata !== null, - ), + $schedulingConditionSpecifications + ? $schedulingConditionSpecifications.filter( + ({ condition_metadata: conditionMetadata }) => conditionMetadata !== null, + ) + : null, ); export const allowedSchedulingGoalSpecs: Readable = derived( [schedulingGoalSpecifications], ([$schedulingGoalSpecifications]) => - $schedulingGoalSpecifications.filter(({ goal_metadata: goalMetadata }) => goalMetadata !== null), + ($schedulingGoalSpecifications || []).filter(({ goal_metadata: goalMetadata }) => goalMetadata !== null), ); export const latestSchedulingGoalAnalyses = derived( @@ -162,7 +164,7 @@ export const latestSchedulingGoalAnalyses = derived( const analysisIdToSpecGoalMap: Record = {}; let latestAnalysisId = -1; - $schedulingGoalSpecifications.forEach(schedulingSpecGoal => { + ($schedulingGoalSpecifications || []).forEach(schedulingSpecGoal => { let analyses: SchedulingGoalAnalysis[] = []; if (schedulingSpecGoal.goal_definition != null) { analyses = schedulingSpecGoal.goal_definition.analyses ?? []; @@ -278,7 +280,7 @@ export const schedulingAnalysisStatus = derived( export const enableScheduling: Readable = derived( [schedulingGoalSpecifications], ([$schedulingGoalSpecifications]) => { - return $schedulingGoalSpecifications.filter(schedulingSpecGoal => schedulingSpecGoal.enabled).length > 0; + return ($schedulingGoalSpecifications || []).filter(schedulingSpecGoal => schedulingSpecGoal.enabled).length > 0; }, ); From f91a27ae3e6f96870d7b52809e3d0f72713fee57 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 09:02:37 -0800 Subject: [PATCH 16/37] Activity layer loading indicator fix --- src/components/timeline/Row.svelte | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/timeline/Row.svelte b/src/components/timeline/Row.svelte index 6b370687f6..779050fac0 100644 --- a/src/components/timeline/Row.svelte +++ b/src/components/timeline/Row.svelte @@ -201,6 +201,7 @@ let timeFilteredExternalEvents: ExternalEvent[] = []; let rowRef: HTMLDivElement; let hasActivityLayer: boolean = false; + let hasActivityLayerFilters: boolean = false; let hasExternalEventsLayer: boolean = false; let hasResourceLayer: boolean = false; @@ -208,6 +209,14 @@ rowRef.scrollIntoView({ block: 'nearest' }); } + $: layers.forEach(layer => { + if (isActivityLayer(layer)) { + if (layer.filter.activity) { + hasActivityLayerFilters = true; + } + } + }); + $: if (plan && simulationDataset !== null && layers && $externalResources && !$resourceTypesLoading) { const simulationDatasetId = simulationDataset.dataset_id; const resourceNamesSet = new Set(); @@ -955,7 +964,7 @@ - {#if (hasResourceLayer && anyResourcesLoading) || (hasActivityLayer && (!activityDirectivesMap || !spansMap))} + {#if (hasResourceLayer && anyResourcesLoading) || (hasActivityLayerFilters && (!activityDirectivesMap || !spansMap))}
    Loading...
    {/if} From 345e95adccae216792a030bf38f8f5e74dad8ac7 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 09:08:51 -0800 Subject: [PATCH 17/37] Style fix --- src/components/modals/ManagePlanSchedulingConditionsModal.svelte | 1 + src/components/modals/ManagePlanSchedulingGoalsModal.svelte | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/modals/ManagePlanSchedulingConditionsModal.svelte b/src/components/modals/ManagePlanSchedulingConditionsModal.svelte index 681fb6e60b..954096d4ca 100644 --- a/src/components/modals/ManagePlanSchedulingConditionsModal.svelte +++ b/src/components/modals/ManagePlanSchedulingConditionsModal.svelte @@ -348,6 +348,7 @@ .conditions-modal-title { font-weight: bold; + white-space: nowrap; } .conditions-modal-table-container { diff --git a/src/components/modals/ManagePlanSchedulingGoalsModal.svelte b/src/components/modals/ManagePlanSchedulingGoalsModal.svelte index f5beac7536..fd742e8483 100644 --- a/src/components/modals/ManagePlanSchedulingGoalsModal.svelte +++ b/src/components/modals/ManagePlanSchedulingGoalsModal.svelte @@ -338,6 +338,7 @@ .goals-modal-title { font-weight: bold; + white-space: nowrap; } .goals-modal-table-container { From 9316d0a71a631a6312502b159e123ac9cefdc994 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 09:35:49 -0800 Subject: [PATCH 18/37] Scheduling goal and condition loading refactor and improvements --- ...ManagePlanSchedulingConditionsModal.svelte | 4 +++- .../ManagePlanSchedulingGoalsModal.svelte | 21 ++++++++++++++----- .../SchedulingConditionsPanel.svelte | 9 ++------ .../scheduling/SchedulingGoalsPanel.svelte | 5 +++-- src/stores/scheduling.ts | 16 +++++++++++++- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/components/modals/ManagePlanSchedulingConditionsModal.svelte b/src/components/modals/ManagePlanSchedulingConditionsModal.svelte index 954096d4ca..695866630d 100644 --- a/src/components/modals/ManagePlanSchedulingConditionsModal.svelte +++ b/src/components/modals/ManagePlanSchedulingConditionsModal.svelte @@ -11,6 +11,7 @@ import { allowedSchedulingConditionSpecs, schedulingConditions, + schedulingConditionsLoading, schedulingPlanSpecification, } from '../../stores/scheduling'; import type { User } from '../../types/app'; @@ -293,10 +294,11 @@

    - {#if filteredConditions.length} + {#if $schedulingConditionsLoading || filteredConditions.length} diff --git a/src/components/modals/ManagePlanSchedulingGoalsModal.svelte b/src/components/modals/ManagePlanSchedulingGoalsModal.svelte index fd742e8483..84fd6308be 100644 --- a/src/components/modals/ManagePlanSchedulingGoalsModal.svelte +++ b/src/components/modals/ManagePlanSchedulingGoalsModal.svelte @@ -7,7 +7,12 @@ import { PlanStatusMessages } from '../../enums/planStatusMessages'; import { SearchParameters } from '../../enums/searchParameters'; import { plan, planReadOnly } from '../../stores/plan'; - import { allowedSchedulingGoalSpecs, schedulingGoals, schedulingPlanSpecification } from '../../stores/scheduling'; + import { + allowedSchedulingGoalSpecs, + schedulingGoals, + schedulingGoalsLoading, + schedulingPlanSpecification, + } from '../../stores/scheduling'; import type { User } from '../../types/app'; import type { DataGridColumnDef } from '../../types/data-grid'; import type { @@ -116,7 +121,7 @@ const includesName = goal.name.toLocaleLowerCase().includes(filterTextLowerCase); return includesId || includesName; }); - $: selectedGoals = $allowedSchedulingGoalSpecs.reduce( + $: selectedGoals = ($allowedSchedulingGoalSpecs || []).reduce( (prevBooleanMap: Record, schedulingGoalPlanSpec: SchedulingGoalPlanSpecification) => { return { ...prevBooleanMap, @@ -202,7 +207,7 @@ } async function onUpdateGoals(selectedGoals: Record) { - if ($plan && $schedulingPlanSpecification) { + if ($plan && $schedulingPlanSpecification && $allowedSchedulingGoalSpecs) { const goalPlanSpecUpdates: { goalPlanSpecIdsToDelete: number[]; goalPlanSpecsToAdd: SchedulingGoalPlanSpecInsertInput[]; @@ -288,8 +293,14 @@

    - {#if filteredGoals.length} - + {#if $schedulingGoalsLoading || filteredGoals.length} + {:else}
    No Scheduling Goals Found
    {/if} diff --git a/src/components/scheduling/SchedulingConditionsPanel.svelte b/src/components/scheduling/SchedulingConditionsPanel.svelte index 36da8f1bd6..226e88328e 100644 --- a/src/components/scheduling/SchedulingConditionsPanel.svelte +++ b/src/components/scheduling/SchedulingConditionsPanel.svelte @@ -6,8 +6,8 @@ import { plan, planReadOnly } from '../../stores/plan'; import { allowedSchedulingConditionSpecs, - schedulingConditionResponses, schedulingConditionSpecifications, + schedulingConditionsLoading, schedulingConditionsMap, } from '../../stores/scheduling'; import type { User } from '../../types/app'; @@ -86,10 +86,6 @@ activeElement.focus(); } }); - - $: console.log('$schedulingConditionSpecifications :>> ', $schedulingConditionSpecifications); - $: console.log('filteredSchedulingConditionSpecs :>> ', filteredSchedulingConditionSpecs); - $: console.log('$schedulingConditionsMap :>> ', $schedulingConditionsMap); @@ -119,8 +115,7 @@
    - - {#if !$allowedSchedulingConditionSpecs || !$schedulingConditionResponses} + {#if $schedulingConditionsLoading}
    diff --git a/src/components/scheduling/SchedulingGoalsPanel.svelte b/src/components/scheduling/SchedulingGoalsPanel.svelte index a8d520d491..93e8baddc1 100644 --- a/src/components/scheduling/SchedulingGoalsPanel.svelte +++ b/src/components/scheduling/SchedulingGoalsPanel.svelte @@ -11,6 +11,7 @@ enableScheduling, schedulingAnalysisStatus, schedulingGoalSpecifications, + schedulingGoalsLoading, schedulingGoalsMap, } from '../../stores/scheduling'; import type { User } from '../../types/app'; @@ -41,7 +42,7 @@ let visibleSchedulingGoalSpecs: SchedulingGoalPlanSpecification[] = []; // TODO: remove this after db merge as it becomes redundant - $: visibleSchedulingGoalSpecs = $allowedSchedulingGoalSpecs.filter(({ goal_metadata: goalMetadata }) => { + $: visibleSchedulingGoalSpecs = ($allowedSchedulingGoalSpecs || []).filter(({ goal_metadata: goalMetadata }) => { if (goalMetadata) { const { public: isPublic, owner } = goalMetadata; if (!isPublic && !isAdminRole(user?.activeRole)) { @@ -208,7 +209,7 @@
    - {#if !$schedulingGoalSpecifications} + {#if $schedulingGoalsLoading}
    diff --git a/src/stores/scheduling.ts b/src/stores/scheduling.ts index 485a0b74f2..463999161a 100644 --- a/src/stores/scheduling.ts +++ b/src/stores/scheduling.ts @@ -152,12 +152,26 @@ export const allowedSchedulingConditionSpecs: Readable = derived( +export const allowedSchedulingGoalSpecs: Readable = derived( [schedulingGoalSpecifications], ([$schedulingGoalSpecifications]) => ($schedulingGoalSpecifications || []).filter(({ goal_metadata: goalMetadata }) => goalMetadata !== null), ); +export const schedulingGoalsLoading = derived( + [schedulingGoalSpecifications, schedulingGoalResponses], + ([$schedulingGoalSpecifications, $schedulingGoalResponses]) => { + return !$schedulingGoalSpecifications || !$schedulingGoalResponses; + }, +); + +export const schedulingConditionsLoading = derived( + [schedulingConditionSpecifications, schedulingConditionResponses], + ([$schedulingConditionSpecifications, $schedulingConditionResponses]) => { + return !$schedulingConditionSpecifications || !$schedulingConditionResponses; + }, +); + export const latestSchedulingGoalAnalyses = derived( [selectedSpecId, schedulingGoalSpecifications], ([$selectedSpecId, $schedulingGoalSpecifications]) => { From f5a89b54027fd45e4f218bf9db641f5bc7e112bb Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 09:46:48 -0800 Subject: [PATCH 19/37] Style plugin loading message --- src/routes/+layout.svelte | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 7f28403003..9e3aaa74c4 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -8,6 +8,7 @@ import { mergeWith } from 'lodash-es'; import { onMount } from 'svelte'; import Nav from '../components/app/Nav.svelte'; + import Loading from '../components/Loading.svelte'; import { plugins, pluginsError, pluginsLoaded } from '../stores/plugins'; import { loadPluginCode } from '../utilities/plugins'; @@ -48,7 +49,9 @@ {$pluginsError}
    {:else} -
    Loading plugins...
    +
    + Loading plugins... +
    {/if}
    @@ -83,7 +86,7 @@ flex-shrink: 0; } - .loading { + .delay-visibility { animation: 1s delayVisibility; } From 4505a31f2b10bb443eb9ec4198c0fc7b4abffcb6 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 11:30:16 -0800 Subject: [PATCH 20/37] Revert change --- src/components/model/ModelAssociations.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/model/ModelAssociations.svelte b/src/components/model/ModelAssociations.svelte index 5444bb1ba9..8f17218a50 100644 --- a/src/components/model/ModelAssociations.svelte +++ b/src/components/model/ModelAssociations.svelte @@ -38,7 +38,7 @@ let metadataMap: Record = {}; let numOfPrivateMetadata: number = 0; - let selectedAssociationId: Association = 'condition'; + let selectedAssociationId: Association = 'constraint'; let selectedAssociationTitle = 'Constraint'; let selectedDefinition: string | null; let selectedViewId: RadioButtonId = 'model'; From 078a10f80c9205416065ca1271322edbe9c09ae5 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 11:30:31 -0800 Subject: [PATCH 21/37] Constraint loading improvements --- src/components/constraints/ConstraintsPanel.svelte | 4 ++-- .../modals/ManagePlanConstraintsModal.svelte | 11 ++++++++--- src/components/timeline/Timeline.svelte | 4 ++-- src/components/timeline/TimelinePanel.svelte | 3 ++- src/stores/constraints.ts | 5 +++++ 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/constraints/ConstraintsPanel.svelte b/src/components/constraints/ConstraintsPanel.svelte index 8808350133..dcd394f300 100644 --- a/src/components/constraints/ConstraintsPanel.svelte +++ b/src/components/constraints/ConstraintsPanel.svelte @@ -15,8 +15,8 @@ cachedConstraintsStatus, constraintPlanSpecs, constraintResponseMap, - constraintRuns, constraintVisibilityMap, + constraintsLoading, constraintsMap, constraintsStatus, setAllConstraintsVisible, @@ -315,7 +315,7 @@
    - {#if !$constraintRuns} + {#if $constraintsLoading}
    diff --git a/src/components/modals/ManagePlanConstraintsModal.svelte b/src/components/modals/ManagePlanConstraintsModal.svelte index 266248964e..7a941e43c6 100644 --- a/src/components/modals/ManagePlanConstraintsModal.svelte +++ b/src/components/modals/ManagePlanConstraintsModal.svelte @@ -6,7 +6,12 @@ import { createEventDispatcher } from 'svelte'; import { PlanStatusMessages } from '../../enums/planStatusMessages'; import { SearchParameters } from '../../enums/searchParameters'; - import { allowedConstraintPlanSpecMap, allowedConstraintSpecs, constraints } from '../../stores/constraints'; + import { + allowedConstraintPlanSpecMap, + allowedConstraintSpecs, + constraints, + constraintsLoading, + } from '../../stores/constraints'; import { plan, planId, planReadOnly } from '../../stores/plan'; import type { User } from '../../types/app'; import type { ConstraintMetadata, ConstraintPlanSpec, ConstraintPlanSpecInsertInput } from '../../types/constraint'; @@ -272,9 +277,9 @@

    - {#if !$constraints || filteredConstraints.length} + {#if $constraintsLoading || filteredConstraints.length} {/if}
    - import { activityDirectivesMap, selectActivity, selectedActivityDirectiveId } from '../../stores/activities'; - import { visibleConstraintResults } from '../../stores/constraints'; + import { constraintsLoading, visibleConstraintResults } from '../../stores/constraints'; import { selectExternalEvent, selectedExternalEventId, selectedExternalEvents } from '../../stores/external-event'; import { maxTimeRange, plan, planReadOnly, viewTimeRange } from '../../stores/plan'; import { @@ -217,6 +217,7 @@ activityDirectivesMap={$activityDirectivesMap} externalEvents={$selectedExternalEvents} constraintResults={$visibleConstraintResults} + constraintsLoading={$constraintsLoading} {hasUpdateDirectivePermission} {hasUpdateSimulationPermission} initialSpanLoadComplete={$initialSpanLoadComplete} diff --git a/src/stores/constraints.ts b/src/stores/constraints.ts index 7770a69b68..30ec2c232c 100644 --- a/src/stores/constraints.ts +++ b/src/stores/constraints.ts @@ -52,6 +52,11 @@ export const constraintMetadata = gqlSubscribable( ); /* Derived. */ +export const constraintsLoading: Readable = derived( + [constraints, constraintRuns], + ([$constraints, $constraintRuns]) => !$constraints || !$constraintRuns, +); + export const constraintsMap: Readable> = derived([constraints], ([$constraints]) => keyBy($constraints, 'id'), ); From b20212f2a69afce15b1287b987809d0cc8f5a853 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 11:37:57 -0800 Subject: [PATCH 22/37] Fixes --- src/components/modals/ManagePlanSchedulingGoalsModal.svelte | 2 +- src/components/timeline/Row.svelte | 4 ++-- .../timeline/form/TimelineEditor/ActivityFilterBuilder.svelte | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/modals/ManagePlanSchedulingGoalsModal.svelte b/src/components/modals/ManagePlanSchedulingGoalsModal.svelte index 84fd6308be..4b5c0cd3f9 100644 --- a/src/components/modals/ManagePlanSchedulingGoalsModal.svelte +++ b/src/components/modals/ManagePlanSchedulingGoalsModal.svelte @@ -224,7 +224,7 @@ // if we find at least one goal invocation with the selected goal_id, we don't want to insert this goal_id into the plan spec // i.e. this goal 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 goalsInPlanSpecification = $allowedSchedulingGoalSpecs.filter( + const goalsInPlanSpecification = ($allowedSchedulingGoalSpecs || []).filter( schedulingGoalPlanSpecification => schedulingGoalPlanSpecification.goal_id === goalId, ); diff --git a/src/components/timeline/Row.svelte b/src/components/timeline/Row.svelte index 779050fac0..1ed760398e 100644 --- a/src/components/timeline/Row.svelte +++ b/src/components/timeline/Row.svelte @@ -447,7 +447,7 @@ if (layer.filter) { const { directives: matchingDirectives, spans: matchingSpans } = applyActivityLayerFilter( layer.filter.activity, - activityDirectives, + activityDirectives || [], spansList, $activityTypes, $activityArgumentDefaultsMap, @@ -460,7 +460,7 @@ uniqueDirectives.push(directive); // Gather spans for directive since we always show all spans for a directive - const childSpans = getAllSpansForActivityDirective(directive.id, spansMap, spanUtilityMaps); + const childSpans = getAllSpansForActivityDirective(directive.id, spansMap || {}, spanUtilityMaps); childSpans.forEach(span => { seenSpanIds[span.span_id] = true; idToColorMaps.spans[span.span_id] = layer.activityColor; diff --git a/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte b/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte index 8e6c57cd36..c9006bd500 100644 --- a/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte +++ b/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte @@ -193,11 +193,11 @@ dirtyFilter = structuredClone(filter); } - $: activityDirectives = Object.values($activityDirectivesMap); + $: activityDirectives = Object.values($activityDirectivesMap || {}); $: appliedFilter = applyActivityLayerFilter( dirtyFilter, activityDirectives, - $spans, + $spans || [], $activityTypes, $activityArgumentDefaultsMap, ); From 30ca747ce6016ee17fd9861cc10efec28cc92b5c Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 11:44:54 -0800 Subject: [PATCH 23/37] Show loading indicator in timeline editor while view and selected timeline id are loading --- .../timeline/form/TimelineEditorPanel.svelte | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/components/timeline/form/TimelineEditorPanel.svelte b/src/components/timeline/form/TimelineEditorPanel.svelte index e1ce91d401..a8864c0a13 100644 --- a/src/components/timeline/form/TimelineEditorPanel.svelte +++ b/src/components/timeline/form/TimelineEditorPanel.svelte @@ -75,6 +75,7 @@ import { tooltip } from '../../../utilities/tooltip'; import ColorPicker from '../../form/ColorPicker.svelte'; import Input from '../../form/Input.svelte'; + import Loading from '../../Loading.svelte'; import GridMenu from '../../menus/GridMenu.svelte'; import ParameterUnits from '../../parameters/ParameterUnits.svelte'; import CssGrid from '../../ui/CssGrid.svelte'; @@ -135,6 +136,8 @@ } $: discreteOptions = selectedRow?.discreteOptions || { ...ViewDefaultDiscreteOptions }; + $: loading = $selectedTimelineId === null || $view === null; + function updateRowEvent(event: Event) { const { name, value } = getTarget(event); viewUpdateRow(name, value); @@ -414,16 +417,6 @@ viewUpdateRow('layers', newLayers); } - // function handleUpdateLayerColorScheme(value: XRangeLayerColorScheme, layer: Layer) { - // const newLayers = layers.map(l => { - // if (layer.id === l.id) { - // (l as XRangeLayer).colorScheme = value; - // } - // return l; - // }); - // viewUpdateRow('layers', newLayers); - // } - function handleNewHorizontalGuideClick() { if (!selectedRow) { return; @@ -502,7 +495,11 @@
    - {#if !selectedTimeline} + {#if loading} +
    + +
    + {:else if !selectedTimeline}
    No timeline selected
    {:else} From a33752b0bc751fefcb6d098a09a0b39f201a572b Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 11:48:41 -0800 Subject: [PATCH 24/37] Improve timeline editor loading by setting default timeline to be 0 --- .../timeline/form/TimelineEditorPanel.svelte | 19 +------------------ src/stores/views.ts | 2 +- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/components/timeline/form/TimelineEditorPanel.svelte b/src/components/timeline/form/TimelineEditorPanel.svelte index a8864c0a13..b7c8f68e58 100644 --- a/src/components/timeline/form/TimelineEditorPanel.svelte +++ b/src/components/timeline/form/TimelineEditorPanel.svelte @@ -6,7 +6,6 @@ import DuplicateIcon from '@nasa-jpl/stellar/icons/duplicate.svg?component'; import PenIcon from '@nasa-jpl/stellar/icons/pen.svg?component'; import GripVerticalIcon from 'bootstrap-icons/icons/grip-vertical.svg?component'; - import { onMount } from 'svelte'; import { dndzone } from 'svelte-dnd-action'; import { default as ExternalEventIcon, @@ -75,7 +74,6 @@ import { tooltip } from '../../../utilities/tooltip'; import ColorPicker from '../../form/ColorPicker.svelte'; import Input from '../../form/Input.svelte'; - import Loading from '../../Loading.svelte'; import GridMenu from '../../menus/GridMenu.svelte'; import ParameterUnits from '../../parameters/ParameterUnits.svelte'; import CssGrid from '../../ui/CssGrid.svelte'; @@ -136,8 +134,6 @@ } $: discreteOptions = selectedRow?.discreteOptions || { ...ViewDefaultDiscreteOptions }; - $: loading = $selectedTimelineId === null || $view === null; - function updateRowEvent(event: Event) { const { name, value } = getTarget(event); viewUpdateRow(name, value); @@ -456,15 +452,6 @@ el.style.background = 'var(--st-gray-10)'; el.classList.add('timeline-element-dragging'); } - - onMount(() => { - if ($selectedTimelineId === null) { - const firstTimeline = $view?.definition.plan.timelines[0]; - if (firstTimeline) { - viewSetSelectedTimeline(firstTimeline.id); - } - } - }); @@ -495,11 +482,7 @@
    - {#if loading} -
    - -
    - {:else if !selectedTimeline} + {#if !selectedTimeline}
    No timeline selected
    {:else} diff --git a/src/stores/views.ts b/src/stores/views.ts index 60fde6fd8d..75132c628b 100644 --- a/src/stores/views.ts +++ b/src/stores/views.ts @@ -47,7 +47,7 @@ export const selectedLayerId: Writable = writable(null); export const selectedRowId: Writable = writable(null); -export const selectedTimelineId: Writable = writable(null); +export const selectedTimelineId: Writable = writable(0); export const selectedYAxisId: Writable = writable(null); From 0c018e51523a38e1511c4e240199f738532a4473 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 13:06:54 -0800 Subject: [PATCH 25/37] Sim event loading --- src/components/simulation/SimulationEventsPanel.svelte | 3 ++- src/components/simulation/SimulationEventsTable.svelte | 2 ++ src/routes/plans/[id]/+page.svelte | 2 +- src/stores/simulation.ts | 4 ++-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/simulation/SimulationEventsPanel.svelte b/src/components/simulation/SimulationEventsPanel.svelte index 34c44f28f1..b9d59fadaa 100644 --- a/src/components/simulation/SimulationEventsPanel.svelte +++ b/src/components/simulation/SimulationEventsPanel.svelte @@ -301,7 +301,8 @@ columnDefs={derivedColumnDefs ?? []} columnStates={simulationEventsTable?.columnStates} {filterExpression} - simulationEvents={$simulationEvents} + loading={!$simulationEvents} + simulationEvents={$simulationEvents || []} on:columnMoved={onColumnMoved} on:columnPinned={onColumnPinned} on:columnResized={onColumnResized} diff --git a/src/components/simulation/SimulationEventsTable.svelte b/src/components/simulation/SimulationEventsTable.svelte index 92f4cad806..c45355cb30 100644 --- a/src/components/simulation/SimulationEventsTable.svelte +++ b/src/components/simulation/SimulationEventsTable.svelte @@ -10,6 +10,7 @@ export let columnDefs: ColDef[]; export let columnStates: ColumnState[] = []; export let dataGrid: DataGrid | undefined = undefined; + export let loading: boolean = false; export let selectedSimulationEventId: number | null = null; export let simulationEvents: SimulationEvent[] = []; export let filterExpression: string = ''; @@ -52,6 +53,7 @@ {columnStates} {filterExpression} {getRowId} + {loading} useCustomContextMenu rowData={simulationEvents} rowSelection="single" diff --git a/src/routes/plans/[id]/+page.svelte b/src/routes/plans/[id]/+page.svelte index 66586c54fc..a69aa2cb70 100644 --- a/src/routes/plans/[id]/+page.svelte +++ b/src/routes/plans/[id]/+page.svelte @@ -379,7 +379,7 @@ } else { simulationDataAbortController?.abort(); $spans = null; - $simulationEvents = []; + $simulationEvents = null; } $: compactNavMode = windowWidth < 1100; diff --git a/src/stores/simulation.ts b/src/stores/simulation.ts index da5ba3acf1..22868d12af 100644 --- a/src/stores/simulation.ts +++ b/src/stores/simulation.ts @@ -42,7 +42,7 @@ export const initialSpanLoadComplete: Writable = writable(false); export const yAxesWithScaleDomainsCache: Writable> = writable({}); -export const simulationEvents: Writable = writable([]); +export const simulationEvents: Writable = writable(null); /* Subscriptions. */ @@ -185,7 +185,7 @@ export function resetSimulationStores() { simulationDatasetId.set(-1); simulationDataset.updateValue(() => null); simulationDatasetLatest.updateValue(() => null); - simulationEvents.set([]); + simulationEvents.set(null); simulationTemplates.updateValue(() => []); simulationDatasetsPlan.updateValue(() => null); simulationDatasetsAll.updateValue(() => null); From c24a411b8726f63154990d4f0f0642382471d983 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Wed, 22 Jan 2025 13:31:08 -0800 Subject: [PATCH 26/37] Loading for plan snapshots --- src/components/menus/PlanMenu.svelte | 2 ++ .../modals/CreatePlanSnapshotModal.svelte | 2 +- src/components/plan/PlanForm.svelte | 14 ++++++++++++-- src/stores/planSnapshots.ts | 8 +++++--- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/menus/PlanMenu.svelte b/src/components/menus/PlanMenu.svelte index 133dc839ab..f592eb2dc4 100644 --- a/src/components/menus/PlanMenu.svelte +++ b/src/components/menus/PlanMenu.svelte @@ -7,6 +7,7 @@ import ChevronDownIcon from '@nasa-jpl/stellar/icons/chevron_down.svg?component'; import { PlanStatusMessages } from '../../enums/planStatusMessages'; import { planReadOnly } from '../../stores/plan'; + import { planSnapshotsLoading } from '../../stores/planSnapshots'; import { viewTogglePanel } from '../../stores/views'; import type { User } from '../../types/app'; import type { Plan } from '../../types/plan'; @@ -157,6 +158,7 @@ {/if} (); let createButtonDisabled: boolean = true; - let snapshotName: string = `${plan.name} – Snapshot ${$planSnapshots.length + 1}`; + let snapshotName: string = `${plan.name} – Snapshot ${($planSnapshots || []).length + 1}`; let snapshotDescription: string = ''; let snapshotTags: Tag[] = []; diff --git a/src/components/plan/PlanForm.svelte b/src/components/plan/PlanForm.svelte index 53b50376e9..cd7d5df1c1 100644 --- a/src/components/plan/PlanForm.svelte +++ b/src/components/plan/PlanForm.svelte @@ -6,7 +6,12 @@ import { SearchParameters } from '../../enums/searchParameters'; import { field } from '../../stores/form'; import { planMetadata, planReadOnly, planReadOnlySnapshot } from '../../stores/plan'; - import { planSnapshotId, planSnapshotsWithSimulations } from '../../stores/planSnapshots'; + import { + planSnapshotId, + planSnapshots, + planSnapshotsLoading, + planSnapshotsWithSimulations, + } from '../../stores/planSnapshots'; import { plans } from '../../stores/plans'; import { plugins } from '../../stores/plugins'; import { simulationDataset, simulationDatasetId } from '../../stores/simulation'; @@ -25,6 +30,7 @@ import { tooltip } from '../../utilities/tooltip'; import { required, unique } from '../../utilities/validators'; import Collapse from '../Collapse.svelte'; + import Loading from '../Loading.svelte'; import Field from '../form/Field.svelte'; import Input from '../form/Input.svelte'; import CancellableProgressRadial from '../ui/CancellableProgressRadial.svelte'; @@ -343,6 +349,7 @@ {/if}
    + {#if $planSnapshotsLoading} + + {/if} {#each filteredPlanSnapshots as planSnapshot (planSnapshot.snapshot_id)} effects.deletePlanSnapshot(planSnapshot, user)} /> {/each} - {#if filteredPlanSnapshots.length < 1} + {#if !$planSnapshotsLoading && filteredPlanSnapshots.length < 1}
    No Plan Snapshots Found
    {/if}
    diff --git a/src/stores/planSnapshots.ts b/src/stores/planSnapshots.ts index 1afc4115e1..cd41aec765 100644 --- a/src/stores/planSnapshots.ts +++ b/src/stores/planSnapshots.ts @@ -8,7 +8,7 @@ import { gqlSubscribable } from './subscribable'; /* Subscriptions. */ -export const planSnapshots = gqlSubscribable(gql.SUB_PLAN_SNAPSHOTS, { planId }, [], null); +export const planSnapshots = gqlSubscribable(gql.SUB_PLAN_SNAPSHOTS, { planId }, null, null); /* Writeable. */ @@ -18,10 +18,12 @@ export const planSnapshotId: Writable = writable(null); /* Derived. */ +export const planSnapshotsLoading: Readable = derived([planSnapshots], ([$planSnapshots]) => !$planSnapshots); + export const planSnapshot: Readable = derived( [planSnapshots, planSnapshotId], ([$planSnapshots, $planSnapshotId]) => { - const selectedPlanSnapshot = $planSnapshots.find(snapshot => { + const selectedPlanSnapshot = ($planSnapshots || []).find(snapshot => { return snapshot.snapshot_id === $planSnapshotId; }); @@ -32,7 +34,7 @@ export const planSnapshot: Readable = derived( export const planSnapshotsWithSimulations: Readable = derived( [planSnapshots, simulationDatasetsPlan], ([$planSnapshots, $simulationDatasetsPlan]) => { - return $planSnapshots.map(planSnapshot => { + return ($planSnapshots || []).map(planSnapshot => { const latestPlanSnapshotSimulation = ($simulationDatasetsPlan || []).find(simulation => { return simulation.plan_revision === planSnapshot?.revision; }); From 3f9cd55c3f25281c8b6fc0c91ad21ba8eda63c04 Mon Sep 17 00:00:00 2001 From: Aaron Plave Date: Mon, 27 Jan 2025 16:57:09 -0800 Subject: [PATCH 27/37] Refactoring --- .../activity/ActivitySpansTable.svelte | 4 +-- src/components/constraints/Constraints.svelte | 6 ++--- .../constraints/ConstraintsPanel.svelte | 8 +++--- src/components/menus/PlanMenu.svelte | 4 +-- .../modals/ManagePlanConstraintsModal.svelte | 7 ++--- src/components/plan/PlanForm.svelte | 6 ++--- src/components/timeline/Timeline.svelte | 7 ++--- src/components/timeline/TimelinePanel.svelte | 20 ++++++++++---- src/routes/models/[id]/+page.svelte | 4 +-- src/routes/plans/[id]/+page.svelte | 10 +++---- src/stores/activities.ts | 9 ++++++- src/stores/constraints.ts | 26 +++++++++++++------ src/stores/planSnapshots.ts | 5 +++- src/stores/simulation.ts | 4 +-- 14 files changed, 77 insertions(+), 43 deletions(-) diff --git a/src/components/activity/ActivitySpansTable.svelte b/src/components/activity/ActivitySpansTable.svelte index 1e5c57dc90..96ded16b66 100644 --- a/src/components/activity/ActivitySpansTable.svelte +++ b/src/components/activity/ActivitySpansTable.svelte @@ -2,7 +2,7 @@