diff --git a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.css b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.css
index f09a3ba168b9..470abcc3626f 100644
--- a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.css
+++ b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.css
@@ -1,6 +1,6 @@
:host {
display: block;
- height: calc(100% - 41px); /* 41px: title height */
+ height: calc(100% - 40px); /* 40px: title height */
}
.guide-text {
@@ -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%;
@@ -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%;
+}
diff --git a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.html b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.html
index 62ceb572c2d2..fb3c7a8cc6fd 100644
--- a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.html
+++ b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.html
@@ -10,6 +10,14 @@
+
+
Sort by:
+
+
+ {{sortOption.display}}
+
+
+
{{errorMessage ? errorMessage : emptyText$ | async}}
@@ -20,6 +28,7 @@
[serverKeyList]="filteredServerKeyList"
[serverList]="filteredServerList"
[agentId]="agentId"
+ [selectedSortOptionKey]="selectedSortOptionKey"
(outSelectAgent)="onSelectAgent($event)">
diff --git a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.ts b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.ts
index 70dba88b71f1..a88361774ab1 100644
--- a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.ts
+++ b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-container.component.ts
@@ -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',
@@ -28,10 +33,12 @@ import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/co
export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
private unsubscribe = new Subject();
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;
@@ -40,6 +47,12 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
inputPlaceholder$: Observable;
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,
@@ -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(
@@ -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(res, 'errorCode', 'errorMessage')) {
this.errorMessage = res.errorMessage;
@@ -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;
@@ -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);
});
}
@@ -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) {
@@ -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);
+ }
}
diff --git a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-data.service.ts b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-data.service.ts
index 6895e10a15e5..aaac26c4ae34 100644
--- a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-data.service.ts
+++ b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list-data.service.ts
@@ -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 {
+ return this.http.get(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
+ }
};
}
}
diff --git a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.css b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.css
index f6049116b904..11ba02e218f8 100644
--- a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.css
+++ b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.css
@@ -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;
}
diff --git a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.html b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.html
index ba41f0949864..31e3469198f8 100644
--- a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.html
+++ b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.html
@@ -1,16 +1,16 @@
- -
-
+
-
+
- {{serverName}}
+ {{server.groupName}}
-
+
-
- -
+
+ -
- {{agent.agentId}}
+ {{getAgentLabel(agent)}}
diff --git a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.ts b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.ts
index e9f9fc2252af..803c69b165d5 100644
--- a/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.ts
+++ b/web/src/main/angular/src/app/core/components/server-and-agent-list/server-and-agent-list.component.ts
@@ -1,5 +1,7 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { trigger, state, style, animate, transition } from '@angular/animations';
+
+import { SortOption } from './server-and-agent-list-container.component';
@Component({
selector: 'pp-server-and-agent-list',
templateUrl: './server-and-agent-list.component.html',
@@ -46,8 +48,9 @@ export class ServerAndAgentListComponent implements OnInit {
return this._serverKeyList;
}
- @Input() serverList: {[key: string]: IServerAndAgentData[]};
+ @Input() serverList: IServerAndAgentDataV2[];
@Input() agentId: string;
+ @Input() selectedSortOptionKey: SortOption
@Output() outSelectAgent = new EventEmitter();
private _serverKeyList: string[];
@@ -91,4 +94,15 @@ export class ServerAndAgentListComponent implements OnInit {
return el.classList.contains('active');
});
}
+
+ getAgentLabel({agentId, agentName}: IServerAndAgentData): string {
+ switch (this.selectedSortOptionKey) {
+ case SortOption.ID:
+ case SortOption.RECENT:
+ default:
+ return agentName ? `${agentId} (${agentName})` : `${agentId} (N/A)`;
+ case SortOption.NAME:
+ return agentName ? agentName : `N/A (${agentId})`;
+ }
+ }
}
diff --git a/web/src/main/angular/src/app/shared/services/web-app-setting-data.service.ts b/web/src/main/angular/src/app/shared/services/web-app-setting-data.service.ts
index 2ea876deb848..7441914edf6c 100644
--- a/web/src/main/angular/src/app/shared/services/web-app-setting-data.service.ts
+++ b/web/src/main/angular/src/app/shared/services/web-app-setting-data.service.ts
@@ -35,6 +35,7 @@ export class WebAppSettingDataService {
LANGUAGE: 'language',
THEME: 'theme',
SIDE_NAV_BAR_SCALE: 'sideNavigationBarScale',
+ AGENT_LIST_SORT_OPTION: 'agentListSortOption'
};
private IMAGE_PATH = './assets/img/';
private IMAGE_EXT = '.png';
@@ -308,4 +309,10 @@ export class WebAppSettingDataService {
getUrlStatFieldNameList(): string[] {
return this.componentDefaultSettingDataService.getUrlStatFieldNameList();
}
+ setAgentListSortOption(sortOption: string): void {
+ this.localStorageService.set(WebAppSettingDataService.KEYS.AGENT_LIST_SORT_OPTION, sortOption);
+ }
+ getAgentListSortOption(): string {
+ return this.localStorageService.get(WebAppSettingDataService.KEYS.AGENT_LIST_SORT_OPTION);
+ }
}