Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion web/src/app/graph/components/graph-layout.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@
limitations under the License.
-->

<div #graphContainer class="svg-container"></div>
<div #graphContainer class="svg-container" [class.hidden]="isLoading()"></div>
@if (isLoading()) {
<div class="loading-overlay">
<mat-spinner diameter="40"></mat-spinner>
<span>Loading architecture graph...</span>
</div>
}
31 changes: 31 additions & 0 deletions web/src/app/graph/components/graph-layout.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,38 @@
* limitations under the License.
*/

@use '@angular/material' as mat;

$loading-text-color: mat.m2-get-color-from-palette(mat.$m2-gray-palette, 600);

:host {
display: grid;
grid-template:
'container' 1fr
/ 1fr;
height: 100%;
}

.svg-container {
grid-area: container;
overflow: hidden;
height: 100%;

&.hidden {
visibility: hidden;
pointer-events: none;
}
}

.loading-overlay {
grid-area: container;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;

width: 100%;
height: 100%;

color: $loading-text-color;
}
7 changes: 7 additions & 0 deletions web/src/app/graph/components/graph-layout.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '@angular/core';
import { GraphData, emptyGraphData } from 'src/app/common/schema/graph-schema';
import { GraphRenderer } from 'src/app/pages/graph/architecture-graph/graph/renderer';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

/**
* Renders the architecture graph layout based on the provided graph data.
Expand All @@ -32,13 +33,19 @@ import { GraphRenderer } from 'src/app/pages/graph/architecture-graph/graph/rend
selector: 'khi-graph-layout',
templateUrl: './graph-layout.component.html',
styleUrls: ['./graph-layout.component.scss'],
imports: [MatProgressSpinnerModule],
})
export class GraphLayoutComponent implements AfterViewInit {
/**
* Input signal holding the graph data to be rendered.
*/
readonly graphData = input<GraphData>(emptyGraphData());

/**
* Input signal indicating whether the graph data is currently loading.
*/
readonly isLoading = input<boolean>(false);

/**
* Reference to the container element for the SVG graph.
*/
Expand Down
10 changes: 9 additions & 1 deletion web/src/app/graph/components/graph-layout.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const meta: Meta<GraphLayoutComponent> = {
tags: ['autodocs'],
args: {
graphData: emptyGraphData(),
isLoading: false,
},
};

Expand All @@ -37,7 +38,7 @@ export const Default: Story = {
},
template: `
<div style="height: 500px; width: 100%;">
<khi-graph-layout [graphData]="graphData"></khi-graph-layout>
<khi-graph-layout [graphData]="graphData" [isLoading]="isLoading"></khi-graph-layout>
</div>
`,
}),
Expand Down Expand Up @@ -107,3 +108,10 @@ export const WithNodeAndPod: Story = {
},
},
};

export const Loading: Story = {
...Default,
args: {
isLoading: true,
},
};
5 changes: 4 additions & 1 deletion web/src/app/graph/graph-smart.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@
limitations under the License.
-->

<khi-graph-layout [graphData]="graphData()"></khi-graph-layout>
<khi-graph-layout
[graphData]="graphData()"
[isLoading]="isLoading()"
></khi-graph-layout>
40 changes: 27 additions & 13 deletions web/src/app/graph/graph-smart.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { Component, computed, inject } from '@angular/core';
import { Component, computed, inject, resource } from '@angular/core';
import { CommonModule } from '@angular/common';
import { GraphLayoutComponent } from 'src/app/graph/components/graph-layout.component';
import { InspectionDataStoreV2 } from 'src/app/services/inspection-data-store-v2.service';
Expand All @@ -36,18 +36,32 @@ export class GraphSmartComponent {
private readonly selectionManager = inject(SelectionManagerV2);
private readonly graphConverter = inject(GraphDataConverterService);

private readonly graphResource = resource({
params: () => ({
log: this.selectionManager.selectedLog(),
timelineView: this.inspectionDataStore.timelineView(),
}),
loader: async ({ params: { log, timelineView }, abortSignal }) => {
if (!log || !timelineView) {
return emptyGraphData();
}
return this.graphConverter.getGraphDataAt(
timelineView.filteredTimelines(),
log.timestamp,
abortSignal,
);
},
});

/**
* Computed signal holding the graph data derived from the currently selected log.
* Signal holding the graph data derived from the currently selected log.
*/
readonly graphData = computed<GraphData>(() => {
const log = this.selectionManager.selectedLog();
const timelineView = this.inspectionDataStore.timelineView();
if (!log || !timelineView) {
return emptyGraphData();
}
return this.graphConverter.getGraphDataAt(
timelineView.filteredTimelines(),
log.timestamp,
);
});
readonly graphData = computed<GraphData>(
() => this.graphResource.value() ?? emptyGraphData(),
);

/**
* Signal indicating whether the graph resource is currently loading.
*/
readonly isLoading = this.graphResource.isLoading;
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,40 @@ export class GraphPageDataSourceServer {
private readonly store = inject(InspectionDataStoreV2);
private readonly selectionManager = inject(SelectionManagerV2);

private abortController?: AbortController;

/**
* Activates the server by listening to graph page open requests and responding with generated graph data asynchronously.
*/
public activate() {
this.connector.receiver(GRAPH_PAGE_OPEN).subscribe((message) => {
this.connector.receiver(GRAPH_PAGE_OPEN).subscribe(async (message) => {
const log = this.selectionManager.selectedLog();
const timelineView = this.store.timelineView();
if (log && timelineView) {
const graphData = this.graphConverter.getGraphDataAt(
timelineView.filteredTimelines(),
log.timestamp,
);
this.connector.unicast<UpdateGraphMessage>(
UPDATE_GRAPH_DATA,
{
graphData,
},
message.sourceFrameId!,
);
if (this.abortController) {
this.abortController.abort();
}
const controller = new AbortController();
this.abortController = controller;

try {
const graphData = await this.graphConverter.getGraphDataAt(
timelineView.filteredTimelines(),
log.timestamp,
controller.signal,
);
if (!controller.signal.aborted) {
this.connector.unicast<UpdateGraphMessage>(
UPDATE_GRAPH_DATA,
{
graphData,
},
message.sourceFrameId!,
);
}
} catch {
// Ignore cancellation error
}
}
});
}
Expand Down
Loading
Loading