You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ab...@apache.org on 2017/10/19 12:47:08 UTC
[2/2] ambari git commit: AMBARI-22269 Log Search UI: provide
navigation between Service and Audit Logs. (ababiichuk)
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 <ab...@hortonworks.com>
Authored: Thu Oct 19 15:11:48 2017 +0300
Committer: ababiichuk <ab...@hortonworks.com>
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,