From 3b32fe03d53a4f49e7cce33b0bbf47719e57092f Mon Sep 17 00:00:00 2001 From: Tal Borenstein Date: Mon, 19 Feb 2024 11:36:57 +0200 Subject: [PATCH] feat: working --- keep-ui/app/mapping/create-new-mapping.tsx | 15 ++++-- keep-ui/app/mapping/mapping.tsx | 27 ++++++++++ keep-ui/app/mapping/page.tsx | 25 +--------- keep-ui/utils/hooks/useMappingRules.ts | 7 ++- keep/api/bl/enrichments.py | 4 +- keep/api/core/db.py | 58 ++++++++++++---------- keep/api/models/db/mapping.py | 10 ++-- keep/api/routes/mapping.py | 9 ++-- 8 files changed, 87 insertions(+), 68 deletions(-) create mode 100644 keep-ui/app/mapping/mapping.tsx diff --git a/keep-ui/app/mapping/create-new-mapping.tsx b/keep-ui/app/mapping/create-new-mapping.tsx index c70862302..ab0cd5f0a 100644 --- a/keep-ui/app/mapping/create-new-mapping.tsx +++ b/keep-ui/app/mapping/create-new-mapping.tsx @@ -17,9 +17,11 @@ 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({ mutate }: { mutate: () => void }) { +export default function CreateNewMapping() { const { data: session } = useSession(); + const { mutate } = useMappings(); const [mapName, setMapName] = useState(""); const [fileName, setFileName] = useState(""); const [mapDescription, setMapDescription] = useState(""); @@ -64,8 +66,10 @@ export default function CreateNewMapping({ mutate }: { mutate: () => void }) { 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, @@ -77,6 +81,7 @@ export default function CreateNewMapping({ mutate }: { mutate: () => void }) { }); if (response.ok) { clearForm(); + mutate(); toast.success("Mapping created successfully"); } else { toast.error( @@ -126,9 +131,11 @@ export default function CreateNewMapping({ mutate }: { mutate: () => void }) { onChange={readFile} required={true} /> - - * Upload a CSV file to start creating a new mapping - + {!parsedData && ( + + * Upload a CSV file to begin with creating a new mapping + + )} Mapping Schema
diff --git a/keep-ui/app/mapping/mapping.tsx b/keep-ui/app/mapping/mapping.tsx new file mode 100644 index 000000000..1b9cba743 --- /dev/null +++ b/keep-ui/app/mapping/mapping.tsx @@ -0,0 +1,27 @@ +"use client"; +import { Badge, Card } from "@tremor/react"; +import CreateNewMapping from "./create-new-mapping"; +import { useMappings } from "utils/hooks/useMappingRules"; +export default function Mapping() { + const { data: mappings, mutate } = useMappings(); + + return ( + + Beta +
+
+

Configure

+

+ Add dynamic context to your alerts with mapping rules +

+ +
+ +
+

Rules

+ {mappings && mappings.length > 0 ? <>Rules! : <>No Rules!} +
+
+
+ ); +} diff --git a/keep-ui/app/mapping/page.tsx b/keep-ui/app/mapping/page.tsx index 3d58bbd7a..f5c1efa80 100644 --- a/keep-ui/app/mapping/page.tsx +++ b/keep-ui/app/mapping/page.tsx @@ -1,28 +1,7 @@ -import { Card } from "@tremor/react"; -import CreateNewMapping from "./create-new-mapping"; -import { useMappings } from "utils/hooks/useMappingRules"; +import Mapping from "./mapping"; export default function Page() { - const { data: mappings, mutate } = useMappings(); - - return ( - -
-
-

Configure

-

- Add dynamic context to your alerts with mapping rules -

- -
- -
-

Rules

- {mappings && mappings.length > 0 ? <>Rules! : <>No Rules!} -
-
-
- ); + return ; } export const metadata = { diff --git a/keep-ui/utils/hooks/useMappingRules.ts b/keep-ui/utils/hooks/useMappingRules.ts index 0b6cb7da5..77592dded 100644 --- a/keep-ui/utils/hooks/useMappingRules.ts +++ b/keep-ui/utils/hooks/useMappingRules.ts @@ -1,13 +1,12 @@ import { MappingRule } from "app/mapping/models"; -import { getServerSession } from "next-auth"; -import { authOptions } from "pages/api/auth/[...nextauth]"; +import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; import { getApiURL } from "utils/apiUrl"; import { fetcher } from "utils/fetcher"; -export const useMappings = async (options: SWRConfiguration = {}) => { +export const useMappings = (options: SWRConfiguration = {}) => { const apiUrl = getApiURL(); - const session = await getServerSession(authOptions); + const { data: session } = useSession(); return useSWR( () => (session ? `${apiUrl}/mapping` : null), diff --git a/keep/api/bl/enrichments.py b/keep/api/bl/enrichments.py index 347812a5f..f5d2c1d08 100644 --- a/keep/api/bl/enrichments.py +++ b/keep/api/bl/enrichments.py @@ -59,7 +59,9 @@ def run_mapping_rules(self, alert: AlertDto): for key, value in row.items() if key not in rule.matchers } - enrich_alert(self.tenant_id, alert.fingerprint, enrichments) + enrich_alert( + self.tenant_id, alert.fingerprint, enrichments, self.db_session + ) self.logger.info( "Alert enriched", extra={"fingerprint": alert.fingerprint} ) diff --git a/keep/api/core/db.py b/keep/api/core/db.py index a8cfac718..258df57a2 100644 --- a/keep/api/core/db.py +++ b/keep/api/core/db.py @@ -602,33 +602,39 @@ def get_last_workflow_executions(tenant_id: str, limit=20): return execution_with_logs -def enrich_alert(tenant_id, fingerprint, enrichments): +def _enrich_alert(session, tenant_id, fingerprint, enrichments): + enrichment = get_enrichment_with_session(session, tenant_id, fingerprint) + if enrichment: + # SQLAlchemy doesn't support updating JSON fields, so we need to do it manually + # https://github.com/sqlalchemy/sqlalchemy/discussions/8396#discussion-4308891 + new_enrichment_data = {**enrichment.enrichments, **enrichments} + stmt = ( + update(AlertEnrichment) + .where(AlertEnrichment.id == enrichment.id) + .values(enrichments=new_enrichment_data) + ) + session.execute(stmt) + session.commit() + # Refresh the instance to get updated data from the database + session.refresh(enrichment) + return enrichment + else: + alert_enrichment = AlertEnrichment( + tenant_id=tenant_id, + alert_fingerprint=fingerprint, + enrichments=enrichments, + ) + session.add(alert_enrichment) + session.commit() + return alert_enrichment + + +def enrich_alert(tenant_id, fingerprint, enrichments, session=None): # else, the enrichment doesn't exist, create it - with Session(engine) as session: - enrichment = get_enrichment_with_session(session, tenant_id, fingerprint) - if enrichment: - # SQLAlchemy doesn't support updating JSON fields, so we need to do it manually - # https://github.com/sqlalchemy/sqlalchemy/discussions/8396#discussion-4308891 - new_enrichment_data = {**enrichment.enrichments, **enrichments} - stmt = ( - update(AlertEnrichment) - .where(AlertEnrichment.id == enrichment.id) - .values(enrichments=new_enrichment_data) - ) - session.execute(stmt) - session.commit() - # Refresh the instance to get updated data from the database - session.refresh(enrichment) - return enrichment - else: - alert_enrichment = AlertEnrichment( - tenant_id=tenant_id, - alert_fingerprint=fingerprint, - enrichments=enrichments, - ) - session.add(alert_enrichment) - session.commit() - return alert_enrichment + if not session: + with Session(engine) as session: + return _enrich_alert(session, tenant_id, fingerprint, enrichments) + return _enrich_alert(session, tenant_id, fingerprint, enrichments) def get_enrichment(tenant_id, fingerprint): diff --git a/keep/api/models/db/mapping.py b/keep/api/models/db/mapping.py index 8d6193f1f..880ad2f9c 100644 --- a/keep/api/models/db/mapping.py +++ b/keep/api/models/db/mapping.py @@ -19,9 +19,9 @@ class MappingRule(SQLModel, table=True): override: bool = Field(default=True) condition: Optional[str] = Field(max_length=2000) # The attributes to match against (e.g. ["service","region"]) - matchers: list = Field(sa_column=Column(JSON), nullable=False) + matchers: list[str] = Field(sa_column=Column(JSON), nullable=False) # The rows of the CSV file [{service: "service1", region: "region1", ...}, ...] - rows: dict = Field( + rows: list[dict] = Field( sa_column=Column(JSON), nullable=False, ) # max_length=204800) @@ -29,8 +29,8 @@ class MappingRule(SQLModel, table=True): class MappingRuleDto(BaseModel): name: str - description: Optional[str] - file_name: Optional[str] + description: Optional[str] = None + file_name: Optional[str] = None priority: int = 0 matchers: list[str] - rows: dict + rows: list[dict] diff --git a/keep/api/routes/mapping.py b/keep/api/routes/mapping.py index e8bdcb52a..b4414cc94 100644 --- a/keep/api/routes/mapping.py +++ b/keep/api/routes/mapping.py @@ -12,11 +12,11 @@ logger = logging.getLogger(__name__) -@router.get("", description="Get all mapping rules", response_class=list[MappingRule]) +@router.get("", description="Get all mapping rules") def get_rules( authenticated_entity: AuthenticatedEntity = Depends(AuthVerifier(["read:rules"])), session: Session = Depends(get_session), -): +) -> list[MappingRule]: logger.info("Getting mapping rules") rules: list[MappingRule] = ( session.query(MappingRule) @@ -27,18 +27,17 @@ def get_rules( return rules -@router.post("", description="Create a new mapping rule", response_class=MappingRule) +@router.post("", description="Create a new mapping rule") def create_rule( rule: MappingRuleDto, authenticated_entity: AuthenticatedEntity = Depends(AuthVerifier(["write:rules"])), session: Session = Depends(get_session), -): +) -> MappingRule: logger.info("Creating a new mapping rule") new_rule = MappingRule( **rule.dict(), tenant_id=authenticated_entity.tenant_id, created_by=authenticated_entity.email, - priority=rule.priority, ) session.add(new_rule) session.commit()