AMBARI-22269 Log Search UI: provide navigation between Service and Audit Logs. (ababiichuk)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/15cec1cb Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/15cec1cb Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/15cec1cb Branch: refs/heads/trunk Commit: 15cec1cb7d6a43361c82e2de31bf320a33005b37 Parents: b4eddc9 Author: ababiichuk <[email protected]> Authored: Thu Oct 19 15:11:48 2017 +0300 Committer: ababiichuk <[email protected]> Committed: Thu Oct 19 15:11:48 2017 +0300 ---------------------------------------------------------------------- .../ambari-logsearch-web/src/app/app.module.ts | 4 + .../src/app/classes/filtering.ts | 369 +++++++++++++++++++ .../src/app/classes/models/app-state.ts | 14 +- .../src/app/classes/models/store.ts | 12 +- .../src/app/classes/models/tab.ts | 53 +++ .../collapsible-panel.component.spec.ts | 14 +- .../dropdown-button.component.spec.ts | 18 +- .../dropdown-list.component.spec.ts | 7 +- .../filter-button.component.spec.ts | 18 +- .../filter-dropdown.component.spec.ts | 18 +- .../filters-panel/filters-panel.component.html | 17 +- .../filters-panel.component.spec.ts | 5 +- .../filters-panel/filters-panel.component.ts | 44 ++- .../log-context/log-context.component.spec.ts | 5 +- .../logs-container.component.html | 17 +- .../logs-container.component.less | 7 +- .../logs-container.component.spec.ts | 11 +- .../logs-container/logs-container.component.ts | 108 +++--- .../logs-list/logs-list.component.html | 24 +- .../components/logs-list/logs-list.component.ts | 25 +- .../main-container.component.html | 10 +- .../main-container.component.less | 4 - .../main-container.component.spec.ts | 15 +- .../main-container/main-container.component.ts | 51 +-- .../menu-button/menu-button.component.spec.ts | 18 +- .../pagination/pagination.component.html | 2 +- .../pagination/pagination.component.ts | 6 +- .../src/app/components/tabs/tabs.component.html | 25 ++ .../src/app/components/tabs/tabs.component.less | 22 ++ .../app/components/tabs/tabs.component.spec.ts | 125 +++++++ .../src/app/components/tabs/tabs.component.ts | 48 +++ .../time-histogram.component.less | 1 + .../time-range-picker.component.spec.ts | 13 +- .../time-range-picker.component.ts | 4 +- .../timezone-picker.component.spec.ts | 18 +- .../services/component-actions.service.spec.ts | 5 +- .../app/services/component-actions.service.ts | 33 +- .../component-generator.service.spec.ts | 7 +- .../src/app/services/filtering.service.spec.ts | 3 + .../src/app/services/filtering.service.ts | 341 +---------------- .../app/services/logs-container.service.spec.ts | 5 +- .../src/app/services/logs-container.service.ts | 60 ++- .../app/services/storage/reducers.service.ts | 4 +- .../src/app/services/storage/tabs.service.ts | 33 ++ .../src/assets/i18n/en.json | 2 + 45 files changed, 1080 insertions(+), 565 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts index 12b95a7..37cd869 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts @@ -53,6 +53,7 @@ import {ClustersService} from '@app/services/storage/clusters.service'; import {ComponentsService} from '@app/services/storage/components.service'; import {ServiceLogsFieldsService} from '@app/services/storage/service-logs-fields.service'; import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service'; +import {TabsService} from '@app/services/storage/tabs.service'; import {reducer} from '@app/services/storage/reducers.service'; import {AppComponent} from '@app/components/app.component'; @@ -80,6 +81,7 @@ import {TimeRangePickerComponent} from '@app/components/time-range-picker/time-r import {DatePickerComponent} from '@app/components/date-picker/date-picker.component'; import {LogContextComponent} from '@app/components/log-context/log-context.component'; import {LogFileEntryComponent} from '@app/components/log-file-entry/log-file-entry.component'; +import {TabsComponent} from '@app/components/tabs/tabs.component'; import {TimeZoneAbbrPipe} from '@app/pipes/timezone-abbr.pipe'; import {TimerSecondsPipe} from '@app/pipes/timer-seconds.pipe'; @@ -131,6 +133,7 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR DatePickerComponent, LogContextComponent, LogFileEntryComponent, + TabsComponent, TimeZoneAbbrPipe, TimerSecondsPipe ], @@ -173,6 +176,7 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR ComponentsService, ServiceLogsFieldsService, AuditLogsFieldsService, + TabsService, { provide: XHRBackend, useFactory: getXHRBackend, http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts new file mode 100644 index 0000000..dde144b --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts @@ -0,0 +1,369 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {FormGroup, FormControl} from '@angular/forms'; +import {ListItem} from '@app/classes/list-item'; + +export interface TimeUnit { + type: 'CURRENT' | 'LAST' | 'PAST'; + unit: 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'Y'; + interval?: number; +} + +export interface TimeUnitListItem extends ListItem { + value: TimeUnit; +} + +export interface FilterCondition { + label?: string; + options?: (ListItem | TimeUnitListItem[])[]; + defaultValue?: string | number | {[key: string]: any}; + defaultLabel?: string; + iconClass?: string; +} + +const paginationOptions: string[] = ['10', '25', '50', '100']; + +export const filters: {[key: string]: FilterCondition} = { + clusters: { + label: 'filter.clusters', + options: [], + defaultValue: '' + }, + timeRange: { + options: [ + [ + { + label: 'filter.timeRange.7d', + value: { + type: 'LAST', + unit: 'd', + interval: 7 + } + }, + { + label: 'filter.timeRange.30d', + value: { + type: 'LAST', + unit: 'd', + interval: 30 + } + }, + { + label: 'filter.timeRange.60d', + value: { + type: 'LAST', + unit: 'd', + interval: 60 + } + }, + { + label: 'filter.timeRange.90d', + value: { + type: 'LAST', + unit: 'd', + interval: 90 + } + }, + { + label: 'filter.timeRange.6m', + value: { + type: 'LAST', + unit: 'M', + interval: 6 + } + }, + { + label: 'filter.timeRange.1y', + value: { + type: 'LAST', + unit: 'Y', + interval: 1 + } + }, + { + label: 'filter.timeRange.2y', + value: { + type: 'LAST', + unit: 'Y', + interval: 2 + } + }, + { + label: 'filter.timeRange.5y', + value: { + type: 'LAST', + unit: 'Y', + interval: 5 + } + } + ], + [ + { + label: 'filter.timeRange.yesterday', + value: { + type: 'PAST', + unit: 'd' + } + }, + // TODO implement time range calculation + /* + { + label: 'filter.timeRange.beforeYesterday', + value: { + type: 'PAST', + unit: 'd' + } + }, + { + label: 'filter.timeRange.thisDayLastWeek', + value: { + type: 'PAST', + unit: 'd' + } + }, + */ + { + label: 'filter.timeRange.previousWeek', + value: { + type: 'PAST', + unit: 'w' + } + }, + { + label: 'filter.timeRange.previousMonth', + value: { + type: 'PAST', + unit: 'M' + } + }, + { + label: 'filter.timeRange.previousYear', + value: { + type: 'PAST', + unit: 'Y' + } + } + ], + [ + { + label: 'filter.timeRange.today', + value: { + type: 'CURRENT', + unit: 'd' + } + }, + { + label: 'filter.timeRange.thisWeek', + value: { + type: 'CURRENT', + unit: 'w' + } + }, + { + label: 'filter.timeRange.thisMonth', + value: { + type: 'CURRENT', + unit: 'M' + } + }, + { + label: 'filter.timeRange.thisYear', + value: { + type: 'CURRENT', + unit: 'Y' + } + } + ], + [ + { + label: 'filter.timeRange.5min', + value: { + type: 'LAST', + unit: 'm', + interval: 5 + } + }, + { + label: 'filter.timeRange.15min', + value: { + type: 'LAST', + unit: 'm', + interval: 15 + } + }, + { + label: 'filter.timeRange.30min', + value: { + type: 'LAST', + unit: 'm', + interval: 30 + } + }, + { + label: 'filter.timeRange.1hr', + value: { + type: 'LAST', + unit: 'h', + interval: 1 + } + }, + { + label: 'filter.timeRange.3hr', + value: { + type: 'LAST', + unit: 'h', + interval: 3 + } + }, + { + label: 'filter.timeRange.6hr', + value: { + type: 'LAST', + unit: 'h', + interval: 6 + } + }, + { + label: 'filter.timeRange.12hr', + value: { + type: 'LAST', + unit: 'h', + interval: 12 + } + }, + { + label: 'filter.timeRange.24hr', + value: { + type: 'LAST', + unit: 'h', + interval: 24 + } + }, + ] + ], + defaultValue: { + type: 'LAST', + unit: 'h', + interval: 1 + }, + defaultLabel: 'filter.timeRange.1hr' + }, + components: { + label: 'filter.components', + iconClass: 'fa fa-cubes', + options: [], + defaultValue: '' + }, + levels: { + label: 'filter.levels', + iconClass: 'fa fa-sort-amount-asc', + options: [ + { + label: 'levels.fatal', + value: 'FATAL' + }, + { + label: 'levels.error', + value: 'ERROR' + }, + { + label: 'levels.warn', + value: 'WARN' + }, + { + label: 'levels.info', + value: 'INFO' + }, + { + label: 'levels.debug', + value: 'DEBUG' + }, + { + label: 'levels.trace', + value: 'TRACE' + }, + { + label: 'levels.unknown', + value: 'UNKNOWN' + } + ], + defaultValue: '' + }, + hosts: { + label: 'filter.hosts', + iconClass: 'fa fa-server', + options: [], + defaultValue: '' + }, + sorting: { + label: 'sorting.title', + options: [ + { + label: 'sorting.time.asc', + value: { + key: 'logtime', + type: 'asc' + } + }, + { + label: 'sorting.time.desc', + value: { + key: 'logtime', + type: 'desc' + } + } + ], + defaultValue: '', + defaultLabel: '' + }, + pageSize: { + label: 'pagination.title', + options: paginationOptions.map((option: string): ListItem => { + return { + label: option, + value: option + } + }), + defaultValue: '10', + defaultLabel: '10' + }, + page: { + defaultValue: 0 + }, + query: {} +}; + +export const filtersFormItemsMap: {[key: string]: string[]} = { + serviceLogs: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'sorting', 'pageSize', 'page', 'query'], + auditLogs: ['clusters', 'timeRange', 'sorting', 'pageSize', 'page', 'query'] // TODO add all the required fields +}; + +export const getFiltersForm = (listType: string): FormGroup => { + const itemsList = filtersFormItemsMap[listType], + keys = Object.keys(filters).filter((key: string): boolean => itemsList.indexOf(key) > -1), + items = keys.reduce((currentObject: any, key: string): any => { + let formControl = new FormControl(), + item = { + [key]: formControl + }; + formControl.setValue(filters[key].defaultValue); + return Object.assign(currentObject, item); + }, {}); + return new FormGroup(items); +}; http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts index beeb670..2c5c083 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts @@ -16,28 +16,28 @@ * limitations under the License. */ +import {FormGroup} from '@angular/forms'; import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry'; +import {Tab, initialTabs} from '@app/classes/models/tab'; export interface AppState { isAuthorized: boolean; isInitialLoading: boolean; isLoginInProgress: boolean; - isAuditLogsSet: boolean; - isServiceLogsSet: boolean; activeLogsType?: string; isServiceLogsFileView: boolean; isServiceLogContextView: boolean; activeLog: ActiveServiceLogEntry | null; + activeFiltersForm: FormGroup; } export const initialState: AppState = { isAuthorized: false, isInitialLoading: false, isLoginInProgress: false, - isAuditLogsSet: false, - isServiceLogsSet: false, - activeLogsType: 'serviceLogs', // TODO implement setting the parameter depending on user's navigation + activeLogsType: 'serviceLogs', isServiceLogsFileView: false, isServiceLogContextView: false, - activeLog: null -} + activeLog: null, + activeFiltersForm: initialTabs.find((tab: Tab): boolean => tab.isActive).appState.activeFiltersForm +}; http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts index c62d3ee..d912b35 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts @@ -29,6 +29,7 @@ import {UserConfig} from '@app/classes/models/user-config'; import {Filter} from '@app/classes/models/filter'; import {AuditLogField} from '@app/classes/models/audit-log-field'; import {ServiceLogField} from '@app/classes/models/service-log-field'; +import {Tab} from '@app/classes/models/tab'; export const storeActions = { 'ARRAY.ADD': 'ADD', @@ -56,6 +57,7 @@ export interface AppStore { components: Node[]; serviceLogsFields: ServiceLogField[]; auditLogsFields: AuditLogField[]; + tabs: Tab[]; } export class ModelService { @@ -115,7 +117,7 @@ export class CollectionModelService extends ModelService { }); } - mapCollection(modifier: (item: any) => {}): void { + mapCollection(modifier: (item: any) => any): void { this.store.dispatch({ type: `${storeActions['ARRAY.MAP']}_${this.modelName}`, payload: { @@ -124,6 +126,14 @@ export class CollectionModelService extends ModelService { }); } + findInCollection(findFunction): Observable<any> { + return this.getAll().map((result: any[]): any => result.find(findFunction)); + } + + filterCollection(filterFunction): Observable<any[]> { + return this.getAll().map((result: any[]): any[] => result.filter(filterFunction)); + } + } export class ObjectModelService extends ModelService { http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/tab.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/tab.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/tab.ts new file mode 100644 index 0000000..bb8028a --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/tab.ts @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {getFiltersForm} from '@app/classes/filtering'; + +export interface Tab { + id: string; + type: string; + isActive: boolean; + isCloseable?: boolean; + label: string; + appState: any; +} + +export const initialTabs: Tab[] = [ + { + id: 'serviceLogs', + type: 'serviceLogs', + isActive: true, + label: 'common.serviceLogs', + appState: { + activeLogsType: 'serviceLogs', + isServiceLogsFileView: false, + activeFiltersForm: getFiltersForm('serviceLogs') + } + }, + { + id: 'auditLogs', + type: 'auditLogs', + isActive: false, + label: 'common.auditLogs', + appState: { + activeLogsType: 'auditLogs', + isServiceLogsFileView: false, + activeFiltersForm: getFiltersForm('auditLogs') + } + } +]; http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts index 60b7d63..5f5f1b0 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts @@ -21,7 +21,6 @@ import {By} from '@angular/platform-browser'; import {TranslationModules} from '@app/test-config.spec'; import {HttpClientService} from '@app/services/http-client.service'; -//import {AppModule} from '@app/app.module'; import {CollapsiblePanelComponent} from './collapsible-panel.component'; describe('CollapsiblePanelComponent', () => { @@ -31,11 +30,22 @@ describe('CollapsiblePanelComponent', () => { let el: HTMLElement; beforeEach(async(() => { + const httpClient = { + get: () => { + return { + subscribe: () => { + } + } + } + }; TestBed.configureTestingModule({ declarations: [CollapsiblePanelComponent], imports: TranslationModules, providers: [ - HttpClientService + { + provide: HttpClientService, + useValue: httpClient + } ] }) .compileComponents(); http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts index e795986..f11ca09 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts @@ -31,6 +31,7 @@ import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-log import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {FilteringService} from '@app/services/filtering.service'; import {UtilsService} from '@app/services/utils.service'; import {ComponentActionsService} from '@app/services/component-actions.service'; @@ -44,6 +45,14 @@ describe('DropdownButtonComponent', () => { let fixture: ComponentFixture<DropdownButtonComponent>; beforeEach(async(() => { + const httpClient = { + get: () => { + return { + subscribe: () => { + } + } + } + }; TestBed.configureTestingModule({ declarations: [DropdownButtonComponent], imports: [ @@ -58,7 +67,8 @@ describe('DropdownButtonComponent', () => { serviceLogs, serviceLogsFields, serviceLogsHistogramData, - serviceLogsTruncated + serviceLogsTruncated, + tabs }), ...TranslationModules ], @@ -74,10 +84,14 @@ describe('DropdownButtonComponent', () => { ServiceLogsFieldsService, ServiceLogsHistogramDataService, ServiceLogsTruncatedService, + TabsService, FilteringService, UtilsService, ComponentActionsService, - HttpClientService, + { + provide: HttpClientService, + useValue: httpClient + }, LogsContainerService ], schemas: [NO_ERRORS_SCHEMA] http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts index 759a0e1..5455e67 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts @@ -30,6 +30,7 @@ import {AppStateService, appState} from '@app/services/storage/app-state.service import {ClustersService, clusters} from '@app/services/storage/clusters.service'; import {ComponentsService, components} from '@app/services/storage/components.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {ComponentGeneratorService} from '@app/services/component-generator.service'; import {LogsContainerService} from '@app/services/logs-container.service'; import {HttpClientService} from '@app/services/http-client.service'; @@ -66,7 +67,8 @@ describe('DropdownListComponent', () => { appState, clusters, components, - serviceLogsTruncated + serviceLogsTruncated, + tabs }) ], providers: [ @@ -88,7 +90,8 @@ describe('DropdownListComponent', () => { AppStateService, ClustersService, ComponentsService, - ServiceLogsTruncatedService + ServiceLogsTruncatedService, + TabsService ] }) .compileComponents(); http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts index 4e6f460..3e40455 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts @@ -31,6 +31,7 @@ import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-log import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {ComponentActionsService} from '@app/services/component-actions.service'; import {FilteringService} from '@app/services/filtering.service'; import {UtilsService} from '@app/services/utils.service'; @@ -44,6 +45,14 @@ describe('FilterButtonComponent', () => { let fixture: ComponentFixture<FilterButtonComponent>; beforeEach(async(() => { + const httpClient = { + get: () => { + return { + subscribe: () => { + } + } + } + }; TestBed.configureTestingModule({ declarations: [FilterButtonComponent], imports: [ @@ -58,7 +67,8 @@ describe('FilterButtonComponent', () => { serviceLogs, serviceLogsFields, serviceLogsHistogramData, - serviceLogsTruncated + serviceLogsTruncated, + tabs }), ...TranslationModules ], @@ -74,10 +84,14 @@ describe('FilterButtonComponent', () => { ServiceLogsFieldsService, ServiceLogsHistogramDataService, ServiceLogsTruncatedService, + TabsService, ComponentActionsService, FilteringService, UtilsService, - HttpClientService, + { + provide: HttpClientService, + useValue: httpClient + }, LogsContainerService ], schemas: [NO_ERRORS_SCHEMA] http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts index f5b9330..c294e8e 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts @@ -27,6 +27,7 @@ import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-log import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {FilteringService} from '@app/services/filtering.service'; import {UtilsService} from '@app/services/utils.service'; import {ComponentActionsService} from '@app/services/component-actions.service'; @@ -56,6 +57,14 @@ describe('FilterDropdownComponent', () => { }; beforeEach(async(() => { + const httpClient = { + get: () => { + return { + subscribe: () => { + } + } + } + }; TestBed.configureTestingModule({ declarations: [FilterDropdownComponent], imports: [ @@ -67,7 +76,8 @@ describe('FilterDropdownComponent', () => { serviceLogs, serviceLogsFields, serviceLogsHistogramData, - serviceLogsTruncated + serviceLogsTruncated, + tabs }), ...TranslationModules ], @@ -80,6 +90,7 @@ describe('FilterDropdownComponent', () => { ServiceLogsFieldsService, ServiceLogsHistogramDataService, ServiceLogsTruncatedService, + TabsService, { provide: FilteringService, useValue: filtering @@ -87,7 +98,10 @@ describe('FilterDropdownComponent', () => { UtilsService, ComponentActionsService, LogsContainerService, - HttpClientService + { + provide: HttpClientService, + useValue: httpClient + } ], schemas: [NO_ERRORS_SCHEMA] }) http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html index 22ec8fe..fa739a4 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -<form class="col-md-12" [formGroup]="filtersForm"> +<form [formGroup]="filtersForm"> <div class="form-inline filter-input-container col-md-8"> <filter-dropdown [label]="filters.clusters.label" formControlName="clusters" [options]="filters.clusters.options" [defaultLabel]="filters.clusters.defaultLabel" [isMultipleChoice]="true" @@ -33,15 +33,16 @@ <div class="filter-buttons col-md-4"> <dropdown-button [options]="searchBoxItems" iconClass="fa fa-search-minus" label="filter.excluded" [hideCaret]="true" [showSelectedValue]="false" action="proceedWithExclude"></dropdown-button> - <filter-button formControlName="hosts" label="{{filters.hosts.label | translate}}" - [iconClass]="filters.hosts.iconClass" [subItems]="filters.hosts.options" - [isMultipleChoice]="true" [isRightAlign]="true" + <filter-button *ngIf="isFilterConditionDisplayed('hosts')" formControlName="hosts" + label="{{filters.hosts.label | translate}}" [iconClass]="filters.hosts.iconClass" + [subItems]="filters.hosts.options" [isMultipleChoice]="true" [isRightAlign]="true" additionalLabelComponentSetter="getDataForHostsNodeBar"></filter-button> - <filter-button formControlName="components" label="{{filters.components.label | translate}}" - [iconClass]="filters.components.iconClass" [subItems]="filters.components.options" - [isMultipleChoice]="true" [isRightAlign]="true" + <filter-button *ngIf="isFilterConditionDisplayed('components')" formControlName="components" + label="{{filters.components.label | translate}}" [iconClass]="filters.components.iconClass" + [subItems]="filters.components.options" [isMultipleChoice]="true" [isRightAlign]="true" additionalLabelComponentSetter="getDataForComponentsNodeBar"></filter-button> - <filter-button formControlName="levels" label="{{filters.levels.label | translate}}" [iconClass]="filters.levels.iconClass" + <filter-button *ngIf="isFilterConditionDisplayed('levels')" formControlName="levels" + label="{{filters.levels.label | translate}}" [iconClass]="filters.levels.iconClass" [subItems]="filters.levels.options" [isMultipleChoice]="true" [isRightAlign]="true"></filter-button> <menu-button *ngIf="!captureSeconds" label="{{'filter.capture' | translate}}" iconClass="fa fa-caret-right" action="startCapture"></menu-button> http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts index 0643ea6..0bb0204 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts @@ -31,6 +31,7 @@ import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; import {AppStateService, appState} from '@app/services/storage/app-state.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {FilteringService} from '@app/services/filtering.service'; import {HttpClientService} from '@app/services/http-client.service'; import {UtilsService} from '@app/services/utils.service'; @@ -69,7 +70,8 @@ describe('FiltersPanelComponent', () => { serviceLogsFields, serviceLogsHistogramData, appState, - serviceLogsTruncated + serviceLogsTruncated, + tabs }), ...TranslationModules ], @@ -85,6 +87,7 @@ describe('FiltersPanelComponent', () => { ServiceLogsHistogramDataService, AppStateService, ServiceLogsTruncatedService, + TabsService, FilteringService, LogsContainerService, { http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts index 5eef03e..9601a0e 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts @@ -16,12 +16,14 @@ * limitations under the License. */ -import {Component} from '@angular/core'; +import {Component, Input} from '@angular/core'; import {FormGroup} from '@angular/forms'; import {Subject} from 'rxjs/Subject'; import {TranslateService} from '@ngx-translate/core'; import {ListItem} from '@app/classes/list-item'; +import {filtersFormItemsMap} from '@app/classes/filtering'; import {CommonEntry} from '@app/classes/models/common-entry'; +import {LogField} from '@app/classes/models/log-field'; import {FilteringService} from '@app/services/filtering.service'; import {LogsContainerService} from '@app/services/logs-container.service'; import {AppStateService} from '@app/services/storage/app-state.service'; @@ -36,38 +38,42 @@ export class FiltersPanelComponent { constructor(private translate: TranslateService, private filtering: FilteringService, private logsContainer: LogsContainerService, private appState: AppStateService) { appState.getParameter('activeLogsType').subscribe(value => { this.logsType = value; - logsContainer.logsTypeMap[value].fieldsModel.getAll().subscribe(fields => { + logsContainer.logsTypeMap[value].fieldsModel.getAll().subscribe((fields: LogField[]): void => { if (fields.length) { - const items = fields.filter(field => this.excludedParameters.indexOf(field.name) === -1).map(field => { + const items = fields.filter((field: LogField): boolean => { + return this.excludedParameters.indexOf(field.name) === -1; + }).map((field: LogField): CommonEntry => { return { name: field.displayName || field.name, value: field.name }; }), - labelKeys = items.map(item => item.name); - this.searchBoxItems = items.map(item => { + labelKeys = items.map((item: CommonEntry): string => item.name); + this.searchBoxItems = items.map((item: CommonEntry): ListItem => { return { label: item.name, value: item.value }; }); - translate.get(labelKeys).first().subscribe(translation => this.searchBoxItemsTranslated = items.map(item => { - return { - name: translation[item.name], - value: item.value - }; - })); + translate.get(labelKeys).first().subscribe((translation: {[key: string]: string}): void => { + this.searchBoxItemsTranslated = items.map((item: CommonEntry): CommonEntry => { + return { + name: translation[item.name], + value: item.value + }; + }) + }); } }) }); - filtering.loadClusters(); - filtering.loadComponents(); - filtering.loadHosts(); } + @Input() + filtersForm: FormGroup; + private readonly excludedParameters = ['cluster', 'host', 'level', 'type', 'logtime']; - private logsType: string; // TODO implement setting the parameter depending on user's navigation + private logsType: string; searchBoxItems: ListItem[] = []; @@ -77,10 +83,6 @@ export class FiltersPanelComponent { return this.filtering.filters; } - get filtersForm(): FormGroup { - return this.filtering.filtersForm; - } - get queryParameterNameChange(): Subject<any> { return this.filtering.queryParameterNameChange; } @@ -93,4 +95,8 @@ export class FiltersPanelComponent { return this.filtering.captureSeconds; } + isFilterConditionDisplayed(key: string): boolean { + return filtersFormItemsMap[this.logsType].indexOf(key) > -1; + } + } http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts index c21750a..4e9bdc9 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts @@ -29,6 +29,7 @@ import {ClustersService, clusters} from '@app/services/storage/clusters.service' import {ComponentsService, components} from '@app/services/storage/components.service'; import {HostsService, hosts} from '@app/services/storage/hosts.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {TranslationModules} from '@app/test-config.spec'; import {ModalComponent} from '@app/components/modal/modal.component'; import {LogsContainerService} from '@app/services/logs-container.service'; @@ -67,7 +68,8 @@ describe('LogContextComponent', () => { clusters, components, hosts, - serviceLogsTruncated + serviceLogsTruncated, + tabs }), ...TranslationModules ], @@ -83,6 +85,7 @@ describe('LogContextComponent', () => { ComponentsService, HostsService, ServiceLogsTruncatedService, + TabsService, LogsContainerService, { provide: HttpClientService, http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html index 8b63278..70150a5 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html @@ -15,25 +15,30 @@ limitations under the License. --> +<div class="tabs-container row"> + <tabs class="col-md-12" [items]="tabs | async" (tabSwitched)="onSwitchTab($event)" + (tabClosed)="onCloseTab($event[0], $event[1])"></tabs> +</div> +<filters-panel class="row" [filtersForm]="filtersForm"></filters-panel> <div *ngIf="autoRefreshRemainingSeconds" class="col-md-12"> <div class="auto-refresh-message pull-right"> {{'filter.capture.triggeringRefresh' | translate: autoRefreshMessageParams}} </div> </div> <!-- TODO use plugin for singular/plural --> -<div class="logs-header col-md-12">{{ +<div class="logs-header">{{ (!totalEventsFoundMessageParams.totalCount ? 'logs.noEventFound' : (totalEventsFoundMessageParams.totalCount === 1 ? 'logs.oneEventFound' : 'logs.totalEventFound')) | translate: totalEventsFoundMessageParams }}</div> -<collapsible-panel openTitle="logs.hideGraph" collapsedTitle="logs.showGraph" class="col-md-12"> - <time-histogram class="col-md-12" [data]="histogramData" [customOptions]="histogramOptions" - svgId="service-logs-histogram" - (selectArea)="setCustomTimeRange($event[0], $event[1])"></time-histogram> +<collapsible-panel openTitle="logs.hideGraph" collapsedTitle="logs.showGraph"> + <time-histogram [data]="histogramData" [customOptions]="histogramOptions" svgId="service-logs-histogram" + (selectArea)="setCustomTimeRange($event[0], $event[1])"></time-histogram> </collapsible-panel> <dropdown-button *ngIf="!isServiceLogsFileView" class="pull-right" label="logs.columns" [options]="availableColumns | async" [isRightAlign]="true" [isMultipleChoice]="true" action="updateSelectedColumns" [additionalArgs]="logsTypeMapObject.fieldsModel"></dropdown-button> -<logs-list [logs]="logs | async" [totalCount]="totalCount" [displayedColumns]="displayedColumns"></logs-list> +<logs-list [logs]="logs | async" [totalCount]="totalCount" [displayedColumns]="displayedColumns" + [isServiceLogsFileView]="isServiceLogsFileView" [filtersForm]="filtersForm"></logs-list> <log-context *ngIf="isServiceLogContextView" [hostName]="activeLog.host_name" [componentName]="activeLog.component_name" [id]="activeLog.id"></log-context> http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less index cd28efc..23d5f92 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less @@ -21,9 +21,12 @@ :host { display: block; overflow: hidden; - padding-top: @block-margin-top; - .auto-refresh-message { + .tabs-container, .auto-refresh-message { background-color: @filters-panel-background-color; } + + filters-panel { + margin-bottom: @block-margin-top; + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts index 9b3a043..0a9418f 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts @@ -31,10 +31,12 @@ import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; import {HostsService, hosts} from '@app/services/storage/hosts.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {HttpClientService} from '@app/services/http-client.service'; import {FilteringService} from '@app/services/filtering.service'; import {UtilsService} from '@app/services/utils.service'; import {LogsContainerService} from '@app/services/logs-container.service'; +import {TabsComponent} from '@app/components/tabs/tabs.component'; import {LogsContainerComponent} from './logs-container.component'; @@ -52,7 +54,10 @@ describe('LogsContainerComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [LogsContainerComponent], + declarations: [ + LogsContainerComponent, + TabsComponent + ], imports: [ StoreModule.provideStore({ appSettings, @@ -64,6 +69,7 @@ describe('LogsContainerComponent', () => { serviceLogs, serviceLogsFields, serviceLogsHistogramData, + tabs, hosts, serviceLogsTruncated }), @@ -85,6 +91,7 @@ describe('LogsContainerComponent', () => { ServiceLogsHistogramDataService, HostsService, ServiceLogsTruncatedService, + TabsService, FilteringService, UtilsService, LogsContainerService @@ -97,7 +104,7 @@ describe('LogsContainerComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(LogsContainerComponent); component = fixture.componentInstance; - component.logsType = 'serviceLogs'; + component['logsType'] = 'serviceLogs'; fixture.detectChanges(); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts index cdc023d..21949f1 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts @@ -16,68 +16,85 @@ * limitations under the License. */ -import {Component, OnInit, Input} from '@angular/core'; +import {Component} from '@angular/core'; import {FormGroup} from '@angular/forms'; import {Observable} from 'rxjs/Observable'; +import {Subject} from 'rxjs/Subject'; import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/takeUntil'; import {FilteringService} from '@app/services/filtering.service'; import {LogsContainerService} from '@app/services/logs-container.service'; import {ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-data.service'; import {AppStateService} from '@app/services/storage/app-state.service'; +import {TabsService} from '@app/services/storage/tabs.service'; import {AuditLog} from '@app/classes/models/audit-log'; import {ServiceLog} from '@app/classes/models/service-log'; import {LogField} from '@app/classes/models/log-field'; +import {Tab} from '@app/classes/models/tab'; +import {BarGraph} from '@app/classes/models/bar-graph'; import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry'; import {HistogramOptions} from '@app/classes/histogram-options'; +import {ListItem} from '@app/classes/list-item'; @Component({ selector: 'logs-container', templateUrl: './logs-container.component.html', styleUrls: ['./logs-container.component.less'] }) -export class LogsContainerComponent implements OnInit { - - constructor(private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private appState: AppStateService, private filtering: FilteringService, private logsContainer: LogsContainerService) { - serviceLogsHistogramStorage.getAll().subscribe(data => this.histogramData = this.logsContainer.getHistogramData(data)); - appState.getParameter('isServiceLogContextView').subscribe((value: boolean) => this.isServiceLogContextView = value); - } - - ngOnInit() { - const fieldsModel = this.logsTypeMapObject.fieldsModel, - logsModel = this.logsTypeMapObject.logsModel; - this.appState.getParameter(this.logsTypeMapObject.isSetFlag).subscribe((value: boolean) => this.isLogsSet = value); - this.availableColumns = fieldsModel.getAll().map(fields => { - return fields.filter(field => field.isAvailable).map(field => { - return { - value: field.name, - label: field.displayName || field.name, - isChecked: field.isDisplayed - }; +export class LogsContainerComponent { + + constructor(private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private appState: AppStateService, private tabsStorage: TabsService, private filtering: FilteringService, private logsContainer: LogsContainerService) { + this.logsContainer.loadColumnsNames(); + this.logsTypeChange.first().subscribe(() => this.logsContainer.loadLogs()); + appState.getParameter('activeLogsType').subscribe((value: string): void => { + this.logsType = value; + this.logsTypeChange.next(); + const fieldsModel = this.logsTypeMapObject.fieldsModel, + logsModel = this.logsTypeMapObject.logsModel; + this.availableColumns = fieldsModel.getAll().takeUntil(this.logsTypeChange).map((fields: LogField[]): ListItem[] => { + return fields.filter((field: LogField): boolean => field.isAvailable).map((field: LogField): ListItem => { + return { + value: field.name, + label: field.displayName || field.name, + isChecked: field.isDisplayed + }; + }); }); - }); - fieldsModel.getAll().subscribe(columns => { - const availableFields = columns.filter(field => field.isAvailable), - availableNames = availableFields.map(field => field.name); - if (availableNames.length && !this.isLogsSet) { - this.logs = logsModel.getAll().map((logs: (AuditLog | ServiceLog)[]): (AuditLog | ServiceLog)[] => { - return logs.map((log: AuditLog | ServiceLog): AuditLog | ServiceLog => { - return availableNames.reduce((obj, key) => Object.assign(obj, { - [key]: log[key] - }), {}); + fieldsModel.getAll().takeUntil(this.logsTypeChange).subscribe(columns => { + const availableFields = columns.filter((field: LogField): boolean => field.isAvailable), + availableNames = availableFields.map((field: LogField): string => field.name); + if (availableNames.length) { + this.logs = logsModel.getAll().map((logs: (AuditLog | ServiceLog)[]): (AuditLog | ServiceLog)[] => { + return logs.map((log: AuditLog | ServiceLog): AuditLog | ServiceLog => { + return availableNames.reduce((obj, key) => Object.assign(obj, { + [key]: log[key] + }), {}); + }); }); - }); - this.appState.setParameter(this.logsTypeMapObject.isSetFlag, true); - } - this.displayedColumns = columns.filter(column => column.isAvailable && column.isDisplayed); + } + this.displayedColumns = columns.filter((column: LogField): boolean => column.isAvailable && column.isDisplayed); + }); + }); + appState.getParameter('activeFiltersForm').subscribe((form: FormGroup): void => { + this.filtersFormChange.next(); + form.valueChanges.takeUntil(this.filtersFormChange).subscribe(() => this.logsContainer.loadLogs()); + this.filtersForm = form; }); - this.logsContainer.loadLogs(this.logsType); - this.filtersForm.valueChanges.subscribe(() => this.logsContainer.loadLogs(this.logsType)); + serviceLogsHistogramStorage.getAll().subscribe((data: BarGraph[]): void => { + this.histogramData = this.logsContainer.getHistogramData(data); + }); + appState.getParameter('isServiceLogContextView').subscribe((value: boolean) => this.isServiceLogContextView = value); } - @Input() - logsType: string; + tabs: Observable<Tab[]> = this.tabsStorage.getAll(); - private isLogsSet: boolean = false; + filtersForm: FormGroup; + + private logsType: string; + + private filtersFormChange: Subject<any> = new Subject(); + + private logsTypeChange: Subject<any> = new Subject(); get logsTypeMapObject(): any { return this.logsContainer.logsTypeMap[this.logsType]; @@ -99,10 +116,6 @@ export class LogsContainerComponent implements OnInit { keysWithColors: this.logsContainer.colors }; - private get filtersForm(): FormGroup { - return this.filtering.filtersForm; - } - get autoRefreshRemainingSeconds(): number { return this.filtering.autoRefreshRemainingSeconds; } @@ -136,4 +149,15 @@ export class LogsContainerComponent implements OnInit { setCustomTimeRange(startTime: number, endTime: number): void { this.filtering.setCustomTimeRange(startTime, endTime); } + + onSwitchTab(activeTab: Tab): void { + this.logsContainer.switchTab(activeTab); + } + + onCloseTab(activeTab: Tab, newActiveTab: Tab): void { + this.tabsStorage.deleteObjectInstance(activeTab); + if (newActiveTab) { + this.onSwitchTab(newActiveTab); + } + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html index b27eb69..1e0f49c 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html @@ -15,22 +15,24 @@ limitations under the License. --> -<form *ngIf="logs && logs.length" [formGroup]="filtersForm" class="pull-right"> +<form *ngIf="logs && logs.length" [formGroup]="filtersForm" class="row pull-right"> <filter-dropdown [label]="filters.sorting.label" formControlName="sorting" [options]="filters.sorting.options" - [defaultLabel]="filters.sorting.defaultLabel" [isRightAlign]="true"></filter-dropdown> + [defaultLabel]="filters.sorting.defaultLabel" [isRightAlign]="true" + class="col-md-12"></filter-dropdown> </form> -<div *ngFor="let log of logs; let i = index"> - <div *ngIf="!isServiceLogsFileView && (i === 0 || isDifferentDates(log.logtime, logs[i - 1].logtime))" class="col-md-12"> - <div class="logs-header">{{log.logtime | amTz: timeZone | amDateFormat: dateFormat}}</div> +<div *ngFor="let log of logs; let i = index" class="row"> + <div class="logs-header col-md-12" + *ngIf="!isServiceLogsFileView && (i === 0 || isDifferentDates(log.logtime, logs[i - 1].logtime))"> + <div class="col-md-12">{{log.logtime | amTz: timeZone | amDateFormat: dateFormat}}</div> </div> <accordion-panel *ngIf="!isServiceLogsFileView" [toggleId]="'details-' + i" class="col-md-12"> <ng-template> - <div *ngIf="isColumnDisplayed('level')" [ngClass]="'hexagon ' + log.level.toLowerCase()"></div> + <div *ngIf="isColumnDisplayed('level')" [ngClass]="'hexagon ' + (log.level ? log.level.toLowerCase() : '')"></div> <div class="col-md-1"> <dropdown-button iconClass="fa fa-ellipsis-h" [hideCaret]="true" [options]="logActions" [additionalArgs]="[log]"></dropdown-button> </div> - <div *ngIf="isColumnDisplayed('level')" [ngClass]="'col-md-1 log-status ' + log.level.toLowerCase()"> + <div *ngIf="isColumnDisplayed('level')" [ngClass]="'col-md-1 log-status ' + (log.level ? log.level.toLowerCase() : '')"> {{log.level}} </div> <div *ngIf="isColumnDisplayed('type') || isColumnDisplayed('logtime')" class="col-md-3"> @@ -45,9 +47,7 @@ </div> <div class="log-content-inner-wrapper"> <div class="log-content" *ngIf="isColumnDisplayed('log_message')" - (contextmenu)="openMessageContextMenu($event)"> - {{log.log_message}} - </div> + (contextmenu)="openMessageContextMenu($event)">{{log.log_message}}</div> </div> </div> <div *ngFor="let column of displayedColumns"> @@ -56,10 +56,10 @@ </div> </ng-template> </accordion-panel> - <log-file-entry *ngIf="isServiceLogsFileView" class="col-md-12" [time]="log.logtime" [level]="log.level" + <log-file-entry *ngIf="isServiceLogsFileView" [time]="log.logtime" [level]="log.level" [fileName]="log.file" [lineNumber]="log.line_number" [message]="log.log_message"></log-file-entry> </div> <ul #contextmenu *ngIf="!isServiceLogsFileView" data-component="dropdown-list" class="dropdown-menu context-menu" [items]="contextMenuItems" (selectedItemChange)="updateQuery($event)"></ul> -<pagination class="col-md-12" *ngIf="logs && logs.length" [totalCount]="totalCount" [filtersForm]="filtersForm" +<pagination class="pull-right" *ngIf="logs && logs.length" [totalCount]="totalCount" [filtersForm]="filtersForm" [filterInstance]="filters.pageSize" [currentCount]="logs.length"></pagination> http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts index 2462a61..017bc82 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts @@ -18,11 +18,11 @@ import {Component, AfterViewInit, Input, ViewChild, ElementRef} from '@angular/core'; import {FormGroup} from '@angular/forms'; import 'rxjs/add/operator/map'; -import {AppStateService} from '@app/services/storage/app-state.service'; import {FilteringService} from '@app/services/filtering.service'; import {UtilsService} from '@app/services/utils.service'; import {AuditLog} from '@app/classes/models/audit-log'; import {ServiceLog} from '@app/classes/models/service-log'; +import {LogField} from '@app/classes/models/log-field'; @Component({ selector: 'logs-list', @@ -31,12 +31,13 @@ import {ServiceLog} from '@app/classes/models/service-log'; }) export class LogsListComponent implements AfterViewInit { - constructor(private filtering: FilteringService, private utils: UtilsService, private appState: AppStateService) { - appState.getParameter('isServiceLogsFileView').subscribe((value: boolean) => this.isServiceLogsFileView = value); + constructor(private filtering: FilteringService, private utils: UtilsService) { } ngAfterViewInit() { - this.contextMenuElement = this.contextMenu.nativeElement; + if (this.contextMenu) { + this.contextMenuElement = this.contextMenu.nativeElement; + } } @Input() @@ -46,7 +47,13 @@ export class LogsListComponent implements AfterViewInit { totalCount: number = 0; @Input() - displayedColumns: any[] = []; + displayedColumns: LogField[] = []; + + @Input() + isServiceLogsFileView: boolean = false; + + @Input() + filtersForm: FormGroup; @ViewChild('contextmenu', { read: ElementRef @@ -103,19 +110,13 @@ export class LogsListComponent implements AfterViewInit { get filters(): any { return this.filtering.filters; } - - get filtersForm(): FormGroup { - return this.filtering.filtersForm; - } - - isServiceLogsFileView: boolean = false; isDifferentDates(dateA, dateB): boolean { return this.utils.isDifferentDates(dateA, dateB, this.timeZone); } isColumnDisplayed(key: string): boolean { - return this.displayedColumns.some(column => column.name === key); + return this.displayedColumns.some((column: LogField): boolean => column.name === key); } openMessageContextMenu(event: MouseEvent): void { http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html index 7e3621a..2061582 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html @@ -20,12 +20,4 @@ <span class="fa fa-spinner fa-spin"></span> </div> <login-form *ngIf="!isInitialLoading && !isAuthorized"></login-form> - -<!-- TODO implement tabs: Service Logs/Audit Logs/active file --> -<div *ngIf="isServiceLogsFileView" class="col-md-12 logs-header"> - {{activeLogHostName}} >> {{activeLogComponentName}} - <span class="fa fa-times close-icon" (click)="closeLog()"></span> -</div> - -<filters-panel *ngIf="isAuthorized" class="row"></filters-panel> -<logs-container *ngIf="isAuthorized" logsType="serviceLogs"></logs-container> +<logs-container *ngIf="isAuthorized" class="col-md-12"></logs-container> http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less index b596a3d..bca668d 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less @@ -21,8 +21,4 @@ :host { .full-size; overflow-x: hidden; - - .close-icon { - .clickable-item; - } } http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts index bbbebdf..18adec7 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts @@ -23,7 +23,6 @@ import {StoreModule} from '@ngrx/store'; import {AppStateService, appState} from '@app/services/storage/app-state.service'; import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; -import {HttpClientService} from '@app/services/http-client.service'; import {MainContainerComponent} from './main-container.component'; @@ -32,14 +31,6 @@ describe('MainContainerComponent', () => { let fixture: ComponentFixture<MainContainerComponent>; beforeEach(async(() => { - const httpClient = { - get: () => { - return { - subscribe: () => { - } - } - } - }; TestBed.configureTestingModule({ declarations: [MainContainerComponent], imports: [ @@ -54,11 +45,7 @@ describe('MainContainerComponent', () => { providers: [ AppStateService, AuditLogsFieldsService, - ServiceLogsFieldsService, - { - provide: HttpClientService, - useValue: httpClient - } + ServiceLogsFieldsService ] }) .compileComponents(); http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts index ad86a74..6747a0c 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts @@ -17,13 +17,7 @@ */ import {Component, ContentChild, TemplateRef} from '@angular/core'; -import {HttpClientService} from '@app/services/http-client.service'; import {AppStateService} from '@app/services/storage/app-state.service'; -import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service'; -import {ServiceLogsFieldsService} from '@app/services/storage/service-logs-fields.service'; -import {AuditLogField} from '@app/classes/models/audit-log-field'; -import {ServiceLogField} from '@app/classes/models/service-log-field'; -import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry'; @Component({ selector: 'main-container', @@ -32,20 +26,9 @@ import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry'; }) export class MainContainerComponent { - constructor(private httpClient: HttpClientService, private appState: AppStateService, private auditLogsFieldsStorage: AuditLogsFieldsService, private serviceLogsFieldsStorage: ServiceLogsFieldsService) { - this.loadColumnsNames(); + constructor(private appState: AppStateService) { appState.getParameter('isAuthorized').subscribe((value: boolean) => this.isAuthorized = value); appState.getParameter('isInitialLoading').subscribe((value: boolean) => this.isInitialLoading = value); - appState.getParameter('isServiceLogsFileView').subscribe((value: boolean) => this.isServiceLogsFileView = value); - appState.getParameter('activeLog').subscribe((value: ActiveServiceLogEntry | null) => { - if (value) { - this.activeLogHostName = value.host_name; - this.activeLogComponentName = value.component_name; - } else { - this.activeLogHostName = ''; - this.activeLogComponentName = ''; - } - }); } @ContentChild(TemplateRef) @@ -55,36 +38,4 @@ export class MainContainerComponent { isInitialLoading: boolean = false; - isServiceLogsFileView: boolean = false; - - activeLogHostName: string = ''; - - activeLogComponentName: string = ''; - - private loadColumnsNames(): void { - this.httpClient.get('serviceLogsFields').subscribe(response => { - const jsonResponse = response.json(); - if (jsonResponse) { - this.serviceLogsFieldsStorage.addInstances(this.getColumnsArray(jsonResponse, ServiceLogField)); - } - }); - this.httpClient.get('auditLogsFields').subscribe(response => { - const jsonResponse = response.json(); - if (jsonResponse) { - this.auditLogsFieldsStorage.addInstances(this.getColumnsArray(jsonResponse, AuditLogField)); - } - }); - } - - private getColumnsArray(keysObject: any, fieldClass: any): any[] { - return Object.keys(keysObject).map(key => new fieldClass(key)); - } - - closeLog(): void { - this.appState.setParameters({ - isServiceLogsFileView: false, - activeLog: null - }); - } - } http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts index 5414f4f..261e213 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts @@ -31,6 +31,7 @@ import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-log import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {ComponentActionsService} from '@app/services/component-actions.service'; import {FilteringService} from '@app/services/filtering.service'; import {HttpClientService} from '@app/services/http-client.service'; @@ -43,6 +44,14 @@ describe('MenuButtonComponent', () => { let fixture: ComponentFixture<MenuButtonComponent>; beforeEach(async(() => { + const httpClient = { + get: () => { + return { + subscribe: () => { + } + } + } + }; TestBed.configureTestingModule({ declarations: [MenuButtonComponent], imports: [ @@ -57,7 +66,8 @@ describe('MenuButtonComponent', () => { serviceLogs, serviceLogsFields, serviceLogsHistogramData, - serviceLogsTruncated + serviceLogsTruncated, + tabs }), ...TranslationModules ], @@ -73,9 +83,13 @@ describe('MenuButtonComponent', () => { ServiceLogsFieldsService, ServiceLogsHistogramDataService, ServiceLogsTruncatedService, + TabsService, ComponentActionsService, FilteringService, - HttpClientService, + { + provide: HttpClientService, + useValue: httpClient + }, LogsContainerService ], schemas: [NO_ERRORS_SCHEMA] http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html index be6591b..679a7e5 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -<form class="pagination-form col-md-12" [formGroup]="filtersForm"> +<form class="pagination-form" [formGroup]="filtersForm"> <filter-dropdown [label]="filterInstance.label" formControlName="pageSize" [options]="filterInstance.options" [defaultLabel]="filterInstance.defaultLabel" [isRightAlign]="true" isDropup="true"></filter-dropdown> <span>{{'pagination.numbers' | translate: numbersTranslateParams}}</span> http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts index d38d0d8..cc5589f 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts @@ -28,7 +28,9 @@ export class PaginationComponent implements OnInit { ngOnInit() { this.setPageSizeFromString(this.filterInstance.defaultValue); - this.filtersForm.controls.pageSize.valueChanges.subscribe(value => this.setPageSizeFromString(value)); + this.filtersForm.controls.pageSize.valueChanges.subscribe((value: string): void => { + this.setPageSizeFromString(value); + }); } @Input() @@ -45,7 +47,7 @@ export class PaginationComponent implements OnInit { private pageSize: number = 0; - setPageSizeFromString(value: string) { + private setPageSizeFromString(value: string) { this.pageSize = parseInt(value); } http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.html new file mode 100644 index 0000000..9bbcacf --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.html @@ -0,0 +1,25 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<ul class="nav nav-tabs"> + <li *ngFor="let tab of items" [ngClass]="{'active': tab.isActive}"> + <a href="#" (click)="switchTab(tab)"> + {{tab.label | translate}} + <span *ngIf="tab.isCloseable" class="fa fa-times close-icon" (click)="closeTab(tab)"></span> + </a> + </li> +</ul> http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.less new file mode 100644 index 0000000..67e4e8c --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.less @@ -0,0 +1,22 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import '../mixins'; + +.close-icon { + .clickable-item; +} http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts new file mode 100644 index 0000000..2df5090 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts @@ -0,0 +1,125 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {Tab} from '@app/classes/models/tab'; +import {TranslationModules} from '@app/test-config.spec'; + +import {TabsComponent} from './tabs.component'; + +describe('TabsComponent', () => { + let component: TabsComponent; + let fixture: ComponentFixture<TabsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TabsComponent], + imports: TranslationModules + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TabsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); + + describe('#switchTab()', () => { + let activeTab; + const tab = { + id: 'tab0', + type: '', + isActive: true, + label: '', + appState: null + }; + + it('new active tab', () => { + component.tabSwitched.subscribe((tab: Tab) => activeTab = tab); + component.switchTab(tab); + expect(activeTab).toEqual(tab); + }); + }); + + describe('#closeTab()', () => { + const items = [ + { + id: 'serviceLogs', + type: '', + isActive: false, + label: '', + appState: null + }, + { + id: 'auditLogs', + type: '', + isActive: false, + label: '', + appState: null + }, + { + id: 'newTab', + type: '', + isActive: false, + label: '', + appState: null + } + ], + cases = [ + { + closedTabIndex: 2, + newActiveTabIndex: 1, + title: 'last tab closed' + }, + { + closedTabIndex: 1, + newActiveTabIndex: 2, + title: 'not last tab closed' + } + ]; + + cases.forEach(test => { + let oldTab, + newTab; + describe(test.title, () => { + beforeEach(() => { + oldTab = null; + newTab = null; + component.items = items; + component.tabClosed.subscribe((tabs: Tab[]): void => { + oldTab = tabs[0]; + newTab = tabs[1]; + }); + component.closeTab(items[test.closedTabIndex]); + }); + + it('closed tab', () => { + expect(oldTab).toEqual(items[test.closedTabIndex]); + }); + + it('new active tab', () => { + expect(newTab).toEqual(items[test.newActiveTabIndex]); + }); + }); + }); + }); +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts new file mode 100644 index 0000000..ef941e6 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, Input, Output, EventEmitter} from '@angular/core'; +import {Tab} from '@app/classes/models/tab'; + +@Component({ + selector: 'tabs', + templateUrl: './tabs.component.html', + styleUrls: ['./tabs.component.less'] +}) +export class TabsComponent { + + @Input() + items: Tab[] = []; + + @Output() + tabSwitched: EventEmitter<Tab> = new EventEmitter(); + + @Output() + tabClosed: EventEmitter<Tab[]> = new EventEmitter(); + + switchTab(tab: Tab): void { + this.tabSwitched.emit(tab); + } + + closeTab(tab: Tab): void { + const tabs = this.items, + tabsCount = tabs.length, + newActiveTab = tabs[tabsCount - 1] === tab ? tabs[tabsCount - 2] : tabs[tabsCount - 1]; + this.tabClosed.emit([tab, newActiveTab]); + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less index 6fe6292..e8d3240 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less @@ -17,6 +17,7 @@ */ :host { + display: block; cursor: crosshair; background: #ECECEC; // TODO add style according to actual design /deep/ .axis { http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts index 08817f4..7612cc3 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts @@ -35,6 +35,14 @@ describe('TimeRangePickerComponent', () => { let fixture: ComponentFixture<TimeRangePickerComponent>; beforeEach(async(() => { + const httpClient = { + get: () => { + return { + subscribe: () => { + } + } + } + }; TestBed.configureTestingModule({ declarations: [TimeRangePickerComponent], imports: [ @@ -48,7 +56,10 @@ describe('TimeRangePickerComponent', () => { ...TranslationModules ], providers: [ - HttpClientService, + { + provide: HttpClientService, + useValue: httpClient + }, FilteringService, AppSettingsService, AppStateService,
