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

feat(job): send a notification when job fails #3713

Merged
merged 20 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export default function JobRunDetail() {
(stateLabel === 'Pending' || stateLabel === 'Running') &&
!currentNode?.stdout

const handleBackNavigation = () => {
if (typeof window !== 'undefined' && window.history.length <= 1) {
router.push('/jobs')
} else {
router.back()
}
}

React.useEffect(() => {
let timer: number
if (currentNode?.createdAt && !currentNode?.finishedAt) {
Expand All @@ -61,7 +69,7 @@ export default function JobRunDetail() {
{currentNode && (
<>
<div
onClick={() => router.back()}
onClick={handleBackNavigation}
className="-ml-1 flex cursor-pointer items-center transition-opacity hover:opacity-60"
>
<IconChevronLeft className="mr-1 h-6 w-6" />
Expand Down
4 changes: 4 additions & 0 deletions ee/tabby-ui/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,7 @@
.dialog-without-close-btn > button {
display: none;
}

.unread-notification::before {
@apply content-[''] float-left w-2 h-2 mr-1.5 mt-2 rounded-full bg-red-400;
}
39 changes: 17 additions & 22 deletions ee/tabby-ui/components/notification-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ import { useMutation } from '@/lib/tabby/gql'
import { notificationsQuery } from '@/lib/tabby/query'
import { ArrayElementType } from '@/lib/types'
import { cn } from '@/lib/utils'

import LoadingWrapper from './loading-wrapper'
import { ListSkeleton } from './skeleton'
import { Button } from './ui/button'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger
} from './ui/dropdown-menu'
import { IconBell, IconCheck } from './ui/icons'
import { Separator } from './ui/separator'
import { Tabs, TabsList, TabsTrigger } from './ui/tabs'
} from '@/components/ui/dropdown-menu'
import { IconBell, IconCheck } from '@/components/ui/icons'
import { Separator } from '@/components/ui/separator'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import LoadingWrapper from '@/components/loading-wrapper'
import { MemoizedReactMarkdown } from '@/components/markdown'
import { ListSkeleton } from '@/components/skeleton'

interface Props extends HTMLAttributes<HTMLDivElement> {}

Expand Down Expand Up @@ -89,7 +89,7 @@ export function NotificationBox({ className, ...rest }: Props) {
</div>
<Separator />
<Tabs
className="relative my-2 flex-1 overflow-y-auto px-4"
className="relative my-2 flex-1 overflow-y-auto px-5"
defaultValue="unread"
>
<TabsList className="sticky top-0 z-10 grid w-full grid-cols-2">
Expand Down Expand Up @@ -156,8 +156,6 @@ interface NotificationItemProps extends HTMLAttributes<HTMLDivElement> {
}

function NotificationItem({ data }: NotificationItemProps) {
const { title, content } = resolveNotification(data.content)

const markNotificationsRead = useMutation(markNotificationsReadMutation)

const onClickMarkRead = () => {
Expand All @@ -168,17 +166,14 @@ function NotificationItem({ data }: NotificationItemProps) {

return (
<div className="space-y-1.5">
<div className="space-y-1.5">
<div className="flex items-center gap-1.5 overflow-hidden text-sm font-medium">
{!data.read && (
<span className="h-2 w-2 shrink-0 rounded-full bg-red-400"></span>
)}
<span className="flex-1 truncate">{title}</span>
</div>
<div className="whitespace-pre-wrap break-words text-sm text-muted-foreground">
{content}
</div>
</div>
<MemoizedReactMarkdown
className={cn(
'prose max-w-none break-words text-sm dark:prose-invert prose-p:my-1 prose-p:leading-relaxed',
{ 'unread-notification': !data.read }
)}
>
{data.content}
</MemoizedReactMarkdown>
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span className="text-muted-foreground">
{formatNotificationTime(data.createdAt)}
Expand Down
44 changes: 25 additions & 19 deletions ee/tabby-webserver/src/service/background_job/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@
context: Arc<dyn ContextService>,
db: DbConn,
) -> tabby_schema::Result<()> {
let mut errors = vec![];
let mut has_error = false;

Check warning on line 23 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L23

Added line #L23 was not covered by tests

if let Err(e) = db.delete_expired_token().await {
errors.push(format!("Failed to delete expired token: {}", e));
has_error = true;
logkit::warn!("Failed to delete expired tokens: {}", e);

Check warning on line 27 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L26-L27

Added lines #L26 - L27 were not covered by tests
};
if let Err(e) = db.delete_expired_password_resets().await {
errors.push(format!("Failed to delete expired password resets: {}", e));
has_error = true;
logkit::warn!("Failed to delete expired password resets: {}", e);

Check warning on line 31 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L30-L31

Added lines #L30 - L31 were not covered by tests
};
if let Err(e) = db.delete_expired_ephemeral_threads().await {
errors.push(format!("Failed to delete expired ephemeral threads: {}", e));
has_error = true;
logkit::warn!("Failed to delete expired ephemeral threads: {}", e);

Check warning on line 35 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L34-L35

Added lines #L34 - L35 were not covered by tests
};

// Read all active sources
Expand All @@ -44,54 +47,57 @@
.delete_unused_source_id_read_access_policy(&active_source_ids)
.await
{
errors.push(format!(
has_error = true;
logkit::warn!(

Check warning on line 51 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L50-L51

Added lines #L50 - L51 were not covered by tests
"Failed to delete unused source id read access policy: {}",
e
));
);
};
}
Err(e) => {
errors.push(format!("Failed to read active sources: {}", e));
has_error = true;
logkit::warn!("Failed to read active sources: {}", e);

Check warning on line 59 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L58-L59

Added lines #L58 - L59 were not covered by tests
}
}

if let Err(e) = Self::data_retention(now, &db).await {
errors.push(format!("Failed to run data retention job: {}", e));
has_error = true;
logkit::warn!("Failed to run data retention job: {}", e);

Check warning on line 65 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L64-L65

Added lines #L64 - L65 were not covered by tests
}

if errors.is_empty() {
if !has_error {

Check warning on line 68 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L68

Added line #L68 was not covered by tests
Ok(())
} else {
Err(CoreError::Other(anyhow::anyhow!(
"Failed to run db maintenance job:\n{}",
errors.join(";\n")
"Failed to run db maintenance job"

Check warning on line 72 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L72

Added line #L72 was not covered by tests
)))
}
}

async fn data_retention(now: DateTime<Utc>, db: &DbConn) -> tabby_schema::Result<()> {
let mut errors = vec![];
let mut has_error = false;

if let Err(e) = db.delete_job_run_before_three_months(now).await {
errors.push(format!(
has_error = true;
logkit::warn!(

Check warning on line 82 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L81-L82

Added lines #L81 - L82 were not covered by tests
"Failed to clean up and retain only the last 3 months of jobs: {}",
e
));
);
}

if let Err(e) = db.delete_user_events_before_three_months(now).await {
errors.push(format!(
has_error = true;
logkit::warn!(

Check warning on line 90 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L89-L90

Added lines #L89 - L90 were not covered by tests
"Failed to clean up and retain only the last 3 months of user events: {}",
e
));
);
}

if errors.is_empty() {
if !has_error {
Ok(())
} else {
Err(CoreError::Other(anyhow::anyhow!(
"Failed to run data retention job:\n{}",
errors.join(";\n")
"Failed to run data retention job"

Check warning on line 100 in ee/tabby-webserver/src/service/background_job/db.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/db.rs#L100

Added line #L100 was not covered by tests
)))
}
}
Expand Down
15 changes: 14 additions & 1 deletion ee/tabby-webserver/src/service/background_job/hourly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,24 @@
repository_service: Arc<dyn RepositoryService>,
) -> tabby_schema::Result<()> {
let now = Utc::now();
let mut has_error = false;

Check warning on line 38 in ee/tabby-webserver/src/service/background_job/hourly.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/hourly.rs#L38

Added line #L38 was not covered by tests

if let Err(err) = DbMaintainanceJob::cron(now, context_service.clone(), db.clone()).await {
has_error = true;

Check warning on line 41 in ee/tabby-webserver/src/service/background_job/hourly.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/hourly.rs#L41

Added line #L41 was not covered by tests
logkit::warn!("Database maintainance failed: {:?}", err);
}

if let Err(err) =
SchedulerGitJob::cron(now, git_repository_service.clone(), job_service.clone()).await
{
has_error = true;

Check warning on line 48 in ee/tabby-webserver/src/service/background_job/hourly.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/hourly.rs#L48

Added line #L48 was not covered by tests
logkit::warn!("Scheduler job failed: {:?}", err);
}

if let Err(err) =
SyncIntegrationJob::cron(now, integration_service.clone(), job_service.clone()).await
{
has_error = true;

Check warning on line 55 in ee/tabby-webserver/src/service/background_job/hourly.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/hourly.rs#L55

Added line #L55 was not covered by tests
logkit::warn!("Sync integration job failed: {:?}", err);
}

Expand All @@ -59,15 +63,24 @@
)
.await
{
has_error = true;

Check warning on line 66 in ee/tabby-webserver/src/service/background_job/hourly.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/hourly.rs#L66

Added line #L66 was not covered by tests
logkit::warn!("Index issues job failed: {err:?}");
}

if let Err(err) = IndexGarbageCollection
.run(repository_service.clone(), context_service.clone())
.await
{
has_error = true;

Check warning on line 74 in ee/tabby-webserver/src/service/background_job/hourly.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/hourly.rs#L74

Added line #L74 was not covered by tests
logkit::warn!("Index garbage collection job failed: {err:?}");
}
Ok(())

if has_error {
Err(tabby_schema::CoreError::Other(anyhow::anyhow!(
"Hourly job failed"
)))

Check warning on line 81 in ee/tabby-webserver/src/service/background_job/hourly.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/hourly.rs#L78-L81

Added lines #L78 - L81 were not covered by tests
} else {
Ok(())

Check warning on line 83 in ee/tabby-webserver/src/service/background_job/hourly.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/hourly.rs#L83

Added line #L83 was not covered by tests
}
}
}
45 changes: 43 additions & 2 deletions ee/tabby-webserver/src/service/background_job/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,17 @@
integration::IntegrationService,
job::JobService,
license::LicenseService,
notification::NotificationService,
notification::{NotificationRecipient, NotificationService},
repository::{GitRepositoryService, RepositoryService, ThirdPartyRepositoryService},
AsID,
};
use third_party_integration::SchedulerGithubGitlabJob;
use tracing::{debug, warn};
pub use web_crawler::WebCrawlerJob;

use self::third_party_integration::SyncIntegrationJob;

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum BackgroundJobEvent {
SchedulerGitRepository(CodeRepository),
SchedulerGithubGitlabRepository(ID),
Expand Down Expand Up @@ -68,6 +69,44 @@
}
}

fn background_job_notification_name(event: &BackgroundJobEvent) -> &str {
match event {
BackgroundJobEvent::SchedulerGitRepository(_) => "Indexing Repository",
BackgroundJobEvent::SchedulerGithubGitlabRepository(_) => "Indexing Repository",
BackgroundJobEvent::SyncThirdPartyRepositories(_) => "Loading Repository",
BackgroundJobEvent::WebCrawler(_) => "Web Indexing",
BackgroundJobEvent::IndexGarbageCollection => "Garbage Collection",
BackgroundJobEvent::Hourly => "Hourly",
BackgroundJobEvent::Daily => "Daily",

Check warning on line 80 in ee/tabby-webserver/src/service/background_job/mod.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/mod.rs#L72-L80

Added lines #L72 - L80 were not covered by tests
}
}

Check warning on line 82 in ee/tabby-webserver/src/service/background_job/mod.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/mod.rs#L82

Added line #L82 was not covered by tests

async fn notify_job_error(
notification_service: Arc<dyn NotificationService>,
err: &str,
event: &BackgroundJobEvent,
id: i64,
) {
warn!("job {:?} failed: {:?}", event, err);
let name = background_job_notification_name(event);
if let Err(err) = notification_service
.create(
NotificationRecipient::Admin,
&format!(
r#"Job **{}** has failed.

Please examine the [logs](/jobs/detail?id={}) to determine the underlying issue.
"#,
name,
id.as_id()
),
)
.await

Check warning on line 104 in ee/tabby-webserver/src/service/background_job/mod.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/mod.rs#L84-L104

Added lines #L84 - L104 were not covered by tests
{
warn!("Failed to send notification: {:?}", err);
}
}

Check warning on line 108 in ee/tabby-webserver/src/service/background_job/mod.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/mod.rs#L106-L108

Added lines #L106 - L108 were not covered by tests

pub async fn start(
db: DbConn,
job_service: Arc<dyn JobService>,
Expand Down Expand Up @@ -108,6 +147,7 @@
continue;
};

let cloned_event = event.clone();

Check warning on line 150 in ee/tabby-webserver/src/service/background_job/mod.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/mod.rs#L150

Added line #L150 was not covered by tests
if let Err(err) = match event {
BackgroundJobEvent::SchedulerGitRepository(repository_config) => {
let job = SchedulerGitJob::new(repository_config);
Expand Down Expand Up @@ -149,6 +189,7 @@
}
} {
logkit::info!(exit_code = 1; "Job failed {}", err);
notify_job_error(notification_service.clone(), &err.to_string(), &cloned_event, job.id).await;

Check warning on line 192 in ee/tabby-webserver/src/service/background_job/mod.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/background_job/mod.rs#L192

Added line #L192 was not covered by tests
} else {
logkit::info!(exit_code = 0; "Job completed successfully");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use super::helper::Job;

const CRAWLER_TIMEOUT_SECS: u64 = 7200;

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
wsxiaoys marked this conversation as resolved.
Show resolved Hide resolved
pub struct WebCrawlerJob {
source_id: String,
url: String,
Expand Down
Loading