Skip to content

Commit

Permalink
[pinpoint-apm#8004] Implement metric page
Browse files Browse the repository at this point in the history
  • Loading branch information
binDongKim authored and minwoo-jung committed Jun 21, 2022
1 parent 16ddb18 commit fc9f4aa
Show file tree
Hide file tree
Showing 59 changed files with 2,365 additions and 28 deletions.
4 changes: 4 additions & 0 deletions web/src/main/angular/src/app/app.routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const appRoutes: Routes = [
configuration: SystemConfigurationResolverService,
},
children: [
{
path: UrlPath.METRIC,
loadChildren: () => import('./routes/metric-page/index').then(m => m.MetricPageModule)
},
{
path: UrlPath.URL_STATISTIC,
loadChildren: () => import('./routes/url-statistic-page/index').then(m => m.UrlStatisticPageModule)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnInit, OnDestroy, HostBinding, Renderer2, ViewChild, ElementRef } from '@angular/core';
import { Subject, Observable, merge, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { map, takeUntil } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';

import { MessageQueueService, MESSAGE_TO, WebAppSettingDataService, NewUrlStateNotificationService } from 'app/shared/services';
Expand Down Expand Up @@ -32,10 +32,12 @@ export class AgentInspectorContentsContainerComponent implements OnInit, OnDestr
this.coverRangeElements$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe(
map((urlService: NewUrlStateNotificationService) => urlService.isRealTimeMode())
);
this.guideMessage$ = this.translateService.get('INSPECTOR.CHART_INTERACTION_GUIDE_MESSAGE');
this.guideMessage$ = this.translateService.get('COMMON.CHART_INTERACTION_GUIDE_MESSAGE');
merge(
of(this.webAppSettingDataService.getChartLayoutOption()),
this.messageQueueService.receiveMessage(this.unsubscribe, MESSAGE_TO.INSPECTOR_CHART_SET_LAYOUT)
this.messageQueueService.receiveMessage(this.unsubscribe, MESSAGE_TO.SET_CHART_LAYOUT)
).pipe(
takeUntil(this.unsubscribe)
).subscribe((chartCountPerRow: number) => {
this.renderer.setStyle(this.chartGroupWrapper.nativeElement, 'grid-template-columns', this.getGridLayout(chartCountPerRow));
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Component, OnInit, OnDestroy, HostBinding, ViewChild, ElementRef, Renderer2 } from '@angular/core';
import { Observable, of, Subject, merge } from 'rxjs';
import { map } from 'rxjs/operators';
import { map, takeUntil } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';

import { WebAppSettingDataService, MessageQueueService, MESSAGE_TO, NewUrlStateNotificationService } from 'app/shared/services';
import { ChartType } from 'app/core/components/inspector-chart/inspector-chart-container-factory';
import { TranslateService } from '@ngx-translate/core';

@Component({
selector: 'pp-application-inspector-contents-container',
Expand Down Expand Up @@ -35,11 +35,13 @@ export class ApplicationInspectorContentsContainerComponent implements OnInit, O
this.coverRangeElements$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe(
map((urlService: NewUrlStateNotificationService) => urlService.isRealTimeMode())
);
this.guideMessage$ = this.translateService.get('INSPECTOR.CHART_INTERACTION_GUIDE_MESSAGE');
this.guideMessage$ = this.translateService.get('COMMON.CHART_INTERACTION_GUIDE_MESSAGE');

merge(
of(this.webAppSettingDataService.getChartLayoutOption()),
this.messageQueueService.receiveMessage(this.unsubscribe, MESSAGE_TO.INSPECTOR_CHART_SET_LAYOUT)
this.messageQueueService.receiveMessage(this.unsubscribe, MESSAGE_TO.SET_CHART_LAYOUT)
).pipe(
takeUntil(this.unsubscribe)
).subscribe((chartCountPerRow: number) => {
this.renderer.setStyle(this.chartGroupWrapper.nativeElement, 'grid-template-columns', this.getGridLayout(chartCountPerRow));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ export class ChartLayoutOptionContainerComponent implements OnInit {
onClickOption(chartNumPerRow: number): void {
this.webAppSettingDataService.setChartLayoutOption(chartNumPerRow);
this.messageQueueService.sendMessage({
to: MESSAGE_TO.INSPECTOR_CHART_SET_LAYOUT,
to: MESSAGE_TO.SET_CHART_LAYOUT,
param: chartNumPerRow
});
// TODO: 현재의 구조로는 어느한쪽(inspector 또는 metric)의 레이아웃 설정이 다른쪽에 영향을 주는데 이걸 어떻게 풀어내야할까.
this.analyticsService.trackEvent(TRACKED_EVENT_LIST.CLICK_INSPECTOR_CHART_LAYOUT_OPTION, `Chart Number Per Row: ${chartNumPerRow}`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
:host {
display: block;
height: 100%;
}

.l-empty-text {
font-size: 14px;
padding: 7px 13px;
}

.l-container-wrapper {
display: flex;
height: 48px;
align-items: center;
width: 100%;
padding: 10px 4px 8px 11px;
}

.l-wrapper {
width: 205px;
height: 100%;
color: var(--text-primary-lightest);
position: relative;
margin-right: 7px;
}

.l-wrapper input {
width: 100%;
height: 100%;
border: 1px solid var(--form-input-border);
background-color: var(--background-default);
padding: 0 28px 0 10px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.l-wrapper input:focus {
border: 1px solid var(--primary);
box-shadow: 0 0 1px 1px var(--primary);
outline: none;
}
.l-wrapper button {
position: absolute;
top: 50%;
right: 8px;
transform: translateY(-50%);
}

::placeholder {
font-size: 12px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<div class="l-container-wrapper">
<div class="l-wrapper">
<input type="text" placeholder="{{inputPlaceholder$ | async}}" class="l-search-input"
ppSearchInput
[searchMinLength]="SEARCH_MIN_LENGTH"
[useEnter]="searchUseEnter"
(outCancel)="onCancel()"
(outSearch)="onSearch($event)">
<button class="fas fa-search"></button>
</div>
</div>
<ng-container *ngIf="filteredHostList && !isEmpty; else emptyText">
<pp-host-group-and-host-list
[hostList]="filteredHostList"
[selectedHostGroup]="selectedHostGroup"
[selectedHost]="selectedHost"
(outSelectHost)="onSelectHost($event)">
</pp-host-group-and-host-list>
</ng-container>
<ng-template #emptyText>
<p class="l-empty-text">{{emptyText$ | async}}</p>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Component, OnInit, OnDestroy, ComponentFactoryResolver, Injector } from '@angular/core';
import { Subject, Observable, EMPTY } from 'rxjs';
import { tap, filter, switchMap, catchError, map } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';

import {
NewUrlStateNotificationService,
UrlRouteManagerService,
AnalyticsService,
DynamicPopupService,
TRACKED_EVENT_LIST,
TranslateReplaceService,
} from 'app/shared/services';
import { ServerErrorPopupContainerComponent } from 'app/core/components/server-error-popup/server-error-popup-container.component';
import { UrlPath, UrlPathId } from 'app/shared/models';
import { isEmpty } from 'app/core/utils/util';
import { HostGroupAndHostListDataService } from './host-group-and-host-list-data.service';

@Component({
selector: 'pp-host-group-and-host-list-container',
templateUrl: './host-group-and-host-list-container.component.html',
styleUrls: ['./host-group-and-host-list-container.component.css'],
})
export class HostGroupAndHostListContainerComponent implements OnInit, OnDestroy {
private unsubscribe = new Subject<void>();
private hostList: string[];
private _query = '';

selectedHostGroup: string;
selectedHost: string;
filteredHostList: string[];
isEmpty: boolean;
emptyText$: Observable<string>;
inputPlaceholder$: Observable<string>;
searchUseEnter = false;
SEARCH_MIN_LENGTH = 2;

constructor(
private newUrlStateNotificationService: NewUrlStateNotificationService,
private urlRouteManagerService: UrlRouteManagerService,
private dynamicPopupService: DynamicPopupService,
private analyticsService: AnalyticsService,
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
private translateService: TranslateService,
private hostGroupAndHostListDataService: HostGroupAndHostListDataService,
private translateReplaceService: TranslateReplaceService,
) {}

// TODO: Add search input box
ngOnInit() {
this.initI18nText();
this.newUrlStateNotificationService.onUrlStateChange$.pipe(
filter((urlService: NewUrlStateNotificationService) => urlService.hasValue(UrlPathId.HOST_GROUP) && urlService.isValueChanged(UrlPathId.HOST_GROUP)),
map((urlService: NewUrlStateNotificationService) => urlService.getPathValue(UrlPathId.HOST_GROUP)),
tap((hostGroup: string) => this.selectedHostGroup = hostGroup),
switchMap((hostGroup: string) => this.hostGroupAndHostListDataService.getHostList(hostGroup).pipe(
catchError((error: IServerErrorFormat) => {
this.dynamicPopupService.openPopup({
data: {
title: 'Server Error',
contents: error
},
component: ServerErrorPopupContainerComponent,
}, {
resolver: this.componentFactoryResolver,
injector: this.injector
});
return EMPTY;
})
)),
).subscribe((hostList: string[]) => {
this.hostList = hostList;
this.filteredHostList = this.filterHostList(hostList);
this.isEmpty = isEmpty(this.filteredHostList);

const selectedHost = this.newUrlStateNotificationService.hasValue(UrlPathId.HOST) ? this.newUrlStateNotificationService.getPathValue(UrlPathId.HOST)
: hostList[0];

this.onSelectHost(selectedHost);
});
}

ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}

private initI18nText(): void {
this.emptyText$ = this.translateService.get('COMMON.EMPTY_ON_SEARCH');
this.inputPlaceholder$ = this.translateService.get('COMMON.MIN_LENGTH').pipe(
map((text: string) => this.translateReplaceService.replace(text, this.SEARCH_MIN_LENGTH))
);
}

onSelectHost(host: string) {
const url = [
UrlPath.METRIC,
this.newUrlStateNotificationService.getPathValue(UrlPathId.HOST_GROUP),
this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(),
this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(),
host
];

this.selectedHost = host;
this.urlRouteManagerService.moveOnPage({url});
this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_HOST);
}

private filterHostList(hostList: string[]): string[] {
return this.query === '' ? hostList
: hostList.filter((host: string) => host.toLowerCase().includes(this.query.toLowerCase()));
}

private set query(query: string) {
this._query = query;
this.filteredHostList = this.filterHostList(this.hostList);
this.isEmpty = isEmpty(this.filteredHostList);
}

private get query(): string {
return this._query;
}

onSearch(query: string): void {
if (this.query === query) {
return;
}

this.query = query;
}

onCancel(): void {
this.query = '';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class HostGroupAndHostListDataService {
private url = 'systemMetric/hostGroup/host.pinpoint';

constructor(
private http: HttpClient,
) {}

getHostList(hostGroup: string): Observable<string[]> {
return this.http.get<string[]>(this.url, this.makeRequestOptionsArgs(hostGroup));
}

private makeRequestOptionsArgs(hostGroup: string): object {
return {
params: {
hostGroupName: hostGroup
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
:host {
display: block;
height: calc(100% - 48px);
font-size: 13px;
overflow-y: auto;
}

.l-host-list {
overflow-x: hidden;
height: 100%;
}

.l-depth-0 .l-name-wrapper {
padding: 8px 10px 8px 17px;
display: flex;
align-items: center;
}

.l-name {
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

.l-depth-1 .l-name-wrapper {
margin-left: 15px;
font-size: 90%;
max-width: calc(100% - 15px);
}

.l-name-wrapper {
display: block;
}

.l-name-wrapper i {
margin-right: 4px;
}

.l-name-wrapper:hover {
cursor: pointer;
background-color: var(--background-hover-default);
color: var(--primary) !important;
}

.l-name-wrapper.active {
color: var(--primary) !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<ul class="l-host-list l-depth-0">
<li>
<div [attr.title]="selectedHostGroup" class="l-name-wrapper" (click)="toggleMenu()">
<span class="l-name">
<i class="fas fa-server"></i> {{selectedHostGroup}}
</span>
<i class="fas fa-angle-right" [@rightDown]="getCollapsedState()"></i>
</div>
<ul class="l-depth-1" [@collapseSpread]="getCollapsedState()">
<li *ngFor="let host of hostList" (click)="onSelectHost(host)" [attr.title]="host">
<div class="l-name-wrapper" [class.active]="selectedHost === host">
<span class="l-name">
<i class="fas fa-hdd"></i> {{host}}
</span>
</div>
</li>
</ul>
</li>
</ul>
Loading

0 comments on commit fc9f4aa

Please sign in to comment.