Skip to content

Commit

Permalink
Remove discover - Implement embeddable dashboard on statistics module (
Browse files Browse the repository at this point in the history
…#6542)

* Migrated visualizations to embeddables

* Changed searchbar and node selector

* Cleaned dashboard obsolete code

* Added selectedNodeFilter

* Integrated new data source on statistics

* Fixed statistics index without data.

* Added apiName filter

* Fixed nodeName filter when a node is selected in cluster mode

* Added No results message, fixed allow agents filters on request and cleaned obsolete code

* Changed condition of apiName filter, validation of statistics-data-source-repository and abstracted behavior between DashboardListenerEngineStatistics and DashboardAnalysisEngineStatistics

* Deleted unused  use-build-statistics-visualizations hook

* Fixed information message depending on active tab

* Added withUserAuthorizationPrompt to add protection with the user permissions check

* remove(statistics): unused message definitions

* Deleted unused index.ts file on cluster integration-files visualizations

---------

Co-authored-by: Antonio David Gutiérrez <antonio.gutierrez@wazuh.com>
  • Loading branch information
jbiset and Desvelao authored Apr 22, 2024
1 parent 0203478 commit 3310c8d
Show file tree
Hide file tree
Showing 22 changed files with 4,162 additions and 3,517 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './statistics-data-source-repository';
export * from './statistics-data-source';
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { PatternDataSourceRepository } from '../pattern-data-source-repository';
import { tParsedIndexPattern } from '../../index';
import { StatisticsDataSource } from './statistics-data-source';

export class StatisticsDataSourceRepository extends PatternDataSourceRepository {
constructor() {
super();
}

async get(id: string) {
const dataSource = await super.get(id);
if (this.validate(dataSource)) {
return dataSource;
} else {
throw new Error('Statistics index pattern not found');
}
}

async getAll() {
const indexs = await super.getAll();
return indexs.filter(this.validate);
}

validate(dataSource): boolean {
// check if the dataSource has the id or the title have the statistics word
const fieldsToCheck = ['id', 'attributes.title'];
/* Keep in mind that the identifier is compared with id and title since it is currently not defined that the id is the only one that validates. But this can generate a problem in which the title matches more than one index. */
const STATISTICS_PATTERN_IDENTIFIER =
StatisticsDataSource.getIdentifierDataSourcePattern();
// must check in the object and the attributes
for (const field of fieldsToCheck) {
if (
dataSource[field] &&
dataSource[field].toLowerCase().includes(STATISTICS_PATTERN_IDENTIFIER)
) {
return true;
}
}
return false;
}

getDefault() {
console.warn(
'getDefault not implemented for statistics data source repository',
);
return Promise.resolve(null);
}

setDefault(_dataSource: tParsedIndexPattern) {
console.warn(
'setDefault not implemented for statistics data source repository',
);
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { AppState } from '../../../../../react-services';
import { tFilter } from '../../index';
import { PatternDataSource } from '../pattern-data-source';
import store from '../../../../../redux/store';

export class StatisticsDataSource extends PatternDataSource {
constructor(id: string, title: string) {
super(id, title);
}

static getIdentifierDataSourcePattern(): string {
// Return Statistics Identifier Index Pattern
const appConfig = store.getState().appConfig;
return `${appConfig.data['cron.prefix']}-${appConfig.data['cron.statistics.index.name']}-*`;
}

getFetchFilters(): tFilter[] {
return [...this.getAPIFilter()];
}

getAPIFilter(): tFilter[] {
const currentApi = AppState.getCurrentAPI();
const parsedCurrentApi = currentApi ? JSON.parse(currentApi) : undefined;
const apiNameFilter = {
meta: {
removable: false,
index: this.id,
negate: false,
disabled: false,
alias: null,
type: 'phrase',
key: null,
value: null,
params: {
query: null,
type: 'phrase',
},
},
query: {
match_phrase: {
apiName: parsedCurrentApi?.id,
},
},
$state: {
store: 'appState',
},
};
return [apiNameFilter];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import React, { useEffect, useState } from 'react';
import { getPlugins } from '../../../../kibana-services';
import { I18nProvider } from '@osd/i18n/react';
import useSearchBar from '../../../common/search-bar/use-search-bar';
import {
EuiFlexItem,
EuiFlexGroup,
EuiSelect,
EuiSpacer,
EuiCallOut,
EuiPanel,
} from '@elastic/eui';
import {
PatternDataSource,
tFilter,
tParsedIndexPattern,
useDataSource,
} from '../../../common/data-source';
import { IndexPattern } from '../../../../../../../src/plugins/data/public';
import { SearchResponse } from '../../../../../../../src/core/server';
import {
StatisticsDataSource,
StatisticsDataSourceRepository,
} from '../../../common/data-source/pattern/statistics';
import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner';
import {
ErrorFactory,
ErrorHandler,
HttpError,
} from '../../../../react-services/error-management';
import { DiscoverNoResults } from '../../../common/no-results/no-results';
import { DashboardAnalysisEngineStatistics } from './dashboard_analysis_engine';
import { DashboardListenerEngineStatistics } from './dashboard_listener_engine';

const SearchBar = getPlugins().data.ui.SearchBar;

interface DashboardTabsPanelsProps {
selectedTab: string;
loadingNode: boolean;
isClusterMode: boolean;
clusterNodes: any[];
clusterNodeSelected: any;
onSelectNode: (event: any) => void;
}

export const DashboardTabsPanels = ({
selectedTab,
loadingNode,
isClusterMode,
clusterNodes,
clusterNodeSelected,
onSelectNode,
}: DashboardTabsPanelsProps) => {
const {
filters,
fetchFilters,
dataSource,
fetchData,
setFilters,
isLoading: isDataSourceLoading,
} = useDataSource<tParsedIndexPattern, PatternDataSource>({
DataSource: StatisticsDataSource,
repository: new StatisticsDataSourceRepository(),
});

const [results, setResults] = useState<SearchResponse>({} as SearchResponse);

const infoMessage = {
remoted:
'Remoted statistics are cumulative, this means that the information shown is since the data exists.',
analysisd:
"Analysisd statistics refer to the data stored from the period indicated in the variable 'analysisd.state_interval'.",
};

const { searchBarProps } = useSearchBar({
indexPattern: dataSource?.indexPattern as IndexPattern,
filters,
setFilters,
});

const { query, dateRangeFrom, dateRangeTo } = searchBarProps;

useEffect(() => {
if (isDataSourceLoading) {
return;
}
fetchData({
query,
dateRange: {
from: dateRangeFrom,
to: dateRangeTo,
},
})
.then(results => {
setResults(results);
})
.catch(error => {
const searchError = ErrorFactory.create(HttpError, {
error,
message: 'Error fetching statistics',
});
ErrorHandler.handleError(searchError);
});
}, [
JSON.stringify(fetchFilters),
JSON.stringify(query),
dateRangeFrom,
dateRangeTo,
]);

const selectedNodeFilter: tFilter = {
meta: {
removable: false,
index: dataSource?.id,
negate: false,
disabled: false,
alias: null,
type: 'phrase',
key: null,
value: null,
params: {
query: null,
type: 'phrase',
},
},
query: {
match: {
nodeName: clusterNodeSelected,
},
},
$state: {
store: 'appState',
},
};
return (
<I18nProvider>
{isDataSourceLoading && !dataSource ? (
<LoadingSpinner />
) : (
<EuiFlexGroup alignItems='center' justifyContent='flexEnd'>
{!!(clusterNodes && clusterNodes.length && clusterNodeSelected) && (
<EuiFlexItem grow={false}>
<EuiSelect
id='selectNode'
options={clusterNodes}
value={clusterNodeSelected}
onChange={onSelectNode}
aria-label='Select node'
/>
</EuiFlexItem>
)}
<SearchBar
appName='statistics-searchbar'
{...searchBarProps}
showDatePicker={true}
showQueryInput={false}
showQueryBar={true}
showFilterBar={false}
/>
</EuiFlexGroup>
)}

<EuiSpacer size={'m'} />
<EuiPanel hasBorder={false} hasShadow={false} color='transparent'>
<EuiCallOut title={infoMessage[selectedTab]} iconType='iInCircle' />
</EuiPanel>
{dataSource && results?.hits?.total === 0 ? <DiscoverNoResults /> : null}
{!isDataSourceLoading && dataSource && results?.hits?.total > 0 ? (
<>
{selectedTab === 'remoted' && !loadingNode && (
<div>
<DashboardListenerEngineStatistics
indexPatternId={dataSource?.id}
searchBarProps={searchBarProps}
filters={
clusterNodeSelected !== 'all'
? [...fetchFilters, selectedNodeFilter]
: [...(fetchFilters ?? [])]
}
/>
</div>
)}
{selectedTab === 'analysisd' && !loadingNode && (
<DashboardAnalysisEngineStatistics
isClusterMode={isClusterMode}
indexPatternId={dataSource?.id}
searchBarProps={searchBarProps}
filters={
clusterNodeSelected !== 'all'
? [...fetchFilters, selectedNodeFilter]
: [...(fetchFilters ?? [])]
}
/>
)}
</>
) : null}
</I18nProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import { getPlugins } from '../../../../kibana-services';
import { ViewMode } from '../../../../../../../src/plugins/embeddable/public';
import { withErrorBoundary } from '../../../common/hocs/error-boundary/with-error-boundary';
import { getDashboardPanelsAnalysisEngine } from './dashboard_panels_analysis_engine';
import { tFilter } from '../../../common/data-source';
import './statistics_dashboard.scss';

const plugins = getPlugins();

const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer;

interface DashboardStatisticsProps {
isClusterMode: boolean;
indexPatternId: string;
filters: tFilter[];
searchBarProps: any;
}

const DashboardStatistics: React.FC<DashboardStatisticsProps> = ({
isClusterMode,
indexPatternId,
filters,
searchBarProps,
}) => {
return (
<div className='server-management-statistics-dashboard-responsive'>
<DashboardByRenderer
input={{
viewMode: ViewMode.VIEW,
panels: getDashboardPanelsAnalysisEngine(
indexPatternId,
isClusterMode,
),
isFullScreenMode: false,
filters: filters,
useMargins: true,
id: 'analysis-engine-statistics-dashboard',
timeRange: {
from: searchBarProps.dateRangeFrom,
to: searchBarProps.dateRangeTo,
},
title: 'Analysis Engine Statistics dashboard',
description: 'Dashboard of the Analysis Engine Statistics',
query: searchBarProps.query,
refreshConfig: {
pause: false,
value: 15,
},
hidePanelTitles: false,
}}
/>
</div>
);
};

export const DashboardAnalysisEngineStatistics =
withErrorBoundary(DashboardStatistics);
Loading

0 comments on commit 3310c8d

Please sign in to comment.