Skip to content

Commit

Permalink
Merge pull request #20 from rajeshj11/feat/runbook-1780-main-final
Browse files Browse the repository at this point in the history
feat: handle the folder case and content title issue
  • Loading branch information
Mubashirshariq authored Oct 25, 2024
2 parents 4f778ec + 12333af commit bb87a56
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 92 deletions.
1 change: 1 addition & 0 deletions docs/api-ref/actions/add-actions.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
openapi: post /actions
title: Add Actions
---
1 change: 1 addition & 0 deletions keep-ui/app/runbooks/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface ContentDto {
link: string;
encoding: string | null;
file_name: string;
title: string | null;
}
export interface RunbookDto {
id: number;
Expand Down
84 changes: 20 additions & 64 deletions keep-ui/app/runbooks/runbook-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { useSession } from "next-auth/react";
import RunbookActions from "./runbook-actions";
import { RunbookDto, RunbookResponse } from "./models";
import { ExclamationCircleIcon } from "@heroicons/react/20/solid";
import Loading from "../loading";
import { useRouter } from "next/navigation";

const customStyles = {
content: {
Expand All @@ -40,82 +42,27 @@ const customStyles = {
},
};

interface Content {
id: string;
content: string;
link: string;
encoding: string | null;
file_name: string;
}
interface RunbookV2 {
id: number;
title: string;
contents: Content[];
provider_type: string;
provider_id: string;
repo_id: string;
file_path: string;
}

const columnHelperv2 = createColumnHelper<RunbookV2>();
const extractMetadataFromMarkdown = (markdown) => {
const charactersBetweenGroupedHyphens = /^---([\s\S]*?)---/;
const metadataMatched = markdown.match(charactersBetweenGroupedHyphens);
const metadata = metadataMatched[1];

if (!metadata) {
return {};
}

const metadataLines = metadata.split("\n");
const metadataObject = metadataLines.reduce((accumulator, line) => {
const [key, ...value] = line.split(":").map((part) => part.trim());

if (key)
accumulator[key] = value[1] ? value.join(":") : value.join("");
return accumulator;
}, {});

return metadataObject;
};

const columnHelperv2 = createColumnHelper<RunbookDto>();
const columnsv2 = [
columnHelperv2.display({
id: "title",
header: "Runbook Title",
cell: ({ row }) => {
const titles = row.original.contents.map(content => {
let decodedContent = Buffer.from(content.content, "base64").toString("utf-8");
console.log(decodedContent);
// const decodedContent = content.decode("utf8");
// const metadata = extractMetadataFromMarkdown(decodedContent);
// return metadata.title || row.original.title;
});
return <div></div>;
const title = row.original.contents?.[0]?.title || row.original.title;
return <div>{title}</div>;
},
}),
columnHelperv2.display({
id: "content",
header: "File Name",
cell: ({ row }) => (
<Badge key={row.original.id} color="green" className="mr-2 mb-1">
{console.log("from inside row", row)}
{
row.original.file_name}
{row.original.contents?.[0]?.file_name}
</Badge>
),
}),
] as DisplayColumnDef<RunbookV2>[];
] as DisplayColumnDef<RunbookDto>[];

const flattenRunbookData = (runbooks: RunbookV2[]) => {
return runbooks.flatMap((runbook) =>
runbook.contents.map((content) => ({
...runbook,
file_name: content.file_name,
content_id: content.id,
}))
);
};
function SettingsPage({handleRunbookMutation}:{
handleRunbookMutation: () => void}) {
const [isModalOpen, setIsModalOpen] = useState(false);
Expand Down Expand Up @@ -259,26 +206,35 @@ function RunbookIncidentTable() {
const [offset, setOffset] = useState(0);
const [limit, setLimit] = useState(10);
const { data: session, status } = useSession();
const router = useRouter();

let shouldFetch = session?.accessToken ? true : false;

let url = getApiURL();
const { data: runbooksData, error, isLoading } = useSWR<RunbookResponse>(
shouldFetch
? `${getApiURL()}/runbooks?limit=${limit}&offset=${offset}`
? `${url}/runbooks?limit=${limit}&offset=${offset}`
: null,
(url: string) => {
return fetcher(url, session?.accessToken!);
}
);

useEffect(() => {
if(status === "unauthenticated"){
router.push("/signin");
}
}, [status])

const handleRunbookMutation = ()=>{
mutate(`${getApiURL()}/runbooks?limit=${limit}&offset=${0}`);
}


if (status === "loading" || isLoading || status === "unauthenticated") return <Loading />;
const { total_count, runbooks } = runbooksData || {
total_count: 0,
runbooks: [],
};
const flattenedData = flattenRunbookData(runbooks || []);
const handlePaginationChange = (newLimit: number, newOffset: number) => {
setLimit(newLimit);
setOffset(newOffset);
Expand All @@ -303,7 +259,7 @@ function RunbookIncidentTable() {
<Card className="flex-grow">
{!isLoading && !error && (
<GenericTable<RunbookDto>
data={flattenedData}
data={runbooks}
columns={columnsv2}
rowCount={total_count}
offset={offset}
Expand Down
44 changes: 22 additions & 22 deletions keep/api/core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -3928,36 +3928,36 @@ def add_runbooks_to_incident_by_incident_id(

def create_runbook_in_db(session: Session, tenant_id: str, runbook_dto: dict):
try:
new_runbook = Runbook(
tenant_id=tenant_id,
title=runbook_dto["title"],
repo_id=runbook_dto["repo_id"],
relative_path=runbook_dto["relative_path"],
provider_type=runbook_dto["provider_type"],
provider_id=runbook_dto["provider_id"]
)

session.add(new_runbook)
session.flush()

contents = runbook_dto["contents"] if runbook_dto["contents"] else []

new_contents = [
RunbookContent(
new_runbooks = []
new_contents = []
for content in contents:
new_runbook = Runbook(
tenant_id=tenant_id,
title=runbook_dto["title"],
repo_id=runbook_dto["repo_id"],
relative_path=runbook_dto["relative_path"],
provider_type=runbook_dto["provider_type"],
provider_id=runbook_dto["provider_id"]
)
new_runbooks.append(new_runbook)
new_content = RunbookContent(
runbook_id=new_runbook.id,
content=content["content"],
link=content["link"],
encoding=content["encoding"],
file_name=content["file_name"]
file_name=content["file_name"],
title=content["title"]
)
for content in contents
]

new_contents.append(new_content)

session.add_all(new_runbooks)
session.flush()

session.add_all(new_contents)
session.commit()
session.expire(new_runbook, ["contents"])
session.refresh(new_runbook) # Refresh the runbook instance
result = RunbookDtoOut.from_orm(new_runbook)
return result
return True
except ValidationError as e:
logger.exception(f"Failed to create runbook {e}")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""add runbook schema
Revision ID: 8af72e1df141
Revises: 83c1020be97d
Revises: 8438f041ee0e
Create Date: 2024-10-13 08:33:24.727292
"""
Expand All @@ -13,7 +13,7 @@

# revision identifiers, used by Alembic.
revision = "8af72e1df141"
down_revision = "83c1020be97d"
down_revision = "8438f041ee0e"
branch_labels = None
depends_on = None

Expand Down Expand Up @@ -47,6 +47,7 @@ def upgrade() -> None:
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
sa.Column("encoding", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column("file_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("title", sa.Text(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(["runbook_id"], ["runbook.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
Expand Down
7 changes: 5 additions & 2 deletions keep/api/models/db/runbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ class RunbookContent(SQLModel, table=True):
runbook_id: UUID = Field(
sa_column=Column(ForeignKey("runbook.id", ondelete="CASCADE")) # Foreign key with CASCADE delete
)
runbook: Optional["Runbook"] = Relationship(back_populates="contents")
runbook: "Runbook" = Relationship(back_populates="contents")
content: str = Field(sa_column=Column(Text), nullable=False) # Using SQLAlchemy's Text type
link: str = Field(sa_column=Column(Text), nullable=False) # Using SQLAlchemy's Text type
encoding: Optional[str] = None
file_name: str
title: Optional[str] = None
created_at: datetime = Field(default_factory=datetime.utcnow) # Timestamp for creation

class Config:
Expand Down Expand Up @@ -106,6 +107,7 @@ class RunbookContentDto(BaseModel, extra="ignore"):
link: str
file_name: str
encoding: Optional[str] = None
title: Optional[str] = ""

@classmethod
def from_orm(cls, content: "RunbookContent") -> "RunbookContentDto":
Expand All @@ -114,7 +116,8 @@ def from_orm(cls, content: "RunbookContent") -> "RunbookContentDto":
content=content.content,
link=content.link,
encoding=content.encoding,
file_name=content.file_name
file_name=content.file_name,
title=content.title
)

class RunbookDtoOut(RunbookDto):
Expand Down
33 changes: 31 additions & 2 deletions keep/providers/github_provider/github_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"""

import dataclasses

import base64
import re
import yaml
import pydantic
from github import Github, GithubException

Expand Down Expand Up @@ -108,15 +110,42 @@ def pull_repositories(self, project_id=None):
return repos_list


def _getContentTitle(self, runbookContent):
"""
Get the content title
"""
try:
if runbookContent.content is not None:
decoded_content = base64.b64decode(runbookContent.content).decode()

front_matter_regex = re.compile(r"^---\n(.*?)\n---", re.DOTALL)
match = front_matter_regex.search(decoded_content)

if match:
# Extract front-matter and body content
front_matter = match.group(1)
# Parse the front-matter using PyYAML
metadata = yaml.safe_load(front_matter)
return metadata.get("title", "")
except Exception as e:
self.logger.error("Failed to get content title: %s", e)
return ""



def _format_content(self, runbookContent, repo):
"""
Format the content data into a dictionary.
"""

title = self._getContentTitle(runbookContent)

return {
"content": runbookContent.content,
"link": f"https://api.github.com/{repo.get('full_name')}/blob/{repo.get('default_branch')}/{runbookContent.path}",
"encoding": runbookContent.encoding,
"file_name": runbookContent.name
"file_name": runbookContent.name,
"title": title if title else "",
}

def _format_runbook(self, runbook, repo, title, md_path):
Expand Down
28 changes: 28 additions & 0 deletions keep/providers/gitlab_provider/gitlab_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import pydantic
import requests
from requests import HTTPError
import base64
import re
import yaml

from keep.contextmanager.contextmanager import ContextManager
from keep.providers.base.base_provider import BaseRunBookProvider
Expand Down Expand Up @@ -213,16 +216,41 @@ def pull_repositories(self, project_id=None):
return self._format_repos(repos, project_id)

raise Exception("Failed to get repositories: personal_access_token not set")

def _getContentTitle(self, runbookContent):
"""
Get the content title
"""
try:
if runbookContent.get("content") is not None:
decoded_content = base64.b64decode(runbookContent.get("content")).decode()

front_matter_regex = re.compile(r"^---\n(.*?)\n---", re.DOTALL)
match = front_matter_regex.search(decoded_content)

if match:
# Extract front-matter and body content
front_matter = match.group(1)

# Parse the front-matter using PyYAML
metadata = yaml.safe_load(front_matter)
return metadata.get("title", "")
except Exception as e:
self.logger.error("Failed to get content title: %s", e)
return ""

def _format_content(self, runbookContent, repo):
"""
Format the content data into a dictionary.
"""

title = self._getContentTitle(runbookContent)
return {
"content": runbookContent.get("content"),
"link": f"{self.gitlab_host}/api/v4/projects/{repo.get('id')}/repository/files/{runbookContent.get('file_path')}/raw",
"encoding": runbookContent.get("encoding"),
"file_name": runbookContent.get("file_name"),
"title": title if title else "",
}


Expand Down

0 comments on commit bb87a56

Please sign in to comment.