Skip to content

Commit f33b36d

Browse files
authored
feat(dashboard): Add feature to remove action events (#688)
* feat: Add feature to remove action events * fix: Fix database revision
1 parent e0995c9 commit f33b36d

File tree

13 files changed

+196
-20
lines changed

13 files changed

+196
-20
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""add_disabled_field_to_action_event
2+
3+
Revision ID: a29b537fabe6
4+
Revises: 98c8851a5321
5+
Create Date: 2024-05-28 11:28:50.353928
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
# revision identifiers, used by Alembic.
12+
revision = "a29b537fabe6"
13+
down_revision = "98c8851a5321"
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade() -> None:
19+
# ### commands auto generated by Alembic - please adjust! ###
20+
with op.batch_alter_table("action_event", schema=None) as batch_op:
21+
batch_op.add_column(sa.Column("disabled", sa.Boolean(), nullable=True))
22+
23+
# ### end Alembic commands ###
24+
25+
26+
def downgrade() -> None:
27+
# ### commands auto generated by Alembic - please adjust! ###
28+
with op.batch_alter_table("action_event", schema=None) as batch_op:
29+
batch_op.drop_column("disabled")
30+
31+
# ### end Alembic commands ###
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""API endpoints for recordings."""
2+
3+
from fastapi import APIRouter
4+
from loguru import logger
5+
6+
from openadapt.db import crud
7+
8+
9+
class ActionEventsAPI:
10+
"""API endpoints for action events."""
11+
12+
def __init__(self) -> None:
13+
"""Initialize the ActionEventsAPI class."""
14+
self.app = APIRouter()
15+
16+
def attach_routes(self) -> APIRouter:
17+
"""Attach routes to the FastAPI app."""
18+
self.app.add_api_route("/{event_id}", self.disable_event, methods=["DELETE"])
19+
return self.app
20+
21+
@staticmethod
22+
def disable_event(event_id: int) -> dict[str, str]:
23+
"""Disable an action event.
24+
25+
Args:
26+
event_id (int): The ID of the event to disable.
27+
28+
Returns:
29+
dict: The response message and status code.
30+
"""
31+
if not crud.acquire_db_lock():
32+
return {"message": "Database is locked", "status": "error"}
33+
session = crud.get_new_session(read_and_write=True)
34+
try:
35+
crud.disable_action_event(session, event_id)
36+
except Exception as e:
37+
logger.error(f"Error deleting event: {e}")
38+
session.rollback()
39+
crud.release_db_lock()
40+
return {"message": "Error deleting event", "status": "error"}
41+
crud.release_db_lock()
42+
return {"message": "Event deleted", "status": "success"}

openadapt/app/dashboard/api/index.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from loguru import logger
1111
import uvicorn
1212

13+
from openadapt.app.dashboard.api.action_events import ActionEventsAPI
1314
from openadapt.app.dashboard.api.recordings import RecordingsAPI
1415
from openadapt.app.dashboard.api.scrubbing import ScrubbingAPI
1516
from openadapt.app.dashboard.api.settings import SettingsAPI
@@ -20,10 +21,12 @@
2021

2122
api = APIRouter()
2223

24+
action_events_app = ActionEventsAPI().attach_routes()
2325
recordings_app = RecordingsAPI().attach_routes()
2426
scrubbing_app = ScrubbingAPI().attach_routes()
2527
settings_app = SettingsAPI().attach_routes()
2628

29+
api.include_router(action_events_app, prefix="/action-events")
2730
api.include_router(recordings_app, prefix="/recordings")
2831
api.include_router(scrubbing_app, prefix="/scrubbing")
2932
api.include_router(settings_app, prefix="/settings")

openadapt/app/dashboard/app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ColorSchemeScript, MantineProvider } from '@mantine/core'
44
import { Notifications } from '@mantine/notifications';
55
import { Shell } from '@/components/Shell'
66
import { CSPostHogProvider } from './providers';
7+
import { ModalsProvider } from '@mantine/modals';
78

89
export const metadata = {
910
title: 'OpenAdapt.AI',
@@ -23,7 +24,9 @@ export default function RootLayout({
2324
<body>
2425
<MantineProvider>
2526
<Notifications />
26-
<Shell>{children}</Shell>
27+
<ModalsProvider>
28+
<Shell>{children}</Shell>
29+
</ModalsProvider>
2730
</MantineProvider>
2831
</body>
2932
</CSPostHogProvider>

openadapt/app/dashboard/app/recordings/detail/page.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function Recording() {
3939
if (!prev) return prev;
4040
return {
4141
...prev,
42-
"action_events": [...prev.action_events, addIdToNullActionEvent(data.value)],
42+
"action_events": [...prev.action_events, modifyActionEvent(data.value, prev.recording.original_recording_id === null)],
4343
}
4444
});
4545
} else if (data.type === "num_events") {
@@ -77,22 +77,26 @@ function Recording() {
7777
)
7878
}
7979

80-
function addIdToNullActionEvent(actionEvent: ActionEventType): ActionEventType {
80+
function modifyActionEvent(actionEvent: ActionEventType, isOriginal: boolean): ActionEventType {
8181
let children = actionEvent.children;
8282
if (actionEvent.children) {
83-
children = actionEvent.children.map(addIdToNullActionEvent);
83+
children = actionEvent.children.map(child => modifyActionEvent(child, isOriginal));
8484
}
8585
let id = actionEvent.id;
86+
let isComputed = false;
8687
if (!id) {
8788
// this is usually the case, when new events like 'singleclick'
8889
// or 'doubleclick' are created while merging several events together,
8990
// but they are not saved in the database
9091
id = crypto.randomUUID();
92+
isComputed = true;
9193
}
9294
return {
9395
...actionEvent,
9496
id,
9597
children,
98+
isComputed,
99+
isOriginal,
96100
}
97101
}
98102

openadapt/app/dashboard/components/ActionEvent/ActionEvent.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { timeStampToDateString } from '@/app/utils';
44
import { ActionEvent as ActionEventType } from '@/types/action-event'
55
import { Accordion, Box, Grid, Image, Table } from '@mantine/core'
66
import { useHover } from '@mantine/hooks';
7+
import { RemoveActionEvent } from './RemoveActionEvent';
78

89
type Props = {
910
event: ActionEventType;
@@ -38,8 +39,8 @@ export const ActionEvent = ({
3839
const imageSrc = (hoveredOverScreenshot ? event.screenshot : event.screenshot) || ''; // change to event.diff to show diff
3940

4041
let content = (
41-
<Grid>
42-
<Grid.Col span={level === 0 ? 4 : 12}>
42+
<Grid align='center'>
43+
<Grid.Col span={8}>
4344
<Table w={400} withTableBorder withColumnBorders my={20} className='border-2 border-gray-300 border-solid'>
4445
<Table.Tbody>
4546
{typeof event.id === 'number' && (
@@ -135,6 +136,9 @@ export const ActionEvent = ({
135136
</Table.Tbody>
136137
</Table>
137138
</Grid.Col>
139+
<Grid.Col span={4}>
140+
<RemoveActionEvent event={event} />
141+
</Grid.Col>
138142
{level === 0 && (
139143
<Grid.Col span={12}>
140144
{event.screenshot !== null && (
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ActionEvent } from '@/types/action-event';
2+
import { Button, Text } from '@mantine/core';
3+
import { modals } from '@mantine/modals';
4+
import { notifications } from '@mantine/notifications';
5+
import React from 'react'
6+
7+
type Props = {
8+
event: ActionEvent;
9+
}
10+
11+
export const RemoveActionEvent = ({
12+
event
13+
}: Props) => {
14+
const openModal = (e: React.MouseEvent<HTMLButtonElement>) => {
15+
e.stopPropagation();
16+
modals.openConfirmModal({
17+
title: 'Please confirm your action',
18+
children: (
19+
<Text size="sm">
20+
Are you sure you want to delete this action event? This action cannot be undone.
21+
</Text>
22+
),
23+
labels: { confirm: 'Confirm', cancel: 'Cancel' },
24+
onCancel: () => {},
25+
onConfirm: deleteActionEvent,
26+
confirmProps: { color: 'red' },
27+
});
28+
}
29+
30+
const deleteActionEvent = () => {
31+
fetch(`/api/action-events/${event.id}`, {
32+
method: 'DELETE',
33+
}).then(res => res.json()).then(data => {
34+
const { message, status } = data;
35+
if (status === 'success') {
36+
window.location.reload();
37+
} else {
38+
notifications.show({
39+
title: 'Error',
40+
message,
41+
color: 'red',
42+
})
43+
}
44+
});
45+
}
46+
if (event.isComputed || !event.isOriginal) return null;
47+
return (
48+
<Button variant='filled' color='red' onClick={openModal}>
49+
Remove action event
50+
</Button>
51+
)
52+
}

openadapt/app/dashboard/package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

openadapt/app/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@mantine/core": "7.7.1",
2020
"@mantine/form": "7.7.1",
2121
"@mantine/hooks": "7.7.1",
22+
"@mantine/modals": "^7.7.1",
2223
"@mantine/notifications": "7.7.1",
2324
"@tabler/icons-react": "^3.1.0",
2425
"@types/node": "20.2.4",

openadapt/app/dashboard/types/action-event.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ export type ActionEvent = {
2727
dimensions?: { width: number, height: number };
2828
children?: ActionEvent[];
2929
words?: string[];
30+
isComputed?: boolean;
31+
isOriginal?: boolean;
3032
}

0 commit comments

Comments
 (0)