From e62784b893a55a6f1e14323c5be9c802ca446fd1 Mon Sep 17 00:00:00 2001 From: QooQooDass Date: Mon, 16 Jan 2023 17:16:08 +0900 Subject: [PATCH] [#9518] Add sorting UI for agent-list in inspector page --- .../agent-info-container.component.ts | 4 +- ...ion-agent-statistic-container.component.ts | 6 +- ...t-data-source-chart-container.component.ts | 4 +- ...n-data-source-chart-container.component.ts | 4 +- .../inspector-chart-container.component.ts | 4 +- .../metric/metric-container.component.ts | 2 +- ...ver-and-agent-list-container.component.css | 50 ++++- ...er-and-agent-list-container.component.html | 27 ++- ...rver-and-agent-list-container.component.ts | 209 ++++++++++++------ .../server-and-agent-list-data.service.ts | 25 ++- .../server-and-agent-list.component.css | 3 +- .../server-and-agent-list.component.html | 14 +- .../server-and-agent-list.component.ts | 17 +- ...eline-command-group-container.component.ts | 4 +- ...-inspector-timeline-container.component.ts | 4 +- ...-inspector-timeline-container.component.ts | 4 +- ...url-statistic-chart-container.component.ts | 2 +- .../inspector-page/inspector-page.service.ts | 31 ++- .../services/web-app-setting-data.service.ts | 7 + 19 files changed, 310 insertions(+), 111 deletions(-) diff --git a/web/src/main/angular/src/app/core/components/agent-info/agent-info-container.component.ts b/web/src/main/angular/src/app/core/components/agent-info/agent-info-container.component.ts index f6e87b25ef07..6104814a9f98 100644 --- a/web/src/main/angular/src/app/core/components/agent-info/agent-info-container.component.ts +++ b/web/src/main/angular/src/app/core/components/agent-info/agent-info-container.component.ts @@ -46,7 +46,8 @@ export class AgentInfoContainerComponent implements OnInit, OnDestroy { this.connectStore(); this.inspectorPageService.sourceForAgentInfo$.pipe( takeUntil(this.unsubscribe), - tap(() => this.showLoading = true), + filter(Boolean), + // tap(() => this.showLoading = true), switchMap(({agentId, selectedTime}: ISourceForAgentInfo) => { this.lastRequestParam = [agentId, selectedTime]; return this.agentInfoDataService.getData(agentId, selectedTime).pipe( @@ -70,6 +71,7 @@ export class AgentInfoContainerComponent implements OnInit, OnDestroy { ngOnDestroy() { this.unsubscribe.next(); this.unsubscribe.complete(); + this.inspectorPageService.reset('agentInfo'); } private connectStore(): void { diff --git a/web/src/main/angular/src/app/core/components/configuration-agent-statistic/configuration-agent-statistic-container.component.ts b/web/src/main/angular/src/app/core/components/configuration-agent-statistic/configuration-agent-statistic-container.component.ts index 910263d06300..79274f836aac 100644 --- a/web/src/main/angular/src/app/core/components/configuration-agent-statistic/configuration-agent-statistic-container.component.ts +++ b/web/src/main/angular/src/app/core/components/configuration-agent-statistic/configuration-agent-statistic-container.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy, ComponentFactoryResolver, Injector } from import { TranslateService } from '@ngx-translate/core'; import { Subject, forkJoin, Observable } from 'rxjs'; import * as moment from 'moment-timezone'; -import { map, tap } from 'rxjs/operators'; +import { map, takeUntil, tap } from 'rxjs/operators'; import { StoreHelperService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService } from 'app/shared/services'; import { AgentStatisticDataService } from './agent-statistic-data.service'; @@ -78,7 +78,9 @@ export class ConfigurationAgentStatisticContainerComponent implements OnInit, On } this.showProcessing(); - this.agentStatisticDataService.getData().subscribe((agentList: IAgentList) => { + this.agentStatisticDataService.getData().pipe( + takeUntil(this.unsubscribe) + ).subscribe((agentList: IAgentList) => { this.storeHelperService.dispatch(new Actions.UpdateAdminAgentList(agentList)); this.hideProcessing(); }, (error: IServerError) => { diff --git a/web/src/main/angular/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.ts b/web/src/main/angular/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.ts index 59e69dd60a92..9a16b10c205e 100644 --- a/web/src/main/angular/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.ts +++ b/web/src/main/angular/src/app/core/components/inspector-chart/agent-data-source-chart-container.component.ts @@ -73,6 +73,7 @@ export class AgentDataSourceChartContainerComponent implements OnInit, OnDestroy ngOnDestroy() { this.unsubscribe.next(); this.unsubscribe.complete(); + this.inspectorPageService.reset('chart'); } private initI18nText(): void { @@ -151,7 +152,8 @@ export class AgentDataSourceChartContainerComponent implements OnInit, OnDestroy private initChartData(): void { this.inspectorPageService.sourceForChart$.pipe( takeUntil(this.unsubscribe), - tap(() => this.activeLayer = Layer.LOADING), + filter(Boolean), + // tap(() => this.activeLayer = Layer.LOADING), tap(({range}: ISourceForChart) => this.previousRange = range), switchMap(({range}: ISourceForChart) => { return this.inspectorChartDataService.getData(range).pipe( diff --git a/web/src/main/angular/src/app/core/components/inspector-chart/application-data-source-chart-container.component.ts b/web/src/main/angular/src/app/core/components/inspector-chart/application-data-source-chart-container.component.ts index 72a24a70d745..30d6e45d20af 100644 --- a/web/src/main/angular/src/app/core/components/inspector-chart/application-data-source-chart-container.component.ts +++ b/web/src/main/angular/src/app/core/components/inspector-chart/application-data-source-chart-container.component.ts @@ -62,6 +62,7 @@ export class ApplicationDataSourceChartContainerComponent implements OnInit, OnD ngOnDestroy() { this.unsubscribe.next(); this.unsubscribe.complete(); + this.inspectorPageService.reset('chart'); } private initI18nText(): void { @@ -100,7 +101,8 @@ export class ApplicationDataSourceChartContainerComponent implements OnInit, OnD private initChartData(): void { this.inspectorPageService.sourceForChart$.pipe( takeUntil(this.unsubscribe), - tap(() => this.activeLayer = Layer.LOADING), + filter(Boolean), + // tap(() => this.activeLayer = Layer.LOADING), tap(({range}: ISourceForChart) => this.previousRange = range), switchMap(({range}: ISourceForChart) => { return this.inspectorChartDataService.getData(range).pipe( diff --git a/web/src/main/angular/src/app/core/components/inspector-chart/inspector-chart-container.component.ts b/web/src/main/angular/src/app/core/components/inspector-chart/inspector-chart-container.component.ts index 65dca9ee9382..69d1f60942b2 100644 --- a/web/src/main/angular/src/app/core/components/inspector-chart/inspector-chart-container.component.ts +++ b/web/src/main/angular/src/app/core/components/inspector-chart/inspector-chart-container.component.ts @@ -88,6 +88,7 @@ export class InspectorChartContainerComponent implements OnInit, OnDestroy { ngOnDestroy() { this.unsubscribe.next(); this.unsubscribe.complete(); + this.inspectorPageService.reset('chart'); } private initI18nText(): void { @@ -136,7 +137,8 @@ export class InspectorChartContainerComponent implements OnInit, OnDestroy { private initChartData(): void { this.inspectorPageService.sourceForChart$.pipe( takeUntil(this.unsubscribe), - tap(() => this.activeLayer = Layer.LOADING), + filter(Boolean), + // tap(() => this.activeLayer = Layer.LOADING), tap(({range}: ISourceForChart) => this.previousRange = range), switchMap(({range}: ISourceForChart) => { return this.chartContainer.getData(range).pipe( diff --git a/web/src/main/angular/src/app/core/components/metric/metric-container.component.ts b/web/src/main/angular/src/app/core/components/metric/metric-container.component.ts index 0c37e58ac92d..146038df04d0 100644 --- a/web/src/main/angular/src/app/core/components/metric/metric-container.component.ts +++ b/web/src/main/angular/src/app/core/components/metric/metric-container.component.ts @@ -70,7 +70,7 @@ export class MetricContainerComponent implements OnInit, OnDestroy { filter((urlService: NewUrlStateNotificationService) => { return !this.chartConfig || (urlService.isValueChanged(UrlPathId.PERIOD) || urlService.isValueChanged(UrlPathId.END_TIME)); }), - tap(() => this.activeLayer = Layer.LOADING), + // tap(() => this.activeLayer = Layer.LOADING), switchMap((urlService: NewUrlStateNotificationService) => { const hostGroupName = urlService.getPathValue(UrlPathId.HOST_GROUP); const hostName = urlService.getPathValue(UrlPathId.HOST); 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..57401af7535b 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,52 @@ gap: 7px; } +.list-wrapper { + width: 100%; + position: relative; + height: calc(100% - 48px - 32px); /* search area(48px) + sort area(32px) */ +} + +.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); +} + .search-input-wrapper { flex: 1; height: 100%; @@ -56,7 +102,7 @@ font-size: 12px; } -.fa-question-circle { +.fas { color: var(--icon-default); font-size: 18px; } 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..1ef6c2b89803 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,16 +10,29 @@ +
+
Sort by:
+
+ +
{{sortOption.display}}
+
+
+

{{errorMessage ? errorMessage : emptyText$ | async}}

- - +
+ + + + +
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 7baa19fffe48..93e381cc119a 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: {app: string, range: number[]}; + private cachedData = {} as {[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,15 @@ 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; + + useDisable: boolean; + showLoading: boolean; constructor( private newUrlStateNotificationService: NewUrlStateNotificationService, @@ -58,17 +74,25 @@ 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( takeUntil(this.unsubscribe), tap((urlService: NewUrlStateNotificationService) => { this.agentId = urlService.getPathValue(UrlPathId.AGENT_ID); + const isAppChanged = urlService.isValueChanged(UrlPathId.APPLICATION); + const isPeriodChanged = urlService.isValueChanged(UrlPathId.PERIOD) || urlService.isValueChanged(UrlPathId.END_TIME); + const isRealTimeMode = urlService.isRealTimeMode(); + + // if (isAppChanged || isPeriodChanged || isRealTimeMode) { + if (isAppChanged) { + this.filteredServerList = []; + this.previousParams = null; + this.showLoading = true; + } + }), - // TODO: Check valid filter for url - // filter((urlService: NewUrlStateNotificationService) => { - // return urlService.isValueChanged(UrlPathId.APPLICATION) || urlService.isValueChanged(UrlPathId.PERIOD) || urlService.isValueChanged(UrlPathId.END_TIME); - // }), map((urlService: NewUrlStateNotificationService) => { if (urlService.isRealTimeMode()) { const to = urlService.getUrlServerTimeData(); @@ -89,51 +113,55 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy { ); }), pluck('range'), + filter(() => this.newUrlStateNotificationService.isRealTimeMode()), ) ).pipe( - filter(() => this.newUrlStateNotificationService.hasValue(UrlPathId.APPLICATION)), // prevent getting event after the component has been destroyed + filter(() => this.newUrlStateNotificationService.hasValue(UrlPathId.APPLICATION)), // prevent from getting event after the component has been destroyed concatMap((range: number[]) => { - const appName = (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) => { - // TODO: 민우님께 에러구분 여쭤보기. 401이면 AuthService 활용한다? 근데이럼 IS_ACCESS_DENYED 출처 불분명같은 문제가 있지않을까.. - if (isThatType(res, 'errorCode', 'errorMessage')) { - this.errorMessage = res.errorMessage; - this.messageQueueService.sendMessage({to: MESSAGE_TO.IS_ACCESS_DENYED, param: true}); - return false; - } else { - this.errorMessage = ''; - this.messageQueueService.sendMessage({to: MESSAGE_TO.IS_ACCESS_DENYED, param: false}); - return true; - } - }), - filter((res: {[key: string]: IServerAndAgentData[]}) => { - 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 urlService = this.newUrlStateNotificationService; + const isAppChanged = urlService.isValueChanged(UrlPathId.APPLICATION); + const isPeriodChanged = urlService.isValueChanged(UrlPathId.PERIOD) || urlService.isValueChanged(UrlPathId.END_TIME); + const isRealTimeMode = urlService.isRealTimeMode(); - if (isAgentIdValid) { - return true; - } else { - const url = this.newUrlStateNotificationService.isRealTimeMode() - ? [this.newUrlStateNotificationService.getStartPath(), UrlPathId.REAL_TIME, this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr()] - : [ - this.newUrlStateNotificationService.getStartPath(), - this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(), - this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(), - this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime() - ]; + const data = this.cachedData[this.selectedSortOptionKey]; - this.urlRouteManagerService.moveOnPage({url}); + const app = (urlService.getPathValue(UrlPathId.APPLICATION) as IApplication).getApplicationName(); + const requestStartAt = Date.now(); + return iif(() => isEmpty(this.filteredServerList) || (isAppChanged || isPeriodChanged || isRealTimeMode), + this.serverAndAgentListDataService.getData(app, range, this.selectedSortOptionKey).pipe( + tap(() => { + this.previousParams = {app, range}; + this.cachedData = {} as {[key in SortOption]: IServerAndAgentDataV2[]}; + }), + filter((res: IServerAndAgentDataV2[] | IServerErrorShortFormat) => { + if (isThatType(res, 'errorCode', 'errorMessage')) { + this.errorMessage = res.errorMessage; + this.messageQueueService.sendMessage({to: MESSAGE_TO.IS_ACCESS_DENYED, param: true}); return false; + } else { + this.errorMessage = ''; + this.messageQueueService.sendMessage({to: MESSAGE_TO.IS_ACCESS_DENYED, param: false}); + return true; } - } else { - return true; - } - }), + }), + catchError((error: IServerError) => { + this.dynamicPopupService.openPopup({ + data: { + title: 'Error', + contents: error + }, + component: ServerErrorPopupContainerComponent + }, { + resolver: this.componentFactoryResolver, + injector: this.injector + }); + + return EMPTY; + }) + ), + of(data) + ).pipe( tap(() => { const responseArriveAt = Date.now(); const deltaT = responseArriveAt - requestStartAt; @@ -148,27 +176,16 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy { } }); }), - catchError((error: IServerError) => { - this.dynamicPopupService.openPopup({ - data: { - title: 'Error', - contents: error - }, - component: ServerErrorPopupContainerComponent - }, { - resolver: this.componentFactoryResolver, - injector: this.injector - }); - - return EMPTY; - }) - ); - }), - ).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); + + this.useDisable = false; + this.showLoading = false; }); } @@ -201,20 +218,19 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy { ]; this.urlRouteManagerService.moveOnPage({url}); - 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) { @@ -256,4 +272,55 @@ 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 || this.showLoading) { + return; + } + + const {app, range} = this.previousParams; + + of(optionKey).pipe( + switchMap((optionKey: SortOption) => { + if (Boolean(this.cachedData[optionKey])) { + return of(this.cachedData[optionKey]); + } else { + this.useDisable = true; + this.showLoading = true; + + return this.serverAndAgentListDataService.getData(app, range, optionKey).pipe( + tap((data: IServerAndAgentDataV2[]) => { + this.useDisable = false; + this.showLoading = false; + this.serverList = this.cachedData[optionKey] = data; + }), + 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.filteredServerList = this.filterServerList(data, 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..cc2c1de05dc0 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,6 @@ :host { display: block; - /* height: 100%; */ - height: calc(100% - 48px); + height: 100%; 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..f9590912eb06 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,8 @@ 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 +49,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 +95,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/core/components/timeline-command-group/timeline-command-group-container.component.ts b/web/src/main/angular/src/app/core/components/timeline-command-group/timeline-command-group-container.component.ts index 99277449ed18..712d48698af8 100644 --- a/web/src/main/angular/src/app/core/components/timeline-command-group/timeline-command-group-container.component.ts +++ b/web/src/main/angular/src/app/core/components/timeline-command-group/timeline-command-group-container.component.ts @@ -28,6 +28,7 @@ export class TimelineCommandGroupContainerComponent implements OnInit, OnDestroy ngOnDestroy() { this.unsubscribe.next(); this.unsubscribe.complete(); + this.inspectorPageService.reset('timelineCommand'); } private connectStore(): void { @@ -37,7 +38,8 @@ export class TimelineCommandGroupContainerComponent implements OnInit, OnDestroy // this.storeHelperService.getInspectorTimelineSelectedTime(this.unsubscribe).pipe(filter((time: number) => time !== 0)) this.inspectorPageService.sourceForTimelineCommand$.pipe( takeUntil(this.unsubscribe), - pluck('selectedTime') + filter(Boolean), + pluck('selectedTime'), ) ).pipe( map(([dateFormat, timezone, pointingTime]: [string, string, number]) => { diff --git a/web/src/main/angular/src/app/core/components/timeline/agent-inspector-timeline-container.component.ts b/web/src/main/angular/src/app/core/components/timeline/agent-inspector-timeline-container.component.ts index f742c828a4de..01b165780ad7 100644 --- a/web/src/main/angular/src/app/core/components/timeline/agent-inspector-timeline-container.component.ts +++ b/web/src/main/angular/src/app/core/components/timeline/agent-inspector-timeline-container.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, OnDestroy, ViewChild, ComponentFactoryResolver, Injector } from '@angular/core'; import { Subject, Observable, EMPTY } from 'rxjs'; -import { map, switchMap, takeUntil, catchError } from 'rxjs/operators'; +import { map, switchMap, takeUntil, catchError, filter } from 'rxjs/operators'; import { StoreHelperService, NewUrlStateNotificationService, AnalyticsService, DynamicPopupService, TRACKED_EVENT_LIST, MessageQueueService, MESSAGE_TO } from 'app/shared/services'; import { ITimelineEventSegment, TimelineUIEvent } from './class'; @@ -71,6 +71,7 @@ export class AgentInspectorTimelineContainerComponent implements OnInit, OnDestr this.inspectorPageService.sourceForTimeline$.pipe( takeUntil(this.unsubscribe), + filter(Boolean), map(({timelineInfo, agentId}: ISourceForTimeline) => { const {range, selectionRange, selectedTime} = timelineInfo; @@ -107,6 +108,7 @@ export class AgentInspectorTimelineContainerComponent implements OnInit, OnDestr ngOnDestroy() { this.unsubscribe.next(); this.unsubscribe.complete(); + this.inspectorPageService.reset('timeline'); } private connectStore(): void { this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); diff --git a/web/src/main/angular/src/app/core/components/timeline/application-inspector-timeline-container.component.ts b/web/src/main/angular/src/app/core/components/timeline/application-inspector-timeline-container.component.ts index 5cddb865d307..0e8185c1c485 100644 --- a/web/src/main/angular/src/app/core/components/timeline/application-inspector-timeline-container.component.ts +++ b/web/src/main/angular/src/app/core/components/timeline/application-inspector-timeline-container.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { Subject, Observable } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { filter, takeUntil } from 'rxjs/operators'; import { StoreHelperService, AnalyticsService, TRACKED_EVENT_LIST, MessageQueueService, MESSAGE_TO } from 'app/shared/services'; import { ITimelineEventSegment, TimelineUIEvent } from './class'; @@ -61,6 +61,7 @@ export class ApplicationInspectorTimelineContainerComponent implements OnInit, O this.inspectorPageService.sourceForTimeline$.pipe( takeUntil(this.unsubscribe), + filter(Boolean), ).subscribe(({timelineInfo: {range, selectionRange, selectedTime}}: ISourceForTimeline) => { this.timelineStartTime = range[0]; this.timelineEndTime = range[1]; @@ -87,6 +88,7 @@ export class ApplicationInspectorTimelineContainerComponent implements OnInit, O ngOnDestroy() { this.unsubscribe.next(); this.unsubscribe.complete(); + this.inspectorPageService.reset('timeline'); } private connectStore(): void { this.timezone$ = this.storeHelperService.getTimezone(this.unsubscribe); diff --git a/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.ts b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.ts index 6d8d814660d4..8616254db0b9 100644 --- a/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.ts +++ b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.ts @@ -99,7 +99,7 @@ export class UrlStatisticChartContainerComponent implements OnInit, OnDestroy { const agentId = urlService.getPathValue(UrlPathId.AGENT_ID) || ''; const params = this.previousParams = {from, to, applicationName, agentId, uri}; - this.activeLayer = Layer.LOADING; + // this.activeLayer = Layer.LOADING; return this.urlStatisticChartDataService.getData(params).pipe( map(({timestamp, metricValueGroups}: IUrlStatChartData) => { this.cachedData[uri] = {timestamp, metricValues: metricValueGroups[0].metricValues}; diff --git a/web/src/main/angular/src/app/routes/inspector-page/inspector-page.service.ts b/web/src/main/angular/src/app/routes/inspector-page/inspector-page.service.ts index dc88eccd3504..18397d0633f5 100644 --- a/web/src/main/angular/src/app/routes/inspector-page/inspector-page.service.ts +++ b/web/src/main/angular/src/app/routes/inspector-page/inspector-page.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Observable, Subject, BehaviorSubject } from 'rxjs'; +import { Observable, Subject, ReplaySubject } from 'rxjs'; import { NewUrlStateNotificationService, @@ -30,10 +30,14 @@ export interface ISourceForTimelineCommand { @Injectable() export class InspectorPageService { - private sourceForTimeline = new Subject(); - private sourceForAgentInfo = new Subject(); - private sourceForChart = new Subject(); - private sourceForTimelineCommand = new Subject(); + // private sourceForTimeline = new Subject(); + // private sourceForAgentInfo = new Subject(); + // private sourceForChart = new Subject(); + // private sourceForTimelineCommand = new Subject(); + private sourceForTimeline = new ReplaySubject(1); + private sourceForAgentInfo = new ReplaySubject(1); + private sourceForChart = new ReplaySubject(1); + private sourceForTimelineCommand = new ReplaySubject(1); private timelineInfo: ITimelineInfo; private agentId: string; @@ -54,6 +58,23 @@ export class InspectorPageService { this.sourceForTimelineCommand$ = this.sourceForTimelineCommand.asObservable(); } + reset(id: string): void { + switch(id) { + case 'timelineCommand': + this.sourceForTimelineCommand.next(null); + break; + case 'timeline': + this.sourceForTimeline.next(null); + break; + case 'chart': + this.sourceForChart.next(null); + break; + case 'agentInfo': + this.sourceForAgentInfo.next(null); + break; + } + } + activate(unsubscribe: Subject): void { this.messageQueueService.receiveMessage(unsubscribe, MESSAGE_TO.AGENT_LIST_VALID).subscribe(({range, now, agentId}: {range: number[], now: number, agentId: string}) => { this.agentId = 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); + } }