-
Notifications
You must be signed in to change notification settings - Fork 801
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api): enrichments - Mapping (#840)
Signed-off-by: asharonbaltazar <[email protected]> Signed-off-by: talboren <[email protected]> Signed-off-by: talboren <[email protected]> Co-authored-by: asharonbaltazar <[email protected]> Co-authored-by: asharonbaltazar <[email protected]>
- Loading branch information
1 parent
8df72c3
commit a2d2baa
Showing
23 changed files
with
815 additions
and
50 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
--- | ||
title: "Alert Enrichment - Mapping" | ||
--- | ||
|
||
# Alert Mapping and Enrichment | ||
|
||
Keep's Alert Mapping and Enrichment feature provides a powerful mechanism for dynamically enhancing alert data by leveraging external data sources, such as CSV files. This feature allows for the matching of incoming alerts to specific records in a CSV file based on predefined attributes (matchers) and enriching those alerts with additional information from the matched records. | ||
|
||
## Introduction | ||
|
||
In complex monitoring environments, the need to enrich alert data with additional context is critical for effective alert analysis and response. Keep's Alert Mapping and Enrichment enables users to define rules that match alerts to rows in a CSV file, appending or modifying alert attributes with the values from matching rows. This process adds significant value to each alert, providing deeper insights and enabling more precise and informed decision-making. | ||
|
||
## How It Works | ||
|
||
1. **Rule Definition**: Users define mapping rules that specify which alert attributes (matchers) should be used for matching alerts to rows in a CSV file. | ||
2. **CSV File Specification**: A CSV file is associated with each mapping rule. This file contains additional data that should be added to alerts matching the rule. | ||
3. **Alert Matching**: When an alert is received, the system checks if it matches the conditions of any mapping rule based on the specified matchers. | ||
4. **Data Enrichment**: If a match is found, the alert is enriched with additional data from the corresponding row in the CSV file. | ||
|
||
## Core Concepts | ||
|
||
- **Matchers**: Attributes within the alert used to identify matching rows within the CSV file. Common matchers include identifiers like `service` or `region`. | ||
- **CSV File**: A structured file containing rows of data. Each column represents a potential attribute that can be added to an alert. | ||
- **Enrichment**: The process of adding new attributes or modifying existing ones in an alert based on the data from a matching CSV row. | ||
|
||
## Creating a Mapping Rule | ||
|
||
To create an alert mapping and enrichment rule: | ||
|
||
<Frame width="100" height="200"> | ||
<img height="10" src="/images/rule-creation.png" /> | ||
</Frame> | ||
|
||
1. **Define the Matchers**: Specify which alert attributes will be used to match rows in the CSV file. | ||
2. **Upload the CSV File**: Provide the CSV file containing the data for enrichment. | ||
3. **Configure the Rule**: Set additional parameters, such as whether the rule should override existing alert attributes. | ||
|
||
## Practical Example | ||
|
||
Imagine you have a CSV file with columns representing different aspects of your infrastructure, such as `region`, `responsible_team`, and `severity_override`. By creating a mapping rule that matches alerts based on `service` and `region`, you can automatically enrich alerts with the responsible team and adjust severity based on the matched row in the CSV file. | ||
|
||
## Best Practices | ||
|
||
- **Keep CSV Files Updated**: Regularly update the CSV files to reflect the current state of your infrastructure and operational data. | ||
- **Use Specific Matchers**: Define matchers that are unique and relevant to ensure accurate matching. | ||
- **Monitor Rule Performance**: Review the application of mapping rules to ensure they are working as expected and adjust them as necessary. | ||
|
||
<Frame width="100" height="200"> | ||
<img height="10" src="/images/rule-table.png" /> | ||
</Frame> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
"use client"; | ||
|
||
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; | ||
import { | ||
TextInput, | ||
Textarea, | ||
Divider, | ||
Subtitle, | ||
Text, | ||
MultiSelect, | ||
MultiSelectItem, | ||
Badge, | ||
Button, | ||
} from "@tremor/react"; | ||
import { useSession } from "next-auth/react"; | ||
import { ChangeEvent, FormEvent, useMemo, useState } from "react"; | ||
import { usePapaParse } from "react-papaparse"; | ||
import { toast } from "react-toastify"; | ||
import { getApiURL } from "utils/apiUrl"; | ||
import { useMappings } from "utils/hooks/useMappingRules"; | ||
|
||
export default function CreateNewMapping() { | ||
const { data: session } = useSession(); | ||
const { mutate } = useMappings(); | ||
const [mapName, setMapName] = useState<string>(""); | ||
const [fileName, setFileName] = useState<string>(""); | ||
const [mapDescription, setMapDescription] = useState<string>(""); | ||
const [selectedAttributes, setSelectedAttributes] = useState<string[]>([]); | ||
|
||
/** This is everything related with the uploaded CSV file */ | ||
const [parsedData, setParsedData] = useState<any[] | null>(null); | ||
const attributes = useMemo(() => { | ||
if (parsedData) { | ||
return Object.keys(parsedData[0]); | ||
} | ||
return []; | ||
}, [parsedData]); | ||
const { readString } = usePapaParse(); | ||
|
||
const readFile = (event: ChangeEvent<HTMLInputElement>) => { | ||
const file = event.target.files?.[0]; | ||
setFileName(file?.name || ""); | ||
const reader = new FileReader(); | ||
reader.onload = (e) => { | ||
const text = e.target?.result; | ||
if (typeof text === "string") { | ||
readString(text, { | ||
header: true, | ||
complete: (results) => { | ||
if (results.data.length > 0) setParsedData(results.data); | ||
}, | ||
}); | ||
} | ||
}; | ||
if (file) reader.readAsText(file); | ||
}; | ||
|
||
const clearForm = () => { | ||
setMapName(""); | ||
setMapDescription(""); | ||
setParsedData(null); | ||
setSelectedAttributes([]); | ||
}; | ||
|
||
const addRule = async (e: FormEvent) => { | ||
e.preventDefault(); | ||
const apiUrl = getApiURL(); | ||
const response = await fetch(`${apiUrl}/mapping`, { | ||
method: "POST", | ||
headers: { | ||
Authorization: `Bearer ${session?.accessToken}`, | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
name: mapName, | ||
description: mapDescription, | ||
file_name: fileName, | ||
matchers: selectedAttributes, | ||
rows: parsedData, | ||
}), | ||
}); | ||
if (response.ok) { | ||
clearForm(); | ||
mutate(); | ||
toast.success("Mapping created successfully"); | ||
} else { | ||
toast.error( | ||
"Failed to create mapping, please contact us if this issue persists." | ||
); | ||
} | ||
}; | ||
|
||
const submitEnabled = (): boolean => { | ||
return ( | ||
!!mapName && | ||
selectedAttributes.length > 0 && | ||
!!parsedData && | ||
attributes.filter( | ||
(attribute) => selectedAttributes.includes(attribute) === false | ||
).length > 0 | ||
); | ||
}; | ||
|
||
return ( | ||
<form className="max-w-lg py-2" onSubmit={addRule}> | ||
<Subtitle>Mapping Metadata</Subtitle> | ||
<div className="mt-2.5"> | ||
<Text> | ||
Name<span className="text-red-500 text-xs">*</span> | ||
</Text> | ||
<TextInput | ||
placeholder="Map Name" | ||
required={true} | ||
value={mapName} | ||
onValueChange={setMapName} | ||
/> | ||
</div> | ||
<div className="mt-2.5"> | ||
<Text>Description</Text> | ||
<Textarea | ||
placeholder="Map Description" | ||
value={mapDescription} | ||
onValueChange={setMapDescription} | ||
/> | ||
</div> | ||
<Divider /> | ||
<div> | ||
<input | ||
type="file" | ||
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" | ||
onChange={readFile} | ||
required={true} | ||
/> | ||
{!parsedData && ( | ||
<Text className="text-xs text-red-500"> | ||
* Upload a CSV file to begin with creating a new mapping | ||
</Text> | ||
)} | ||
</div> | ||
<Subtitle className="mt-2.5">Mapping Schema</Subtitle> | ||
<div className="mt-2.5"> | ||
<Text>Alert lookup attributes to match against the uploaded CSV</Text> | ||
<Text className="text-xs"> | ||
(E.g. the attributes that we will try to match before enriching) | ||
</Text> | ||
<MultiSelect | ||
className="mt-1" | ||
value={selectedAttributes} | ||
onValueChange={setSelectedAttributes} | ||
disabled={!parsedData} | ||
icon={MagnifyingGlassIcon} | ||
> | ||
{attributes && | ||
attributes.map((attribute) => ( | ||
<MultiSelectItem key={attribute} value={attribute}> | ||
{attribute} | ||
</MultiSelectItem> | ||
))} | ||
</MultiSelect> | ||
</div> | ||
<div className="mt-2.5"> | ||
<Text>Result attributes</Text> | ||
<Text className="text-xs"> | ||
(E.g. attributes that will be added to matching incoming alerts) | ||
</Text> | ||
<div className="flex flex-col gap-1 py-1"> | ||
{selectedAttributes.length === 0 ? ( | ||
<Badge color="gray">...</Badge> | ||
) : ( | ||
attributes | ||
.filter( | ||
(attribute) => selectedAttributes.includes(attribute) === false | ||
) | ||
.map((attribute) => ( | ||
<Badge key={attribute} color="orange"> | ||
{attribute} | ||
</Badge> | ||
)) | ||
)} | ||
</div> | ||
</div> | ||
<Button | ||
disabled={!submitEnabled()} | ||
color="orange" | ||
size="xs" | ||
className="float-right" | ||
type="submit" | ||
> | ||
Create | ||
</Button> | ||
</form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Title, Subtitle } from "@tremor/react"; | ||
|
||
export default function Layout({ children }: { children: any }) { | ||
return <main className="p-4 md:p-10 mx-auto max-w-full">{children}</main>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"use client"; | ||
import { Badge, Callout, Card } from "@tremor/react"; | ||
import CreateNewMapping from "./create-new-mapping"; | ||
import { useMappings } from "utils/hooks/useMappingRules"; | ||
import RulesTable from "./rules-table"; | ||
import { MdWarning } from "react-icons/md"; | ||
import Loading from "app/loading"; | ||
export default function Mapping() { | ||
const { data: mappings, isLoading } = useMappings(); | ||
|
||
return ( | ||
<Card className="p-4 md:p-10 mx-auto"> | ||
<Badge | ||
color="orange" | ||
size="xs" | ||
tooltip="Slack us if something isn't working properly :)" | ||
className="absolute top-[-10px] left-[-10px]" | ||
> | ||
Beta | ||
</Badge> | ||
<div className="flex divide-x p-2"> | ||
<div className="w-1/3 pr-2.5"> | ||
<h2 className="text-lg">Configure</h2> | ||
<p className="text-slate-400"> | ||
Add dynamic context to your alerts with mapping rules | ||
</p> | ||
<CreateNewMapping /> | ||
</div> | ||
|
||
<div className="w-2/3 pl-2.5"> | ||
{isLoading ? ( | ||
<Loading /> | ||
) : mappings && mappings.length > 0 ? ( | ||
<RulesTable mappings={mappings} /> | ||
) : ( | ||
<Callout | ||
color="orange" | ||
title="Mapping rules does not exist" | ||
icon={MdWarning} | ||
> | ||
<p className="text-slate-400">No mapping rules found</p> | ||
<p className="text-slate-400"> | ||
Configure new mapping rule using the mapping rules wizard. | ||
</p> | ||
</Callout> | ||
)} | ||
</div> | ||
</div> | ||
</Card> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export interface MappingRule { | ||
id: number; | ||
tenant_id: string; | ||
priority: number; | ||
name: string; | ||
description?: string; | ||
file_name?: string; | ||
created_by?: string; | ||
created_at: Date; | ||
disabled: boolean; | ||
override: boolean; | ||
condition?: string; | ||
matchers: string[]; | ||
rows: { [key: string]: any }[]; | ||
attributes?: string[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Mapping from "./mapping"; | ||
|
||
export default function Page() { | ||
return <Mapping />; | ||
} | ||
|
||
export const metadata = { | ||
title: "Keep - Alert Mapping", | ||
description: "Add dynamic context to your alerts with mapping", | ||
}; |
Oops, something went wrong.