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 118190df1c48..68ec34936347 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
@@ -1,6 +1,6 @@
import { Component, OnInit, OnDestroy, ComponentFactoryResolver, Injector } from '@angular/core';
-import { Subject, iif, of, Observable, EMPTY } from 'rxjs';
-import { tap, concatMap, filter, switchMap, delay, takeUntil, catchError, map } from 'rxjs/operators';
+import { Subject, iif, of, Observable, EMPTY, merge } from 'rxjs';
+import { tap, concatMap, filter, switchMap, delay, takeUntil, catchError, map, pluck } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import {
@@ -15,7 +15,6 @@ import {
TranslateReplaceService,
} from 'app/shared/services';
import { ServerErrorPopupContainerComponent } from 'app/core/components/server-error-popup/server-error-popup-container.component';
-import { InspectorPageService, ISourceForServerAndAgentList } from 'app/routes/inspector-page/inspector-page.service';
import { UrlPath, UrlPathId } from 'app/shared/models';
import { ServerAndAgentListDataService } from './server-and-agent-list-data.service';
import { isEmpty, isThatType } from 'app/core/utils/util';
@@ -50,7 +49,6 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
private analyticsService: AnalyticsService,
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
- private inspectorPageService: InspectorPageService,
private serverAndAgentListDataService: ServerAndAgentListDataService,
private messageQueueService: MessageQueueService,
private translateService: TranslateService,
@@ -60,86 +58,107 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
ngOnInit() {
this.initI18nText();
this.funcImagePath = this.webAppSettingDataService.getImagePathMakeFunc();
- this.inspectorPageService.sourceForServerAndAgentList$.pipe(
+
+ merge(
+ this.newUrlStateNotificationService.onUrlStateChange$.pipe(
takeUntil(this.unsubscribe),
- filter((data: ISourceForServerAndAgentList) => !!data),
- switchMap((data: ISourceForServerAndAgentList) => {
+ tap((urlService: NewUrlStateNotificationService) => {
+ this.agentId = urlService.getPathValue(UrlPathId.AGENT_ID);
+ }),
+ map((urlService: NewUrlStateNotificationService) => {
+ if (urlService.isRealTimeMode()) {
+ const to = urlService.getUrlServerTimeData();
+ const from = to - this.webAppSettingDataService.getSystemDefaultPeriod().getMiliSeconds();
+
+ return [from, to];
+ } else {
+ return [urlService.getStartTimeToNumber(), urlService.getEndTimeToNumber()];
+ }
+
+ })
+ ),
+ this.messageQueueService.receiveMessage(this.unsubscribe, MESSAGE_TO.FETCH_AGENT_LIST).pipe(
+ switchMap((data: {range: number[], emitAfter: number}) => {
return iif(() => data.emitAfter === 0,
of(data),
of(data).pipe(delay(data.emitAfter))
);
}),
- tap(({agentId}: ISourceForServerAndAgentList) => {
- this.agentId = agentId;
- }),
- concatMap(({range}: ISourceForServerAndAgentList) => {
- 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});
+ pluck('range'),
+ )
+ ).pipe(
+ 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;
+
+ if (isAgentIdValid) {
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;
-
- if (isAgentIdValid) {
- return true;
- } else {
- const url = this.newUrlStateNotificationService.isRealTimeMode()
- ? [ UrlPath.INSPECTOR, UrlPathId.REAL_TIME, this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr() ]
- : [
- UrlPath.INSPECTOR,
- this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(),
- this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(),
- this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime()
- ];
-
- this.urlRouteManagerService.moveOnPage({ url });
-
- return false;
- }
} else {
- return true;
+ 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()
+ ];
+
+ this.urlRouteManagerService.moveOnPage({url});
+
+ return false;
}
- }),
- tap(() => {
- const responseArriveAt = Date.now();
- const deltaT = responseArriveAt - requestStartAt;
- const now = range[1] + deltaT;
-
- this.messageQueueService.sendMessage({
- to: MESSAGE_TO.INSPECTOR_PAGE_VALID,
- param: {range, now}
- });
- }),
- catchError((error: IServerError) => {
- this.dynamicPopupService.openPopup({
- data: {
- title: 'Error',
- contents: error
- },
- component: ServerErrorPopupContainerComponent
- }, {
- resolver: this.componentFactoryResolver,
- injector: this.injector
- });
-
- return EMPTY;
- })
- );
- }),
+ } else {
+ return true;
+ }
+ }),
+ tap(() => {
+ const responseArriveAt = Date.now();
+ const deltaT = responseArriveAt - requestStartAt;
+ const now = range[1] + deltaT;
+
+ this.messageQueueService.sendMessage({
+ to: MESSAGE_TO.AGENT_LIST_VALID,
+ param: {
+ range,
+ now,
+ agentId: this.agentId
+ }
+ });
+ }),
+ 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);
@@ -163,21 +182,21 @@ export class ServerAndAgentListContainerComponent implements OnInit, OnDestroy {
onSelectAgent(agentId: string) {
const url = this.newUrlStateNotificationService.isRealTimeMode() ?
[
- UrlPath.INSPECTOR,
+ this.newUrlStateNotificationService.getStartPath(),
UrlPath.REAL_TIME,
this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(),
agentId
] :
[
- UrlPath.INSPECTOR,
+ this.newUrlStateNotificationService.getStartPath(),
this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(),
this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(),
this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(),
agentId
];
- this.urlRouteManagerService.moveOnPage({ url });
- this.analyticsService.trackEvent(TRACKED_EVENT_LIST.GO_TO_AGENT_INSPECTOR);
+ 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[]} {
diff --git a/web/src/main/angular/src/app/core/components/side-navigation-bar/side-navigation-bar-container.component.ts b/web/src/main/angular/src/app/core/components/side-navigation-bar/side-navigation-bar-container.component.ts
index 2daf031bce78..bce1fe5105b9 100644
--- a/web/src/main/angular/src/app/core/components/side-navigation-bar/side-navigation-bar-container.component.ts
+++ b/web/src/main/angular/src/app/core/components/side-navigation-bar/side-navigation-bar-container.component.ts
@@ -33,6 +33,9 @@ interface ISNBMeta {
export class SideNavigationBarContainerComponent implements OnInit, OnDestroy {
private unsubscribe = new Subject();
+ private showMetric: boolean;
+ private showUrlStat: boolean;
+
enableRealTime: boolean;
logoPath: string;
minimize = false;
@@ -40,8 +43,6 @@ export class SideNavigationBarContainerComponent implements OnInit, OnDestroy {
userId = '';
meta: ISNBMeta;
- showMetric: boolean;
-
constructor(
private cd: ChangeDetectorRef,
private windowRefService: WindowRefService,
@@ -58,6 +59,10 @@ export class SideNavigationBarContainerComponent implements OnInit, OnDestroy {
this.showMetric = showMetric;
});
+ this.webAppSettingDataService.showUrlStat().subscribe((showUrlStat: boolean) => {
+ this.showUrlStat = showUrlStat;
+ });
+
this.webAppSettingDataService.getUserId().subscribe(userId => {
this.userId = userId || 'dev';
this.meta = this.generatNavItemMeta();
@@ -98,6 +103,10 @@ export class SideNavigationBarContainerComponent implements OnInit, OnDestroy {
this.urlRouteManagerService.moveToHostMenu(UrlPath.METRIC);
}
+ onClickUrlStat(): void {
+ this.urlRouteManagerService.moveToAppMenu(UrlPath.URL_STATISTIC);
+ }
+
onClickGithubLink() {
this.windowRefService.nativeWindow.open('http://github.com/naver/pinpoint');
}
@@ -134,7 +143,15 @@ export class SideNavigationBarContainerComponent implements OnInit, OnDestroy {
iconClass: 'fas fa-server',
showItem: this.showMetric,
onClick: () => this.onClickMetric(),
- }
+ },
+ {
+ id: 'url-stat',
+ title: 'URL Statistic',
+ path: '/urlStatistic',
+ iconClass: 'fas fa-chart-bar',
+ showItem: this.showUrlStat,
+ onClick: () => this.onClickUrlStat(),
+ },
],
bottomItems: [
{
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-chart/index.ts b/web/src/main/angular/src/app/core/components/url-statistic-chart/index.ts
new file mode 100644
index 000000000000..41224b8a51c8
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-chart/index.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+
+import { SharedModule } from 'app/shared';
+import { UrlStatisticChartContainerComponent } from './url-statistic-chart-container.component';
+import { UrlStatisticChartComponent } from './url-statistic-chart.component';
+
+@NgModule({
+ imports: [
+ SharedModule
+ ],
+ exports: [
+ UrlStatisticChartContainerComponent
+ ],
+ declarations: [
+ UrlStatisticChartContainerComponent,
+ UrlStatisticChartComponent
+ ],
+ providers: [],
+})
+export class UrlStatisticChartModule { }
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.css b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.css
new file mode 100644
index 000000000000..cd962e8a3b46
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.css
@@ -0,0 +1,29 @@
+:host {
+ display: block;
+ width: 100%;
+ height: 100%;
+
+ --chart-most-success: var(--emerald-green-100);
+ --chart-success: var(--emerald-green-300);
+ --chart-kinda-success: var(--emerald-green-400);
+ --chart-almost-normal: var(--emerald-green-500);
+ --chart-normal: var(--blue-300);
+ --chart-slow: var(--orange-500);
+ --chart-very-slow: var(--orange-800);
+ --chart-fail: var(--red-400);
+}
+
+.dark-mode :host {
+ --chart-most-success: var(--emerald-green-800);
+ --chart-success: var(--emerald-green-600);
+ --chart-kinda-success: var(--emerald-green-500);
+ --chart-almost-normal: var(--emerald-green-400);
+
+ --chart-very-slow: var(--orange-100);
+}
+
+.chart-area-wrapper {
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.html b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.html
new file mode 100644
index 000000000000..b2478560a3be
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ {{guideMessage}}
+
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
new file mode 100644
index 000000000000..036ee0f7d296
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-container.component.ts
@@ -0,0 +1,318 @@
+import { Component, ElementRef, OnDestroy, OnInit } from '@angular/core';
+import { EMPTY, forkJoin, iif, Observable, of, Subject } from 'rxjs';
+import { catchError, map, switchMap, takeUntil, tap } from 'rxjs/operators';
+import { Data, PrimitiveArray, bar, zoom, areaStep } from 'billboard.js';
+import * as moment from 'moment-timezone';
+import { TranslateService } from '@ngx-translate/core';
+
+import { MessageQueueService, MESSAGE_TO, NewUrlStateNotificationService, StoreHelperService, WebAppSettingDataService } from 'app/shared/services';
+import { IUrlStatChartDataParams, UrlStatisticChartDataService } from './url-statistic-chart-data.service';
+import { UrlPathId } from 'app/shared/models';
+import { makeYData, makeXData, getMaxTickValue, getStackedData } from 'app/core/utils/chart-util';
+
+export enum Layer {
+ LOADING = 'loading',
+ RETRY = 'retry',
+ CHART = 'chart'
+}
+
+@Component({
+ selector: 'pp-url-statistic-chart-container',
+ templateUrl: './url-statistic-chart-container.component.html',
+ styleUrls: ['./url-statistic-chart-container.component.css']
+})
+export class UrlStatisticChartContainerComponent implements OnInit, OnDestroy {
+ private unsubscribe = new Subject();
+ private defaultYMax = 100;
+ private chartColorList: string[];
+ private timezone: string;
+ private dateFormatMonth: string;
+ private dateFormatDay: string;
+ private cachedData: {[key: string]: {timestamp: number[], metricValues: IMetricValue[]}} = {};
+ private fieldNameList: string[];
+ private previousParams: IUrlStatChartDataParams;
+
+ isUriSelected: boolean;
+ selectedUri: string;
+ chartConfig: IChartConfig;
+ showLoading: boolean;
+ showRetry: boolean;
+ retryMessage: string;
+ guideMessage: string;
+ emptyMessage: string;
+ chartVisibility = {};
+ _activeLayer: Layer = Layer.LOADING;
+
+ constructor(
+ private messageQueueService: MessageQueueService,
+ private webAppSettingDataService: WebAppSettingDataService,
+ private storeHelperService: StoreHelperService,
+ private newUrlStateNotificationService: NewUrlStateNotificationService,
+ private urlStatisticChartDataService: UrlStatisticChartDataService,
+ private translateService: TranslateService,
+ private el: ElementRef,
+ ) { }
+
+ ngOnInit() {
+ this.initFieldNameList();
+ this.initChartColorList();
+ this.initI18nText();
+ this.listenToEmitter();
+
+ this.newUrlStateNotificationService.onUrlStateChange$.pipe(
+ takeUntil(this.unsubscribe)
+ ).subscribe(() => {
+ this.cachedData = {};
+ this.isUriSelected = false;
+ this.selectedUri = null;
+ this.chartConfig = null;
+ });
+
+ this.messageQueueService.receiveMessage(this.unsubscribe, MESSAGE_TO.SELECT_URL_INFO).pipe(
+ tap((uri) => {
+ this.isUriSelected = true;
+ this.selectedUri = uri;
+ }),
+ switchMap((uri: string) => {
+ if (Boolean(this.cachedData[uri])) {
+ return of(this.cachedData[uri]);
+ } else {
+ const urlService = this.newUrlStateNotificationService;
+ const from = urlService.getStartTimeToNumber();
+ const to = urlService.getEndTimeToNumber();
+ const applicationName = urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName();
+ const agentId = urlService.getPathValue(UrlPathId.AGENT_ID) || '';
+ const params = this.previousParams = {from, to, applicationName, agentId, uri};
+
+ this.activeLayer = Layer.LOADING;
+ return this.urlStatisticChartDataService.getData(params).pipe(
+ map(({timestamp, metricValueGroups}: IUrlStatChartData) => {
+ this.cachedData[uri] = {timestamp, metricValues: metricValueGroups[0].metricValues};
+ return this.cachedData[uri];
+ }),
+ catchError((error: IServerError) => {
+ this.activeLayer = Layer.RETRY;
+ this.setRetryMessage(error.message);
+
+ return EMPTY;
+ })
+ );
+ }
+ })
+ ).subscribe((data: {timestamp: number[], metricValues: IMetricValue[]}) => {
+ this.setChartConfig(this.makeChartData(data));
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.unsubscribe.next();
+ this.unsubscribe.complete();
+ }
+
+ set activeLayer(layer: Layer) {
+ this._activeLayer = layer;
+ this.setChartVisibility(this._activeLayer === Layer.CHART);
+ }
+
+ get activeLayer(): Layer {
+ return this._activeLayer;
+ }
+
+ private setChartVisibility(showChart: boolean): void {
+ this.chartVisibility = {
+ 'show-chart': showChart,
+ 'shady-chart': !showChart && this.chartConfig !== undefined,
+ };
+ }
+
+ onRendered(): void {
+ this.activeLayer = Layer.CHART;
+ }
+
+ isActiveLayer(layer: string): boolean {
+ return this.activeLayer === layer;
+ }
+
+ onRetry(): void {
+ this.activeLayer = Layer.LOADING;
+ this.urlStatisticChartDataService.getData(this.previousParams).pipe(
+ map(({timestamp, metricValueGroups}: IUrlStatChartData) => {
+ this.cachedData[this.selectedUri] = {timestamp, metricValues: metricValueGroups[0].metricValues};
+ return this.cachedData[this.selectedUri];
+ }),
+ catchError((error: IServerError) => {
+ this.activeLayer = Layer.RETRY;
+ this.setRetryMessage(error.message);
+ return EMPTY;
+ }),
+ ).subscribe((data: {timestamp: number[], metricValues: IMetricValue[]}) => {
+ this.setChartConfig(this.makeChartData(data));
+ });
+ }
+
+ private initFieldNameList(): void {
+ this.fieldNameList = this.webAppSettingDataService.getUrlStatFieldNameList();
+ }
+
+ private initChartColorList(): void {
+ const computedStyle = getComputedStyle(this.el.nativeElement);
+
+ this.chartColorList = [
+ computedStyle.getPropertyValue('--chart-most-success'),
+ computedStyle.getPropertyValue('--chart-success'),
+ computedStyle.getPropertyValue('--chart-kinda-success'),
+ computedStyle.getPropertyValue('--chart-almost-normal'),
+ computedStyle.getPropertyValue('--chart-normal'),
+ computedStyle.getPropertyValue('--chart-slow'),
+ computedStyle.getPropertyValue('--chart-very-slow'),
+ computedStyle.getPropertyValue('--chart-fail'),
+ ]
+ }
+
+ private initI18nText(): void {
+ forkJoin([
+ this.translateService.get('URL_STAT.SELECT_URL_INFO'),
+ this.translateService.get('COMMON.NO_DATA')
+ ]).subscribe(([guideMessage, emptyMessage]: string[]) => {
+ this.guideMessage = guideMessage;
+ this.emptyMessage = emptyMessage;
+ });
+ }
+
+ private listenToEmitter(): void {
+ this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => {
+ this.timezone = timezone;
+ });
+
+ this.storeHelperService.getDateFormatArray(this.unsubscribe, 6, 7).subscribe(([dateFormatMonth, dateFormatDay]: string[]) => {
+ this.dateFormatMonth = dateFormatMonth;
+ this.dateFormatDay = dateFormatDay;
+ });
+ }
+
+ private setRetryMessage(message: string): void {
+ this.retryMessage = message;
+ }
+
+ private makeChartData({timestamp, metricValues}: {[key: string]: any}): PrimitiveArray[] {
+ return [
+ ['x', ...makeXData(timestamp)],
+ ...metricValues.map(({values}: IMetricValue, i: number) => {
+ return [this.fieldNameList[i], ...values.map((v: number) => v < 0 ? null : v)];
+ })
+ ];
+ }
+
+ private setChartConfig(data: PrimitiveArray[]): void {
+ this.chartConfig = {
+ dataConfig: this.makeDataOption(data),
+ elseConfig: this.makeElseOption(getMaxTickValue(getStackedData(data), 1)),
+ };
+ }
+
+ private makeDataOption(columns: PrimitiveArray[]): Data {
+ const keyList = columns.slice(1).map(([key]: PrimitiveArray) => key as string);
+
+ return {
+ x: 'x',
+ columns,
+ empty: {
+ label: {
+ text: this.emptyMessage
+ }
+ },
+ type: bar(),
+ // type: areaStep(),
+ colors: keyList.reduce((acc: {[key: string]: string}, curr: string, i: number) => {
+ return { ...acc, [curr]: this.chartColorList[i] };
+ }, {}),
+ groups: [keyList],
+ order: null
+ };
+ }
+
+ private makeElseOption(yMax: number): {[key: string]: any} {
+ return {
+ bar: {
+ width: {
+ ratio: 0.8
+ }
+ },
+ padding: {
+ top: 20,
+ bottom: 20,
+ right: 10
+ },
+ axis: {
+ x: {
+ type: 'timeseries',
+ tick: {
+ count: 6,
+ show: false,
+ format: (time: Date) => {
+ return moment(time).tz(this.timezone).format(this.dateFormatMonth) + '\n' + moment(time).tz(this.timezone).format(this.dateFormatDay);
+ }
+ },
+ padding: {
+ left: 0,
+ right: 0
+ }
+ },
+ y: {
+ label: {
+ text: 'Total Count',
+ position: 'outer-middle'
+ },
+ tick: {
+ // count: 3,
+ format: (v: number): string => this.convertWithUnit(v)
+ },
+ padding: {
+ // top: 0,
+ bottom: 0
+ },
+ min: 0,
+ // max: yMax,
+ default: [0, this.defaultYMax]
+ }
+ },
+ grid: {
+ y: {
+ show: true
+ }
+ },
+ point: {
+ show: false,
+ },
+ tooltip: {
+ order: '',
+ format: {
+ // value: (v: number): string => this.addComma(v.toString())
+ value: (v: number): string => this.convertWithUnit(v)
+ }
+ },
+ transition: {
+ duration: 0
+ },
+ zoom: {
+ enabled: zoom()
+ },
+ };
+ }
+
+ private addComma(str: string): string {
+ return str.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, '$1,');
+ }
+
+ private convertWithUnit(value: number): string {
+ const unitList = ['', 'K', 'M', 'G'];
+
+ return [...unitList].reduce((acc: string, curr: string, i: number, arr: string[]) => {
+ const v = Number(acc);
+
+ return v >= 1000
+ ? (v / 1000).toString()
+ : (arr.splice(i + 1), Number.isInteger(v) ? `${v}${curr}` : `${v.toFixed(2)}${curr}`);
+ }, value.toString());
+ }
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-data.service.ts b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-data.service.ts
new file mode 100644
index 000000000000..96badb639a0d
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart-data.service.ts
@@ -0,0 +1,32 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+export interface IUrlStatChartDataParams {
+ from: number;
+ to: number;
+ applicationName: string;
+ agentId: string;
+ uri: string;
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class UrlStatisticChartDataService {
+ private url = 'uriStat/chart.pinpoint'
+
+ constructor(
+ private http: HttpClient,
+ ) { }
+
+ getData(params: IUrlStatChartDataParams): Observable {
+ return this.http.get(this.url, this.makeRequestOptionsArgs(params));
+ }
+
+ private makeRequestOptionsArgs(params: IUrlStatChartDataParams): object {
+ return {
+ params: {...params}
+ };
+ }
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart.component.css b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart.component.css
new file mode 100644
index 000000000000..68f5927f54aa
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart.component.css
@@ -0,0 +1,22 @@
+:host {
+ display: block;
+ width: 100%;
+ height: 100%;
+ /* background-color: var(--background-default); */
+}
+
+:host(.show-chart) {
+ opacity: 1;
+ transition: all 1s;
+}
+
+:host(.shady-chart) {
+ opacity: 0.5;
+ pointer-events: none;
+ transition: all 0.5s;
+}
+
+.l-chart-section {
+ width: 100%;
+ height: 100%;
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-chart.component.html b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart.component.html
similarity index 100%
rename from web/src/main/angular/src/app/core/components/url-statistic/url-statistic-chart.component.html
rename to web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart.component.html
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-chart.component.ts b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart.component.ts
similarity index 98%
rename from web/src/main/angular/src/app/core/components/url-statistic/url-statistic-chart.component.ts
rename to web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart.component.ts
index 627019072f38..8b518f37a7c0 100644
--- a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-chart.component.ts
+++ b/web/src/main/angular/src/app/core/components/url-statistic-chart/url-statistic-chart.component.ts
@@ -59,7 +59,7 @@ export class UrlStatisticChartComponent implements OnInit, OnChanges {
: this.getEmptyDataKeys(currColumns);
this.chartInstance.config('data.groups', [currKeys.slice(1)]);
- this.chartInstance.config('axis.y.max', y.max);
+ // this.chartInstance.config('axis.y.max', y.max);
this.chartInstance.load({
columns: currColumns,
colors,
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-contents/index.ts b/web/src/main/angular/src/app/core/components/url-statistic-contents/index.ts
new file mode 100644
index 000000000000..90a99f323669
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-contents/index.ts
@@ -0,0 +1,22 @@
+import { NgModule } from '@angular/core';
+
+import { SharedModule } from 'app/shared';
+import { UrlStatisticChartModule } from 'app/core/components/url-statistic-chart';
+import { UrlStatisticInfoModule } from 'app/core/components/url-statistic-info';
+import { UrlStatisticContentsContainerComponent } from './url-statistic-contents-container.component';
+
+@NgModule({
+ declarations: [
+ UrlStatisticContentsContainerComponent
+ ],
+ imports: [
+ SharedModule,
+ UrlStatisticInfoModule,
+ UrlStatisticChartModule
+ ],
+ exports: [
+ UrlStatisticContentsContainerComponent
+ ],
+ providers: [],
+})
+export class UrlStatisticContentsModule { }
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-contents/url-statistic-contents-container.component.css b/web/src/main/angular/src/app/core/components/url-statistic-contents/url-statistic-contents-container.component.css
new file mode 100644
index 000000000000..2ddcabbd08c9
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-contents/url-statistic-contents-container.component.css
@@ -0,0 +1,46 @@
+:host {
+ display: block;
+ width: 100%;
+ height: 100%;
+ padding: 15px;
+}
+
+.page-title-wrapper {
+ margin-bottom: 15px;
+}
+
+.page-title {
+ font-size: 20px;
+ display: flex;
+ align-items: center;
+}
+
+.beta-text {
+ font-style: italic;
+ font-weight: bold;
+ color: var(--primary);
+ margin-left: 5px;
+}
+
+.page-title-icon {
+ margin-right: 5px;
+}
+
+.main-contents {
+ width: 100%;
+ /* height: 100%; */
+ height: calc(100% - 38px);
+ position: relative;
+ /* overflow: auto; */
+}
+
+.chart-wrapper {
+ width: 100%;
+ height: 45%;
+ margin-bottom: 30px;
+}
+
+.info-wrapper {
+ width: 100%;
+ height: calc(55% - 30px);
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-contents/url-statistic-contents-container.component.html b/web/src/main/angular/src/app/core/components/url-statistic-contents/url-statistic-contents-container.component.html
new file mode 100644
index 000000000000..143db2dd7b1c
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-contents/url-statistic-contents-container.component.html
@@ -0,0 +1,11 @@
+
+
URL Statistic Beta
+
+
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-contents/url-statistic-contents-container.component.ts b/web/src/main/angular/src/app/core/components/url-statistic-contents/url-statistic-contents-container.component.ts
new file mode 100644
index 000000000000..9ca0527d2fe6
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-contents/url-statistic-contents-container.component.ts
@@ -0,0 +1,11 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'pp-url-statistic-contents-container',
+ templateUrl: './url-statistic-contents-container.component.html',
+ styleUrls: ['./url-statistic-contents-container.component.css']
+})
+export class UrlStatisticContentsContainerComponent implements OnInit {
+ constructor() {}
+ ngOnInit() {}
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-info/index.ts b/web/src/main/angular/src/app/core/components/url-statistic-info/index.ts
new file mode 100644
index 000000000000..70a27dd2cdf9
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-info/index.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+
+import { SharedModule } from 'app/shared';
+import { UrlStatisticInfoContainerComponent } from './url-statistic-info-container.component';
+import { UrlStatisticInfoComponent } from './url-statistic-info.component';
+
+@NgModule({
+ imports: [
+ SharedModule
+ ],
+ exports: [
+ UrlStatisticInfoContainerComponent
+ ],
+ declarations: [
+ UrlStatisticInfoContainerComponent,
+ UrlStatisticInfoComponent
+ ],
+ providers: [],
+})
+export class UrlStatisticInfoModule { }
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-chart.component.css b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-container.component.css
similarity index 53%
rename from web/src/main/angular/src/app/core/components/url-statistic/url-statistic-chart.component.css
rename to web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-container.component.css
index 4f6884f63039..7d6a180af5c3 100644
--- a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-chart.component.css
+++ b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-container.component.css
@@ -2,9 +2,5 @@
display: block;
width: 100%;
height: 100%;
-}
-
-.l-chart-section {
- width: 100%;
- height: 100%;
+ position: relative;
}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-container.component.html b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-container.component.html
new file mode 100644
index 000000000000..8181ac83a2ad
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-container.component.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-container.component.ts b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-container.component.ts
new file mode 100644
index 000000000000..b73bcbc333ad
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-container.component.ts
@@ -0,0 +1,64 @@
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Observable, of, Subject } from 'rxjs';
+import { catchError, switchMap, takeUntil } from 'rxjs/operators';
+import { TranslateService } from '@ngx-translate/core';
+
+import { MessageQueueService, MESSAGE_TO, NewUrlStateNotificationService } from 'app/shared/services';
+import { UrlStatisticInfoDataService } from './url-statistic-info-data.service';
+import { UrlPathId } from 'app/shared/models';
+
+@Component({
+ selector: 'pp-url-statistic-info-container',
+ templateUrl: './url-statistic-info-container.component.html',
+ styleUrls: ['./url-statistic-info-container.component.css']
+})
+export class UrlStatisticInfoContainerComponent implements OnInit, OnDestroy {
+ private unsubscribe = new Subject();
+
+ data$: Observable;
+ emptyMessage$: Observable;
+ errorMessage: string;
+
+ constructor(
+ private urlStatisticInfoDataService: UrlStatisticInfoDataService,
+ private newUrlStateNotificationService: NewUrlStateNotificationService,
+ private messageQueueService: MessageQueueService,
+ private translateService: TranslateService,
+ ) { }
+
+ ngOnInit() {
+ this.emptyMessage$ = this.translateService.get('COMMON.NO_DATA');
+ this.data$ = this.newUrlStateNotificationService.onUrlStateChange$.pipe(
+ takeUntil(this.unsubscribe),
+ switchMap((urlService: NewUrlStateNotificationService) => {
+ const from = urlService.getStartTimeToNumber();
+ const to = urlService.getEndTimeToNumber();
+ const applicationName = urlService.getPathValue(UrlPathId.APPLICATION).getApplicationName();
+ const agentId = urlService.getPathValue(UrlPathId.AGENT_ID) || '';
+ const params = {from, to, applicationName, agentId};
+
+ return this.urlStatisticInfoDataService.getData(params);
+ }),
+ catchError((error: IServerError) => {
+ this.errorMessage = error.message;
+ return of([])
+ })
+ );
+ }
+
+ ngOnDestroy() {
+ this.unsubscribe.next();
+ this.unsubscribe.complete();
+ }
+
+ onSelectUrlInfo(url: string): void {
+ this.messageQueueService.sendMessage({
+ to: MESSAGE_TO.SELECT_URL_INFO,
+ param: url
+ })
+ }
+
+ onCloseErrorMessage(): void {
+ this.errorMessage = '';
+ }
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-data.service.ts b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-data.service.ts
new file mode 100644
index 000000000000..3d93de8a04c5
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info-data.service.ts
@@ -0,0 +1,32 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+export interface IUrlStatInfoDataParams {
+ from: number;
+ to: number;
+ applicationName: string;
+ agentId: string;
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class UrlStatisticInfoDataService {
+ private url = 'uriStat/top50.pinpoint';
+
+ constructor(
+ private http: HttpClient,
+ ) { }
+
+
+ getData(params: IUrlStatInfoDataParams): Observable {
+ return this.http.get(this.url, this.makeRequestOptionsArgs(params));
+ }
+
+ private makeRequestOptionsArgs(params: IUrlStatInfoDataParams): object {
+ return {
+ params: {...params}
+ };
+ }
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info.component.css b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info.component.css
new file mode 100644
index 000000000000..93ba35e2a175
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info.component.css
@@ -0,0 +1,117 @@
+:host {
+ display: block;
+ width: 100%;
+ height: 100%;
+
+ --url-count-ratio: var(--blue-200);
+ --url-count-ratio-background: var(--blue-grey-200);
+}
+
+.url-info-table {
+ width: 100%;
+ /* height: 100%; */
+ font-family: 'Open Sans',sans-serif;
+ font-size: 12px;
+ color: var(--text-primary-lighter);
+}
+
+.url-info-header {
+ width: 100%;
+ /* height: 40px; */
+}
+
+.url-info-row {
+ display: flex;
+ height: 36px;
+
+ gap: 15px;
+ padding-right: 20px;
+}
+
+.url-info-row.active {
+ background-color: var(--background-hover-secondary);
+}
+
+.url-info-row.active:hover {
+ background-color: var(--background-hover-secondary) !important;
+}
+
+.url-info-row:not(.header-row, .empty):hover {
+ background-color: var(--background-hover-default);
+}
+
+.url-info-row:not(.header-row) {
+ cursor: pointer;
+ border-top: 1px solid var(--border-primary);
+}
+
+.url-info-row.header-row {
+ width: 100%;
+ height: 40px;
+}
+
+.url-info-row.empty {
+ cursor: default;
+}
+
+.url-info-cell {
+ display: flex;
+ align-items: center;
+ flex: 1;
+}
+
+.url-info-cell.index-cell {
+ flex-grow: 0;
+ flex-basis: 40px;
+ justify-content: center;
+}
+
+.url-info-cell.url-cell {
+ flex: 1.5;
+}
+
+.url-info-cell.total-count-cell {
+ gap: 7px;
+}
+
+.url-info-cell.failure-count-cell {
+ /* flex-grow: 0;
+ flex-basis: 150px; */
+ /* justify-content: right; */
+}
+
+.url-info-cell.avg-cell,
+.url-info-cell.max-cell {
+ /* flex-grow: 0;
+ flex-basis: 150px; */
+ /* justify-content: right; */
+}
+
+.url-info-cell.header-cell {
+ height: 100%;
+ color: var(--text-primary);
+ font-weight: bold;
+ font-size: 13px;
+}
+
+.url-info-body {
+ width: 100%;
+ /* height: calc(100% - 40px); */
+ overflow: auto;
+}
+
+.url-info-cell.empty {
+ font-size: 13px;
+ justify-content: center;
+ align-items: center;
+}
+
+.ratio-bar {
+ width: 150px;
+ height: 20px;
+}
+
+.total-count {
+ width: 50px;
+ text-align: right;
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info.component.html b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info.component.html
new file mode 100644
index 000000000000..80f31e7b2a5c
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info.component.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
{{i + 1}}
+
{{urlInfo.uri}}
+
+
{{urlInfo.totalCount | number}}
+
+
+
{{urlInfo.failureCount | number}}
+
{{urlInfo.avgTimeMs | number: '1.0-0'}}
+
{{urlInfo.maxTimeMs | number: '1.0-0'}}
+
+
+
+
+
diff --git a/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info.component.ts b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info.component.ts
new file mode 100644
index 000000000000..98ed305f1df4
--- /dev/null
+++ b/web/src/main/angular/src/app/core/components/url-statistic-info/url-statistic-info.component.ts
@@ -0,0 +1,67 @@
+import { Component, Input, OnInit, Output, EventEmitter, SimpleChanges, OnChanges, ElementRef, Renderer2, ViewChild } from '@angular/core';
+
+import { isEmpty } from 'app/core/utils/util';
+
+@Component({
+ selector: 'pp-url-statistic-info',
+ templateUrl: './url-statistic-info.component.html',
+ styleUrls: ['./url-statistic-info.component.css']
+})
+export class UrlStatisticInfoComponent implements OnInit, OnChanges {
+ @ViewChild('urlInfoTableBody' , {static: true}) urlInfoTableBody: ElementRef;
+ @Input() data: IUrlStatInfoData[];
+ @Input() emptyMessage: string;
+ @Output() outSelectUrlInfo = new EventEmitter();
+
+ totalCount: number;
+ selectedUrl: string;
+ isEmpty: boolean;
+
+ constructor(
+ private el: ElementRef,
+ private renderer: Renderer2
+ ) {}
+
+ ngOnChanges(changes: SimpleChanges) {
+ const dataChange = changes['data'];
+
+ if (dataChange && dataChange.currentValue) {
+ const data = dataChange.currentValue as IUrlStatInfoData[];
+
+ this.isEmpty = isEmpty(data);
+ if (this.isEmpty) {
+ return;
+ }
+
+ this.selectedUrl = '';
+ this.totalCount = data.reduce((acc: number, {totalCount}: any) => {
+ return acc + totalCount;
+ }, 0);
+ }
+ }
+
+ ngOnInit() {
+ this.renderer.setStyle(this.urlInfoTableBody.nativeElement, 'max-height', `${this.el.nativeElement.offsetHeight - 40}px`);
+ }
+
+ onSelectUrlInfo(url: string): void {
+ if (this.selectedUrl === url) {
+ return;
+ }
+
+ this.selectedUrl = url;
+ this.outSelectUrlInfo.emit(url);
+ }
+
+ isSelectedUrl(url: string): boolean {
+ return this.selectedUrl === url;
+ }
+
+ getRatioBackgroundColor(count: number): string {
+ const computedStyle = getComputedStyle(this.el.nativeElement);
+ const urlCountRatio = computedStyle.getPropertyValue('--url-count-ratio');
+ const urlCountRatioBG = computedStyle.getPropertyValue('--url-count-ratio-background');
+
+ return `linear-gradient(to right, ${urlCountRatio} ${count / this.totalCount * 100}%, ${urlCountRatioBG} ${count / this.totalCount * 100}% 100%)`;
+ }
+}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/index.ts b/web/src/main/angular/src/app/core/components/url-statistic/index.ts
deleted file mode 100644
index 2e5ff6034e8a..000000000000
--- a/web/src/main/angular/src/app/core/components/url-statistic/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { NgModule } from '@angular/core';
-import { SharedModule } from 'app/shared';
-
-import { UrlStatisticContainerComponent } from './url-statistic-container.component';
-import { UrlStatisticChartComponent } from './url-statistic-chart.component';
-import { UrlStatisticDataService } from './url-statistic-data.service';
-import { UrlStatisticInfoComponent } from './url-statistic-info.component';
-
-@NgModule({
- imports: [
- SharedModule
- ],
- exports: [
- UrlStatisticContainerComponent
- ],
- declarations: [
- UrlStatisticContainerComponent,
- UrlStatisticChartComponent,
- UrlStatisticInfoComponent
- ],
- providers: [
- UrlStatisticDataService
- ],
-})
-export class UrlStatisticModule { }
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-container.component.css b/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-container.component.css
deleted file mode 100644
index fb40be427b60..000000000000
--- a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-container.component.css
+++ /dev/null
@@ -1,11 +0,0 @@
-:host {
- display: block;
- height: 100%;
- padding: 25px;
-}
-
-.l-chart-section {
- position: relative;
- height: 450px;
- margin-bottom: 30px;
-}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-container.component.html b/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-container.component.html
deleted file mode 100644
index f1565804e8fc..000000000000
--- a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-container.component.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-container.component.ts b/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-container.component.ts
deleted file mode 100644
index df8e6ffe95ce..000000000000
--- a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-container.component.ts
+++ /dev/null
@@ -1,231 +0,0 @@
-import { map, tap } from 'rxjs/operators';
-import { Component, OnInit, OnDestroy } from '@angular/core';
-import { Data, PrimitiveArray, bar, zoom } from 'billboard.js';
-import * as moment from 'moment-timezone';
-import { Subject } from 'rxjs';
-
-import { UrlStatisticDataService } from './url-statistic-data.service';
-import { makeYData, makeXData, getMaxTickValue, getStackedData } from 'app/core/utils/chart-util';
-import { StoreHelperService, WebAppSettingDataService } from 'app/shared/services';
-
-@Component({
- selector: 'pp-url-statistic-container',
- templateUrl: './url-statistic-container.component.html',
- styleUrls: ['./url-statistic-container.component.css']
-})
-export class UrlStatisticContainerComponent implements OnInit, OnDestroy {
- private unsubscribe = new Subject();
- private defaultYMax = 100;
- private chartColors: string[];
- private timezone: string;
- private dateFormatMonth: string;
- private dateFormatDay: string;
-
- urlStatisticData: any[];
- chartConfig: IChartConfig;
- showLoading = true;
-
- constructor(
- private urlStatisticDataService: UrlStatisticDataService,
- private webAppSettingDataService: WebAppSettingDataService,
- private storeHelperService: StoreHelperService,
- ) { }
-
- ngOnInit() {
- this.initChartColors();
- this.listenToEmitter();
- this.initData();
- }
-
- ngOnDestroy() {
- this.unsubscribe.next();
- this.unsubscribe.complete();
- }
-
- onRendered(): void {
- this.showLoading = false;
- }
-
- onSelectUrl(selectedUrl: string): void {
- // TODO: Refactor/extract set chartdata logic
- const data = this.urlStatisticData.find(({uri}: any) => uri === selectedUrl);
-
- this.setChartConfig(this.makeChartData(data));
- }
-
- private initChartColors(): void {
- this.chartColors = this.webAppSettingDataService.getColorByRequestInDetail();
- }
-
- private listenToEmitter(): void {
- this.storeHelperService.getTimezone(this.unsubscribe).subscribe((timezone: string) => {
- this.timezone = timezone;
- });
-
- this.storeHelperService.getDateFormatArray(this.unsubscribe, 6, 7).subscribe(([dateFormatMonth, dateFormatDay]: string[]) => {
- this.dateFormatMonth = dateFormatMonth;
- this.dateFormatDay = dateFormatDay;
- });
- }
-
- private initData(): void {
- // TODO: Add Error handling
- this.urlStatisticDataService.getData().pipe(
- tap((data: any[]) => {
- console.log(data);
- this.urlStatisticData = data;
- }),
- map((data: any[]) => this.makeChartData(data[0]))
- ).subscribe((data: PrimitiveArray[]) => {
- this.setChartConfig(data);
- });
- }
-
- private makeChartData({charts}: any): PrimitiveArray[] {
- // const data = charts.y['HISTOGRAM_BUCKET'];
- // const parsedData = data.reduce((acc: number[][], curr: number[]) => {
- // return acc.length === 0 ? curr.map((c: number) => [c])
- // : acc.map((a: number[], i: number) => [...a, curr[i]]);
- // }, []);
-
- // * Use mock data temporarily
- const size = 128;
- const startDatetime = 1607463120000;
- const interval = 1000 * 60;
-
- const x = Array(size)
- .fill(0)
- .map((v, i) => startDatetime + interval * i);
-
- const data = [300, 200, 100, 50, 30, 20, 10, 10]
- .map(v => Array(size)
- .fill(0)
- .map(() => Math.round(Math.random() * v))
- );
-
- return [
- ['x', ...x],
- ['0 ~ 100', ...data[0]],
- ['100 ~ 300', ...data[1]],
- ['300 ~ 500', ...data[2]],
- ['500 ~ 1000', ...data[3]],
- ['1000 ~ 3000', ...data[4]],
- ['3000 ~ 5000', ...data[5]],
- ['5000 ~ 8000', ...data[6]],
- ['8000 ~ ', ...data[7]],
- // ['x', ...makeXData(charts.x)],
- // ['0 ~ 100', ...makeYData(charts.y['HISTOGRAM_BUCKET'], 0)],
- // ['100 ~ 300', ...makeYData(charts.y['HISTOGRAM_BUCKET'], 1)],
- // ['300 ~ 500', ...makeYData(charts.y['HISTOGRAM_BUCKET'], 2)],
- // ['500 ~ 1000', ...makeYData(charts.y['HISTOGRAM_BUCKET'], 3)],
- // ['1000 ~ 3000', ...makeYData(charts.y['HISTOGRAM_BUCKET'], 4)],
- // ['3000 ~ 5000', ...makeYData(charts.y['HISTOGRAM_BUCKET'], 5)],
- // ['5000 ~ 8000', ...makeYData(charts.y['HISTOGRAM_BUCKET'], 6)],
- // ['8000 ~ ', ...makeYData(charts.y['HISTOGRAM_BUCKET'], 7)],
- ];
- }
-
- private setChartConfig(data: PrimitiveArray[]): void {
- this.chartConfig = {
- dataConfig: this.makeDataOption(data),
- elseConfig: this.makeElseOption(getMaxTickValue(getStackedData(data), 1)),
- };
- }
-
- private makeDataOption(columns: PrimitiveArray[]): Data {
- const keyList = columns.slice(1).map(([key]: PrimitiveArray) => key as string);
-
- return {
- x: 'x',
- columns,
- // empty: {
- // label: {
- // text: this.dataEmptyText
- // }
- // },
- type: bar(),
- colors: keyList.reduce((acc: {[key: string]: string}, curr: string, i: number) => {
- return { ...acc, [curr]: this.chartColors[i] };
- }, {}),
- groups: [keyList],
- order: null
- };
- }
-
- private makeElseOption(yMax: number): {[key: string]: any} {
- return {
- bar: {
- width: {
- ratio: 0.8
- }
- },
- padding: {
- top: 20,
- bottom: 20,
- right: 10
- },
- axis: {
- x: {
- type: 'timeseries',
- tick: {
- count: 6,
- show: false,
- format: (time: Date) => {
- return moment(time).tz(this.timezone).format(this.dateFormatMonth) + '\n' + moment(time).tz(this.timezone).format(this.dateFormatDay);
- }
- },
- padding: {
- left: 0,
- right: 0
- }
- },
- y: {
- tick: {
- count: 3,
- format: (v: number): string => this.convertWithUnit(v)
- },
- padding: {
- top: 0,
- bottom: 0
- },
- min: 0,
- max: yMax,
- default: [0, this.defaultYMax]
- }
- },
- grid: {
- y: {
- show: true
- }
- },
- tooltip: {
- order: '',
- format: {
- value: (v: number) => this.addComma(v.toString())
- }
- },
- transition: {
- duration: 0
- },
- zoom: {
- enabled: zoom()
- },
- };
- }
-
- private addComma(str: string): string {
- return str.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, '$1,');
- }
-
- private convertWithUnit(value: number): string {
- const unitList = ['', 'K', 'M', 'G'];
-
- return [...unitList].reduce((acc: string, curr: string, i: number, arr: string[]) => {
- const v = Number(acc);
-
- return v >= 1000
- ? (v / 1000).toString()
- : (arr.splice(i + 1), Number.isInteger(v) ? `${v}${curr}` : `${v.toFixed(2)}${curr}`);
- }, value.toString());
- }
-}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-data.service.ts b/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-data.service.ts
deleted file mode 100644
index 6c45501fdb04..000000000000
--- a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-data.service.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Observable } from 'rxjs';
-import { HttpClient } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-
-@Injectable()
-export class UrlStatisticDataService {
- private url = 'getAgentStat/uriStat/chartList.pinpoint';
-
- constructor(
- private http: HttpClient,
- ) {}
-
- getData(): Observable {
- return this.http.get(this.url, this.makeRequestOptionsArgs());
- }
-
- private makeRequestOptionsArgs(): any {
- // TODO: Deliver real param
- return {
- params: {
- agentId: 'FrontWAS2',
- from: 1614044341000,
- to: 1614045541000,
- sampleRate: 1
- }
- };
- }
-}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-info.component.css b/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-info.component.css
deleted file mode 100644
index a2927b3d78b0..000000000000
--- a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-info.component.css
+++ /dev/null
@@ -1,50 +0,0 @@
-:host {
- display: block;
- width: 100%;
- height: 100%;
-}
-
-.l-content-group {
- height: 36px;
-}
-
-.l-content-group.active {
- background-color: var(--background-hover-secondary);
-}
-
-.l-content-group.active:hover {
- background-color: var(--background-hover-secondary);
-}
-
-.l-content-group:hover {
- background-color: var(--background-hover-default);
-}
-
-.l-content-item {
- text-align: center;
- border-top: 1px solid var(--border-primary);
- padding: 0;
-}
-
-.l-url {
- cursor: pointer;
-}
-
-.l-ratio-bar {
- display: inline-block;
- width: 100px;
- height: 20px;
- margin-left: 7px;
-}
-
-.l-sub {
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.l-count {
- width: 50px;
- text-align: right;
-}
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-info.component.html b/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-info.component.html
deleted file mode 100644
index b6735300a31a..000000000000
--- a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-info.component.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
- URL |
- Total Count |
- Avg |
- Max |
-
-
-
-
- {{urlInfo.uri}} |
-
-
- {{urlInfo.totalCount}}
-
-
-
-
- |
- {{urlInfo.avgTime}} |
- {{urlInfo.maxTime}} |
-
-
-
-
diff --git a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-info.component.ts b/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-info.component.ts
deleted file mode 100644
index 0fc9d11a2e61..000000000000
--- a/web/src/main/angular/src/app/core/components/url-statistic/url-statistic-info.component.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Component, Input, OnInit, Output, EventEmitter, SimpleChanges, OnChanges } from '@angular/core';
-
-@Component({
- selector: 'pp-url-statistic-info',
- templateUrl: './url-statistic-info.component.html',
- styleUrls: ['./url-statistic-info.component.css']
-})
-export class UrlStatisticInfoComponent implements OnInit, OnChanges {
- @Input() data: any[];
- @Output() outSelectUrl = new EventEmitter();
-
- private totalCount: number;
-
- selectedUrl: string;
-
- constructor() {}
- ngOnChanges(changes: SimpleChanges) {
- const dataChange = changes['data'];
-
- if (dataChange && dataChange.currentValue) {
- this.selectedUrl = dataChange.currentValue[0].uri;
- this.totalCount = dataChange.currentValue.reduce((acc: number, {totalCount}: any) => {
- return acc + totalCount;
- }, 0);
- }
- }
-
- ngOnInit() {}
- onSelectUrl(url: string): void {
- if (this.selectedUrl === url) {
- return;
- }
-
- this.selectedUrl = url;
- this.outSelectUrl.emit(url);
- }
-
- isSelectedUrl(url: string): boolean {
- return this.selectedUrl === url;
- }
-
- getRatioBackgroundColor(count: number): string {
- return `linear-gradient(to right, #DAE6F0 ${count / this.totalCount * 100}%, #F2F2F2 ${count / this.totalCount * 100}% 100%)`;
- }
-}
diff --git a/web/src/main/angular/src/app/routes/inspector-page/index.ts b/web/src/main/angular/src/app/routes/inspector-page/index.ts
index 52cbd82fc911..cd3c2f023399 100644
--- a/web/src/main/angular/src/app/routes/inspector-page/index.ts
+++ b/web/src/main/angular/src/app/routes/inspector-page/index.ts
@@ -7,7 +7,6 @@ import { NoticeModule } from 'app/core/components/notice';
import { ApplicationListModule } from 'app/core/components/application-list';
import { PeriodSelectorModule } from 'app/core/components/period-selector';
import { ConfigurationIconModule } from 'app/core/components/configuration-icon';
-import { ApplicationInspectorTitleModule } from 'app/core/components/application-inspector-title';
import { ServerAndAgentListModule } from 'app/core/components/server-and-agent-list';
import { ApplicationInspectorContentsModule } from 'app/core/components/application-inspector-contents';
import { AgentInspectorContentsModule } from 'app/core/components/agent-inspector-contents';
@@ -27,7 +26,6 @@ import { SideNavigationBarModule } from 'app/core/components/side-navigation-bar
ApplicationListModule,
PeriodSelectorModule,
ConfigurationIconModule,
- ApplicationInspectorTitleModule,
ServerAndAgentListModule,
ApplicationInspectorContentsModule,
AgentInspectorContentsModule,
diff --git a/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.css b/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.css
index 78b4661c5086..cadf3e9c3de2 100644
--- a/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.css
+++ b/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.css
@@ -78,3 +78,36 @@ button {
.l-auth-img {
width: 200px;
}
+
+.selected-app-wrapper {
+ display: flex;
+ border-bottom: 1px solid var(--border-primary);
+ height: 40px;
+ gap: 4px;
+ padding: 0 6px 0 13px;
+ align-items: center;
+}
+
+.selected-app-wrapper.active {
+ color: var(--primary);
+ font-weight: 600;
+}
+
+.selected-app-wrapper:hover {
+ cursor: pointer;
+ color: var(--primary);
+ background-color: var(--background-hover-default);
+}
+
+.app-img {
+ width: 22px;
+ height: 18px;
+}
+
+.selected-app-name {
+ flex: 1;
+ font-size: 14px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
\ No newline at end of file
diff --git a/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.html b/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.html
index ba2d80f47516..4f2b4895dbc0 100644
--- a/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.html
+++ b/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.html
@@ -18,9 +18,9 @@
diff --git a/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.ts b/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.ts
index 9ca6bd3b6181..5b0909877623 100644
--- a/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.ts
+++ b/web/src/main/angular/src/app/routes/inspector-page/inspector-page.component.ts
@@ -1,10 +1,10 @@
import { Component, OnInit, ComponentFactoryResolver, Injector, OnDestroy } from '@angular/core';
import { state, style, animate, transition, trigger } from '@angular/animations';
import { Observable, Subject } from 'rxjs';
-import { map, takeUntil } from 'rxjs/operators';
+import { map, takeUntil, tap } from 'rxjs/operators';
-import { NewUrlStateNotificationService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService, WebAppSettingDataService, MessageQueueService, MESSAGE_TO } from 'app/shared/services';
-import { UrlPathId } from 'app/shared/models';
+import { NewUrlStateNotificationService, AnalyticsService, TRACKED_EVENT_LIST, DynamicPopupService, WebAppSettingDataService, MessageQueueService, MESSAGE_TO, UrlRouteManagerService } from 'app/shared/services';
+import { UrlPath, UrlPathId } from 'app/shared/models';
import { HELP_VIEWER_LIST, HelpViewerPopupContainerComponent } from 'app/core/components/help-viewer-popup/help-viewer-popup-container.component';
import { InspectorPageService } from './inspector-page.service';
@@ -34,6 +34,7 @@ import { InspectorPageService } from './inspector-page.service';
})
export class InspectorPageComponent implements OnInit, OnDestroy {
private unsubscribe = new Subject();
+ private funcImagePath: Function;
sideNavigationUI: boolean;
unAuthImgPath: string;
@@ -42,6 +43,9 @@ export class InspectorPageComponent implements OnInit, OnDestroy {
isAccessDenyed$: Observable;
mainSectionStyle = {};
+ isAppActivated: boolean;
+ selectedAppImg: string;
+ selectedAppName: string;
constructor(
private inspectorPageService: InspectorPageService,
@@ -52,14 +56,25 @@ export class InspectorPageComponent implements OnInit, OnDestroy {
private injector: Injector,
private webAppSettingDataService: WebAppSettingDataService,
private messageQueueService: MessageQueueService,
+ private urlRouteManagerService: UrlRouteManagerService,
) {}
ngOnInit() {
+ this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc();
this.sideNavigationUI = this.webAppSettingDataService.getExperimentalOption('sideNavigationUI');
this.unAuthImgPath = this.webAppSettingDataService.getServerMapIconPathMakeFunc()('UNAUTHORIZED');
this.newUrlStateNotificationService.onUrlStateChange$.pipe(
takeUntil(this.unsubscribe),
+ tap((urlService: NewUrlStateNotificationService) => {
+ if (urlService.hasValue(UrlPathId.APPLICATION)) {
+ const selectedApp: IApplication = urlService.getPathValue(UrlPathId.APPLICATION);
+
+ this.selectedAppName = selectedApp.getApplicationName();
+ this.selectedAppImg = this.funcImagePath(selectedApp.getServiceType());
+ this.isAppActivated = !urlService.hasValue(UrlPathId.AGENT_ID);
+ }
+ }),
map((urlService: NewUrlStateNotificationService) => {
return urlService.isRealTimeMode() || urlService.hasValue(UrlPathId.END_TIME);
})
@@ -78,6 +93,17 @@ export class InspectorPageComponent implements OnInit, OnDestroy {
this.unsubscribe.complete();
}
+ onSelectApp(): void {
+ this.urlRouteManagerService.moveOnPage({
+ url: [
+ UrlPath.INSPECTOR,
+ this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(),
+ this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(),
+ this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(),
+ ]
+ })
+ }
+
onShowHelp($event: MouseEvent): void {
this.analyticsService.trackEvent(TRACKED_EVENT_LIST.TOGGLE_HELP_VIEWER, HELP_VIEWER_LIST.NAVBAR);
const {left, top, width, height} = ($event.target as HTMLElement).getBoundingClientRect();
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 6641efdf00db..dc88eccd3504 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,4 @@
import { Injectable } from '@angular/core';
-import { map, tap, takeUntil } from 'rxjs/operators';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import {
@@ -11,12 +10,6 @@ import {
import { UrlPath, UrlPathId } from 'app/shared/models';
import { Timeline } from 'app/core/components/timeline/class';
-export interface ISourceForServerAndAgentList {
- range: number[];
- agentId: string;
- emitAfter: number;
-}
-
export interface ISourceForTimeline {
timelineInfo: ITimelineInfo;
agentId?: string;
@@ -37,7 +30,6 @@ export interface ISourceForTimelineCommand {
@Injectable()
export class InspectorPageService {
- private sourceForServerAndAgentList = new BehaviorSubject(null);
private sourceForTimeline = new Subject();
private sourceForAgentInfo = new Subject();
private sourceForChart = new Subject();
@@ -46,7 +38,6 @@ export class InspectorPageService {
private timelineInfo: ITimelineInfo;
private agentId: string;
- sourceForServerAndAgentList$: Observable;
sourceForTimeline$: Observable;
sourceForAgentInfo$: Observable;
sourceForChart$: Observable;
@@ -57,7 +48,6 @@ export class InspectorPageService {
private webAppSettingDataService: WebAppSettingDataService,
private messageQueueService: MessageQueueService,
) {
- this.sourceForServerAndAgentList$ = this.sourceForServerAndAgentList.asObservable();
this.sourceForTimeline$ = this.sourceForTimeline.asObservable();
this.sourceForAgentInfo$ = this.sourceForAgentInfo.asObservable();
this.sourceForChart$ = this.sourceForChart.asObservable();
@@ -65,27 +55,8 @@ export class InspectorPageService {
}
activate(unsubscribe: Subject): void {
- this.newUrlStateNotificationService.onUrlStateChange$.pipe(
- takeUntil(unsubscribe),
- tap((urlService: NewUrlStateNotificationService) => {
- this.agentId = urlService.getPathValue(UrlPathId.AGENT_ID);
- }),
- map((urlService: NewUrlStateNotificationService) => {
- if (urlService.isRealTimeMode()) {
- const to = urlService.getUrlServerTimeData();
- const from = to - this.webAppSettingDataService.getSystemDefaultPeriod().getMiliSeconds();
-
- return [from, to];
- } else {
- return [urlService.getStartTimeToNumber(), urlService.getEndTimeToNumber()];
- }
-
- })
- ).subscribe((range: number[]) => {
- this.notifyToServerAndAgentList(range);
- });
-
- this.messageQueueService.receiveMessage(unsubscribe, MESSAGE_TO.INSPECTOR_PAGE_VALID).subscribe(({range, now}: {range: number[], now: number}) => {
+ this.messageQueueService.receiveMessage(unsubscribe, MESSAGE_TO.AGENT_LIST_VALID).subscribe(({range, now, agentId}: {range: number[], now: number, agentId: string}) => {
+ this.agentId = agentId;
this.notifyToTimeline(range);
this.notifyToTimelineCommand();
this.notifyToAgentInfo();
@@ -99,7 +70,13 @@ export class InspectorPageService {
const from = to - this.webAppSettingDataService.getSystemDefaultPeriod().getMiliSeconds();
const emitAfter = isDelayed ? 0 : reservedNextTo - now;
- this.notifyToServerAndAgentList([from, to], emitAfter);
+ this.messageQueueService.sendMessage({
+ to: MESSAGE_TO.FETCH_AGENT_LIST,
+ param: {
+ range: [from, to],
+ emitAfter
+ }
+ })
}
});
}
@@ -151,14 +128,6 @@ export class InspectorPageService {
});
}
- private notifyToServerAndAgentList(range: number[], emitAfter = 0): void {
- this.sourceForServerAndAgentList.next({
- range,
- agentId: this.agentId,
- emitAfter
- });
- }
-
private notifyToAgentInfo(): void {
this.sourceForAgentInfo.next({
selectedTime: this.timelineInfo.selectedTime,
diff --git a/web/src/main/angular/src/app/routes/url-statistic-page/index.ts b/web/src/main/angular/src/app/routes/url-statistic-page/index.ts
index 63ffc8d03e1c..941d444f0009 100644
--- a/web/src/main/angular/src/app/routes/url-statistic-page/index.ts
+++ b/web/src/main/angular/src/app/routes/url-statistic-page/index.ts
@@ -3,7 +3,12 @@ import { NgModule } from '@angular/core';
import { SharedModule } from 'app/shared';
import { UrlStatisticPageRoutingModule } from './url-statistic-page.routing';
import { UrlStatisticPageComponent } from './url-statistic-page.component';
-import { UrlStatisticModule } from 'app/core/components/url-statistic';
+import { ApplicationListModule } from 'app/core/components/application-list';
+import { NoticeModule } from 'app/core/components/notice';
+import { PeriodSelectorModule } from 'app/core/components/period-selector';
+import { ServerAndAgentListModule } from 'app/core/components/server-and-agent-list';
+import { SideNavigationBarModule } from 'app/core/components/side-navigation-bar';
+import { UrlStatisticContentsModule } from 'app/core/components/url-statistic-contents';
@NgModule({
declarations: [
@@ -11,8 +16,13 @@ import { UrlStatisticModule } from 'app/core/components/url-statistic';
],
imports: [
SharedModule,
+ NoticeModule,
+ SideNavigationBarModule,
+ ApplicationListModule,
+ PeriodSelectorModule,
+ ServerAndAgentListModule,
+ UrlStatisticContentsModule,
UrlStatisticPageRoutingModule,
- UrlStatisticModule
],
exports: [],
providers: []
diff --git a/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.css b/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.css
index adb41ad830a2..4ec547938f65 100644
--- a/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.css
+++ b/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.css
@@ -1,9 +1,80 @@
:host {
display: block;
+ width: 100%;
height: 100%;
- background-color: var(--background-primary);
}
-.l-main-container {
+.container {
+ display: flex;
+}
+
+.widget-group {
+ display: flex;
+ padding: 0 15px;
+ height: 50px;
+ width: 100%;
+ border-bottom: 1px solid var(--border-primary);
+ align-items: center;
+ background: var(--background-default);
+}
+
+.main-container {
+ display: flex;
+ flex-flow: row wrap;
+ height: 100vh;
+ width: 100%;
+ overflow-y: hidden;
+}
+
+.sidemenu-wrap {
+ position: relative;
+ height: calc(100% - 50px);
+}
+
+.sidemenu-left {
+ width: 250px;
+ overflow: visible;
+ border-right: 1px solid var(--border-primary);
+ background: var(--background-default);
+ height: 100%;
+}
+
+.main-section {
+ /* flex: 1; */
+ background: var(--background-primary);
+ position: relative;
height: calc(100% - 50px);
}
+
+.selected-app-wrapper {
+ display: flex;
+ border-bottom: 1px solid var(--border-primary);
+ height: 40px;
+ gap: 4px;
+ padding: 0 6px 0 13px;
+ align-items: center;
+}
+
+.selected-app-wrapper.active {
+ color: var(--primary);
+ font-weight: 600;
+}
+
+.selected-app-wrapper:hover {
+ cursor: pointer;
+ color: var(--primary);
+ background-color: var(--background-hover-default);
+}
+
+.app-img {
+ width: 22px;
+ height: 18px;
+}
+
+.selected-app-name {
+ flex: 1;
+ font-size: 14px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
diff --git a/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.html b/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.html
index 23fbeab94740..fe6ab85fb62e 100644
--- a/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.html
+++ b/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.html
@@ -1,6 +1,23 @@
-
-
+
+
+
+
diff --git a/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.ts b/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.ts
index abb762f1a7f9..71a8df6394d8 100644
--- a/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.ts
+++ b/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.component.ts
@@ -1,11 +1,77 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Observable, Subject } from 'rxjs';
+import { takeUntil, map, tap } from 'rxjs/operators';
+
+import { NewUrlStateNotificationService, UrlRouteManagerService, WebAppSettingDataService } from 'app/shared/services';
+import { UrlPath, UrlPathId } from 'app/shared/models';
@Component({
selector: 'pp-url-statistic-page',
templateUrl: './url-statistic-page.component.html',
styleUrls: ['./url-statistic-page.component.css']
})
-export class UrlStatisticPageComponent implements OnInit {
- constructor() {}
- ngOnInit() {}
+export class UrlStatisticPageComponent implements OnInit, OnDestroy {
+ private unsubscribe = new Subject();
+ private funcImagePath: Function;
+
+ showUrlStat$: Observable;
+ showSideMenu: boolean;
+ mainSectionStyle = {};
+ isAppActivated: boolean;
+ selectedAppImg: string;
+ selectedAppName: string;
+
+ constructor(
+ private newUrlStateNotificationService: NewUrlStateNotificationService,
+ private webAppSettingDataService: WebAppSettingDataService,
+ private urlRouteManagerService: UrlRouteManagerService,
+ ) {}
+
+ ngOnInit() {
+ this.showUrlStat$ = this.webAppSettingDataService.showUrlStat().pipe(
+ tap((showUrlStat) => {
+ if (!showUrlStat) {
+ this.urlRouteManagerService.moveToMain();
+ }
+ })
+ );
+
+ this.funcImagePath = this.webAppSettingDataService.getIconPathMakeFunc();
+ this.newUrlStateNotificationService.onUrlStateChange$.pipe(
+ takeUntil(this.unsubscribe),
+ tap((urlService: NewUrlStateNotificationService) => {
+ if (urlService.hasValue(UrlPathId.APPLICATION)) {
+ const selectedApp: IApplication = urlService.getPathValue(UrlPathId.APPLICATION);
+
+ this.selectedAppName = selectedApp.getApplicationName();
+ this.selectedAppImg = this.funcImagePath(selectedApp.getServiceType());
+ this.isAppActivated = !urlService.hasValue(UrlPathId.AGENT_ID);
+ }
+ }),
+ map((urlService: NewUrlStateNotificationService) => {
+ return urlService.isRealTimeMode() || urlService.hasValue(UrlPathId.END_TIME);
+ })
+ ).subscribe((showSideMenu: boolean) => {
+ this.showSideMenu = showSideMenu;
+ this.mainSectionStyle = {
+ width: showSideMenu ? 'calc(100% - 250px)' : '100%'
+ };
+ });
+ }
+
+ ngOnDestroy() {
+ this.unsubscribe.next();
+ this.unsubscribe.complete();
+ }
+
+ onSelectApp(): void {
+ this.urlRouteManagerService.moveOnPage({
+ url: [
+ UrlPath.URL_STATISTIC,
+ this.newUrlStateNotificationService.getPathValue(UrlPathId.APPLICATION).getUrlStr(),
+ this.newUrlStateNotificationService.getPathValue(UrlPathId.PERIOD).getValueWithTime(),
+ this.newUrlStateNotificationService.getPathValue(UrlPathId.END_TIME).getEndTime(),
+ ]
+ })
+ }
}
diff --git a/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.routing.ts b/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.routing.ts
index 9cb3104a525d..fb3ce67225c8 100644
--- a/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.routing.ts
+++ b/web/src/main/angular/src/app/routes/url-statistic-page/url-statistic-page.routing.ts
@@ -1,13 +1,72 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
+import { UrlStatisticContentsContainerComponent } from 'app/core/components/url-statistic-contents/url-statistic-contents-container.component';
+import { EmptyContentsComponent } from 'app/shared/components/empty-contents';
+import { UrlRedirectorComponent } from 'app/shared/components/url-redirector';
+import { UrlPath, UrlPathId } from 'app/shared/models';
import { UrlStatisticPageComponent } from './url-statistic-page.component';
const routes: Routes = [
{
path: '',
- component: UrlStatisticPageComponent
- }
+ component: UrlStatisticPageComponent,
+ children: [
+ {
+ path: ':' + UrlPathId.APPLICATION,
+ children: [
+ {
+ path: '',
+ pathMatch: 'full',
+ data: {
+ path: UrlPath.URL_STATISTIC
+ },
+ component: UrlRedirectorComponent
+ },
+ {
+ path: ':' + UrlPathId.PERIOD,
+ children: [
+ {
+ path: '',
+ pathMatch: 'full',
+ data: {
+ path: UrlPath.URL_STATISTIC
+ },
+ component: UrlRedirectorComponent
+ },
+ {
+ path: ':' + UrlPathId.END_TIME,
+ data: {
+ showRealTimeButton: false,
+ enableRealTimeMode: false
+ },
+ children: [
+ {
+ path: '',
+ pathMatch: 'full',
+ component: UrlStatisticContentsContainerComponent
+ },
+ {
+ path: ':' + UrlPathId.AGENT_ID,
+ component: UrlStatisticContentsContainerComponent
+ }
+ ]
+ }
+ ]
+ },
+ ]
+ },
+ {
+ path: '',
+ pathMatch: 'full',
+ data: {
+ showRealTimeButton: false,
+ enableRealTimeMode: false
+ },
+ component: EmptyContentsComponent
+ }
+ ]
+ },
];
@NgModule({
diff --git a/web/src/main/angular/src/app/shared/services/analytics.service.ts b/web/src/main/angular/src/app/shared/services/analytics.service.ts
index dbb9c1778c4b..edcdb656cc77 100644
--- a/web/src/main/angular/src/app/shared/services/analytics.service.ts
+++ b/web/src/main/angular/src/app/shared/services/analytics.service.ts
@@ -60,8 +60,7 @@ export enum TRACKED_EVENT_LIST {
CLICK_FIXED_PERIOD_MOVE_BUTTON = 'Click Fixed Period Move Button',
OPEN_INSPECTOR_WITH_AGENT = 'Open Inspector with Agent',
SELECT_AREA_ON_SCATTER = 'Select Area on Scatter',
- GO_TO_APPLICATION_INSPECTOR = 'Go to Application Inspector',
- GO_TO_AGENT_INSPECTOR = 'Go To Agent Inspector',
+ SELECT_AGENT_ON_THE_LIST = 'Select Agent on the List',
ZOOM_IN_TIMELINE = 'Zoom in Timeline',
ZOOM_OUT_TIMELINE = 'Zoom out Timeline',
MOVE_TO_PREV_ON_TIMELINE = 'Move to Prev on Timeline',
diff --git a/web/src/main/angular/src/app/shared/services/component-default-setting-data.service.ts b/web/src/main/angular/src/app/shared/services/component-default-setting-data.service.ts
index 39463c3a582d..ec02632a0429 100644
--- a/web/src/main/angular/src/app/shared/services/component-default-setting-data.service.ts
+++ b/web/src/main/angular/src/app/shared/services/component-default-setting-data.service.ts
@@ -50,6 +50,16 @@ export class ComponentDefaultSettingDataService {
new Period(10080),
new Period(20160),
new Period(40320),
+ ],
+ [UrlPath.URL_STATISTIC]: [
+ new Period(5, 'Last'),
+ new Period(20),
+ new Period(60),
+ new Period(720),
+ new Period(1440),
+ new Period(10080),
+ new Period(20160),
+ new Period(40320),
]
};
private maxPeriodTime = 60 * 24 * 2; // 2day
@@ -116,6 +126,17 @@ export class ComponentDefaultSettingDataService {
'Mapped Buffer Memory',
'Data Source'
];
+ private urlStatFieldNameList = [
+ '0 ~ 100ms',
+ '100 ~ 300ms',
+ '300 ~ 500ms',
+ '500 ~ 1000ms',
+ '1000 ~ 3000ms',
+ '3000 ~ 5000ms',
+ '5000 ~ 8000ms',
+ '8000ms ~ ',
+ ];
+
constructor() {}
getInboundList(): number[] {
return this.inboundList;
@@ -168,4 +189,7 @@ export class ComponentDefaultSettingDataService {
getAgentInspectorDefaultChartOrderList(): string[] {
return this.agentChartOrderList;
}
+ getUrlStatFieldNameList(): string[] {
+ return this.urlStatFieldNameList;
+ }
}
diff --git a/web/src/main/angular/src/app/shared/services/message-queue.service.ts b/web/src/main/angular/src/app/shared/services/message-queue.service.ts
index 42340a809146..74cd89f6f985 100644
--- a/web/src/main/angular/src/app/shared/services/message-queue.service.ts
+++ b/web/src/main/angular/src/app/shared/services/message-queue.service.ts
@@ -43,7 +43,6 @@ export enum MESSAGE_TO {
REAL_TIME_SCATTER_CHART_X_RANGE = 'REAL_TIME_SCATTER_CHART_X_RANGE',
- INSPECTOR_PAGE_VALID = 'INSPECTOR_PAGE_VALID',
INSPECTOR_CHART_MANAGER_ADD = 'INSPECTOR_CHART_MANAGER_ADD',
INSPECTOR_CHART_MANAGER_REMOVE = 'INSPECTOR_CHART_MANAGER_REMOVE',
INSPECTOR_CHART_MANAGER_CHANGE_ORDER = 'INSPECTOR_CHART_MANAGER_CHANGE_ORDER',
@@ -64,4 +63,9 @@ export enum MESSAGE_TO {
IS_ACCESS_DENYED = 'IS_ACCESS_DENYED',
SET_CHART_LAYOUT = 'SET_CHART_LAYOUT',
+
+ AGENT_LIST_VALID = 'AGENT_LIST_VALID',
+ FETCH_AGENT_LIST = 'FETCH_AGENT_LIST',
+
+ SELECT_URL_INFO = 'SELECT_URL_INFO'
}
diff --git a/web/src/main/angular/src/app/shared/services/system-configuration-data.service.ts b/web/src/main/angular/src/app/shared/services/system-configuration-data.service.ts
index dddf69131345..b45c971cc76d 100644
--- a/web/src/main/angular/src/app/shared/services/system-configuration-data.service.ts
+++ b/web/src/main/angular/src/app/shared/services/system-configuration-data.service.ts
@@ -43,6 +43,7 @@ export class SystemConfigurationDataService {
userName: '',
userDepartment: '',
showSystemMetric: false,
+ showUrlStat: false
};
constructor(
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 16ee212fd8b3..2ea876deb848 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
@@ -80,6 +80,9 @@ export class WebAppSettingDataService {
showMetric(): Observable {
return this.newUrlStateNotificationService.getConfiguration('showSystemMetric');
}
+ showUrlStat(): Observable {
+ return this.newUrlStateNotificationService.getConfiguration('showUrlStat');
+ }
getImagePath(): string {
return this.IMAGE_PATH;
}
@@ -302,4 +305,7 @@ export class WebAppSettingDataService {
getExperimentalConfiguration(): Observable {
return this.newUrlStateNotificationService.getConfiguration('experimental');
}
+ getUrlStatFieldNameList(): string[] {
+ return this.componentDefaultSettingDataService.getUrlStatFieldNameList();
+ }
}
diff --git a/web/src/main/angular/src/assets/i18n/en.json b/web/src/main/angular/src/assets/i18n/en.json
index 6a1ad83a3229..d73207d490cc 100644
--- a/web/src/main/angular/src/assets/i18n/en.json
+++ b/web/src/main/angular/src/assets/i18n/en.json
@@ -29,6 +29,9 @@
"SELECT_HOST_GROUP": "Select your host-group",
"INPUT_HOST_GROUP_NAME_PLACE_HOLDER": "Input host-group name"
},
+ "URL_STAT": {
+ "SELECT_URL_INFO": "Select a URL info below"
+ },
"INSPECTOR": {
"APPLICATION_INSPECTOR_USAGE_GUIDE_MESSAGE": "[TEXT]:{value=Application Inspector is not enabled.
To enable Application Inspector, please refer to}|[LINK]:{href=https://pinpoint-apm.gitbook.io/pinpoint/documents/application-inspector\\target=blank\\style=color:#428bca\\linkText=this link}",
"APPLICAITION_NAME_ISSUE": {
diff --git a/web/src/main/angular/src/assets/i18n/ko.json b/web/src/main/angular/src/assets/i18n/ko.json
index b59a7d07d231..88f78f21b6cf 100644
--- a/web/src/main/angular/src/assets/i18n/ko.json
+++ b/web/src/main/angular/src/assets/i18n/ko.json
@@ -29,6 +29,9 @@
"SELECT_HOST_GROUP": "호스트 그룹을 선택하세요.",
"INPUT_HOST_GROUP_NAME_PLACE_HOLDER": "호스트 그룹 이름을 입력하세요."
},
+ "URL_STAT": {
+ "SELECT_URL_INFO": "아래에서 URL 정보를 선택하세요."
+ },
"INSPECTOR": {
"APPLICATION_INSPECTOR_USAGE_GUIDE_MESSAGE": "[TEXT]:{value=Application Inspector 기능이 활성화 되어 있지 않습니다.
Application Inspector 기능을 사용하려면,}|[LINK]:{href=https://pinpoint-apm.gitbook.io/pinpoint/documents/application-inspector\\target=_blank\\style=color:#428bca\\linkText=링크}|[TEXT]:{value=를 참고하세요.}",
"APPLICAITION_NAME_ISSUE": {
diff --git a/web/src/main/angular/src/globals.d.ts b/web/src/main/angular/src/globals.d.ts
index da73e3f0bcbc..41083a62923b 100644
--- a/web/src/main/angular/src/globals.d.ts
+++ b/web/src/main/angular/src/globals.d.ts
@@ -412,6 +412,7 @@ interface ISystemConfiguration {
userName?: string;
userDepartment?: string;
showSystemMetric: boolean;
+ showUrlStat: boolean;
}
interface IFormFieldErrorType {
@@ -457,15 +458,26 @@ interface IMetricData {
unit: string;
}
-// interface IMetricData {
-// title: string;
-// timestamp: number[];
-// metricValues: IMetricValue[];
-// }
-
interface IMetricValue {
fieldName: string;
// tagList: {name: string, value: string}[];
tags?: any[]; // TODO: Check format
values: number[];
}
+
+interface IUrlStatInfoData {
+ uri: string;
+ totalCount: number;
+ failureCount: number;
+ maxTimeMs: number;
+ avgTimeMs: number;
+}
+
+interface IUrlStatChartData {
+ title: string;
+ timestamp: number[];
+ metricValueGroups: {
+ groupName: string;
+ metricValues: IMetricValue[]
+ }[];
+}
\ No newline at end of file
diff --git a/web/src/main/angular/src/main.css b/web/src/main/angular/src/main.css
index c9ee64edb801..2c3f4abda0be 100644
--- a/web/src/main/angular/src/main.css
+++ b/web/src/main/angular/src/main.css
@@ -22,7 +22,16 @@ body {
--emerald-green-800: #006843;
--emerald-green-900: #004c29;
/* green */
- --green-default: #41c464;
+ --green-50: #e5f7e9;
+ --green-100: #c1eac8;
+ --green-200: #98dca5;
+ --green-300: #6acf80;
+ --green-400: #41c464;
+ --green-500: #00b947;
+ --green-600: #00a93d;
+ --green-700: #009731;
+ --green-800: #008626;
+ --green-900: #006612;
/* yellow */
--yellow-50: #ffffe6;
--yellow-100: #fcfdc0;
@@ -120,7 +129,17 @@ body.dark-mode {
--emerald-green-800: #acdfd0;
--emerald-green-900: #ddf2ed;
/* green */
- --green-default: #41c464;
+ --green-50: #006612;
+ --green-100: #008626;
+ --green-200: #009731;
+ --green-300: #00a93d;
+ --green-400: #00b947;
+ --green-500: #41c464;
+ --green-600: #6acf80;
+ --green-700: #98dca5;
+ --green-800: #c1eac8;
+ --green-900: #e5f7e9;
+
/* yellow */
--yellow-50: #ff8a00;
--yellow-100: #ffb700;
@@ -199,7 +218,7 @@ body {
--secondary: var(--emerald-green-300);
--secondary-lighter: var(--emerald-green-100);
- --status-success: var(--green-default);
+ --status-success: var(--green-400);
--status-good: var(--blue-500);
--status-warn: var(--orange-600);
--status-fail: var(--red-400);
@@ -295,7 +314,7 @@ body.dark-mode {
--secondary: var(--emerald-green-600);
--secondary-lighter: var(--emerald-green-100);
- --status-success: var(--green-default);
+ --status-success: var(--green-400);
--status-good: var(--blue-500);
--status-warn: var(--orange-600);
--status-fail: var(--red-400);
diff --git a/web/src/main/angular/src/proxy.conf.js b/web/src/main/angular/src/proxy.conf.js
index e3e0022d52a7..06003a5cf5a1 100644
--- a/web/src/main/angular/src/proxy.conf.js
+++ b/web/src/main/angular/src/proxy.conf.js
@@ -54,7 +54,6 @@ const PROXY_CONFIG = [
"/admin/removeAgentId.pinpoint",
"/admin/removeInactiveAgents.pinpoint",
"/bind.pinpoint",
- "/getAgentStat/uriStat/chartList.pinpoint",
"/heatmap/drag.pinpoint",
"/traceViewerData.pinpoint",
"/application/webhook.pinpoint",
@@ -68,7 +67,9 @@ const PROXY_CONFIG = [
"/systemMetric/hostGroup/host/collectedMetricData.pinpoint",
"/getApdexScore.pinpoint",
"/getApplicationStat/apdexScore/chart.pinpoint",
- "/getAgentStat/apdexScore/chart.pinpoint"
+ "/getAgentStat/apdexScore/chart.pinpoint",
+ "/uriStat/top50.pinpoint",
+ "/uriStat/chart.pinpoint"
],
target: 'http://localhost:8080',
secure: false
diff --git a/web/src/main/angular/src/styles.css b/web/src/main/angular/src/styles.css
index 06fbf485174c..202a09ffdc94 100644
--- a/web/src/main/angular/src/styles.css
+++ b/web/src/main/angular/src/styles.css
@@ -230,14 +230,11 @@ caption {
}
}
-.l-agent-inspector-contents .bb-axis-y-label,
-.l-agent-inspector-contents .bb-axis-y2-label,
-.l-application-inspector-contents .bb-axis-y-label,
-.l-transaction-view-top-contents .bb-axis-y-label,
-.l-transaction-view-top-contents .bb-axis-y2-label {
+.bb-axis-y-label,
+.bb-axis-y2-label {
text-anchor: middle !important;
font-family: 'Open Sans';
- font-size: 0.75vw;
+ font-size: 13px;
fill: var(--text-primary);
font-weight: bold;
}
@@ -409,7 +406,8 @@ caption {
opacity: 1 !important;
}
-.l-load-chart .bb-line {
+.l-load-chart .bb-line,
+.l-url-statistic-chart .bb-line {
stroke-width: 0;
}