Skip to content

Commit fad86d9

Browse files
Frontend - Data model mapping (#2699)
* data model mapping gui * refactor + test * prettier * fix * fixed mock * adjusted frontend env var
1 parent 41642d8 commit fad86d9

File tree

10 files changed

+202
-9
lines changed

10 files changed

+202
-9
lines changed

frontend/src/components/GuideWrapper.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Joyride from "react-joyride";
33
import { Outlet, useNavigate } from "react-router-dom";
44
import { useMount } from "react-use";
55
import { useGuideContext } from "../contexts/GuideContext";
6+
import { INTELOWL_DOCS_URL } from "../constants/environment";
67

78
export default function GuideWrapper() {
89
const { guideState, setGuideState } = useGuideContext();
@@ -17,8 +18,7 @@ export default function GuideWrapper() {
1718
<p>
1819
Welcome to IntelOwls Guide for First Time Visitors! For further
1920
questions you could either check out our{" "}
20-
<a href="https://intelowlproject.github.io/docs/">docs</a> or reach
21-
us out on{" "}
21+
<a href={INTELOWL_DOCS_URL}>docs</a> or reach us out on{" "}
2222
<a href="https://gsoc-slack.honeynet.org/">
2323
the official IntelOwl slack channel
2424
</a>

frontend/src/components/common/form/TLPSelectInput.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { TLPDescriptions } from "../../../constants/miscConst";
1414
import { TlpChoices } from "../../../constants/advancedSettingsConst";
1515
import { TLPTag } from "../TLPTag";
1616
import { TLPColors } from "../../../constants/colorConst";
17+
import { INTELOWL_DOCS_URL } from "../../../constants/environment";
1718

1819
export function TLPSelectInputLabel(props) {
1920
const { size } = props;
@@ -36,7 +37,7 @@ export function TLPSelectInputLabel(props) {
3637
<br />
3738
For more info check the{" "}
3839
<Link
39-
to="https://intelowlproject.github.io/docs/IntelOwl/usage/#tlp-support"
40+
to={`${INTELOWL_DOCS_URL}IntelOwl/usage/#tlp-support`}
4041
target="_blank"
4142
>
4243
official doc.

frontend/src/components/plugins/PluginConfigModal.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { AnalyzerConfigForm } from "./forms/AnalyzerConfigForm";
99
import { PivotConfigForm } from "./forms/PivotConfigForm";
1010
import { PlaybookConfigForm } from "./forms/PlaybookConfigForm";
1111
import { PluginConfigContainer } from "./PluginConfigContainer";
12+
import { INTELOWL_DOCS_URL } from "../../constants/environment";
1213

1314
export function PluginConfigModal({
1415
pluginConfig,
@@ -45,7 +46,7 @@ export function PluginConfigModal({
4546
<br />
4647
For more info check the{" "}
4748
<Link
48-
to="https://intelowlproject.github.io/docs/IntelOwl/usage/#parameters"
49+
to={`${INTELOWL_DOCS_URL}IntelOwl/usage/#parameters`}
4950
target="_blank"
5051
>
5152
official doc.

frontend/src/components/plugins/tables/PluginWrapper.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { useOrganizationStore } from "../../../stores/useOrganizationStore";
1515
import { usePluginConfigurationStore } from "../../../stores/usePluginConfigurationStore";
1616
import { PluginsTypes } from "../../../constants/pluginConst";
17+
import { INTELOWL_DOCS_URL } from "../../../constants/environment";
1718

1819
// table config
1920
const tableConfig = {};
@@ -67,7 +68,7 @@ export default function PluginWrapper({
6768
<span className="text-muted">
6869
{description} For more info check the{" "}
6970
<Link
70-
to="https://intelowlproject.github.io/docs/IntelOwl/usage/#plugins-framework"
71+
to={`${INTELOWL_DOCS_URL}IntelOwl/usage/#plugins-framework`}
7172
target="_blank"
7273
>
7374
official doc.

frontend/src/components/plugins/tables/pluginActionsButtons.jsx

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
import React from "react";
22
import PropTypes from "prop-types";
3-
import { Button, Modal, ModalHeader, ModalBody } from "reactstrap";
3+
import {
4+
Button,
5+
Modal,
6+
ModalHeader,
7+
ModalBody,
8+
UncontrolledTooltip,
9+
} from "reactstrap";
410
import { RiHeartPulseLine } from "react-icons/ri";
5-
import { MdDelete, MdFileDownload, MdEdit } from "react-icons/md";
11+
import {
12+
MdDelete,
13+
MdFileDownload,
14+
MdEdit,
15+
MdInfoOutline,
16+
} from "react-icons/md";
617
import { BsPeopleFill } from "react-icons/bs";
718
import { AiFillSetting } from "react-icons/ai";
819
import { FaDiagramProject } from "react-icons/fa6";
20+
import { VscJson } from "react-icons/vsc";
21+
import { Link } from "react-router-dom";
922

10-
import { IconButton } from "@certego/certego-ui";
23+
import { IconButton, CustomJsonInput } from "@certego/certego-ui";
1124

1225
import { useAuthStore } from "../../../stores/useAuthStore";
1326
import { useOrganizationStore } from "../../../stores/useOrganizationStore";
@@ -17,6 +30,10 @@ import { deleteConfiguration } from "../pluginsApi";
1730
import { PluginsTypes } from "../../../constants/pluginConst";
1831
import { PluginConfigModal } from "../PluginConfigModal";
1932
import { PlaybookFlows } from "../flows/PlaybookFlows";
33+
import {
34+
INTELOWL_DOCS_URL,
35+
INTELOWL_REPO_URL,
36+
} from "../../../constants/environment";
2037

2138
export function PluginHealthCheckButton({ pluginName, pluginType_ }) {
2239
const { checkPluginHealth } = usePluginConfigurationStore(
@@ -459,3 +476,100 @@ export function PlaybookFlowsButton({ playbook }) {
459476
PlaybookFlowsButton.propTypes = {
460477
playbook: PropTypes.object.isRequired,
461478
};
479+
480+
export function MappingDataModel({ data, type, pythonModule }) {
481+
// state
482+
const [showModal, setShowModal] = React.useState(false);
483+
const pythonModuleName = pythonModule.split(".")[0];
484+
485+
return (
486+
<div className="d-flex flex-column align-items-center p-1">
487+
<IconButton
488+
id={`mapping-data-model__${pythonModuleName}`}
489+
color="info"
490+
size="sm"
491+
Icon={VscJson}
492+
title="View data model mapping"
493+
onClick={() => setShowModal(!showModal)}
494+
titlePlacement="top"
495+
disabled={Object.keys(data).length === 0}
496+
/>
497+
{showModal && (
498+
<Modal
499+
id="mapping-data-model-modal"
500+
autoFocus
501+
centered
502+
zIndex="1050"
503+
size="lg"
504+
keyboard={false}
505+
backdrop="static"
506+
labelledBy="Data model modal"
507+
isOpen={showModal}
508+
style={{ minWidth: "50%" }}
509+
>
510+
<ModalHeader className="mx-2" toggle={() => setShowModal(false)}>
511+
<small className="text-info">
512+
Data model mapping
513+
<MdInfoOutline
514+
id="dataModelMapping_infoicon"
515+
fontSize="16"
516+
className="ms-2"
517+
/>
518+
<UncontrolledTooltip
519+
trigger="hover"
520+
target="dataModelMapping_infoicon"
521+
placement="right"
522+
fade={false}
523+
autohide={false}
524+
innerClassName="p-2 text-start text-nowrap md-fit-content"
525+
>
526+
The main functionality of a `DataModel` is to model an
527+
`Analyzer` result to a set of prearranged keys, allowing users
528+
to easily search, evaluate and use the analyzer result.
529+
<br />
530+
For more info check the{" "}
531+
<Link
532+
to={`${INTELOWL_DOCS_URL}IntelOwl/usage/#datamodels`}
533+
target="_blank"
534+
>
535+
official doc.
536+
</Link>
537+
</UncontrolledTooltip>
538+
</small>
539+
</ModalHeader>
540+
<ModalBody className="d-flex flex-column mx-2">
541+
<small>
542+
The <strong className="text-info">keys </strong>
543+
represent the path used to retrieve the value in the analyzer
544+
report and the <strong className="text-info">value</strong> the
545+
path of the data model.
546+
</small>
547+
<small>
548+
For more info check the{" "}
549+
<Link
550+
to={`${INTELOWL_REPO_URL}tree/master/api_app/analyzers_manager/${type}_analyzers/${pythonModuleName}.py`}
551+
target="_blank"
552+
>
553+
analyzer&apos;s source code.
554+
</Link>
555+
</small>
556+
<div className="my-2 d-flex justify-content-center">
557+
<CustomJsonInput
558+
id="data_model_mapping_json"
559+
placeholder={data}
560+
viewOnly
561+
confirmGood={false}
562+
/>
563+
</div>
564+
</ModalBody>
565+
</Modal>
566+
)}
567+
</div>
568+
);
569+
}
570+
571+
MappingDataModel.propTypes = {
572+
type: PropTypes.string.isRequired,
573+
data: PropTypes.object.isRequired,
574+
pythonModule: PropTypes.string.isRequired,
575+
};

frontend/src/components/plugins/tables/pluginTableColumns.jsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
PluginDeletionButton,
2222
PluginConfigButton,
2323
PlaybookFlowsButton,
24+
MappingDataModel,
2425
} from "./pluginActionsButtons";
2526
import { JobTypes } from "../../../constants/jobConst";
2627
import TableCell from "../../common/TableCell";
@@ -158,8 +159,23 @@ export const analyzersTableColumns = [
158159
Cell: ({ value }) => <TLPTag value={value} />,
159160
Filter: SelectOptionsFilter,
160161
selectOptions: TlpChoices,
162+
disableSortBy: true,
161163
maxWidth: 80,
162164
},
165+
{
166+
Header: "Data model",
167+
id: "data_model",
168+
accessor: (analyzerConfig) => analyzerConfig,
169+
Cell: ({ value }) => (
170+
<MappingDataModel
171+
data={value.mapping_data_model}
172+
type={value.type}
173+
pythonModule={value.python_module}
174+
/>
175+
),
176+
maxWidth: 70,
177+
disableSortBy: true,
178+
},
163179
{
164180
Header: "Actions",
165181
id: "actions",

frontend/src/components/search/Search.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { format } from "date-fns";
2020
import { PluginsTypes, PluginFinalStatuses } from "../../constants/pluginConst";
2121
import { searchTableColumns } from "./searchTableColumns";
2222
import { pluginReportQueries } from "./searchApi";
23+
import { INTELOWL_DOCS_URL } from "../../constants/environment";
2324

2425
// table config
2526
const tableConfig = { enableExpanded: true, enableFlexLayout: true };
@@ -145,7 +146,7 @@ export default function Search() {
145146
This section only works if Elasticsearch has been configured
146147
correctly. For more info check the{" "}
147148
<Link
148-
to="https://intelowlproject.github.io/docs/IntelOwl/advanced_configuration/#elasticsearch"
149+
to={`${INTELOWL_DOCS_URL}IntelOwl/advanced_configuration/#elasticsearch`}
149150
target="_blank"
150151
>
151152
official doc.

frontend/src/constants/environment.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const INTELOWL_DOCS_URL = "https://intelowlproject.github.io/docs/";
33
export const PYINTELOWL_GH_URL =
44
"https://github.com/intelowlproject/pyintelowl";
55
export const INTELOWL_TWITTER_ACCOUNT = "intel_owl";
6+
export const INTELOWL_REPO_URL = "https://github.com/intelowlproject/IntelOwl/";
67

78
// env variables
89
export const VERSION = process.env.REACT_APP_INTELOWL_VERSION;

frontend/tests/components/plugins/tables/pluginActionsButtons.test.jsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
PlaybooksEditButton,
1515
PluginConfigButton,
1616
PlaybookFlowsButton,
17+
MappingDataModel,
1718
} from "../../../../src/components/plugins/tables/pluginActionsButtons";
1819
import {
1920
mockedUseOrganizationStoreOwner,
@@ -485,3 +486,59 @@ describe("PlaybookFlowsButton test", () => {
485486
});
486487
});
487488
});
489+
490+
describe("DataModel mapping test", () => {
491+
test("DataModel mapping button", async () => {
492+
const userAction = userEvent.setup();
493+
const data = {
494+
mapping_data_model: {
495+
permalink: "external_references",
496+
"data.hostnames": "resolutions",
497+
},
498+
type: "observable",
499+
python_module: "pythonmodule.pythonclass",
500+
};
501+
const { container } = render(
502+
<BrowserRouter>
503+
<MappingDataModel
504+
data={data.mapping_data_model}
505+
type={data.type}
506+
pythonModule={data.python_module}
507+
/>
508+
</BrowserRouter>,
509+
);
510+
511+
const dataModelMappingIcon = container.querySelector(
512+
"#mapping-data-model__pythonmodule",
513+
);
514+
expect(dataModelMappingIcon).toBeInTheDocument();
515+
516+
userAction.click(dataModelMappingIcon);
517+
await waitFor(() => {
518+
expect(screen.getByText("Data model mapping")).toBeInTheDocument();
519+
});
520+
});
521+
522+
test("DataModel mapping button - disabled", async () => {
523+
const data = {
524+
mapping_data_model: {},
525+
type: "observable",
526+
python_module: "pythonmodule.pythonclass",
527+
};
528+
const { container } = render(
529+
<BrowserRouter>
530+
<MappingDataModel
531+
data={data.mapping_data_model}
532+
type={data.type}
533+
pythonModule={data.python_module}
534+
/>
535+
</BrowserRouter>,
536+
);
537+
538+
const dataModelMappingIcon = container.querySelector(
539+
"#mapping-data-model__pythonmodule",
540+
);
541+
expect(dataModelMappingIcon).toBeInTheDocument();
542+
expect(dataModelMappingIcon.className).toContain("disabled");
543+
});
544+
});

frontend/tests/mock.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ export const mockedPlugins = {
315315
run_hash: false,
316316
run_hash_type: "",
317317
not_supported_filetypes: [],
318+
mapping_data_model: {},
318319
params: {
319320
query_type: {
320321
type: "str",

0 commit comments

Comments
 (0)