Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GROUP-89 Improve RSocket Services & Event Handling #21

Merged
merged 15 commits into from
Mar 19, 2024
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
3,136 changes: 1,413 additions & 1,723 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
"@angular/platform-browser-dynamic": "^17.2.3",
"@angular/router": "^17.2.3",
"buffer": "^6.0.3",
"rsocket-adapter-rxjs": "^1.0.0-alpha.4",
"rsocket-composite-metadata": "^1.0.0-alpha.3",
"rsocket-core": "^1.0.0-alpha.3",
"rsocket-messaging": "^1.0.0-alpha.3",
"rsocket-websocket-client": "^1.0.0-alpha.3",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
Expand All @@ -49,7 +51,6 @@
"eslint-config-prettier": "^9.0.0",
"husky": "^8.0.3",
"jasmine-core": "~4.6.0",
"jasmine-marbles": "^0.9.2",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
Expand All @@ -59,6 +60,6 @@
"stylelint": "^15.11.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^11.0.0",
"typescript": "~5.2.2"
"typescript": "~5.3.0"
}
}
9 changes: 0 additions & 9 deletions src/app/app-tokens.ts

This file was deleted.

17 changes: 5 additions & 12 deletions src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { AppComponent } from "./app.component";
import { ConfigService } from "./config/config.service";
import { RouterTestingModule } from "@angular/router/testing";
import { NavComponent } from "./shared/nav/nav.component";
import { FooterComponent } from "./shared/footer/footer.component";
import { NotificationService } from "./services/user/notification.service";

describe("AppComponent", () => {
let fixture: ComponentFixture<AppComponent>;
let appComponentInstance: AppComponent;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule,
NavComponent,
FooterComponent,
AppComponent,
],
providers: [{ provide: NotificationService, useValue: {} }],
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AppComponent, RouterTestingModule],
providers: [{ provide: ConfigService, useValue: {} }],
}).compileComponents();

fixture = TestBed.createComponent(AppComponent);
Expand Down
6 changes: 4 additions & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Component, HostBinding } from "@angular/core";
import { RouterOutlet } from "@angular/router";
import { NavComponent } from "./shared/nav/nav.component";
import { FooterComponent } from "./shared/footer/footer.component";
import { NotificationService } from "./services/user/notification.service";
import { RsocketService } from "./services/network/rsocket/rsocket.service";

@Component({
selector: "app-root",
Expand All @@ -14,5 +14,7 @@ export class AppComponent {
title = "groupHQ-UI";
@HostBinding("class") classes = "page-container";

constructor(readonly notificationService: NotificationService) {}
constructor(private readonly rsocketService: RsocketService) {
this.rsocketService.initializeRsocketConnection();
}
}
26 changes: 26 additions & 0 deletions src/app/config/config.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Inject, Injectable } from "@angular/core";
import { APP_CONFIG, Config } from "./config";
import { RetryOptions } from "../services/retry/retry.options";

@Injectable({
providedIn: "root",
Expand Down Expand Up @@ -62,4 +63,29 @@ export class ConfigService {
public get apiProtocol() {
return this.config.api.protocol;
}

public get retryDefaultStrategy(): RetryOptions {
return {
MAX_ATTEMPTS: this.config.retryServices.retryDefault.MAX_ATTEMPTS ?? 5,
MIN_RETRY_INTERVAL:
this.config.retryServices.retryDefault.MIN_RETRY_INTERVAL ?? 5,
MAX_RETRY_INTERVAL:
this.config.retryServices.retryDefault.MAX_RETRY_INTERVAL ?? 5,
};
}

public get retryForeverStrategy(): RetryOptions {
const MAX_RETRY_ATTEMPTS =
this.config.retryServices.retryForeverConstant.MAX_ATTEMPTS == -1
? Number.MAX_VALUE
: this.config.retryServices.retryForeverConstant.MAX_ATTEMPTS;

return {
MAX_ATTEMPTS: MAX_RETRY_ATTEMPTS ?? Number.MAX_VALUE,
MIN_RETRY_INTERVAL:
this.config.retryServices.retryForeverConstant.MIN_RETRY_INTERVAL ?? 5,
MAX_RETRY_INTERVAL:
this.config.retryServices.retryForeverConstant.MAX_RETRY_INTERVAL ?? 5,
};
}
}
6 changes: 3 additions & 3 deletions src/app/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RetryServiceOptions } from "../services/retry/abstractRetry.service";
import { RetryOptions } from "../services/retry/retry.options";
import { InjectionToken } from "@angular/core";

export type Config = {
Expand All @@ -19,8 +19,8 @@ export type Config = {
maximumDisconnectRetryTime: number;
};
retryServices: {
retryDefault: RetryServiceOptions;
retryForeverConstant: RetryServiceOptions;
retryDefault: RetryOptions;
retryForeverConstant: RetryOptions;
};
groupBoardComponent: {
loadingDelaySeconds: number;
Expand Down
50 changes: 50 additions & 0 deletions src/app/groups/dialogs/groupDetailsDialog/date-ago.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DateAgoPipe } from "./date-ago.pipe";

describe("DateAgoPipe", () => {
let pipe: DateAgoPipe;

beforeEach(() => {
pipe = new DateAgoPipe();
});

it("should return singular hour description when time since is one hour", () => {
const date = new Date();
date.setHours(date.getHours() - 1);
const timeSince = pipe.transform(date.toISOString());
expect(timeSince).toBe("1 hour ago");
});

it("should return plural hour description when time since is multiple hours", () => {
const date = new Date();
date.setHours(date.getHours() - 2);
const timeSince = pipe.transform(date.toISOString());
expect(timeSince).toBe("2 hours ago");
});

it("should return singular minute description when time since is one minute", () => {
const date = new Date();
date.setMinutes(date.getMinutes() - 1);
const timeSince = pipe.transform(date.toISOString());
expect(timeSince).toBe("1 minute ago");
});

it("should return plural minute description when time since is multiple minutes", () => {
const date = new Date();
date.setMinutes(date.getMinutes() - 2);
const timeSince = pipe.transform(date.toISOString());
expect(timeSince).toBe("2 minutes ago");
});

it("should return generic plural second description when time since is in seconds", () => {
const date = new Date();
date.setSeconds(date.getSeconds() - 2);
const timeSince = pipe.transform(date.toISOString());
expect(timeSince).toBe("a few seconds ago");
});

it("should throw an error when time since is negative", () => {
const date = new Date();
date.setSeconds(date.getSeconds() + 1);
expect(() => pipe.transform(date.toISOString())).toThrowError();
});
});
37 changes: 37 additions & 0 deletions src/app/groups/dialogs/groupDetailsDialog/date-ago.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Pipe, PipeTransform } from "@angular/core";

@Pipe({
name: "dateAgo",
standalone: true,
pure: false,
})
export class DateAgoPipe implements PipeTransform {
transform(date: string): string {
return this.timeSince(date);
}

private timeSince(date: string): string {
const seconds = Math.floor(
(new Date().getTime() - new Date(date).getTime()) / 1000,
);

if (seconds < 0) {
throw new Error("Date must be in the past");
}

let interval: number;

interval = seconds / 3600;
if (interval >= 1) {
const floored = Math.floor(interval);
return floored === 1 ? "1 hour ago" : floored + " hours ago";
}
interval = seconds / 60;
if (interval >= 1) {
const floored = Math.floor(interval);
return floored === 1 ? "1 minute ago" : floored + " minutes ago";
}

return "a few seconds ago";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
<div class="group-extra-info" data-test="group-extra-info">
<div class="group-detail" data-test="group-creation-time">
<p class="detail-title" data-test="detail-title">Created</p>
<p data-test="detail-content">{{ timeSince(group.createdDate) }}</p>
<p data-test="detail-content">{{ group.createdDate | dateAgo }}</p>
</div>
<div class="group-detail" data-test="group-last-activity">
<p class="detail-title" data-test="detail-title">Last Activity</p>
<p data-test="detail-content">
{{ timeSince(group.lastModifiedDate) }}
{{ group.lastModifiedDate | dateAgo }}
</p>
</div>
<div class="group-detail" data-test="group-members-count">
Expand All @@ -40,32 +40,30 @@
Members
</h3>
@for (member of group.members; track member) {
<mat-list-item class="member-list-item" data-test="group-member">
<mat-icon
matListItemIcon
aria-hidden="false"
aria-label="Default profile icon"
fontSet="material-symbols-outlined"
data-test="group-member-profile-icon"
>account_circle</mat-icon
>
<h4 matListItemTitle data-test="member-name">{{ member.username }}</h4>
<p matListItemLine data-test="member-joined-date">
Joined {{ timeSince(member.joinedDate) }}
</p>
</mat-list-item>
<mat-list-item class="member-list-item" data-test="group-member">
<mat-icon
matListItemIcon
aria-hidden="false"
aria-label="Default profile icon"
fontSet="material-symbols-outlined"
data-test="group-member-profile-icon"
>account_circle</mat-icon
>
<h4 matListItemTitle data-test="member-name">
{{ member.username }}
</h4>
<p matListItemLine data-test="member-joined-date">
Joined {{ member.joinedDate | dateAgo }}
</p>
</mat-list-item>
} @empty {
<mat-list-item class="member-list-item" data-test="no-members-message">
<h4 matListItemTitle>No members yet</h4>
</mat-list-item>
<mat-list-item class="member-list-item" data-test="no-members-message">
<h4 matListItemTitle>No members yet</h4>
</mat-list-item>
}
</mat-list>
</mat-dialog-content>
@if (errorLeavingGroup) {
<div data-test="server-unavailable-error">
Error joining group. Please try again
</div>
}

<mat-dialog-actions
appMedia
app-media-base-class="group-actions"
Expand All @@ -80,49 +78,53 @@ <h4 matListItemTitle>No members yet</h4>
>
Close
</button>
@if (!userService.currentGroupId) { @if (loading) {
<div class="dialog-actions">
<mat-progress-bar mode="buffer" data-test="loading__progress-bar">
</mat-progress-bar>
</div>
} @else {
<button
class="dialog-actions"
mat-raised-button
color="primary"
[disabled]="group.members.length >= group.maxGroupSize || loading"
(click)="openInputNameDialog()"
data-test="group-details-action-dialog-button"
>
{{
group.members.length >= group.maxGroupSize
? "Group is full"
: "Join Group"
}}
</button>
} } @else { @if (loading) {
<div class="dialog-actions">
<mat-progress-bar
mode="buffer"
data-test="loading-progress-bar"
color="warn"
></mat-progress-bar>
</div>
@if (!userService.currentGroupId) {
@if (loading) {
<div class="dialog-actions">
<mat-progress-bar mode="buffer" data-test="loading__progress-bar">
</mat-progress-bar>
</div>
} @else {
<button
class="dialog-actions"
mat-raised-button
color="primary"
[disabled]="group.members.length >= group.maxGroupSize || loading"
(click)="openInputNameDialog()"
data-test="group-details-action-dialog-button"
>
{{
group.members.length >= group.maxGroupSize
? "Group is full"
: "Join Group"
}}
</button>
}
} @else {
<button
class="dialog-actions"
mat-raised-button
[color]="userService.currentGroupId === group.id ? 'warn' : 'primary'"
[disabled]="userService.currentGroupId !== group.id || loading"
(click)="leaveGroup()"
data-test="group-details-action-dialog-button"
>
{{
userService.currentGroupId !== group.id
? "You're already in another group"
: "Leave Group"
}}
</button>
} }
@if (loading) {
<div class="dialog-actions">
<mat-progress-bar
mode="buffer"
data-test="loading-progress-bar"
color="warn"
></mat-progress-bar>
</div>
} @else {
<button
class="dialog-actions"
mat-raised-button
[color]="userService.currentGroupId === group.id ? 'warn' : 'primary'"
[disabled]="userService.currentGroupId !== group.id || loading"
(click)="leaveGroup()"
data-test="group-details-action-dialog-button"
>
{{
userService.currentGroupId !== group.id
? "You're already in another group"
: "Leave Group"
}}
</button>
}
}
</mat-dialog-actions>
</div>
Loading
Loading