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

Update url with data source id; redirect on reload if ds id not present; minor fixes #1125

Merged
merged 4 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 55 additions & 15 deletions public/components/MDS/DataSourceMenuWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import React, { useContext } from 'react';
import { Route, Switch } from 'react-router-dom';
import { Route, RouteComponentProps, Switch, matchPath } from 'react-router-dom';
import {
DataSourceManagementPluginSetup,
DataSourceSelectableConfig,
Expand All @@ -15,7 +15,7 @@ import { ROUTES } from '../../utils/constants';
import { DataSourceContext } from '../../services/DataSourceContext';
import { DataSourceAttributes } from 'src/plugins/data_source/common/data_sources';

export interface DataSourceMenuWrapperProps {
export interface DataSourceMenuWrapperProps extends RouteComponentProps {
core: CoreStart;
dataSourceManagement?: DataSourceManagementPluginSetup;
dataSourceMenuReadOnly: boolean;
Expand All @@ -31,11 +31,12 @@ export const DataSourceMenuWrapper: React.FC<DataSourceMenuWrapperProps> = ({
dataSourceLoading,
setHeaderActionMenu,
dataSourceFilterFn,
location,
history,
}) => {
if (!dataSourceManagement) {
return null;
}

const { dataSource, setDataSource } = useContext(DataSourceContext)!;
const DataSourceMenuViewComponent = dataSourceManagement.ui?.getDataSourceMenu<
DataSourceViewConfig
Expand All @@ -44,29 +45,66 @@ export const DataSourceMenuWrapper: React.FC<DataSourceMenuWrapperProps> = ({
DataSourceSelectableConfig
>();

const readonlyDataSourcePaths = [
ROUTES.EDIT_DETECTOR_ALERT_TRIGGERS,
ROUTES.EDIT_DETECTOR_DETAILS,
ROUTES.EDIT_DETECTOR_RULES,
ROUTES.EDIT_FIELD_MAPPINGS,
ROUTES.RULES_EDIT,
ROUTES.CORRELATION_RULE_EDIT,
ROUTES.DETECTOR_DETAILS,
`${ROUTES.LOG_TYPES}/:logTypeId`,
`${ROUTES.ALERTS}/:detectorId`,
`${ROUTES.FINDINGS}/:detectorId`,
`${ROUTES.THREAT_INTEL_SOURCE_DETAILS}/:sourceId`,
ROUTES.THREAT_INTEL_EDIT_SCAN_CONFIG,
];

const pathToParentMap = {
[ROUTES.EDIT_DETECTOR_ALERT_TRIGGERS]: ROUTES.DETECTORS,
[ROUTES.EDIT_DETECTOR_DETAILS]: ROUTES.DETECTORS,
[ROUTES.EDIT_DETECTOR_RULES]: ROUTES.DETECTORS,
[ROUTES.EDIT_FIELD_MAPPINGS]: ROUTES.DETECTORS,
[ROUTES.RULES_EDIT]: ROUTES.RULES,
[ROUTES.CORRELATION_RULE_EDIT]: ROUTES.CORRELATION_RULES,
[ROUTES.DETECTOR_DETAILS]: ROUTES.DETECTORS,
[`${ROUTES.LOG_TYPES}/:logTypeId`]: ROUTES.LOG_TYPES,
[`${ROUTES.ALERTS}/:detectorId`]: ROUTES.ALERTS,
[`${ROUTES.FINDINGS}/:detectorId`]: ROUTES.FINDINGS,
[`${ROUTES.THREAT_INTEL_SOURCE_DETAILS}/:sourceId`]: ROUTES.THREAT_INTEL_OVERVIEW,
[ROUTES.THREAT_INTEL_EDIT_SCAN_CONFIG]: ROUTES.THREAT_INTEL_OVERVIEW,
};

const matchedPath = matchPath(location.pathname, {
path: readonlyDataSourcePaths,
});

if (matchedPath) {
// should have the data source id in url, if not then redirect back to the overview or related page for each path
const searchParams = new URLSearchParams(location.search);
const dataSourceId = searchParams.get('dataSourceId');
if (dataSourceId !== null && dataSourceId !== undefined) {
setDataSource([{ id: dataSourceId }]);
} else {
const parentPath = pathToParentMap[matchedPath.path] || ROUTES.OVERVIEW;
history.replace(parentPath);
}
}

const activeOption = dataSourceLoading ? undefined : [dataSource];

return (
<Switch>
<Route
path={[
ROUTES.EDIT_DETECTOR_ALERT_TRIGGERS,
ROUTES.EDIT_DETECTOR_DETAILS,
ROUTES.EDIT_DETECTOR_RULES,
ROUTES.EDIT_FIELD_MAPPINGS,
ROUTES.RULES_EDIT,
ROUTES.CORRELATION_RULE_EDIT,
ROUTES.DETECTOR_DETAILS,
`${ROUTES.LOG_TYPES}/:logTypeId`,
`${ROUTES.ALERTS}/:detectorId`,
`${ROUTES.FINDINGS}/:detectorId`,
]}
path={readonlyDataSourcePaths}
render={() => {
return (
<DataSourceMenuViewComponent
componentConfig={{
fullWidth: false,
activeOption: [dataSource],
notifications: core.notifications,
savedObjects: core.savedObjects.client,
dataSourceFilter: dataSourceFilterFn,
}}
componentType="DataSourceView"
Expand All @@ -83,6 +121,8 @@ export const DataSourceMenuWrapper: React.FC<DataSourceMenuWrapperProps> = ({
componentConfig={{
fullWidth: false,
activeOption: [dataSource],
notifications: core.notifications,
savedObjects: core.savedObjects.client,
dataSourceFilter: dataSourceFilterFn,
}}
componentType="DataSourceView"
Expand Down
42 changes: 27 additions & 15 deletions public/pages/Main/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ import { ThreatIntelOverview } from '../ThreatIntel/containers/Overview/ThreatIn
import { AddThreatIntelSource } from '../ThreatIntel/containers/AddThreatIntelSource/AddThreatIntelSource';
import { ThreatIntelScanConfigForm } from '../ThreatIntel/containers/ScanConfiguration/ThreatIntelScanConfigForm';
import { ThreatIntelSource } from '../ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource';
import * as pluginManifest from "../../../opensearch_dashboards.json";
import { DataSourceAttributes } from "../../../../../src/plugins/data_source/common/data_sources";
import semver from "semver";
import queryString from "query-string";
import * as pluginManifest from '../../../opensearch_dashboards.json';
import { DataSourceAttributes } from '../../../../../src/plugins/data_source/common/data_sources';
import semver from 'semver';
import queryString from 'query-string';

enum Navigation {
SecurityAnalytics = 'Security Analytics',
Expand Down Expand Up @@ -202,11 +202,19 @@ export default class Main extends Component<MainProps, MainState> {
prevState: Readonly<MainState>,
snapshot?: any
): void {
if (this.props.location.pathname === prevProps.location.pathname) {
return;
const pathnameChanged = this.props.location.pathname !== prevProps.location.pathname;

if (pathnameChanged || this.state.selectedDataSource.id !== prevState.selectedDataSource.id) {
const searchParams = new URLSearchParams(this.props.location.search);
searchParams.set('dataSourceId', this.state.selectedDataSource.id);
this.props.history.replace({
search: searchParams.toString(),
});
}

this.updateSelectedNavItem();
if (pathnameChanged) {
this.updateSelectedNavItem();
}
}

setDateTimeFilter = (dateTimeFilter: DateTimeFilter) => {
Expand Down Expand Up @@ -399,11 +407,13 @@ export default class Main extends Component<MainProps, MainState> {
};

dataSourceFilterFn = (dataSource: SavedObject<DataSourceAttributes>) => {
const dataSourceVersion = dataSource?.attributes?.dataSourceVersion || "";
const dataSourceVersion = dataSource?.attributes?.dataSourceVersion || '';
const installedPlugins = dataSource?.attributes?.installedPlugins || [];
return (
semver.satisfies(dataSourceVersion, pluginManifest.supportedOSDataSourceVersions) &&
pluginManifest.requiredOSDataSourcePlugins.every((plugin) => installedPlugins.includes(plugin))
pluginManifest.requiredOSDataSourcePlugins.every((plugin) =>
installedPlugins.includes(plugin)
)
);
};

Expand Down Expand Up @@ -447,6 +457,7 @@ export default class Main extends Component<MainProps, MainState> {
<>
{multiDataSourceEnabled && (
<DataSourceMenuWrapper
{...this.props}
dataSourceManagement={dataSourceManagement}
core={core}
dataSourceLoading={this.state.dataSourceLoading}
Expand All @@ -458,12 +469,12 @@ export default class Main extends Component<MainProps, MainState> {
{!dataSourceLoading && services && (
<EuiPage restrictWidth={'100%'}>
{/* Hide side navigation bar when on any HIDDEN_NAV_ROUTES pages. */}
{!HIDDEN_NAV_ROUTES.some((route) => pathname.match(route)) && (
!core.chrome.navGroup.getNavGroupEnabled() &&
<EuiPageSideBar style={{ minWidth: 200 }}>
<EuiSideNav style={{ width: 200 }} items={sideNav} />
</EuiPageSideBar>
)}
{!HIDDEN_NAV_ROUTES.some((route) => pathname.match(route)) &&
!core.chrome.navGroup.getNavGroupEnabled() && (
<EuiPageSideBar style={{ minWidth: 200 }}>
<EuiSideNav style={{ width: 200 }} items={sideNav} />
</EuiPageSideBar>
)}
<EuiPageBody>
{callout ? <Callout {...callout} /> : null}
{showFlyoutData ? (
Expand Down Expand Up @@ -784,6 +795,7 @@ export default class Main extends Component<MainProps, MainState> {
<ThreatIntelOverview
{...props}
threatIntelService={services.threatIntelService}
dataSource={selectedDataSource}
/>
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,19 +596,21 @@ export const AddThreatIntelSource: React.FC<AddThreatIntelSourceProps> = ({
</p>
</EuiText>
<EuiSpacer size="m" />
<EuiFormRow isInvalid={!!inputErrors.ioc_types} error={inputErrors.ioc_types}>
<EuiCompressedFormRow isInvalid={!!inputErrors.ioc_types} error={inputErrors.ioc_types}>
<EuiCompressedCheckboxGroup
options={checkboxes}
idToSelectedMap={checkboxIdToSelectedMap}
onChange={onIocTypesChange}
/>
</EuiFormRow>
</EuiCompressedFormRow>
<EuiSpacer />
</EuiPanel>
<EuiSpacer />
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiSmallButton onClick={() => history.push(ROUTES.THREAT_INTEL_OVERVIEW)}>Cancel</EuiSmallButton>
<EuiSmallButton onClick={() => history.push(ROUTES.THREAT_INTEL_OVERVIEW)}>
Cancel
</EuiSmallButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSmallButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import React, { MouseEventHandler, useCallback, useEffect, useMemo } from 'react
import { BREADCRUMBS, ROUTES } from '../../../../utils/constants';
import { useState } from 'react';
import {
DataSourceProps,
ThreatIntelNextStepId,
ThreatIntelScanConfig,
ThreatIntelSourceItem,
Expand All @@ -34,13 +35,14 @@ import { ThreatIntelLogScanConfig } from '../../components/ThreatIntelLogScanCon
import { setBreadcrumbs } from '../../../../utils/helpers';
import { PageHeader } from '../../../../components/PageHeader/PageHeader';

export interface ThreatIntelOverviewProps extends RouteComponentProps {
export interface ThreatIntelOverviewProps extends RouteComponentProps, DataSourceProps {
threatIntelService: ThreatIntelService;
}

export const ThreatIntelOverview: React.FC<ThreatIntelOverviewProps> = ({
history,
threatIntelService,
dataSource,
}) => {
const [threatIntelSources, setThreatIntelSources] = useState<ThreatIntelSourceItem[]>([]);
const [scanConfig, setScanConfig] = useState<ThreatIntelScanConfig | undefined>(undefined);
Expand Down Expand Up @@ -136,7 +138,7 @@ export const ThreatIntelOverview: React.FC<ThreatIntelOverviewProps> = ({

searchSources();
getScanConfig();
}, [threatIntelService]);
}, [threatIntelService, dataSource.id]);

const nextStepClickHandlerById: Record<ThreatIntelNextStepId, MouseEventHandler> = {
['add-source']: addThreatIntelSourceActionHandler,
Expand Down
73 changes: 58 additions & 15 deletions public/security_analytics_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { CHANNEL_TYPES } from './pages/CreateDetector/components/ConfigureAlerts
import { DataSourceManagementPluginSetup } from '../../../src/plugins/data_source_management/public';
import { getPlugins, setIsNotificationPluginInstalled } from './utils/helpers';
import { OS_NOTIFICATION_PLUGIN } from './utils/constants';
import { dataSourceInfo } from './services/utils/constants';
import { History } from 'history';
import { getBrowserServices } from './services/utils/constants';

export function renderApp(
Expand All @@ -37,21 +39,62 @@ export function renderApp(
ReactDOM.render(
<Router>
<Route
render={(props) => (
<DarkModeContext.Provider value={isDarkMode}>
<SecurityAnalyticsContext.Provider value={{ services, metrics }}>
<CoreServicesContext.Provider value={coreStart}>
<Main
{...props}
landingPage={landingPage}
multiDataSourceEnabled={!!depsStart.dataSource}
dataSourceManagement={dataSourceManagement}
setActionMenu={params.setHeaderActionMenu}
/>
</CoreServicesContext.Provider>
</SecurityAnalyticsContext.Provider>
</DarkModeContext.Provider>
)}
render={(props) => {
const originalMethods = {
push: props.history.push,
replace: props.history.replace,
};
const wrapper = (method: 'push' | 'replace') => (
...args: Parameters<History['push']>
) => {
if (typeof args[0] === 'string') {
const url = new URL(args[0], window.location.origin);
const searchParams = url.searchParams;
searchParams.set('dataSourceId', dataSourceInfo.activeDataSource.id);
originalMethods[method](
{
pathname: url.pathname,
search: searchParams.toString(),
},
...args.slice(1)
);
} else if (typeof args[0] === 'object') {
const searchParams = new URLSearchParams(args[0].search);
searchParams.set('dataSourceId', dataSourceInfo.activeDataSource.id);
originalMethods[method](
{
...args[0],
search: searchParams.toString(),
},
...args.slice(1)
);
} else {
originalMethods[method](...args);
}
};

props.history = {
...props.history,
push: wrapper('push'),
replace: wrapper('replace'),
};

return (
<DarkModeContext.Provider value={isDarkMode}>
<SecurityAnalyticsContext.Provider value={{ services, metrics }}>
<CoreServicesContext.Provider value={coreStart}>
<Main
{...props}
landingPage={landingPage}
multiDataSourceEnabled={!!depsStart.dataSource}
dataSourceManagement={dataSourceManagement}
setActionMenu={params.setHeaderActionMenu}
/>
</CoreServicesContext.Provider>
</SecurityAnalyticsContext.Provider>
</DarkModeContext.Provider>
);
}}
/>
</Router>,
params.element
Expand Down
1 change: 0 additions & 1 deletion types/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { CorrelationFinding } from './Correlations';
import { DetectorHit } from './Detector';
import { Finding, FindingDetailsFlyoutProps } from './Finding';
Expand Down
Loading