Skip to content

Commit

Permalink
[pinpoint-apm#9518] Add sorting UI for agent-list in inspector page
Browse files Browse the repository at this point in the history
  • Loading branch information
binDongKim committed Dec 16, 2022
1 parent 55237a2 commit 7407133
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
:host {
display: block;
height: calc(100% - 41px); /* 41px: title height */
height: calc(100% - 40px); /* 40px: title height */
}

.guide-text {
Expand All @@ -21,6 +21,55 @@
gap: 7px;
}

.sort-option-wrapper {
display: flex;
align-items: center;;
width: 100%;
height: 32px;
padding:0px 15px 8px;
border-bottom: 1px solid var(--border-primary);
gap: 2px;
}

.sort-label {
font-size: 12px;
color: var(--text-secondary);
}

.sort-option-list-wrapper {
flex: 1;
display: flex;
/* gap: 5px; */
}

.sort-option {
font-size: 12px;
color: var(--text-secondary);
padding: 3px 6px;
border-radius: 3px;
cursor: pointer;
}

.sort-option:hover {
/* background: var(--background-hover-default); */
/* font-weight: bold; */
color: var(--primary);
}

.sort-option.active {
font-weight: bold;
color: var(--primary);
}

.display-option-wrapper {
display: flex;
/* flex-direction: column; */
gap: 5px;
width: 100%;
padding:0px 17px 8px;
border-bottom: 1px solid var(--border-primary);
}

.search-input-wrapper {
flex: 1;
height: 100%;
Expand Down Expand Up @@ -56,7 +105,37 @@
font-size: 12px;
}

.fa-question-circle {
.fas {
color: var(--icon-default);
font-size: 18px;
}

.display-option-select-wrapper,
.sort-option-select-wrapper {
display: flex;
align-items: center;
gap: 3px;
padding: 2px 0;
}

.option-label {
font-size: 12px;
color: var(--text-secondary);
}

.option-label .fas {
font-size: 12px;
color: var(--text-secondary);
}

.option-select {
cursor: pointer;
/* padding: 6px 0; */
outline: 0;
font-size: 11px;
font-weight: bold;
color: var(--text-primary-lightest);
appearance: none;
display: inline-block;
height: 100%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
</div>
<button class="fas fa-question-circle" (click)="onShowHelp($event)"></button>
</div>
<div class="sort-option-wrapper">
<div class="sort-label">Sort by: </div>
<div class="sort-option-list-wrapper">
<ng-container *ngFor="let sortOption of sortOptionList">
<div class="sort-option" [class.active]="isActiveSortOption(sortOption.key)" (click)="onSelectSortOption(sortOption.key)">{{sortOption.display}}</div>
</ng-container>
</div>
</div>
<ng-container *ngIf="errorMessage || isEmpty; then guideText; else contents"></ng-container>
<ng-template #guideText>
<p class="guide-text" [class.error-text]="errorMessage">{{errorMessage ? errorMessage : emptyText$ | async}}</p>
Expand All @@ -20,6 +28,7 @@
[serverKeyList]="filteredServerKeyList"
[serverList]="filteredServerList"
[agentId]="agentId"
[selectedSortOptionKey]="selectedSortOptionKey"
(outSelectAgent)="onSelectAgent($event)">
</pp-server-and-agent-list>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ import { ServerAndAgentListDataService } from './server-and-agent-list-data.serv
import { isEmpty, isThatType } from 'app/core/utils/util';
import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component';

export const enum SortOption {
ID = 'id',
NAME = 'name',
RECENT = 'recent'
}
@Component({
selector: 'pp-server-and-agent-list-container',
templateUrl: './server-and-agent-list-container.component.html',
Expand All @@ -28,10 +33,12 @@ import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/co
export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
private unsubscribe = new Subject<void>();
private _query = '';
private previousParams: {[key: string]: any};
private cachedData: {[key in SortOption]: IServerAndAgentDataV2[]};

agentId: string;
serverList: {[key: string]: IServerAndAgentData[]};
filteredServerList: {[key: string]: IServerAndAgentData[]} = {};
serverList: IServerAndAgentDataV2[];
filteredServerList: IServerAndAgentDataV2[] = [];
filteredServerKeyList: string[] = [];
funcImagePath: Function;
isEmpty: boolean;
Expand All @@ -40,6 +47,12 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
inputPlaceholder$: Observable<string>;
searchUseEnter = false;
SEARCH_MIN_LENGTH = 2;
sortOptionList = [
{display: 'ID', key: SortOption.ID},
{display: 'Name', key: SortOption.NAME},
{display: 'Recent', key: SortOption.RECENT}
];
selectedSortOptionKey: SortOption;

constructor(
private newUrlStateNotificationService: NewUrlStateNotificationService,
Expand All @@ -58,6 +71,7 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
ngOnInit() {
this.initI18nText();
this.funcImagePath = this.webAppSettingDataService.getImagePathMakeFunc();
this.selectedSortOptionKey = this.webAppSettingDataService.getAgentListSortOption() as SortOption || SortOption.ID;

merge(
this.newUrlStateNotificationService.onUrlStateChange$.pipe(
Expand Down Expand Up @@ -92,11 +106,23 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
)
).pipe(
concatMap((range: number[]) => {
const appName = (this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION) as IApplication).getApplicationName();
const app = (this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION) as IApplication).getApplicationName();
const requestStartAt = Date.now();

return this.serverAndAgentListDataService.getData(appName, range).pipe(
filter((res: {[key: string]: IServerAndAgentData[]} | IServerErrorShortFormat) => {
return this.serverAndAgentListDataService.getData(app, range, this.selectedSortOptionKey).pipe(
tap(() => {
this.previousParams = {app, range};
}),
tap(() => {
const urlService = this.newUrlStateNotificationService;
const isAppChanged = urlService.isValueChanged(UrlPathId.APPLICATION);
const isPeriodChanged = urlService.isValueChanged(UrlPathId.PERIOD) || urlService.isValueChanged(UrlPathId.END_TIME);

if (isAppChanged || isPeriodChanged) {
this.cachedData = {} as {[key in SortOption]: IServerAndAgentDataV2[]};
}
}),
filter((res: IServerAndAgentDataV2[] | IServerErrorShortFormat) => {
// TODO: 민우님께 에러구분 여쭤보기. 401이면 AuthService 활용한다? 근데이럼 IS_ACCESS_DENYED 출처 불분명같은 문제가 있지않을까..
if (isThatType<IServerErrorShortFormat>(res, 'errorCode', 'errorMessage')) {
this.errorMessage = res.errorMessage;
Expand All @@ -108,10 +134,10 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
return true;
}
}),
filter((res: {[key: string]: IServerAndAgentData[]}) => {
filter((res: IServerAndAgentDataV2[]) => {
if (this.agentId) {
const filteredList = this.filterServerList(res, this.agentId, ({agentId}: IServerAndAgentData) => this.agentId.toLowerCase() === agentId.toLowerCase());
const isAgentIdValid = Object.keys(filteredList).length !== 0;
const filteredList = this.filterServerList(res, this.agentId, ({agentId}: IAgentDataV2) => this.agentId.toLowerCase() === agentId.toLowerCase());
const isAgentIdValid = !isEmpty(filteredList);

if (isAgentIdValid) {
return true;
Expand Down Expand Up @@ -163,10 +189,10 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
})
);
}),
).subscribe((data: {[key: string]: IServerAndAgentData[]}) => {
this.serverList = data;
this.filteredServerList = this.filterServerList(data, this.query);
this.filteredServerKeyList = Object.keys(this.filteredServerList).sort();
).subscribe((data: IServerAndAgentDataV2[]) => {
this.serverList = this.cachedData[this.selectedSortOptionKey] = data;
this.filteredServerList = this.filterServerList(this.serverList, this.query);
this.filteredServerKeyList = this.filteredServerList.map(({groupName}: IServerAndAgentDataV2) => groupName).sort();
this.isEmpty = isEmpty(this.filteredServerList);
});
}
Expand Down Expand Up @@ -203,17 +229,17 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
this.analyticsService.trackEvent(TRACKED_EVENT_LIST.SELECT_AGENT_ON_THE_LIST);
}

private filterServerList(serverList: {[key: string]: IServerAndAgentData[]}, query: string, predi?: (data: IServerAndAgentData) => boolean): {[key: string]: IServerAndAgentData[]} {
const filterCallback = predi ? predi : ({agentId, agentName}: IServerAndAgentData) => {
private filterServerList(serverList: IServerAndAgentDataV2[], query: string, predi?: (data: IAgentDataV2) => boolean): IServerAndAgentDataV2[] {
const filterCallback = predi ? predi : ({agentId, agentName}: IAgentDataV2) => {
return agentId.toLowerCase().includes(query.toLowerCase()) || (agentName && agentName.toLowerCase().includes(query.toLowerCase()));
};

return query === '' ? serverList
: Object.entries(serverList).reduce((acc: {[key: string]: IServerAndAgentData[]}, [key, serverAndAgentDataList]: [string, IServerAndAgentData[]]) => {
const matchedList = serverAndAgentDataList.filter(filterCallback);
return isEmpty(matchedList) ? acc : {...acc, [key]: matchedList};
}, {} as {[key: string]: IServerAndAgentData[]});
: serverList.reduce((acc: IServerAndAgentDataV2[], {groupName, instancesList}: IServerAndAgentDataV2) => {
const matchedList = instancesList.filter(filterCallback);

return isEmpty(matchedList) ? acc : [...acc, {groupName, instancesList: matchedList}]
}, []);
}

private set query(query: string) {
Expand Down Expand Up @@ -255,4 +281,46 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
injector: this.injector
});
}

isActiveSortOption(optionKey: SortOption): boolean {
return optionKey === this.selectedSortOptionKey;
}

onSelectSortOption(optionKey: SortOption): void {
if (optionKey === this.selectedSortOptionKey) {
return;
}

const {app, range} = this.previousParams;

if (this.cachedData[optionKey]) {
this.filteredServerList = this.filterServerList(this.cachedData[optionKey], this.query);
this.filteredServerKeyList = this.filteredServerList.map(({groupName}: IServerAndAgentDataV2) => groupName).sort();
} else {
this.serverAndAgentListDataService.getData(app, range, optionKey).pipe(
catchError((error: IServerError) => {
this.dynamicPopupService.openPopup({
data: {
title: 'Error',
contents: error
},
component: ServerErrorPopupContainerComponent
}, {
resolver: this.componentFactoryResolver,
injector: this.injector
});

return EMPTY;
})
).subscribe((data: IServerAndAgentDataV2[]) => {
this.serverList = this.cachedData[optionKey] = data;
this.filteredServerList = this.filterServerList(this.serverList, this.query);
this.filteredServerKeyList = this.filteredServerList.map(({groupName}: IServerAndAgentDataV2) => groupName).sort();
});
}

this.isEmpty = isEmpty(this.filteredServerList);
this.selectedSortOptionKey = optionKey;
this.webAppSettingDataService.setAgentListSortOption(optionKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,36 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

import { SortOption } from './server-and-agent-list-container.component';


const enum SortOptionParamKey {
ID = 'AGENT_ID_ASC',
NAME = 'AGENT_NAME_ASC',
RECENT = 'RECENT'
}
@Injectable()
export class ServerAndAgentListDataService {
private url = 'getAgentList.pinpoint';
private url = 'agents/search-application.pinpoint';

constructor(
private http: HttpClient,
) {}

getData(applicationName: string, range: number[]): Observable<{[key: string]: IServerAndAgentData[]}> {
return this.http.get<{[key: string]: IServerAndAgentData[]}>(this.url, this.makeRequestOptionsArgs(applicationName, range));
getData(applicationName: string, range: number[], sortOption: SortOption): Observable<IServerAndAgentDataV2[]> {
return this.http.get<IServerAndAgentDataV2[]>(this.url, this.makeRequestOptionsArgs(applicationName, range, sortOption));
}

private makeRequestOptionsArgs(application: string, [from, to]: number[]): object {
private makeRequestOptionsArgs(application: string, [from, to]: number[], sortOption: SortOption): object {
return {
params: { application, from, to }
params: {
application,
from,
to,
sortBy: sortOption === SortOption.ID ? SortOptionParamKey.ID
: sortOption === SortOption.NAME ? SortOptionParamKey.NAME
: SortOptionParamKey.RECENT
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
:host {
display: block;
/* height: 100%; */
height: calc(100% - 48px);
height: calc(100% - 48px - 32px); /* search area(48px) + sort area(32px) */
overflow-y: auto;
font-size: 13px;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<ul class="l-servers-list l-depth-0">
<li *ngFor="let serverName of serverKeyList">
<div [attr.title]="serverName" class="l-server-name-wrapper l-name-wrapper" [class.active]="isActive(serverDiv)" (click)="toggleMenu(serverName)" #serverDiv>
<li *ngFor="let server of serverList">
<div [attr.title]="server.groupName" class="l-server-name-wrapper l-name-wrapper" [class.active]="isActive(serverDiv)" (click)="toggleMenu(server.groupName)" #serverDiv>
<span class="l-name">
<i class="fas fa-server"></i> {{serverName}}
<i class="fas fa-server"></i> {{server.groupName}}
</span>
<i class="fas fa-angle-right" [@rightDown]="getCollapsedState(serverName)"></i>
<i class="fas fa-angle-right" [@rightDown]="getCollapsedState(server.groupName)"></i>
</div>
<ul class="l-agent-list l-depth-1" [@collapseSpread]="getCollapsedState(serverName)">
<li class="l-agent" *ngFor="let agent of serverList[serverName]" (click)="onSelectAgent(agent.agentId)" [attr.title]="agent.agentName || agent.agentId">
<ul class="l-agent-list l-depth-1" [@collapseSpread]="getCollapsedState(server.groupName)">
<li class="l-agent" *ngFor="let agent of server.instancesList" (click)="onSelectAgent(agent.agentId)" [attr.title]="agent.agentName || agent.agentId">
<div class="l-agent-name-wrapper l-name-wrapper" [class.active]="agent.agentId === agentId">
<span class="l-name">
<i class="fas fa-hdd"></i> {{agent.agentId}}
<i class="fas fa-hdd"></i> {{getAgentLabel(agent)}}
</span>
<span class="l-icon-alert" *ngIf="agent.status.state.code !== 100"><img [src]="getIconPath(agent.status.state.code)"></span>
</div>
Expand Down
Loading

0 comments on commit 7407133

Please # to comment.