Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement embeddable dashboard on GitHub #6537

Draft
wants to merge 32 commits into
base: 4.9.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ff47123
feat(GitHub): replace the dashboard on GitHub application
Desvelao Mar 15, 2024
98e2f70
fix(GitHub): move GitHub Panel tab directory
Desvelao Mar 15, 2024
7413890
fix(GitHub): adapt import to move the directory of GitHub panel
Desvelao Mar 15, 2024
af1d172
move(GitHub): move events directory
Desvelao Mar 15, 2024
e31e266
fix(GitHub): add metric visualizations to the dashboard tab
Desvelao Mar 15, 2024
04e6f0f
fix(github): fix dashboard tab of GitHub
Desvelao Mar 15, 2024
d2f5134
remove(GitHub): old visualization definition
Desvelao Mar 18, 2024
9ff0d62
feat(GitHub): replace visulizations on drilldowns of Panel tab
Desvelao Mar 18, 2024
150ce7f
fix(GitHub): minor fixes
Desvelao Mar 18, 2024
6445bdd
Merge branch '4.9.0' of https://github.com/wazuh/wazuh-kibana-app int…
Desvelao Apr 11, 2024
67905b2
feat(github): use data source on Dashboard and Events tabs
Desvelao Apr 11, 2024
80001ad
fix(github): fix Dashboard tab id
Desvelao Apr 11, 2024
245f34c
fix(github): fix layout of Dashboard when there is a pinned agent
Desvelao Apr 11, 2024
0cae71a
Merge branch '4.9.0' into feat/6522-implement-embeddable-dashboard-on…
asteriscos Apr 17, 2024
bf330bb
Merge branch '4.9.0' into feat/6522-implement-embeddable-dashboard-on…
Machi3mfl Apr 26, 2024
f8e4d74
Merge branch '4.9.0' into feat/6522-implement-embeddable-dashboard-on…
Machi3mfl Apr 29, 2024
50d7cb8
Rename data source files
Machi3mfl Apr 29, 2024
e29e939
Refactor custom search bar with data source
Machi3mfl Apr 30, 2024
f6478f5
Create wz data grid to reuse
Machi3mfl Apr 30, 2024
793071d
Change security alerts table for data grid
Machi3mfl Apr 30, 2024
de1caa0
Merge branch '4.9.0' into feat/6522-implement-embeddable-dashboard-on…
Machi3mfl May 2, 2024
45d3346
Update data grid in drilldown detail
Machi3mfl May 2, 2024
47bfdd3
Reuse components in wz data grid and discover
Machi3mfl May 2, 2024
772911e
Decouple agg table from filter manager, index pattern, etc
Machi3mfl May 2, 2024
a67b5ea
Decouple main panel from filter manager and index pattern
Machi3mfl May 2, 2024
2b0d365
Adjust props to receive data source
Machi3mfl May 2, 2024
df14c28
Refactor add and remove filter when panel change
Machi3mfl May 2, 2024
02ec523
Merge branch '4.9.0' into feat/6522-implement-embeddable-dashboard-on…
Machi3mfl May 3, 2024
8af8bce
Add generate filter in data source filter manager
Machi3mfl May 3, 2024
1fc8699
Renamed datasour docker, malware detection
Machi3mfl May 3, 2024
499dbfc
Merge branch '4.9.0' into feat/6522-implement-embeddable-dashboard-on…
Machi3mfl May 7, 2024
8db1b5f
Clean some use effects
Machi3mfl May 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugins/main/common/constants.ts
Expand Up @@ -227,6 +227,8 @@ export const DATA_SOURCE_FILTER_CONTROLLED_PINNED_AGENT = 'pinned-agent';
export const DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER = 'cluster-manager';
export const DATA_SOURCE_FILTER_CONTROLLED_VULNERABILITIES_RULE_GROUP =
'vulnerabilities-rule-group';
export const DATA_SOURCE_FILTER_CONTROLLED_GITHUB_RULE_GROUP =
'github-rule-group';
export const DATA_SOURCE_FILTER_CONTROLLED_DOCKER_RULE_GROUP =
'docker-rule-group';
export const DATA_SOURCE_FILTER_CONTROLLED_MITRE_ATTACK_RULE =
Expand Down
@@ -1,66 +1,98 @@
import React, { useState, useEffect } from 'react';
import { Filter } from '../../../../../../src/plugins/data/public/';
import {
Filter,
IndexPattern,
} from '../../../../../../src/plugins/data/public/';
import {
FilterMeta,
FilterState,
FilterStateStore,
} from '../../../../../../src/plugins/data/common';

import { AppState } from '../../../react-services/app-state';

import { EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui';

import { tFilter } from '../data-source';
//@ts-ignore
import { KbnSearchBar } from '../../kbn-search-bar';
import { MultiSelect } from './components';
import { useFilterManager } from '../hooks';
import useSearchBar, { tUseSearchBarProps } from '../search-bar/use-search-bar';
import { getCustomValueSuggestion } from '../../../components/overview/office-panel/config/helpers/helper-value-suggestion';
import { I18nProvider } from '@osd/i18n/react';
import { getPlugins } from '../../../kibana-services';

type CustomSearchBarProps = {
filterInputs: {
type: string;
key: string;
placeholder: string;
}[];
filterDrillDownValue: { field: string; value: string };
searchBarProps: tUseSearchBarProps;
indexPattern: IndexPattern;
setFilters: (filters: tFilter[]) => void;
};

export const CustomSearchBar = ({ filtersValues, filterDrillDownValue = { field: '', value: '' }, ...props }) => {
const { filterManager, filters } = useFilterManager();
const plugins = getPlugins();
const SearchBar = getPlugins().data.ui.SearchBar;

export const CustomSearchBar = ({
filterInputs,
filterDrillDownValue = { field: '', value: '' },
searchBarProps,
indexPattern,
setFilters
}: CustomSearchBarProps) => {
//const { filterManager, filters } = useFilterManager(); // remove
const { filters } = searchBarProps;
const defaultSelectedOptions = () => {
const array = [];
filtersValues.forEach((item) => {
filterInputs.forEach(item => {
array[item.key] = [];
});

return array;
};
const [avancedFiltersState, setAvancedFiltersState] = useState(false);
const [selectedOptions, setSelectedOptions] = useState(defaultSelectedOptions);
const [selectedOptions, setSelectedOptions] = useState(
defaultSelectedOptions,
);
const [values, setValues] = useState(Array);
const [selectReference, setSelectReference] = useState('');

useEffect(() => {
setPluginPlatformFilters(values, selectReference);
refreshCustomSelectedFilter();
setPluginPlatformFilters(values, selectReference);
refreshCustomSelectedFilter();
}, [values]);

useEffect(() => {
onFiltersUpdated();
}, [filters]);


const checkSelectDrillDownValue = (key) => {
return filterDrillDownValue.field === key && filterDrillDownValue.value != '' ? true : false
}
const checkSelectDrillDownValue = key => {
return filterDrillDownValue.field === key &&
filterDrillDownValue.value != ''
? true
: false;
};
const onFiltersUpdated = () => {
refreshCustomSelectedFilter();
};

const changeSwitch = () => {
setAvancedFiltersState((state) => !state);
setAvancedFiltersState(state => !state);
};

const buildCustomFilter = (isPinned: boolean, values?: any): Filter => {
const newFilters = values.map((element) => ({
const newFilters = values.map(element => ({
match_phrase: {
[element.value]: {
query: element.filterByKey ? element.key : element.label,
},
},
}));
const params = values.map((item) => item.filterByKey ? item.key.toString() : item.label);
const params = values.map(item =>
item.filterByKey ? item.key.toString() : item.label,
);
const meta: FilterMeta = {
disabled: false,
negate: false,
Expand All @@ -69,10 +101,12 @@ export const CustomSearchBar = ({ filtersValues, filterDrillDownValue = { field:
alias: null,
type: 'phrases',
value: params.join(','),
index: AppState.getCurrentPattern(),
index: indexPattern.id,
};
const $state: FilterState = {
store: isPinned ? FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE,
store: isPinned
? FilterStateStore.GLOBAL_STATE
: FilterStateStore.APP_STATE,
};
const query = {
bool: {
Expand All @@ -84,58 +118,73 @@ export const CustomSearchBar = ({ filtersValues, filterDrillDownValue = { field:
return { meta, $state, query };
};

// not use filter manager
const setPluginPlatformFilters = (values: any[], selectReference: String) => {
const currentFilters = filterManager
.getFilters()
.filter((item) => item.meta.key != selectReference);
filterManager.removeAll();
filterManager.addFilters(currentFilters);
const currentFilters = filters.filter(
item => item.meta.key != selectReference,
);
//filterManager.removeAll();
//filterManager.addFilters(currentFilters);
setFilters(currentFilters);
if (values.length != 0) {
const customFilter = buildCustomFilter(false, values);
filterManager.addFilters(customFilter);
//filterManager.addFilters(customFilter);
setFilters([...currentFilters, customFilter]);
}
};

// not use filter manager
const refreshCustomSelectedFilter = () => {
setSelectedOptions(defaultSelectedOptions);
const filters =
filterManager
.getFilters()
const currentFilters =
filters
.filter(
(item) =>
item.meta.type === 'phrases' && Object.keys(selectedOptions).includes(item.meta.key)
item =>
item.meta.type === 'phrases' &&
Object.keys(selectedOptions).includes(item.meta.key),
)
.map((element) => ({ params: element.meta.params, key: element.meta.key })) || [];

const getFilterCustom = (item) => {
return item.params.map((element) => ({ checked: 'on', label: item.key === 'data.office365.UserType' ? getLabelUserType(element) : element, value: item.key, key: element, filterByKey: item.key === 'data.office365.UserType' ? true : false}));
.map(element => ({
params: element.meta.params,
key: element.meta.key,
})) || [];

const getFilterCustom = item => {
return item;
// ToDo: Check how it works
//return item.params.map((element) => ({ checked: 'on', label: item.key === 'data.office365.UserType' ? getLabelUserType(element) : element, value: item.key, key: element, filterByKey: item.key === 'data.office365.UserType' ? true : false}));
};
const getLabelUserType = (element) => {
const userTypeOptions = getCustomValueSuggestion('data.office365.UserType')
return userTypeOptions.find((item,index) => index.toString() === element)
}
const filterCustom = filters.map((item) => getFilterCustom(item)) || [];
const getLabelUserType = element => {
const userTypeOptions = getCustomValueSuggestion(
'data.office365.UserType',
);
return userTypeOptions.find(
(item, index) => index.toString() === element,
);
};
const filterCustom = filters.map(item => getFilterCustom(item)) || [];
if (filterCustom.length != 0) {
filterCustom.forEach((item) => {
/*filterCustom.forEach((item) => {
item.forEach((element) => {
setSelectedOptions((prevState) => ({
...prevState,
[element.value]: [...prevState[element.value], element],
}));
});
});
*/
}
};

const onChange = (values: any[], id: string) => {
setSelectReference(id)
setSelectReference(id);
setValues(values);
};

const onRemove = (filter) => {
const currentFilters = filterManager.getFilters().filter((item) => item.meta.key != filter);
filterManager.removeAll();
filterManager.addFilters(currentFilters);
const onRemove = filter => {
const currentFilters = filters.filter(item => item.meta.key != filter);
//filterManager.removeAll();
//filterManager.addFilters(currentFilters);
setFilters(currentFilters);
refreshCustomSelectedFilter();
};

Expand All @@ -157,41 +206,47 @@ export const CustomSearchBar = ({ filtersValues, filterDrillDownValue = { field:
};

return (
<>
<I18nProvider>
<EuiFlexGroup
className="custom-kbn-search-bar"
alignItems="center"
className='custom-kbn-search-bar'
alignItems='center'
style={{ margin: '0 8px' }}
>
{!avancedFiltersState
? filtersValues.map((item, key) => (
<EuiFlexItem key={key}>{getComponent(item)}</EuiFlexItem>
))
? filterInputs.map((item, key) => (
<EuiFlexItem key={key}>{getComponent(item)}</EuiFlexItem>
))
: ''}
<EuiFlexItem>
<KbnSearchBar
showFilterBar={false}
showQueryInput={avancedFiltersState}
onFiltersUpdated={onFiltersUpdated}
/>
<div className='wz-search-bar'>
<SearchBar
{...searchBarProps}
showFilterBar={false}
showQueryInput={avancedFiltersState}
onFiltersUpdated={onFiltersUpdated}
/>
</div>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup justifyContent="flexEnd" style={{ margin: '0 20px' }}>
<EuiFlexGroup justifyContent='flexEnd' style={{ margin: '0 20px' }}>
<EuiFlexItem className={'filters-search-bar'} style={{ margin: '0px' }}>
<KbnSearchBar
showDatePicker={false}
showQueryInput={false}
onFiltersUpdated={onFiltersUpdated}
/>
<div className='wz-search-bar'>
<SearchBar
{...searchBarProps}
showDatePicker={false}
showQueryInput={false}
onFiltersUpdated={onFiltersUpdated}
/>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSwitch
label="Advanced filters"
label='Advanced filters'
checked={avancedFiltersState}
onChange={() => changeSwitch()}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
</I18nProvider>
);
};
Expand Up @@ -26,7 +26,7 @@ export type tDataGridColumn = {
) => string | React.ReactNode;
} & EuiDataGridColumn;

type tDataGridProps = {
export type tDataGridProps = {
indexPattern: IndexPattern;
results: SearchResponse;
defaultColumns: tDataGridColumn[];
Expand Down
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import {
IDataSourceFactoryConstructor,
tDataSourceRepository,
Expand Down Expand Up @@ -43,8 +43,11 @@ type tUseDataSourceNotLoadedReturns = {
filterManager: null;
};

export function useDataSource<T extends tParsedIndexPattern, K extends PatternDataSource>(
props: tUseDataSourceProps<T, K>
export function useDataSource<
T extends tParsedIndexPattern,
K extends PatternDataSource,
>(
props: tUseDataSourceProps<T, K>,
): tUseDataSourceLoadedReturns<K> | tUseDataSourceNotLoadedReturns {
const {
filters: initialFilters = [],
Expand All @@ -59,6 +62,7 @@ export function useDataSource<T extends tParsedIndexPattern, K extends PatternDa
throw new Error('DataSource and repository are required');
}

const [subscription, setSubscription] = useState<any>();
const [dataSource, setDataSource] = useState<PatternDataSource>();
const [dataSourceFilterManager, setDataSourceFilterManager] =
useState<PatternDataSourceFilterManager | null>(null);
Expand All @@ -82,14 +86,14 @@ export function useDataSource<T extends tParsedIndexPattern, K extends PatternDa
return await dataSourceFilterManager?.fetch(params);
};

useEffect(() => {

let subscription;
(async () => {
const initialize = async () => {
setIsLoading(true);
const factory = injectedFactory || new PatternDataSourceFactory();
const patternsData = await repository.getAll();
const dataSources = await factory.createAll(DataSourceConstructor, patternsData);
const dataSources = await factory.createAll(
DataSourceConstructor,
patternsData,
);
const selector = new PatternDataSourceSelector(dataSources, repository);
const dataSource = await selector.getSelectedDataSource();
if (!dataSource) {
Expand All @@ -100,23 +104,29 @@ export function useDataSource<T extends tParsedIndexPattern, K extends PatternDa
dataSource,
initialFilters,
injectedFilterManager,
initialFetchFilters
initialFetchFilters,
);
// what the filters update
subscription = dataSourceFilterManager.getUpdates$().subscribe({
let subscription = dataSourceFilterManager.getUpdates$().subscribe({
next: () => {
// this is necessary to remove the hidden filters from the filter manager and not show them in the search bar
dataSourceFilterManager.setFilters(dataSourceFilterManager.getFilters());
dataSourceFilterManager.setFilters(
dataSourceFilterManager.getFilters(),
);
setAllFilters(dataSourceFilterManager.getFilters());
setFetchFilters(dataSourceFilterManager.getFetchFilters());
},
});
setSubscription(subscription);
setAllFilters(dataSourceFilterManager.getFilters());
setFetchFilters(dataSourceFilterManager.getFetchFilters());
setDataSourceFilterManager(dataSourceFilterManager);
setIsLoading(false);
})();
return () => subscription.unsubscribe();
};

useEffect(() => {
initialize();
return () => subscription && subscription.unsubscribe();
}, []);

if (isLoading) {
Expand Down