feat: task ui & custom filter mutation

This commit is contained in:
2025-06-16 07:56:52 +08:00
parent 7eb4e41708
commit 421f9d0293
20 changed files with 594 additions and 434 deletions

View File

@@ -26,7 +26,7 @@ import { memo, useCallback } from 'react';
import { toast } from 'sonner';
export type SubscriptionSyncViewCompletePayload = {
taskId: string;
id: string;
};
export interface SubscriptionSyncViewProps {
@@ -43,7 +43,7 @@ export const SubscriptionSyncView = memo(
>(SYNC_SUBSCRIPTION_FEEDS_INCREMENTAL, {
onCompleted: (data) => {
toast.success('Sync completed');
onComplete(data.subscriptionSyncOneFeedsIncremental);
onComplete(data.subscriptionsSyncOneFeedsIncremental);
},
onError: (error) => {
toast.error('Failed to sync subscription', {
@@ -58,7 +58,7 @@ export const SubscriptionSyncView = memo(
>(SYNC_SUBSCRIPTION_FEEDS_FULL, {
onCompleted: (data) => {
toast.success('Sync completed');
onComplete(data.subscriptionSyncOneFeedsFull);
onComplete(data.subscriptionsSyncOneFeedsFull);
},
onError: (error) => {
toast.error('Failed to sync subscription', {
@@ -73,7 +73,7 @@ export const SubscriptionSyncView = memo(
>(SYNC_SUBSCRIPTION_SOURCES, {
onCompleted: (data) => {
toast.success('Sync completed');
onComplete(data.subscriptionSyncOneSources);
onComplete(data.subscriptionsSyncOneSources);
},
onError: (error) => {
toast.error('Failed to sync subscription', {
@@ -89,7 +89,11 @@ export const SubscriptionSyncView = memo(
<Button
size="lg"
variant="outline"
onClick={() => syncSubscriptionSources({ variables: { id } })}
onClick={() =>
syncSubscriptionSources({
variables: { filter: { id: { eq: id } } },
})
}
>
<RefreshCcwIcon className="h-4 w-4" />
<span>Sources</span>
@@ -98,7 +102,9 @@ export const SubscriptionSyncView = memo(
size="lg"
variant="outline"
onClick={() =>
syncSubscriptionFeedsIncremental({ variables: { id } })
syncSubscriptionFeedsIncremental({
variables: { filter: { id: { eq: id } } },
})
}
>
<RefreshCcwIcon className="h-4 w-4" />
@@ -107,7 +113,11 @@ export const SubscriptionSyncView = memo(
<Button
size="lg"
variant="outline"
onClick={() => syncSubscriptionFeedsFull({ variables: { id } })}
onClick={() =>
syncSubscriptionFeedsFull({
variables: { filter: { id: { eq: id } } },
})
}
>
<RefreshCcwIcon className="h-4 w-4" />
<span>Full Feeds</span>
@@ -138,7 +148,7 @@ export const SubscriptionSyncDialogContent = memo(
navigate({
to: '/tasks/detail/$id',
params: {
id: `${payload.taskId}`,
id: `${payload.id}`,
},
});
},

View File

@@ -25,13 +25,9 @@ import {
apolloErrorToMessage,
getApolloQueryError,
} from '@/infra/errors/apollo';
import type {
GetSubscriptionsQuery,
SubscriptionsUpdateInput,
} from '@/infra/graphql/gql/graphql';
import type { GetSubscriptionsQuery } from '@/infra/graphql/gql/graphql';
import type { RouteStateDataOption } from '@/infra/routes/traits';
import { useDebouncedSkeleton } from '@/presentation/hooks/use-debounded-skeleton';
import { useEvent } from '@/presentation/hooks/use-event';
import { cn } from '@/presentation/utils';
import { useMutation, useQuery } from '@apollo/client';
import { createFileRoute } from '@tanstack/react-router';
@@ -39,7 +35,6 @@ import { useNavigate } from '@tanstack/react-router';
import {
type ColumnDef,
type PaginationState,
type Row,
type SortingState,
type VisibilityState,
flexRender,
@@ -131,29 +126,6 @@ function SubscriptionManageRouteComponent() {
const subscriptions = data?.subscriptions;
const handleUpdateRecord = useEvent(
(row: Row<SubscriptionDto>) => async (data: SubscriptionsUpdateInput) => {
await updateSubscription({
variables: {
data,
filters: {
id: {
eq: row.original.id,
},
},
},
});
}
);
const handleDeleteRecord = useEvent(
(row: Row<SubscriptionDto>) => async () => {
await deleteSubscription({
variables: { filters: { id: { eq: row.original.id } } },
});
}
);
const columns = useMemo(() => {
const cs: ColumnDef<SubscriptionDto>[] = [
{
@@ -166,7 +138,18 @@ function SubscriptionManageRouteComponent() {
<Switch
checked={enabled}
onCheckedChange={(enabled) =>
handleUpdateRecord(row)({ enabled: enabled })
updateSubscription({
variables: {
data: {
enabled,
},
filters: {
id: {
eq: row.original.id,
},
},
},
})
}
/>
</div>
@@ -242,7 +225,11 @@ function SubscriptionManageRouteComponent() {
params: { id: `${row.original.id}` },
});
}}
onDelete={handleDeleteRecord(row)}
onDelete={() =>
deleteSubscription({
variables: { filters: { id: { eq: row.original.id } } },
})
}
>
<Dialog>
<DialogTrigger asChild>
@@ -257,7 +244,7 @@ function SubscriptionManageRouteComponent() {
},
];
return cs;
}, [handleUpdateRecord, handleDeleteRecord, navigate]);
}, [updateSubscription, deleteSubscription, navigate]);
const table = useReactTable({
data: useMemo(() => subscriptions?.nodes ?? [], [subscriptions]),

View File

@@ -1,5 +0,0 @@
import { memo } from 'react';
export const TaskActionsView = memo(() => {
return null;
});

View File

@@ -12,14 +12,18 @@ import { DetailEmptyView } from '@/components/ui/detail-empty-view';
import { Label } from '@/components/ui/label';
import { QueryErrorView } from '@/components/ui/query-error-view';
import { Separator } from '@/components/ui/separator';
import { GET_TASKS } from '@/domains/recorder/schema/tasks';
import { GET_TASKS, RETRY_TASKS } from '@/domains/recorder/schema/tasks';
import { getApolloQueryError } from '@/infra/errors/apollo';
import { apolloErrorToMessage } from '@/infra/errors/apollo';
import {
type GetTasksQuery,
type GetTasksQueryVariables,
type RetryTasksMutation,
type RetryTasksMutationVariables,
SubscriberTaskStatusEnum,
} from '@/infra/graphql/gql/graphql';
import type { RouteStateDataOption } from '@/infra/routes/traits';
import { useQuery } from '@apollo/client';
import { useMutation, useQuery } from '@apollo/client';
import {
createFileRoute,
useCanGoBack,
@@ -28,6 +32,7 @@ import {
} from '@tanstack/react-router';
import { format } from 'date-fns';
import { ArrowLeft, RefreshCw } from 'lucide-react';
import { toast } from 'sonner';
import { getStatusBadge } from './-status-badge';
export const Route = createFileRoute('/_app/tasks/detail/$id')({
@@ -76,6 +81,28 @@ function TaskDetailRouteComponent() {
const task = data?.subscriberTasks?.nodes?.[0];
const [retryTasks] = useMutation<
RetryTasksMutation,
RetryTasksMutationVariables
>(RETRY_TASKS, {
onCompleted: async () => {
const refetchResult = await refetch();
const error = getApolloQueryError(refetchResult);
if (error) {
toast.error('Failed to retry task', {
description: apolloErrorToMessage(error),
});
return;
}
toast.success('Task retried successfully');
},
onError: (error) => {
toast.error('Failed to retry task', {
description: apolloErrorToMessage(error),
});
},
});
if (loading) {
return <DetailCardSkeleton />;
}
@@ -123,6 +150,21 @@ function TaskDetailRouteComponent() {
</div>
<div className="flex items-center gap-2">
{getStatusBadge(task.status)}
{task.status ===
(SubscriberTaskStatusEnum.Killed ||
SubscriberTaskStatusEnum.Failed) && (
<Button
variant="ghost"
size="sm"
onClick={() =>
retryTasks({
variables: { filters: { id: { eq: task.id } } },
})
}
>
Retry
</Button>
)}
</div>
</div>
</CardHeader>

View File

@@ -5,14 +5,23 @@ import { DetailEmptyView } from '@/components/ui/detail-empty-view';
import { DropdownMenuActions } from '@/components/ui/dropdown-menu-actions';
import { QueryErrorView } from '@/components/ui/query-error-view';
import { Skeleton } from '@/components/ui/skeleton';
import { GET_TASKS, type TaskDto } from '@/domains/recorder/schema/tasks';
import {
DELETE_TASKS,
GET_TASKS,
RETRY_TASKS,
type TaskDto,
} from '@/domains/recorder/schema/tasks';
import {
type DeleteTasksMutation,
type DeleteTasksMutationVariables,
type GetTasksQuery,
type RetryTasksMutation,
type RetryTasksMutationVariables,
SubscriberTaskStatusEnum,
} from '@/infra/graphql/gql/graphql';
import type { RouteStateDataOption } from '@/infra/routes/traits';
import { useDebouncedSkeleton } from '@/presentation/hooks/use-debounded-skeleton';
import { useQuery } from '@apollo/client';
import { useMutation, useQuery } from '@apollo/client';
import { createFileRoute, useNavigate } from '@tanstack/react-router';
import {
type ColumnDef,
@@ -26,7 +35,13 @@ import {
import { format } from 'date-fns';
import { RefreshCw } from 'lucide-react';
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
import {
apolloErrorToMessage,
getApolloQueryError,
} from '@/infra/errors/apollo';
import { useMemo, useState } from 'react';
import { toast } from 'sonner';
import { getStatusBadge } from './-status-badge';
export const Route = createFileRoute('/_app/tasks/manage')({
@@ -70,6 +85,42 @@ function TaskManageRouteComponent() {
const tasks = data?.subscriberTasks;
const [deleteTasks] = useMutation<
DeleteTasksMutation,
DeleteTasksMutationVariables
>(DELETE_TASKS, {
onCompleted: async () => {
const refetchResult = await refetch();
const error = getApolloQueryError(refetchResult);
if (error) {
toast.error('Failed to delete tasks', {
description: apolloErrorToMessage(error),
});
return;
}
toast.success('Tasks deleted');
},
onError: (error) => {
toast.error('Failed to delete tasks', {
description: error.message,
});
},
});
const [retryTasks] = useMutation<
RetryTasksMutation,
RetryTasksMutationVariables
>(RETRY_TASKS, {
onCompleted: () => {
toast.success('Tasks retried');
},
onError: (error) => {
toast.error('Failed to retry tasks', {
description: error.message,
});
},
});
const columns = useMemo(() => {
const cs: ColumnDef<TaskDto>[] = [
{
@@ -167,7 +218,39 @@ function TaskManageRouteComponent() {
params: { id: task.id },
});
}}
/>
showDelete
onDelete={() =>
deleteTasks({
variables: {
filters: {
id: {
eq: task.id,
},
},
},
})
}
>
{task.status ===
(SubscriberTaskStatusEnum.Killed ||
SubscriberTaskStatusEnum.Failed) && (
<DropdownMenuItem
onSelect={() =>
retryTasks({
variables: {
filters: {
id: {
eq: task.id,
},
},
},
})
}
>
Retry
</DropdownMenuItem>
)}
</DropdownMenuActions>
</div>
</div>