Skip to content

Commit

Permalink
Generate URL with predefined filters (#6745)
Browse files Browse the repository at this point in the history
* Add transform filter method

* Add unit test to transform filter format

* Add new create filter and convert to url format filter methods

* Add unit tests

* Apply new methods in the rule info link

* Update CHANGELOG

* Fix some errors

* Change type by enum

* Add is between and is not between operator

* Linter

* Add new filter creator in last alerts stats

* change filtersToURLFormat function name

* Fix unused code

* Fix app id in agent id link

* Fix link agent id

---------

Co-authored-by: Federico Rodriguez <federico.rodriguez@wazuh.com>
  • Loading branch information
Machi3mfl and asteriscos committed Jun 7, 2024
1 parent 30ff6ee commit 8bd040f
Show file tree
Hide file tree
Showing 10 changed files with 1,047 additions and 510 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Changed agent log collector socket API response controller component [#6660](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6660)
- Improve margins and paddins in the Events, Inventory and Control tabs [#6708](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6708)
- Refactored the search bar to correctly handle fixed and user-added filters [#6716](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6716)
- Generate URL with predefined filters [#6745](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6745)

### Fixed

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import rison from 'rison-node';
import store from '../../../../redux/store';
import { AppState } from '../../../../react-services/app-state';
import { getDataPlugin } from '../../../../kibana-services';
Expand Down Expand Up @@ -80,13 +81,16 @@ export function getFilterAllowedAgents(
};
}

type tFilterType =
| 'is'
| 'is not'
| 'exists'
| 'does not exist'
| 'is one of'
| 'is not one of';
export enum FILTER_OPERATOR {
IS = 'is',
IS_NOT = 'is not',
EXISTS = 'exists',
DOES_NOT_EXISTS = 'does not exists',
IS_ONE_OF = 'is one of',
IS_NOT_ONE_OF = 'is not one of',
IS_BETWEEN = 'is between',
IS_NOT_BETWEEN = 'is not between',
}

export class PatternDataSourceFilterManager
implements tDataSourceFilterManager
Expand Down Expand Up @@ -394,24 +398,40 @@ export class PatternDataSourceFilterManager
/********************** FILTERS FACTORY ***************************/
/******************************************************************/

/**
* Returns a filter with the field and value received
* @param field
* @param value
* @returns
*/
createFilter(
type: tFilterType,
static createFilter(
type: FILTER_OPERATOR,
key: string,
value: string | [],
indexPatternId: string,
controlledBy?: string,
): tFilter {
) {
if (
type === FILTER_OPERATOR.IS_ONE_OF ||
type === FILTER_OPERATOR.IS_NOT_ONE_OF
) {
if (!Array.isArray(value)) {
throw new Error('The value must be an array');
}
}

if (type === FILTER_OPERATOR.IS_BETWEEN) {
if (
!Array.isArray(value) &&
value.length <= 2 &&
value.length > 0 &&
value.some(v => isNaN(Number(v)))
) {
throw new Error('The value must be an array with one or two numbers');
}
}

switch (type) {
case 'is':
return this.generateFilter(
case FILTER_OPERATOR.IS:
case FILTER_OPERATOR.IS_NOT:
return PatternDataSourceFilterManager.generateFilter(
key,
value,
this.dataSource.id,
indexPatternId,
{
query: {
match_phrase: {
Expand All @@ -422,59 +442,30 @@ export class PatternDataSourceFilterManager
},
},
controlledBy,
type === FILTER_OPERATOR.IS_NOT,
);
case 'is not':
return this.generateFilter(
key,
value,
this.dataSource.id,
{
query: {
match_phrase: {
[key]: {
query: value,
},
},
},
},
controlledBy,
true,
);
case 'exists':
return {
meta: {
alias: null,
disabled: false,
key: key,
value: 'exists',
negate: false,
type: 'exists',
index: this.dataSource.id,
controlledBy,
},
exists: { field: key },
$state: { store: 'appState' },
};
case 'does not exist':
case FILTER_OPERATOR.EXISTS:
case FILTER_OPERATOR.DOES_NOT_EXISTS:
return {
meta: {
alias: null,
disabled: false,
key: key,
value: 'exists',
negate: true,
negate: type === FILTER_OPERATOR.DOES_NOT_EXISTS,
type: 'exists',
index: this.dataSource.id,
index: indexPatternId,
controlledBy,
},
exists: { field: key },
$state: { store: 'appState' },
};
case 'is one of':
return this.generateFilter(
case FILTER_OPERATOR.IS_ONE_OF:
case FILTER_OPERATOR.IS_NOT_ONE_OF:
return PatternDataSourceFilterManager.generateFilter(
key,
value,
this.dataSource.id,
indexPatternId,
{
query: {
bool: {
Expand All @@ -490,48 +481,65 @@ export class PatternDataSourceFilterManager
},
},
controlledBy,
type === FILTER_OPERATOR.IS_NOT_ONE_OF,
);
case 'is not one of':
return this.generateFilter(
key,
value,
this.dataSource.id,
{
query: {
bool: {
minimum_should_match: 1,
should: value.map((v: string) => ({
match_phrase: {
[key]: {
query: v,
},
},
})),
},
case FILTER_OPERATOR.IS_BETWEEN:
case FILTER_OPERATOR.IS_NOT_BETWEEN:
return {
meta: {
alias: null,
disabled: false,
key: key,
params: {
gte: value[0],
lte: value[1] || NaN,
},
negate: type === FILTER_OPERATOR.IS_NOT_BETWEEN,
type: 'range',
index: indexPatternId,
controlledBy,
},
controlledBy,
true,
);
range: {
[key]: {
gte: value[0],
lte: value[1] || NaN,
},
},
$state: { store: 'appState' },
};
default:
return this.generateFilter(
key,
value,
this.dataSource.id,
undefined,
controlledBy,
);
throw new Error('Invalid filter type');
}
}

/**
* Returns a filter object depending on the type of filter received
*
* @returns
*/
createFilter(
type: FILTER_OPERATOR,
key: string,
value: string | [],
controlledBy?: string,
): tFilter {
return PatternDataSourceFilterManager.createFilter(
type,
key,
value,
this.dataSource.id,
controlledBy,
);
}

/**
* Return a simple filter object with the field, value and index pattern received
*
* @param field
* @param value
* @param indexPatternId
*/
private generateFilter(
static generateFilter(
field: string,
value: string | string[],
indexPatternId: string,
Expand All @@ -555,4 +563,16 @@ export class PatternDataSourceFilterManager
$state: { store: 'appState' },
};
}

/**
* Transform the filter format to the format used in the URL
* Receives a filter object and returns a filter object with the format used in the URL using rison-node
*/

static filtersToURLFormat(filters: tFilter[]) {
const filterCopy = filters || [];
return rison.encode({
filters: filterCopy,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ import { VisualizationBasicWidgetSelector } from '../../../charts/visualizations
import { getRequirementAlerts } from './lib';
import { useTimeFilter } from '../../../hooks';
import { getCore } from '../../../../../kibana-services';
import { getIndexPattern } from '../../../../../react-services';
import { buildPhraseFilter } from '../../../../../../../../src/plugins/data/common';
import rison from 'rison-node';
import { AppState } from '../../../../../react-services';
import { WAZUH_MODULES } from '../../../../../../common/wazuh-modules';
import { PinnedAgentManager } from '../../../../wz-agent-selector/wz-agent-selector-service';
import { FILTER_OPERATOR, PatternDataSourceFilterManager } from '../../../data-source/pattern/pattern-data-source-filter-manager';

const selectionOptionsCompliance = [
{ value: 'pci_dss', text: 'PCI DSS' },
Expand All @@ -48,32 +47,16 @@ export function RequirementVis(props) {
const pinnedAgentManager = new PinnedAgentManager();

const goToDashboardWithFilter = async (requirement, key, agent) => {
try {
pinnedAgentManager.pinAgent(agent);
const indexPattern = getIndexPattern();
const filters = [
{
...buildPhraseFilter(
{ name: `rule.${requirement}`, type: 'text' },
key,
indexPattern,
),
$state: { isImplicit: false, store: 'appState' },
},
];
const _w = { filters };
const params = {
tab: requirementNameModuleID[requirement],
_w: rison.encode(_w),
};
const url = Object.entries(params)
.map(e => e.join('='))
.join('&');
// TODO: redirection to gdpr will fail
getCore().application.navigateToApp(WAZUH_MODULES[params.tab].appId, {
path: `#/overview?${url}`,
});
} catch (error) {}
pinnedAgentManager.pinAgent(agent);
const indexPatternId = AppState.getCurrentPattern();
const filters = [
PatternDataSourceFilterManager.createFilter(FILTER_OPERATOR.IS, `rule.${requirement}`, key, indexPatternId)
];
const tabName = requirementNameModuleID[requirement];
const params = `tab=${tabName}&agentId=${agent.id}&_g=${PatternDataSourceFilterManager.filtersToURLFormat(filters)}`
getCore().application.navigateToApp(WAZUH_MODULES[tabName].appId, {
path: `#/overview?${params}`,
});
};

const fetchData = useCallback(
Expand All @@ -85,15 +68,15 @@ export function RequirementVis(props) {
);
return buckets?.length
? buckets.map(({ key, doc_count }, index) => ({
label: key,
value: doc_count,
color: colors[index],
onClick:
selectedOptionValue === 'gpg13'
? undefined
: () =>
goToDashboardWithFilter(selectedOptionValue, key, agent),
}))
label: key,
value: doc_count,
color: colors[index],
onClick:
selectedOptionValue === 'gpg13'
? undefined
: () =>
goToDashboardWithFilter(selectedOptionValue, key, agent),
}))
: null;
},
[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { withReduxProvider, withUserAuthorizationPrompt } from '../../../hocs';
import { compose } from 'redux';
import SCAPoliciesTable from '../../../../agents/sca/inventory/agent-policies-table';
import { MODULE_SCA_CHECK_RESULT_LABEL } from '../../../../../../common/constants';
import { configurationAssessment } from '../../../../../utils/applications';
import { configurationAssessment, endpointSummary } from '../../../../../utils/applications';
import { RedirectAppLinks } from '../../../../../../../../src/plugins/opensearch_dashboards_react/public';
import { PinnedAgentManager } from '../../../../wz-agent-selector/wz-agent-selector-service';

Expand Down Expand Up @@ -152,7 +152,9 @@ export const ScaScan = compose(
'scaPolicies',
JSON.stringify(this.state.policies),
);
window.location.href = `#/overview?tab=sca&redirectPolicy=${policy.policy_id}&agentId=${this.props.agent.id}`;
getCore().application.navigateToApp(endpointSummary.id, {
path: `#/overview?tab=sca&redirectPolicy=${policy.policy_id}&agentId=${this.props.agent.id}`
});
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,13 @@ export const RequirementFlyout = connect(mapStateToProps)(
id: 'agent.id',
displayAsText: 'Agent',
render: value => (
<EuiLink
onClick={e => {
getCore().application.navigateToApp(endpointSummary.id, {
path: `#/agents/?tab=welcome&agent=${value}`,
});
}}
>
{value}
</EuiLink>
<RedirectAppLinks application={getCore().application}>
<EuiLink
href={`${endpointSummary.id}#/agents/?tab=welcome&agent=${value}`}
>
{value}
</EuiLink >
</RedirectAppLinks>
),
},
{
Expand Down
Loading

0 comments on commit 8bd040f

Please sign in to comment.