Skip to content

Commit

Permalink
Loading indicator for line and x-range layers (#1017)
Browse files Browse the repository at this point in the history
* Display a loading indicator when a line or x-range layer is awaiting resource download
* Consider external resource loading when presenting row loading indicator. Delay loading indicator by 1 second to prevent flash of content for quick loading resources.
* Fix redundant resource and span fetches during simulation progress updates and abort stale requests.
  • Loading branch information
AaronPlave authored Dec 20, 2023
1 parent a15bda7 commit 32d3a80
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 21 deletions.
33 changes: 32 additions & 1 deletion src/components/timeline/Row.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { zoom as d3Zoom, zoomIdentity, type D3ZoomEvent, type ZoomBehavior, type ZoomTransform } from 'd3-zoom';
import { pick } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { allResources } from '../../stores/simulation';
import { allResources, fetchingResources, fetchingResourcesExternal } from '../../stores/simulation';
import { selectedRow } from '../../stores/views';
import type {
ActivityDirective,
Expand Down Expand Up @@ -118,6 +118,7 @@
$: overlaySvgSelection = select(overlaySvg) as Selection<SVGElement, unknown, any, any>;
$: rowClasses = classNames('row', { 'row-collapsed': !expanded });
$: hasActivityLayer = !!layers.find(layer => layer.chartType === 'activity');
$: hasResourceLayer = !!layers.find(layer => layer.chartType === 'line' || layer.chartType === 'x-range');
// Compute scale domains for axes since it is optionally defined in the view
$: if ($allResources && yAxes) {
Expand Down Expand Up @@ -333,6 +334,10 @@
{/if}
</g>
</svg>
<!-- Loading indicator -->
{#if hasResourceLayer && ($fetchingResources || $fetchingResourcesExternal)}
<div class="loading st-typography-label">Loading</div>
{/if}
<!-- Layers of Canvas Visualizations. -->
<div class="layers" style="width: {drawWidth}px">
{#each layers as layer (layer.id)}
Expand Down Expand Up @@ -526,4 +531,30 @@
.active-row :global(.row-header) {
background: rgba(47, 128, 237, 0.06);
}
.loading {
align-items: center;
animation: 1s delayVisibility;
color: var(--st-gray-50);
display: flex;
font-size: 10px;
height: 100%;
justify-content: center;
pointer-events: none;
position: absolute;
width: 100%;
z-index: 3;
}
@keyframes delayVisibility {
0% {
visibility: hidden;
}
99% {
visibility: hidden;
}
100% {
visibility: visible;
}
}
</style>
42 changes: 31 additions & 11 deletions src/routes/plans/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import {
enableSimulation,
externalResources,
fetchingResources,
resetSimulationStores,
resourceTypes,
resources,
Expand Down Expand Up @@ -152,6 +153,8 @@
let simulationExtent: string | null;
let selectedSimulationStatus: string | null;
let windowWidth = 0;
let simulationDataAbortController: AbortController;
let resourcesExternalAbortController: AbortController;
$: activityErrorCounts = $activityErrorRollups.reduce(
(prevCounts, activityErrorRollup) => {
Expand Down Expand Up @@ -275,8 +278,16 @@
}
$: if ($plan) {
resourcesExternalAbortController?.abort();
resourcesExternalAbortController = new AbortController();
effects
.getResourcesExternal($plan.id, $simulationDatasetId ?? null, $plan.start_time, data.user)
.getResourcesExternal(
$plan.id,
$simulationDatasetId ?? null,
$plan.start_time,
data.user,
resourcesExternalAbortController.signal,
)
.then(newResources => ($externalResources = newResources));
}
Expand All @@ -285,16 +296,25 @@
selectActivity(null, null);
}
$: if ($plan && $simulationDataset !== undefined) {
if ($simulationDataset !== null && $simulationDatasetId !== -1) {
const datasetId = $simulationDataset.dataset_id;
const startTimeYmd = $simulationDataset?.simulation_start_time ?? $plan.start_time;
effects.getResources(datasetId, startTimeYmd, data.user).then(newResources => ($resources = newResources));
effects.getSpans(datasetId, data.user).then(newSpans => ($spans = newSpans));
} else {
$resources = [];
$spans = [];
}
$: if (
$plan &&
$simulationDatasetId !== -1 &&
$simulationDataset?.id === $simulationDatasetId &&
getSimulationStatus($simulationDataset) === Status.Complete
) {
const datasetId = $simulationDatasetId;
const startTimeYmd = $simulationDataset?.simulation_start_time ?? $plan.start_time;
simulationDataAbortController?.abort();
simulationDataAbortController = new AbortController();
effects
.getResources(datasetId, startTimeYmd, data.user, simulationDataAbortController.signal)
.then(newResources => ($resources = newResources));
effects.getSpans(datasetId, data.user, simulationDataAbortController.signal).then(newSpans => ($spans = newSpans));
} else {
simulationDataAbortController?.abort();
fetchingResources.set(false);
$resources = [];
$spans = [];
}
$: {
Expand Down
2 changes: 2 additions & 0 deletions src/stores/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const resources: Writable<Resource[]> = writable([]);

export const fetchingResources: Writable<boolean> = writable(false);

export const fetchingResourcesExternal: Writable<boolean> = writable(false);

export const resourceTypes: Writable<ResourceType[]> = writable([]);

export const spans: Writable<Span[]> = writable([]);
Expand Down
38 changes: 29 additions & 9 deletions src/utilities/effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import {
import { createModelError, createPlanError, creatingModel, creatingPlan, models } from '../stores/plan';
import { schedulingStatus, selectedSpecId } from '../stores/scheduling';
import { commandDictionaries } from '../stores/sequencing';
import { fetchingResources, selectedSpanId, simulationDatasetId } from '../stores/simulation';
import {
fetchingResources,
fetchingResourcesExternal,
selectedSpanId,
simulationDatasetId,
} from '../stores/simulation';
import { createTagError } from '../stores/tags';
import { applyViewUpdate, view, viewUpdateTimeline } from '../stores/views';
import type {
Expand Down Expand Up @@ -2848,17 +2853,25 @@ const effects = {
}
},

async getResources(datasetId: number, startTimeYmd: string, user: User | null): Promise<Resource[]> {
async getResources(
datasetId: number,
startTimeYmd: string,
user: User | null,
signal: AbortSignal | undefined = undefined,
): Promise<Resource[]> {
try {
fetchingResources.set(true);
const data = await reqHasura<Profile[]>(gql.GET_PROFILES, { datasetId }, user);
const data = await reqHasura<Profile[]>(gql.GET_PROFILES, { datasetId }, user, signal);
const { profile: profiles } = data;
const sampledProfiles = sampleProfiles(profiles, startTimeYmd);
fetchingResources.set(false);
return sampledProfiles;
} catch (e) {
catchError(e as Error);
fetchingResources.set(false);
const error = e as Error;
if (error.name !== 'AbortError') {
catchError(error);
fetchingResources.set(false);
}
return [];
}
},
Expand All @@ -2868,8 +2881,10 @@ const effects = {
simulationDatasetId: number | null,
startTimeYmd: string,
user: User | null,
signal: AbortSignal | undefined = undefined,
): Promise<Resource[]> {
try {
fetchingResourcesExternal.set(true);
const data = await reqHasura<PlanDataset[]>(
gql.GET_PROFILES_EXTERNAL,
{
Expand All @@ -2882,6 +2897,7 @@ const effects = {
: { _eq: simulationDatasetId },
},
user,
signal,
);
const { plan_dataset: plan_datasets } = data;
if (plan_datasets != null) {
Expand All @@ -2894,13 +2910,17 @@ const effects = {
const sampledResources: Resource[] = sampleProfiles(profiles, startTimeYmd, offset_from_plan_start);
resources = [...resources, ...sampledResources];
}

fetchingResourcesExternal.set(false);
return resources;
} else {
throw Error('Unable to get external resources');
}
} catch (e) {
catchError(e as Error);
const error = e as Error;
if (error.name !== 'AbortError') {
catchError(error);
fetchingResourcesExternal.set(false);
}
return [];
}
},
Expand Down Expand Up @@ -3004,9 +3024,9 @@ const effects = {
}
},

async getSpans(datasetId: number, user: User | null): Promise<Span[]> {
async getSpans(datasetId: number, user: User | null, signal: AbortSignal | undefined = undefined): Promise<Span[]> {
try {
const data = await reqHasura<Span[]>(gql.GET_SPANS, { datasetId }, user);
const data = await reqHasura<Span[]>(gql.GET_SPANS, { datasetId }, user, signal);
const { span: spans } = data;
if (spans != null) {
return spans;
Expand Down

0 comments on commit 32d3a80

Please sign in to comment.