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

Fix Google Login button disappearing issue #3197

Merged
merged 22 commits into from
Jan 19, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ class GoogleAuthResource {

@GET
@Path("/clientid")
def getClientId: String = {
clientId
}
def getClientId: String = clientId

@POST
@Consumes(Array(MediaType.TEXT_PLAIN))
Expand Down
1 change: 1 addition & 0 deletions core/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"private": true,
"dependencies": {
"@abacritt/angularx-social-login": "2.1.0",
"@ali-hm/angular-tree-component": "12.0.5",
"@angular/animations": "16.2.12",
"@angular/cdk": "16.2.12",
Expand Down
22 changes: 20 additions & 2 deletions core/gui/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,16 @@ import { SearchBarComponent } from "./dashboard/component/user/search-bar/search
import { ListItemComponent } from "./dashboard/component/user/list-item/list-item.component";
import { HubComponent } from "./hub/component/hub.component";
import { HubWorkflowSearchComponent } from "./hub/component/workflow/search/hub-workflow-search.component";
import { GoogleLoginComponent } from "./dashboard/component/user/google-login/google-login.component";
import { HubWorkflowComponent } from "./hub/component/workflow/hub-workflow.component";
import { HubWorkflowDetailComponent } from "./hub/component/workflow/detail/hub-workflow-detail.component";
import { LandingPageComponent } from "./hub/component/landing-page/landing-page.component";
import { BrowseSectionComponent } from "./hub/component/browse-section/browse-section.component";
import { BreakpointConditionInputComponent } from "./workspace/component/code-editor-dialog/breakpoint-condition-input/breakpoint-condition-input.component";
import { CodeDebuggerComponent } from "./workspace/component/code-editor-dialog/code-debugger.component";
import { GoogleAuthService } from "./common/service/user/google-auth.service";
import { SocialLoginModule, SocialAuthServiceConfig, GoogleSigninButtonModule } from "@abacritt/angularx-social-login";
import { GoogleLoginProvider } from "@abacritt/angularx-social-login";
import { lastValueFrom } from "rxjs";

registerLocaleData(en);

Expand Down Expand Up @@ -225,7 +228,6 @@ registerLocaleData(en);
HubWorkflowComponent,
HubWorkflowSearchComponent,
HubWorkflowDetailComponent,
GoogleLoginComponent,
LandingPageComponent,
BrowseSectionComponent,
BreakpointConditionInputComponent,
Expand Down Expand Up @@ -289,6 +291,8 @@ registerLocaleData(en);
NzTreeViewModule,
NzNoAnimationModule,
TreeModule,
SocialLoginModule,
GoogleSigninButtonModule,
],
providers: [
provideNzI18n(en_US),
Expand All @@ -303,6 +307,20 @@ registerLocaleData(en);
useClass: BlobErrorHttpInterceptor,
multi: true,
},
{
provide: "SocialAuthServiceConfig",
useFactory: (googleAuthService: GoogleAuthService, userService: UserService) => {
return lastValueFrom(googleAuthService.getClientId()).then(clientId => ({
providers: [
{
id: GoogleLoginProvider.PROVIDER_ID,
provider: new GoogleLoginProvider(clientId, { oneTapEnabled: !userService.isLogin() }),
},
],
})) as Promise<SocialAuthServiceConfig>;
},
deps: [GoogleAuthService, UserService],
},
],
bootstrap: [AppComponent],
})
Expand Down
8 changes: 7 additions & 1 deletion core/gui/src/app/common/service/user/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ export class AuthService {
public googleAuth(credential: string): Observable<Readonly<{ accessToken: string }>> {
return this.http.post<Readonly<{ accessToken: string }>>(
`${AppSettings.getApiEndpoint()}/${AuthService.GOOGLE_LOGIN_ENDPOINT}`,
`${credential}`
credential,
{
headers: {
"Content-Type": "text/plain",
Accept: "application/json",
},
}
);
}

Expand Down
33 changes: 4 additions & 29 deletions core/gui/src/app/common/service/user/google-auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,15 @@
import { Injectable } from "@angular/core";
import { Subject } from "rxjs";
import { Observable } from "rxjs";
import { HttpClient } from "@angular/common/http";
import { AppSettings } from "../../app-setting";
declare var window: any;
export interface CredentialResponse {
client_id: string;
credential: string;
select_by: string;
}

@Injectable({
providedIn: "root",
})
export class GoogleAuthService {
private _googleCredentialResponse = new Subject<CredentialResponse>();
constructor(private http: HttpClient) {}
public googleAuthInit(parent: HTMLElement | null) {
this.http.get(`${AppSettings.getApiEndpoint()}/auth/google/clientid`, { responseType: "text" }).subscribe({
next: response => {
window.onGoogleLibraryLoad = () => {
window.google.accounts.id.initialize({
client_id: response,
callback: (auth: CredentialResponse) => {
this._googleCredentialResponse.next(auth);
},
});
window.google.accounts.id.renderButton(parent, { width: 200 });
window.google.accounts.id.prompt();
};
},
error: (err: unknown) => {
console.error(err);
},
});
}

get googleCredentialResponse() {
return this._googleCredentialResponse.asObservable();
getClientId(): Observable<string> {
return this.http.get(`${AppSettings.getApiEndpoint()}/auth/google/clientid`, { responseType: "text" });
}
}
8 changes: 6 additions & 2 deletions core/gui/src/app/dashboard/component/dashboard.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,17 @@
<div class="page-container">
<nz-layout>
<div
*ngIf="displayNavbar"
[hidden]="!displayNavbar"
id="nav">
<texera-search-bar></texera-search-bar>
<ng-container *ngIf="isLogin">
<texera-user-icon></texera-user-icon>
</ng-container>
<texera-google-login [hidden]="isLogin"></texera-google-login>
<asl-google-signin-button
*ngIf="!isLogin"
type="standard"
size="large"
width="200"></asl-google-signin-button>
</div>

<nz-content>
Expand Down
11 changes: 6 additions & 5 deletions core/gui/src/app/dashboard/component/dashboard.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ texera-search-bar {
padding-right: 10px;
}

texera-google-login {
float: right;
padding: 5px 0;
}

texera-user-icon {
padding: 0 24px;
}
Expand Down Expand Up @@ -70,3 +65,9 @@ nz-content {
.hidden {
display: none;
}

#nav {
max-width: 100%;
max-height: 100%;
overflow: hidden;
}
125 changes: 125 additions & 0 deletions core/gui/src/app/dashboard/component/dashboard.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { TestBed, ComponentFixture } from "@angular/core/testing";
import { DashboardComponent } from "./dashboard.component";
import { NO_ERRORS_SCHEMA, ChangeDetectorRef, NgZone, EventEmitter } from "@angular/core";
import { By } from "@angular/platform-browser";
import { of } from "rxjs";

import { UserService } from "../../common/service/user/user.service";
import { FlarumService } from "../service/user/flarum/flarum.service";
import { SocialAuthService } from "@abacritt/angularx-social-login";
import {
Router,
NavigationEnd,
ActivatedRoute,
ActivatedRouteSnapshot,
UrlSegment,
Params,
Data,
} from "@angular/router";
import { convertToParamMap } from "@angular/router";

describe("DashboardComponent", () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;

let userServiceMock: Partial<UserService>;
let routerMock: Partial<Router>;
let flarumServiceMock: Partial<FlarumService>;
let cdrMock: Partial<ChangeDetectorRef>;
let ngZoneMock: Partial<NgZone>;
let socialAuthServiceMock: Partial<SocialAuthService>;
let activatedRouteMock: Partial<ActivatedRoute>;

const activatedRouteSnapshotMock: Partial<ActivatedRouteSnapshot> = {
queryParams: {},
url: [] as UrlSegment[],
params: {} as Params,
fragment: null,
data: {} as Data,
paramMap: convertToParamMap({}),
queryParamMap: convertToParamMap({}),
outlet: "",
routeConfig: null,
root: null as any,
parent: null as any,
firstChild: null as any,
children: [],
pathFromRoot: [],
};

beforeEach(async () => {
userServiceMock = {
isAdmin: jasmine.createSpy("isAdmin").and.returnValue(false),
isLogin: jasmine.createSpy("isLogin").and.returnValue(false),
userChanged: jasmine.createSpy("userChanged").and.returnValue(of(null)),
};

routerMock = {
events: of(new NavigationEnd(1, "/dashboard", "/dashboard")),
url: "/dashboard",
navigateByUrl: jasmine.createSpy("navigateByUrl"),
};

flarumServiceMock = {
auth: jasmine.createSpy("auth").and.returnValue(of({ token: "dummyToken" })),
register: jasmine.createSpy("register").and.returnValue(of(null)),
};

cdrMock = {
detectChanges: jasmine.createSpy("detectChanges"),
};

ngZoneMock = {
hasPendingMicrotasks: false,
hasPendingMacrotasks: false,
onUnstable: new EventEmitter<any>(),
onMicrotaskEmpty: new EventEmitter<any>(),
onStable: new EventEmitter<any>(),
onError: new EventEmitter<any>(),
run: (fn: () => any) => fn(),
runGuarded: (fn: () => any) => fn(),
runOutsideAngular: (fn: () => any) => fn(),
runTask: (fn: () => any) => fn(),
};

socialAuthServiceMock = {
authState: of(),
};

activatedRouteMock = {
snapshot: activatedRouteSnapshotMock as ActivatedRouteSnapshot,
};

await TestBed.configureTestingModule({
declarations: [DashboardComponent],
providers: [
{ provide: UserService, useValue: userServiceMock },
{ provide: Router, useValue: routerMock },
{ provide: FlarumService, useValue: flarumServiceMock },
{ provide: ChangeDetectorRef, useValue: cdrMock },
{ provide: NgZone, useValue: ngZoneMock },
{ provide: SocialAuthService, useValue: socialAuthServiceMock },
{ provide: ActivatedRoute, useValue: activatedRouteMock },
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it("should create the component", () => {
expect(component).toBeTruthy();
});

it("should render Google sign-in button when user is NOT logged in", () => {
(userServiceMock.isLogin as jasmine.Spy).and.returnValue(false);
fixture.detectChanges();

const googleSignInBtn = fixture.debugElement.query(By.css("asl-google-signin-button"));
expect(googleSignInBtn).toBeTruthy();
});
});
23 changes: 17 additions & 6 deletions core/gui/src/app/dashboard/component/dashboard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { UserService } from "../../common/service/user/user.service";
import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
import { FlarumService } from "../service/user/flarum/flarum.service";
import { HttpErrorResponse } from "@angular/common/http";
import { NavigationEnd, Router } from "@angular/router";
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
import { HubComponent } from "../../hub/component/hub.component";
import { SocialAuthService } from "@abacritt/angularx-social-login";

import {
DASHBOARD_ADMIN_EXECUTION,
Expand All @@ -27,7 +28,7 @@ export class DashboardComponent implements OnInit {
@ViewChild(HubComponent) hubComponent!: HubComponent;

isAdmin: boolean = this.userService.isAdmin();
isLogin = this.userService.isLogin();
isLogin: boolean = this.userService.isLogin();
displayForum: boolean = true;
displayNavbar: boolean = true;
isCollpased: boolean = false;
Expand All @@ -47,13 +48,12 @@ export class DashboardComponent implements OnInit {
private router: Router,
private flarumService: FlarumService,
private cdr: ChangeDetectorRef,
private ngZone: NgZone
private ngZone: NgZone,
private socialAuthService: SocialAuthService,
private route: ActivatedRoute
) {}

ngOnInit(): void {
this.isLogin = this.userService.isLogin();
this.isAdmin = this.userService.isAdmin();

this.isCollpased = false;

this.router.events.pipe(untilDestroyed(this)).subscribe(() => {
Expand All @@ -78,6 +78,17 @@ export class DashboardComponent implements OnInit {
this.cdr.detectChanges();
});
});

this.socialAuthService.authState.pipe(untilDestroyed(this)).subscribe(user => {
this.userService
.googleLogin(user.idToken)
.pipe(untilDestroyed(this))
.subscribe(() => {
this.ngZone.run(() => {
this.router.navigateByUrl(this.route.snapshot.queryParams["returnUrl"] || DASHBOARD_USER_WORKFLOW);
});
});
});
}

forumLogin() {
Expand Down

This file was deleted.

Loading
Loading