diff --git a/components/automate-ui/src/app/entities/desktop/desktop.actions.ts b/components/automate-ui/src/app/entities/desktop/desktop.actions.ts index b045c93a288..5bfabd4e41c 100644 --- a/components/automate-ui/src/app/entities/desktop/desktop.actions.ts +++ b/components/automate-ui/src/app/entities/desktop/desktop.actions.ts @@ -1,16 +1,20 @@ import { Action } from '@ngrx/store'; import { HttpErrorResponse } from '@angular/common/http'; -import { DailyCheckInCountCollection, TopErrorsCollection } from './desktop.model'; +import { DailyCheckInCountCollection, + TopErrorsCollection, CountedDurationCollection } from './desktop.model'; export enum DesktopActionTypes { - GET_DAILY_CHECK_IN_TIME_SERIES = 'DESKTOP::GET::DAILY_CHECK_IN_TIME_SERIES', - GET_DAILY_CHECK_IN_TIME_SERIES_SUCCESS = 'DESKTOP::GET::DAILY_CHECK_IN_TIME_SERIES::SUCCESS', - GET_DAILY_CHECK_IN_TIME_SERIES_FAILURE = 'DESKTOP::GET::DAILY_CHECK_IN_TIME_SERIES::FAILURE', - SET_DAYS_AGO_SELECTED = 'DESKTOP::SET::DAYS_AGO_SELECTED', - GET_TOP_ERRORS_COLLECTION = 'DESKTOP::GET::TOP_ERRORS_COLLECTION', - GET_TOP_ERRORS_COLLECTION_SUCCESS = 'DESKTOP::GET::TOP_ERRORS_COLLECTION::SUCCESS', - GET_TOP_ERRORS_COLLECTION_FAILURE = 'DESKTOP::GET::TOP_ERRORS_COLLECTION::FAILURE' + GET_DAILY_CHECK_IN_TIME_SERIES = 'DESKTOP::GET::DAILY_CHECK_IN_TIME_SERIES', + GET_DAILY_CHECK_IN_TIME_SERIES_SUCCESS = 'DESKTOP::GET::DAILY_CHECK_IN_TIME_SERIES::SUCCESS', + GET_DAILY_CHECK_IN_TIME_SERIES_FAILURE = 'DESKTOP::GET::DAILY_CHECK_IN_TIME_SERIES::FAILURE', + SET_DAYS_AGO_SELECTED = 'DESKTOP::SET::DAYS_AGO_SELECTED', + GET_TOP_ERRORS_COLLECTION = 'DESKTOP::GET::TOP_ERRORS_COLLECTION', + GET_TOP_ERRORS_COLLECTION_SUCCESS = 'DESKTOP::GET::TOP_ERRORS_COLLECTION::SUCCESS', + GET_TOP_ERRORS_COLLECTION_FAILURE = 'DESKTOP::GET::TOP_ERRORS_COLLECTION::FAILURE', + GET_UNKNOWN_DESKTOP_DURATION_COUNTS = 'DESKTOP::GET::UNKNOWN_DESKTOP_DURATION_COUNT', + GET_UNKNOWN_DESKTOP_DURATION_COUNTS_SUCCESS = 'DESKTOP::GET::UNKNOWN_DESKTOP_DURATION_COUNT::SUCCESS', + GET_UNKNOWN_DESKTOP_DURATION_COUNTS_FAILURE = 'DESKTOP::GET::UNKNOWN_DESKTOP_DURATION_COUNT::FAILURE' } export class SetDaysAgoSelected implements Action { @@ -46,6 +50,20 @@ export class GetTopErrorsCollectionFailure implements Action { constructor(public payload: HttpErrorResponse) { } } +export class GetUnknownDesktopDurationCounts implements Action { + readonly type = DesktopActionTypes.GET_UNKNOWN_DESKTOP_DURATION_COUNTS; +} + +export class GetUnknownDesktopDurationCountsSuccess implements Action { + readonly type = DesktopActionTypes.GET_UNKNOWN_DESKTOP_DURATION_COUNTS_SUCCESS; + constructor(public payload: CountedDurationCollection) { } +} + +export class GetUnknownDesktopDurationCountsFailure implements Action { + readonly type = DesktopActionTypes.GET_UNKNOWN_DESKTOP_DURATION_COUNTS_FAILURE; + constructor(public payload: HttpErrorResponse) { } +} + export type DesktopActions = | SetDaysAgoSelected | GetDailyCheckInTimeSeries @@ -53,4 +71,7 @@ export type DesktopActions = | GetDailyCheckInTimeSeriesFailure | GetTopErrorsCollection | GetTopErrorsCollectionSuccess - | GetTopErrorsCollectionFailure; + | GetTopErrorsCollectionFailure + | GetUnknownDesktopDurationCounts + | GetUnknownDesktopDurationCountsSuccess + | GetUnknownDesktopDurationCountsFailure; diff --git a/components/automate-ui/src/app/entities/desktop/desktop.effects.ts b/components/automate-ui/src/app/entities/desktop/desktop.effects.ts index 8095e95d75a..45368b3d68a 100644 --- a/components/automate-ui/src/app/entities/desktop/desktop.effects.ts +++ b/components/automate-ui/src/app/entities/desktop/desktop.effects.ts @@ -12,7 +12,10 @@ import { DesktopActionTypes, GetTopErrorsCollection, GetTopErrorsCollectionSuccess, - GetTopErrorsCollectionFailure + GetTopErrorsCollectionFailure, + GetUnknownDesktopDurationCounts, + GetUnknownDesktopDurationCountsSuccess, + GetUnknownDesktopDurationCountsFailure } from './desktop.actions'; import { DesktopRequests } from './desktop.requests'; import { CreateNotification } from 'app/entities/notifications/notification.actions'; @@ -78,4 +81,26 @@ export class DesktopEffects { message: `Could not get top errors: ${msg || error}` }); })); + + @Effect() + getUnknownDesktopDurationCounts$ = + this.actions$.pipe(ofType( + DesktopActionTypes.GET_UNKNOWN_DESKTOP_DURATION_COUNTS), + mergeMap((_action) => + this.requests.getUnknownDesktopDurationCounts().pipe( + map(countedDurationCollection => + new GetUnknownDesktopDurationCountsSuccess(countedDurationCollection)), + catchError((error) => of(new GetUnknownDesktopDurationCountsFailure(error))))) + ); + + @Effect() + getUnknownDesktopDurationCountsFailure$ = this.actions$.pipe( + ofType(DesktopActionTypes.GET_UNKNOWN_DESKTOP_DURATION_COUNTS_FAILURE), + map(({ payload: { error } }: GetUnknownDesktopDurationCountsFailure) => { + const msg = error.error; + return new CreateNotification({ + type: Type.error, + message: `Could not get unknown desktop duration counts errors: ${msg || error}` + }); + })); } diff --git a/components/automate-ui/src/app/entities/desktop/desktop.model.ts b/components/automate-ui/src/app/entities/desktop/desktop.model.ts index f232796dcb1..45099435ae6 100644 --- a/components/automate-ui/src/app/entities/desktop/desktop.model.ts +++ b/components/automate-ui/src/app/entities/desktop/desktop.model.ts @@ -25,3 +25,13 @@ export interface TopErrorsItem { type: string; message: string; } + +export interface CountedDurationCollection { + items: CountedDurationItem[]; + updated: Date; +} + +export interface CountedDurationItem { + duration: string; + count: number; +} diff --git a/components/automate-ui/src/app/entities/desktop/desktop.reducer.ts b/components/automate-ui/src/app/entities/desktop/desktop.reducer.ts index 3b4240ec948..b7ba4c4ad1b 100644 --- a/components/automate-ui/src/app/entities/desktop/desktop.reducer.ts +++ b/components/automate-ui/src/app/entities/desktop/desktop.reducer.ts @@ -2,7 +2,8 @@ import { set, pipe } from 'lodash/fp'; import { EntityStatus } from '../entities'; import { DesktopActionTypes, DesktopActions } from './desktop.actions'; -import { DailyCheckInCountCollection, TopErrorsCollection } from './desktop.model'; +import { DailyCheckInCountCollection, TopErrorsCollection, + CountedDurationCollection } from './desktop.model'; export interface DesktopEntityState { dailyCheckInCountCollection: DailyCheckInCountCollection; @@ -10,6 +11,8 @@ export interface DesktopEntityState { selectedDaysAgo: number; topErrorCollection: TopErrorsCollection; getTopErrorCollectionStatus: EntityStatus; + unknownDesktopDurationCounts: CountedDurationCollection; + getUnknownDesktopDurationCountsStatus: EntityStatus; } export const desktopEntityInitialState: DesktopEntityState = { @@ -17,7 +20,9 @@ export const desktopEntityInitialState: DesktopEntityState = { getDailyCheckInTimeSeriesStatus: EntityStatus.notLoaded, selectedDaysAgo: 3, topErrorCollection: {items: [], updated: new Date(0)}, - getTopErrorCollectionStatus: EntityStatus.notLoaded + getTopErrorCollectionStatus: EntityStatus.notLoaded, + unknownDesktopDurationCounts: {items: [], updated: new Date(0)}, + getUnknownDesktopDurationCountsStatus: EntityStatus.notLoaded }; export function userEntityReducer(state: DesktopEntityState = desktopEntityInitialState, @@ -49,6 +54,17 @@ export function userEntityReducer(state: DesktopEntityState = desktopEntityIniti case DesktopActionTypes.GET_TOP_ERRORS_COLLECTION_FAILURE: return set('getTopErrorCollectionStatus', EntityStatus.loadingFailure, state); + case DesktopActionTypes.GET_UNKNOWN_DESKTOP_DURATION_COUNTS: + return set('getUnknownDesktopDurationCountsStatus', EntityStatus.loading, state); + + case DesktopActionTypes.GET_UNKNOWN_DESKTOP_DURATION_COUNTS_SUCCESS: + return pipe( + set('getUnknownDesktopDurationCountsStatus', EntityStatus.loadingSuccess), + set('unknownDesktopDurationCounts', action.payload))(state); + + case DesktopActionTypes.GET_UNKNOWN_DESKTOP_DURATION_COUNTS_FAILURE: + return set('getUnknownDesktopDurationCountsStatus', EntityStatus.loadingFailure, state); + default: return state; diff --git a/components/automate-ui/src/app/entities/desktop/desktop.requests.ts b/components/automate-ui/src/app/entities/desktop/desktop.requests.ts index 281af02db88..0f33227cfe4 100644 --- a/components/automate-ui/src/app/entities/desktop/desktop.requests.ts +++ b/components/automate-ui/src/app/entities/desktop/desktop.requests.ts @@ -1,9 +1,10 @@ import { map } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { DailyCheckInCountCollection, DailyCheckInCount, - TopErrorsCollection, TopErrorsItem } from './desktop.model'; + TopErrorsCollection, TopErrorsItem, + CountedDurationCollection, CountedDurationItem } from './desktop.model'; import { environment } from '../../../environments/environment'; const CONFIG_MGMT_URL = environment.config_mgmt_url; @@ -30,6 +31,15 @@ interface RespErrorItem { error_message: string; } +interface RespCountedDurationCollection { + counted_durations: RespCountedDurationItem[]; +} + +interface RespCountedDurationItem { + duration: string; + count: number; +} + @Injectable() export class DesktopRequests { @@ -48,6 +58,44 @@ export class DesktopRequests { this.createTopErrorCollection(respTopNodeErrors))); } + public getUnknownDesktopDurationCounts(): Observable { + const url = `${CONFIG_MGMT_URL}/stats/missing_node_duration_counts`; + const options = { + params: this.buildUnknownDesktopDurationCountsParams() + }; + + return this.http.get(url, options) + .pipe(map(respCountedDurationCollection => + this.createCountedDurationCollection(respCountedDurationCollection))); + } + + private buildUnknownDesktopDurationCountsParams(): HttpParams { + let searchParam: HttpParams = new HttpParams(); + + searchParam = searchParam.append('durations', '1M'); + searchParam = searchParam.append('durations', '2w'); + searchParam = searchParam.append('durations', '1w'); + searchParam = searchParam.append('durations', '3d'); + + return searchParam; + } + + private createCountedDurationCollection( + respCountedDurationCollection: RespCountedDurationCollection): CountedDurationCollection { + return { + items: respCountedDurationCollection.counted_durations.map( + respItem => this.createCountedDurationItem(respItem)), + updated: new Date() + }; + } + + private createCountedDurationItem(item: RespCountedDurationItem): CountedDurationItem { + return { + count: item.count, + duration: item.duration + }; + } + private createTopErrorCollection(respTopNodeErrors: RespTopNodeErrors): TopErrorsCollection { return { items: respTopNodeErrors.errors.map(respItem => this.createErrorItem(respItem)), diff --git a/components/automate-ui/src/app/entities/desktop/desktop.selectors.ts b/components/automate-ui/src/app/entities/desktop/desktop.selectors.ts index 176240b86e5..b16bb47e8b9 100644 --- a/components/automate-ui/src/app/entities/desktop/desktop.selectors.ts +++ b/components/automate-ui/src/app/entities/desktop/desktop.selectors.ts @@ -23,3 +23,8 @@ export const topErrorsCollection = createSelector( desktopState, (state) => state.topErrorCollection ); + +export const unknownDesktopDurationCounts = createSelector( + desktopState, + (state) => state.unknownDesktopDurationCounts +); diff --git a/components/automate-ui/src/app/modules/desktop/check-in-time-series/check-in-time-series.component.scss b/components/automate-ui/src/app/modules/desktop/check-in-time-series/check-in-time-series.component.scss index 7f0643055ec..0e2602f8843 100644 --- a/components/automate-ui/src/app/modules/desktop/check-in-time-series/check-in-time-series.component.scss +++ b/components/automate-ui/src/app/modules/desktop/check-in-time-series/check-in-time-series.component.scss @@ -24,6 +24,7 @@ font-family: Muli; font-size: 13px; font-weight: 100; + width: 500px; background-color: var(--chef-badge-primary-border); border: 1px solid $chef-blue-medium; border-radius: 4px; diff --git a/components/automate-ui/src/app/modules/desktop/daily-check-in/daily-check-in.component.scss b/components/automate-ui/src/app/modules/desktop/daily-check-in/daily-check-in.component.scss index 46377a2643b..b89d1a9d432 100644 --- a/components/automate-ui/src/app/modules/desktop/daily-check-in/daily-check-in.component.scss +++ b/components/automate-ui/src/app/modules/desktop/daily-check-in/daily-check-in.component.scss @@ -24,6 +24,7 @@ font-family: Muli; font-size: 13px; font-weight: 100; + width: 500px; background-color: $chef-med-grey; border: 1px solid $chef-blue-medium; border-radius: 4px; diff --git a/components/automate-ui/src/app/modules/desktop/dashboard/dashboard.component.html b/components/automate-ui/src/app/modules/desktop/dashboard/dashboard.component.html index cb4408b8e31..e08a448f47f 100644 --- a/components/automate-ui/src/app/modules/desktop/dashboard/dashboard.component.html +++ b/components/automate-ui/src/app/modules/desktop/dashboard/dashboard.component.html @@ -19,6 +19,10 @@ [topErrorsItems] = "topErrorsItems$ | async" [lastUpdated] = "topErrorsUpdated$ | async"> + + diff --git a/components/automate-ui/src/app/modules/desktop/dashboard/dashboard.component.ts b/components/automate-ui/src/app/modules/desktop/dashboard/dashboard.component.ts index dd6eca61702..4b4e62e3484 100644 --- a/components/automate-ui/src/app/modules/desktop/dashboard/dashboard.component.ts +++ b/components/automate-ui/src/app/modules/desktop/dashboard/dashboard.component.ts @@ -8,17 +8,19 @@ import { last, reverse } from 'lodash/fp'; import { GetDailyCheckInTimeSeries, SetDaysAgoSelected, - GetTopErrorsCollection + GetTopErrorsCollection, + GetUnknownDesktopDurationCounts } from 'app/entities/desktop/desktop.actions'; import { dailyCheckInCountCollection, getSelectedDaysAgo, - topErrorsCollection + topErrorsCollection, + unknownDesktopDurationCounts } from 'app/entities/desktop/desktop.selectors'; import { - DailyCheckInCount, DailyCheckInCountCollection, DayPercentage, TopErrorsItem + DailyCheckInCount, DailyCheckInCountCollection, DayPercentage, TopErrorsItem, CountedDurationItem } from 'app/entities/desktop/desktop.model'; @Component({ @@ -40,6 +42,8 @@ export class DashboardComponent implements OnInit { public topErrorsItems$: Observable; public checkInCountCollectedUpdated$: Observable; public topErrorsUpdated$: Observable; + public unknownDesktopCountedDurationItems$: Observable; + public unknownDesktopCountedDurationUpdated$: Observable; constructor( private store: Store @@ -48,6 +52,7 @@ export class DashboardComponent implements OnInit { ngOnInit() { this.store.dispatch(new GetDailyCheckInTimeSeries()); this.store.dispatch(new GetTopErrorsCollection()); + this.store.dispatch(new GetUnknownDesktopDurationCounts()); this.selectedDaysAgo$ = this.store.select(getSelectedDaysAgo); @@ -110,6 +115,15 @@ export class DashboardComponent implements OnInit { this.topErrorsUpdated$ = this.store.select(topErrorsCollection).pipe( map(collection => collection.updated) ); + + this.unknownDesktopCountedDurationItems$ = this.store.select(unknownDesktopDurationCounts).pipe( + map(counts => counts.items) + ); + + this.unknownDesktopCountedDurationUpdated$ = + this.store.select(unknownDesktopDurationCounts).pipe( + map(counts => counts.updated) + ); } handleDaysAgoChange(daysAgo: number) { diff --git a/components/automate-ui/src/app/modules/desktop/desktop.module.ts b/components/automate-ui/src/app/modules/desktop/desktop.module.ts index 86de956714a..d762c65e9b3 100644 --- a/components/automate-ui/src/app/modules/desktop/desktop.module.ts +++ b/components/automate-ui/src/app/modules/desktop/desktop.module.ts @@ -9,7 +9,9 @@ import { DailyCheckInComponent } from './daily-check-in/daily-check-in.component import { CheckInTimeSeriesComponent } from './check-in-time-series/check-in-time-series.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { TopErrorsComponent } from './top-errors/top-errors.component'; - +import { + UnknownDesktopDurationCountsComponent +} from './unknown-desktop-duration-counts/unknown-desktop-duration-counts.component'; import { DesktopRoutingModule } from './desktop-routing.module'; @NgModule({ @@ -25,13 +27,15 @@ import { DesktopRoutingModule } from './desktop-routing.module'; DailyCheckInComponent, DashboardComponent, CheckInTimeSeriesComponent, - TopErrorsComponent + TopErrorsComponent, + UnknownDesktopDurationCountsComponent ], declarations: [ DailyCheckInComponent, DashboardComponent, CheckInTimeSeriesComponent, - TopErrorsComponent + TopErrorsComponent, + UnknownDesktopDurationCountsComponent ], schemas: [ CUSTOM_ELEMENTS_SCHEMA ] }) diff --git a/components/automate-ui/src/app/modules/desktop/top-errors/top-errors.component.scss b/components/automate-ui/src/app/modules/desktop/top-errors/top-errors.component.scss index 0e118212f0c..65ac49b181b 100644 --- a/components/automate-ui/src/app/modules/desktop/top-errors/top-errors.component.scss +++ b/components/automate-ui/src/app/modules/desktop/top-errors/top-errors.component.scss @@ -18,6 +18,7 @@ background-color: $chef-med-grey; border: 1px solid $chef-blue-medium; border-radius: 4px; + width: 500px; } .update-time { diff --git a/components/automate-ui/src/app/modules/desktop/unknown-desktop-duration-counts/unknown-desktop-duration-counts.component.html b/components/automate-ui/src/app/modules/desktop/unknown-desktop-duration-counts/unknown-desktop-duration-counts.component.html new file mode 100644 index 00000000000..bbf580bfdab --- /dev/null +++ b/components/automate-ui/src/app/modules/desktop/unknown-desktop-duration-counts/unknown-desktop-duration-counts.component.html @@ -0,0 +1,19 @@ +
+
+
+
Unknown Duration
+
Updated {{ lastUpdatedMessage }}
+ + + + + + + + + +
{{ formatDuration(items.duration) }}{{ items.count }}
+ +
+
+
diff --git a/components/automate-ui/src/app/modules/desktop/unknown-desktop-duration-counts/unknown-desktop-duration-counts.component.scss b/components/automate-ui/src/app/modules/desktop/unknown-desktop-duration-counts/unknown-desktop-duration-counts.component.scss new file mode 100644 index 00000000000..6d1407b9e7a --- /dev/null +++ b/components/automate-ui/src/app/modules/desktop/unknown-desktop-duration-counts/unknown-desktop-duration-counts.component.scss @@ -0,0 +1,31 @@ +@import "~styles/variables"; + +.page-title { + color: var(--chef-primary-dark); + margin-top: 0; + margin-bottom: 12px; + font-weight: inherit; + font-style: inherit; + font-size: 18px; +} + +.content-container { + margin: 10px; + padding: 10px; + font-family: Muli; + font-size: 13px; + font-weight: 100; + background-color: var(--chef-badge-primary-border); + border: 1px solid $chef-blue-medium; + border-radius: 4px; + width: 500px; +} + +.update-time { + color: var(--chef-primary-dark); + margin-top: 0; + margin-bottom: 12px; + font-weight: inherit; + font-style: inherit; + font-size: 10px; +} diff --git a/components/automate-ui/src/app/modules/desktop/unknown-desktop-duration-counts/unknown-desktop-duration-counts.component.ts b/components/automate-ui/src/app/modules/desktop/unknown-desktop-duration-counts/unknown-desktop-duration-counts.component.ts new file mode 100644 index 00000000000..bdbf7e76c6c --- /dev/null +++ b/components/automate-ui/src/app/modules/desktop/unknown-desktop-duration-counts/unknown-desktop-duration-counts.component.ts @@ -0,0 +1,62 @@ +import { Component, Input, OnInit, OnDestroy, OnChanges, SimpleChange } from '@angular/core'; +import { Subject, timer } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { TimeFromNowPipe } from 'app/pipes/time-from-now.pipe'; + +import { + CountedDurationItem +} from 'app/entities/desktop/desktop.model'; + +@Component({ + selector: 'app-unknown-desktop-duration-counts', + templateUrl: './unknown-desktop-duration-counts.component.html', + styleUrls: ['./unknown-desktop-duration-counts.component.scss'] +}) +export class UnknownDesktopDurationCountsComponent implements OnInit, OnDestroy, OnChanges { + + @Input() countedDurationItems: CountedDurationItem[]; + @Input() lastUpdated: Date; + + private isDestroyed = new Subject(); + public lastUpdatedMessage = '-'; + private timeFromNowPipe = new TimeFromNowPipe(); + private minute = 6000; + + constructor() { } + + ngOnInit(): void { + timer(1, this.minute).pipe( + takeUntil(this.isDestroyed) + ).subscribe(() => { + this.lastUpdatedMessage = this.timeFromNowPipe.transform(this.lastUpdated); + }); + } + + ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { + if (changes['lastUpdated']) { + this.lastUpdatedMessage = this.timeFromNowPipe.transform(changes['lastUpdated'].currentValue); + } + } + + ngOnDestroy(): void { + this.isDestroyed.next(true); + this.isDestroyed.complete(); + } + + // Make this more general once we don't have hard coded durations. + formatDuration(duration: string): string { + switch (duration) { + case '1M': + return 'Over a month'; + case '2w': + return '2 Weeks'; + case '1w': + return '1 Week'; + case '3d': + return '3 Days'; + + default: + return duration; + } + } +}