Skip to content

Commit 6efdf5f

Browse files
authored
Added request loader component (#2915)
### Fixes # <!-- Mention the issues this PR addresses --> ### Checks - [ ] Ran `yarn test-build` - [ ] Updated relevant documentations - [ ] Updated matching config options in altair-static ### Changes proposed in this pull request: <!-- Describe the changes being introduced in this PR --> ## Summary by Sourcery Introduce a request loader component and corresponding store logic to track and display the full request lifecycle (pre-request, auth, query, post-request) per window, updating QueryService to dispatch progress actions and embedding the loader UI into existing components New Features: - Add RequestLoaderComponent to display per-window request loading progress in the UI - Introduce NGRX actions, reducer cases, and selectors to manage temporary window loading request state entries Enhancements: - Refactor QueryService to sequence pre-request scripts, authorization, query, and post-request transforms with loading state dispatches - Integrate the request loader into UrlBoxComponent and WindowComponent and wire it to the store - Extend the icon set with a dashed circle icon for pending states Tests: - Update WindowComponent spec to initialize the new temporaryWindowStates slice Chores: - Add SCSS styles for the request-loader component and include them in global stylesheet - Adjust font sizing in editor and URL box loader z-index for visual consistency <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Request loading indicator added to show per-request progress (pending, active, done, error) including pre-/post-request and auth steps; surfaced in the URL bar and window UI. * **Improvements** * Loading indicators tied to request lifecycle (accurate handling for subscriptions vs. non-subscriptions) and per-window tracking. * **Style** * Added overlay styles for the loader; tightened URI font and adjusted URL-box layering. * **Tests** * Unit tests added for the request loader component. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent f76e4dd commit 6efdf5f

File tree

24 files changed

+673
-58
lines changed

24 files changed

+673
-58
lines changed

packages/altair-app/src/app/modules/altair/components/components.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { BannerComponent } from './banner/banner.component';
5454
import { BannerContainerComponent } from './banner-container/banner-container.component';
5555
import { HeadersEditorComponent } from './headers-editor/headers-editor.component';
5656
import { WindowSwitcherItemComponent } from './window-switcher-item/window-switcher-item.component';
57+
import { RequestLoaderComponent } from './request-loader/request-loader.component';
5758

5859
// const STANDALONE_COMPONENTS = [];
5960
const COMPONENTS = [
@@ -103,6 +104,7 @@ const COMPONENTS = [
103104
AuthorizationOauth2Component,
104105
BannerComponent,
105106
BannerContainerComponent,
107+
RequestLoaderComponent,
106108
];
107109

108110
@NgModule({
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
@if (isLoading() && requestState().length > 1) {
2+
<div class="request-loader">
3+
<ul class="request-loader__items">
4+
@for (item of requestState(); track item.id) {
5+
<li
6+
class="request-loader__item"
7+
[ngClass]="{
8+
'request-loader__item--active': item.state === 'active',
9+
'request-loader__item--done': item.state === 'done',
10+
'request-loader__item--pending': item.state === 'pending',
11+
'request-loader__item--error': item.state === 'error',
12+
}"
13+
>
14+
<div
15+
class="request-loader__item-icon"
16+
nz-tooltip
17+
[nzTooltipTitle]="item.name"
18+
>
19+
@switch (item.state) {
20+
@case ('done') {
21+
<app-icon name="check" />
22+
}
23+
@case ('error') {
24+
<app-icon name="x" />
25+
}
26+
@case ('active') {
27+
<app-icon name="loader" class="anim anim-rotate" />
28+
}
29+
@default {
30+
<app-icon name="circle-dashed" />
31+
}
32+
}
33+
</div>
34+
<div class="request-loader__item-text">{{ item.name }}</div>
35+
</li>
36+
}
37+
</ul>
38+
</div>
39+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { RequestLoaderComponent } from './request-loader.component';
4+
5+
describe('RequestLoaderComponent', () => {
6+
let component: RequestLoaderComponent;
7+
let fixture: ComponentFixture<RequestLoaderComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [RequestLoaderComponent]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(RequestLoaderComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
2+
import { LoadingRequestStateEntry } from 'altair-graphql-core/build/types/state/local.interfaces';
3+
4+
@Component({
5+
selector: 'app-request-loader',
6+
standalone: false,
7+
templateUrl: './request-loader.component.html',
8+
styles: ``,
9+
changeDetection: ChangeDetectionStrategy.OnPush,
10+
})
11+
export class RequestLoaderComponent {
12+
readonly requestState = input<LoadingRequestStateEntry[]>([]);
13+
readonly isLoading = input(false);
14+
}

packages/altair-app/src/app/modules/altair/components/teams-dialog/teams-dialog.component.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
signal,
88
inject,
99
output,
10+
ChangeDetectionStrategy,
1011
} from '@angular/core';
1112
import { FormControl, NonNullableFormBuilder, Validators } from '@angular/forms';
1213
import { Team } from 'altair-graphql-core/build/types/state/account.interfaces';
@@ -22,6 +23,7 @@ import * as windowsMetaActions from '../../store/windows-meta/windows-meta.actio
2223
templateUrl: './teams-dialog.component.html',
2324
styles: [],
2425
standalone: false,
26+
changeDetection: ChangeDetectionStrategy.OnPush,
2527
})
2628
export class TeamsDialogComponent {
2729
private readonly accountService = inject(AccountService);
@@ -76,9 +78,16 @@ export class TeamsDialogComponent {
7678
this.membersOfSelectedTeam.set([]);
7779
return;
7880
}
79-
this.membersOfSelectedTeam.set(
80-
(await this.accountService.getTeamMembers(selectedTeamId)) || []
81-
);
81+
try {
82+
this.membersOfSelectedTeam.set(
83+
(await this.accountService.getTeamMembers(selectedTeamId)) || []
84+
);
85+
} catch (err) {
86+
this.notifyService.errorWithError(
87+
await getErrorResponse(err),
88+
'Could not load team members'
89+
);
90+
}
8291
});
8392

8493
effect(() => {
@@ -94,8 +103,15 @@ export class TeamsDialogComponent {
94103

95104
async onDeleteTeam(id: string) {
96105
if (confirm('Are you sure you want to delete this team?')) {
97-
await this.accountService.deleteTeam(id);
98-
this.reloadTeamChange.emit();
106+
try {
107+
await this.accountService.deleteTeam(id);
108+
this.reloadTeamChange.emit();
109+
} catch (err) {
110+
this.notifyService.errorWithError(
111+
await getErrorResponse(err),
112+
'Could not delete team'
113+
);
114+
}
99115
}
100116
}
101117

packages/altair-app/src/app/modules/altair/components/url-box/__snapshots__/url-box.component.spec.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ exports[`UrlBoxComponent should render correctly 1`] = `
6666
track-id="set_url"
6767
/>
6868
69+
<app-request-loader />
6970
</div>
7071
<div
7172
class="url-box__input-actions"
@@ -217,6 +218,7 @@ exports[`UrlBoxComponent should render correctly with queryOperations > 1 1`] =
217218
track-id="set_url"
218219
/>
219220
221+
<app-request-loader />
220222
</div>
221223
<div
222224
class="url-box__input-actions"

packages/altair-app/src/app/modules/altair/components/url-box/url-box.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
(blurChange)="setApiUrl()"
5858
(submitChange)="setApiUrl()"
5959
/>
60+
<app-request-loader [requestState]="requestState()" [isLoading]="isLoading()" />
6061
</div>
6162
<div class="url-box__input-actions">
6263
@if (currentCollection()) {

packages/altair-app/src/app/modules/altair/components/url-box/url-box.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from 'altair-graphql-core/build/types/state/query.interfaces';
1313
import { OperationDefinitionNode } from 'graphql';
1414
import { BATCHED_REQUESTS_OPERATION } from '../../services/gql/gql.service';
15+
import { LoadingRequestStateEntry } from 'altair-graphql-core/build/types/state/local.interfaces';
1516

1617
@Component({
1718
selector: 'app-url-box',
@@ -31,6 +32,7 @@ export class UrlBoxComponent {
3132
readonly currentCollection = input<IQueryCollection>();
3233
readonly hasUnsavedChanges = input(false);
3334
readonly windowId = input('');
35+
readonly requestState = input<LoadingRequestStateEntry[]>([]);
3436

3537
readonly toggleDocsChange = output();
3638
readonly reloadDocsChange = output();

packages/altair-app/src/app/modules/altair/containers/window/window.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
[currentCollection]="currentCollection$ | async"
1212
[hasUnsavedChanges]="hasUnsavedChanges$ | async"
1313
[windowId]="windowId()"
14+
[requestState]="loadingRequestState$ | async"
1415
(urlChange)="setApiUrl($event)"
1516
(httpVerbChange)="setApiMethod($event)"
1617
(sendRequest)="sendRequest()"

packages/altair-app/src/app/modules/altair/containers/window/window.component.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ describe('WindowComponent', () => {
3939
installedPlugins: {},
4040
panels: [],
4141
uiActions: [],
42+
temporaryWindowStates: {},
4243
},
4344
collection: {
4445
activeCollection: undefined,

0 commit comments

Comments
 (0)