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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
* Copyright 2023-2023 VMware, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!--
~ Copyright 2021-2023 VMware, Inc.
~ SPDX-License-Identifier: Apache-2.0
-->

<clr-main-container appWootric *ngIf="idToken">
<header class="header-7">
<div class="branding">
<a href="..." class="nav-link">
<clr-icon shape="vm-bug"></clr-icon>
<span class="title" data-cy="app-main-title">Data Pipelines</span>
</a>
</div>
<div class="header-actions">
<clr-dropdown>
<button class="nav-text" clrDropdownTrigger aria-label="open user profile">
{{userName}}
</button>
<clr-dropdown-menu clrPosition="bottom-right">
<div (click)="logout()" aria-label="Logout" clrDropdownItem>Logout</div>
</clr-dropdown-menu>
</clr-dropdown>
</div>
</header>

<div class="content-container switch-btn-bottom">
<clr-vertical-nav [clrVerticalNavCollapsible]="true" [(clrVerticalNavCollapsed)]="collapsed">

<a id="navLinkGetStarted" clrVerticalNavLink routerLink="/get-started" routerLinkActive="active">
<clr-icon shape="home"></clr-icon>
Get started
</a>

<clr-vertical-nav-group routerLinkActive="active">
<clr-icon clrVerticalNavIcon shape="search"></clr-icon>
Explore
<clr-vertical-nav-group-children *clrIfExpanded="true">

<!-- Data Jobs -->
<a id="navLinkExploreDataJobs" clrVerticalNavLink routerLink="explore/data-jobs"
routerLinkActive="active"
data-cy="navigation-link-explore-datajobs">
<clr-icon shape="data-cluster"></clr-icon>
Data jobs
</a>
</clr-vertical-nav-group-children>
</clr-vertical-nav-group>

<div class="nav-divider"></div>

<!-- Manage -->
<clr-vertical-nav-group routerLinkActive="active">
<clr-icon clrVerticalNavIcon shape="cog"></clr-icon>
Manage

<clr-vertical-nav-group-children *clrIfExpanded="true">
<!-- Data Jobs -->
<a id="navLinkManageDataJobs" clrVerticalNavLink routerLink="/manage/data-jobs"
routerLinkActive="active"
title="Manage Data Jobs" data-cy="navigation-link-manage-datajobs">Data Jobs</a>
</clr-vertical-nav-group-children>
</clr-vertical-nav-group>

</clr-vertical-nav>
<div class="content-area">
<router-outlet></router-outlet>
</div>
</div>
</clr-main-container>

<div class="checking-user" *ngIf="!idToken">
<div class="loading-spinner">
<h2 class="loading-title">Loading Data Pipelines</h2>
<clr-spinner></clr-spinner>
</div>
</div>

<shared-toasts></shared-toasts>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2021-2023 VMware, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

.small-text {
font-size: smaller;
}

.checking-user {
display: table;
height: 28rem;
width: 100%;

.loading-spinner {
text-align: center;
display: table-cell;
vertical-align: middle;

.loading-title {
margin-bottom: .6rem;
}
}
}

.nav-right {
float: right;
display: contents;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2021-2023 VMware, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

import { TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';

import { Subject } from 'rxjs';

import { OAuthService, UrlHelperService } from 'angular-oauth2-oidc';

import { NavigationService, RouterService } from '@vdk/shared';

import { AppComponent } from './app.component';
import { TokenResponse } from 'angular-oauth2-oidc/types';

describe('AppComponent', () => {
let routerServiceStub: jasmine.SpyObj<RouterService>;
let oAuthServiceStub: jasmine.SpyObj<OAuthService>;
let navigationServiceStub: jasmine.SpyObj<NavigationService>;

beforeEach(() => {
routerServiceStub = jasmine.createSpyObj<RouterService>('routerService', ['getState']);
oAuthServiceStub = jasmine.createSpyObj<OAuthService>('oAuthService', [
'configure',
'loadDiscoveryDocumentAndLogin',
'getAccessTokenExpiration',
'refreshToken',
'logOut',
'getIdToken',
'getIdentityClaims'
]);
navigationServiceStub = jasmine.createSpyObj<NavigationService>('navigationService', ['initialize']);

routerServiceStub.getState.and.returnValue(new Subject());
oAuthServiceStub.getIdentityClaims.and.returnValue({});
oAuthServiceStub.loadDiscoveryDocumentAndLogin.and.returnValue(Promise.resolve(true));
oAuthServiceStub.getAccessTokenExpiration.and.returnValue(0);
oAuthServiceStub.refreshToken.and.returnValue(Promise.resolve({} as TokenResponse));

TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
declarations: [AppComponent],
imports: [],
providers: [
UrlHelperService,
{ provide: OAuthService, useValue: oAuthServiceStub },
{ provide: NavigationService, useValue: navigationServiceStub },
{ provide: RouterService, useValue: routerServiceStub }
]
});
});

it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2021-2023 VMware, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

import { Component, OnInit } from '@angular/core';

import { timer } from 'rxjs';

import { OAuthService } from 'angular-oauth2-oidc';

import { NavigationService } from '@vdk/shared';

import { authCodeFlowConfig, refreshTokenConfig } from './auth';

const REFRESH_TOKEN_START = 500;
const ORG_LINK_ROOT = '/csp/gateway/am/api/orgs/';
const CONSOLE_CLOUD_URL = 'https://console-stg.cloud.vmware.com/';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'core';
collapsed = false;

constructor(private readonly oauthService: OAuthService,
private readonly navigationService: NavigationService) {
this.oauthService
.configure(authCodeFlowConfig);
this.oauthService
.loadDiscoveryDocumentAndLogin()
.then(() => {
this.initTokenRefresh();
})
.catch(() => {
// No-op.
});
}

logout(): void {
this.oauthService.logOut();
}

get idToken(): string {
return this.oauthService.getIdToken();
}

get userName(): string {
return this.oauthService.getIdentityClaims()
? this.getIdentityClaim('username')
: 'N/A';
}

/**
* @inheritDoc
*/
ngOnInit(): void {
this.navigationService.initialize();
}

private getIdentityClaim(userNamePropName: string): string {
const identityClaims = this.oauthService.getIdentityClaims() as { [key: string]: string };

return identityClaims[userNamePropName];
}

private initTokenRefresh() {
timer(
REFRESH_TOKEN_START,
AppComponent.toMillis(refreshTokenConfig.refreshTokenCheckInterval))
.subscribe(() => {
const remainiTimeMillis = this.oauthService.getAccessTokenExpiration() - Date.now();
if (remainiTimeMillis <= AppComponent.toMillis(refreshTokenConfig.refreshTokenRemainingTime)) {
this.setCustomTokenAttributes(false, null);
this.oauthService
.refreshToken()
.finally(() => {
// No-op.
});
}
});
}

private setCustomTokenAttributes(redirectToConsole: boolean, defaultOrg: { refLink: string }) {
const linkOrgQuery = AppComponent.getOrgLinkFromQueryParams(defaultOrg);
this.oauthService.customQueryParams = {
orgLink: linkOrgQuery,
targetUri: redirectToConsole ? CONSOLE_CLOUD_URL : window.location.href
};
if (redirectToConsole) {
// Redirect to console cloud because we dont know the tenant url, but console does
this.oauthService.redirectUri = CONSOLE_CLOUD_URL;
}
}

private static getOrgLinkFromQueryParams(defaultOrg: { refLink: string }): string {
const params = new URLSearchParams(window.location.search);
const orgLinkUnderscored = params.get('org_link');
const orgLinkBase = params.get('orgLink');
if (orgLinkBase || orgLinkUnderscored) {
return [orgLinkBase, orgLinkUnderscored].find(el => el);
} else {
return (defaultOrg ? defaultOrg.refLink : ORG_LINK_ROOT);
}
}

private static toMillis(seconds: number) {
return seconds * 1000;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2021-2023 VMware, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { AuthConfig, OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';

import { TimeagoModule } from 'ngx-timeago';
import { LottieModule } from 'ngx-lottie';

import { ApolloModule } from 'apollo-angular';

import { ClarityModule } from '@clr/angular';

import { VmwComponentsModule } from '@vdk/shared';

import { TaurusSharedCoreModule, TaurusSharedFeaturesModule, TaurusSharedNgRxModule } from '@vdk/shared';

import { DataPipelinesModule } from '@vdk/data-pipelines';

import { AppComponent } from './app.component';
import { AppRouting } from './app.routing';
import { authCodeFlowConfig } from './auth';
import { AuthorizationInterceptor } from './http.interceptor';
import { GettingStartedComponent } from './getting-started/getting-started.component';

// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
export function lottiePlayerLoader() {
return import('lottie-web');
}

@NgModule({
declarations: [AppComponent, GettingStartedComponent],
imports: [
AppRouting,
BrowserModule,
ClarityModule,
BrowserAnimationsModule,
ApolloModule,
TaurusSharedCoreModule.forRoot(),
TaurusSharedFeaturesModule.forRoot(),
TaurusSharedNgRxModule.forRootWithDevtools(),
TimeagoModule.forRoot(),
LottieModule.forRoot({ player: lottiePlayerLoader }),
VmwComponentsModule.forRoot(),
DataPipelinesModule.forRoot({
defaultOwnerTeamName: 'taurus',
manageConfig: {
allowKeyTabDownloads: true
},
exploreConfig: {
showTeamsColumn: true
},
healthStatusUrl: '/explore/data-jobs?search={0}',
showExecutionsPage: true,
showLineagePage: false
}),
HttpClientModule,
OAuthModule.forRoot({
resourceServer: {
allowedUrls: [
'https://console-stg.cloud.vmware.com/',
Comment thread
ivakoleva marked this conversation as resolved.
'https://gaz-preview.csp-vidm-prod.com/',
'/data-jobs'
],
sendAccessToken: true
}
})
],
providers: [
{
provide: OAuthStorage,
useValue: localStorage
},
{
provide: AuthConfig,
useValue: authCodeFlowConfig
},
{
provide: HTTP_INTERCEPTORS,
useClass: AuthorizationInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
Loading