Skip to content

Commit 4a25040

Browse files
author
iberdinsky-skilld
committed
Common Context
1 parent 7b6fb7a commit 4a25040

File tree

14 files changed

+338
-216
lines changed

14 files changed

+338
-216
lines changed

client/src/App.tsx

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,45 +34,46 @@ const apiUrl = getApiUrl()
3434
export function App() {
3535
const [isLoading, setLoading] = useState(true)
3636

37-
const setTitle = useDocumentTitle();
37+
const setTitle = useDocumentTitle()
3838
useEffect(() => {
39-
(async () => {
40-
const data = await setCustomisation();
41-
const customisation: { plasmactl_web_ui_platform_page_name?: string } = data;
42-
setTitle(customisation?.plasmactl_web_ui_platform_page_name ?? 'Platform');
43-
setLoading(false);
44-
})();
39+
;(async () => {
40+
const data = await setCustomisation()
41+
const customisation: { plasmactl_web_ui_platform_page_name?: string } =
42+
data
43+
setTitle(customisation?.plasmactl_web_ui_platform_page_name ?? 'Platform')
44+
setLoading(false)
45+
})()
4546
}, [])
4647

4748
if (isLoading) {
48-
return null;
49+
return null
4950
}
5051

5152
return (
52-
<ActionProvider>
53-
<BrowserRouter>
54-
<ThemeProvider>
55-
<Refine
56-
dataProvider={{
57-
default: launchrDataProvider(apiUrl),
58-
}}
59-
liveProvider={liveProvider}
60-
notificationProvider={useNotificationProvider}
61-
routerProvider={routerBindings}
62-
resources={[
63-
{
64-
name: 'actions',
65-
list: '/actions',
66-
show: '/actions/:id/show',
67-
meta: {
68-
canDelete: false,
69-
},
53+
<BrowserRouter>
54+
<ThemeProvider>
55+
<Refine
56+
dataProvider={{
57+
default: launchrDataProvider(apiUrl),
58+
}}
59+
liveProvider={liveProvider}
60+
notificationProvider={useNotificationProvider}
61+
routerProvider={routerBindings}
62+
resources={[
63+
{
64+
name: 'actions',
65+
list: '/actions',
66+
show: '/actions/:id/show',
67+
meta: {
68+
canDelete: false,
7069
},
71-
]}
72-
options={{
73-
liveMode: 'manual',
74-
}}
75-
>
70+
},
71+
]}
72+
options={{
73+
liveMode: 'manual',
74+
}}
75+
>
76+
<ActionProvider>
7677
<GlobalKBarProvider>
7778
<Routes>
7879
<Route
@@ -105,9 +106,9 @@ export function App() {
105106
<UnsavedChangesNotifier />
106107
<RefineKbar />
107108
</GlobalKBarProvider>
108-
</Refine>
109-
</ThemeProvider>
110-
</BrowserRouter>
111-
</ActionProvider>
109+
</ActionProvider>
110+
</Refine>
111+
</ThemeProvider>
112+
</BrowserRouter>
112113
)
113-
}
114+
}

client/src/components/ActionsListFlow.tsx

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import ListItem from '@mui/material/ListItem'
77
import ListItemIcon from '@mui/material/ListItemIcon'
88
import Typography from '@mui/material/Typography'
99
import { type FC, Fragment } from 'react'
10-
import { useDebouncedCallback } from 'use-debounce'
1110

1211
import ArrowRightIcon from '/images/arrow-right.svg'
1312

@@ -26,16 +25,10 @@ export const ActionsListFlow: FC<ActionsListFlowProps> = ({ actionsGroup }) => {
2625
label: string
2726
items: components['schemas']['ActionShort'][]
2827
}[] = []
29-
30-
const breadcrumbs = actionsGroup.id.includes('.')
31-
? actionsGroup.id
32-
.split('.')
33-
.slice(0, -1)
34-
.map((a) => sentenceCase(a))
35-
: []
36-
const title = actionsGroup.id.includes('.')
37-
? actionsGroup.id.split('.').pop()
38-
: actionsGroup.id
28+
const { levels, id } = splitActionId(actionsGroup.id)
29+
const breadcrumbs =
30+
levels.length > 0 ? levels.map((a) => sentenceCase(a)) : []
31+
const title = id
3932

4033
if (actionsGroup.list.length > 0) {
4134
const attachedActions = actionsGroup.list.filter((a) =>
@@ -105,27 +98,6 @@ export const ActionsListFlow: FC<ActionsListFlowProps> = ({ actionsGroup }) => {
10598
}
10699
}
107100

108-
const debounced = useDebouncedCallback((id = false) => {
109-
if (id) {
110-
dispatch?.({
111-
type: 'set-hover-action',
112-
id,
113-
})
114-
} else {
115-
dispatch?.({
116-
type: 'set-hover-action',
117-
})
118-
}
119-
}, 25)
120-
121-
const wrappedHandleMouseEnter = (id: string) => {
122-
debounced(id)
123-
}
124-
125-
const wrappedHandleMouseLeave = () => {
126-
debounced()
127-
}
128-
129101
const actionClickHandler = (id: string) => {
130102
dispatch?.({
131103
type: 'set-active-action',
@@ -222,8 +194,6 @@ export const ActionsListFlow: FC<ActionsListFlowProps> = ({ actionsGroup }) => {
222194
>
223195
<Box
224196
className={'action-button'}
225-
onMouseEnter={() => wrappedHandleMouseEnter(item.id)}
226-
onMouseLeave={() => wrappedHandleMouseLeave()}
227197
onClick={() => actionClickHandler(item.id)}
228198
sx={{
229199
display: 'flex',

client/src/components/AnimatedFab.tsx

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,71 @@
1-
import TerminalIcon from '@mui/icons-material/Terminal'
2-
import Badge from '@mui/material/Badge'
3-
import Fab from '@mui/material/Fab'
4-
import { styled } from '@mui/system'
5-
import { useEffect, useState } from 'react'
6-
7-
const AnimatedBadge = styled(Badge)(() => ({
8-
transition: 'transform 0.3s ease-in-out',
9-
'&.animate': {
10-
transform: 'scale(1.5)',
11-
},
12-
}))
1+
import SpeedDial from '@mui/material/SpeedDial'
2+
import SpeedDialAction from '@mui/material/SpeedDialAction'
133

4+
import TerminalIcon from '@mui/icons-material/Terminal'
5+
import BoltIcon from '@mui/icons-material/Bolt'
146
interface IAnimatedFabProps {
15-
badgeLength: number
7+
startedLength: number
8+
runningLength?: number
9+
errorLength?: number
10+
finishedLength?: number
1611
handleOpen: () => void
1712
}
1813

19-
const AnimatedFab = ({ badgeLength, handleOpen }: IAnimatedFabProps) => {
20-
const [animate, setAnimate] = useState(false)
21-
const [prevBadgeLength, setPrevBadgeLength] = useState(badgeLength)
22-
23-
useEffect(() => {
24-
if (badgeLength !== prevBadgeLength && badgeLength > 0) {
25-
setAnimate(true)
26-
const timeout = setTimeout(() => setAnimate(false), 300)
27-
return () => clearTimeout(timeout)
28-
}
29-
setPrevBadgeLength(badgeLength)
30-
}, [badgeLength, prevBadgeLength])
14+
const AnimatedFab = ({
15+
startedLength,
16+
runningLength,
17+
errorLength,
18+
finishedLength,
19+
handleOpen,
20+
}: IAnimatedFabProps) => {
3121

3222
return (
33-
<Fab
34-
color="primary"
35-
aria-label="status"
36-
onClick={handleOpen}
37-
style={{ position: 'fixed', bottom: 16, right: 16, zIndex: 1000 }}
23+
<SpeedDial
24+
ariaLabel="Actions state"
25+
sx={{ position: 'absolute', bottom: 16, right: 16 }}
26+
icon={<TerminalIcon />}
3827
>
39-
<AnimatedBadge
40-
badgeContent={badgeLength}
41-
color="secondary"
42-
className={animate ? 'animate' : ''}
43-
>
44-
<TerminalIcon />
45-
</AnimatedBadge>
46-
</Fab>
28+
<SpeedDialAction
29+
icon={<BoltIcon />}
30+
onClick={handleOpen}
31+
slotProps={{
32+
tooltip: {
33+
title: `${startedLength} launched`,
34+
open: true,
35+
},
36+
}}
37+
/>
38+
<SpeedDialAction
39+
icon={<BoltIcon />}
40+
onClick={handleOpen}
41+
slotProps={{
42+
tooltip: {
43+
title: `${runningLength} running`,
44+
open: true,
45+
},
46+
}}
47+
/>
48+
<SpeedDialAction
49+
icon={<BoltIcon />}
50+
onClick={handleOpen}
51+
slotProps={{
52+
tooltip: {
53+
title: `${errorLength} errors`,
54+
open: true,
55+
},
56+
}}
57+
/>
58+
<SpeedDialAction
59+
icon={<BoltIcon />}
60+
onClick={handleOpen}
61+
slotProps={{
62+
tooltip: {
63+
title: `${finishedLength} finished`,
64+
open: true,
65+
},
66+
}}
67+
/>
68+
</SpeedDial>
4769
)
4870
}
4971

client/src/components/FormFlow.tsx

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
useNotification,
1616
useOne,
1717
usePublish,
18-
useSubscription,
1918
} from '@refinedev/core'
2019
import type { IChangeEvent } from '@rjsf/core'
2120
import { withTheme } from '@rjsf/core'
@@ -28,9 +27,10 @@ import { type FC, useEffect, useState } from 'react'
2827
import { components } from '../../openapi'
2928
import formTemplates from '../components/rjsf/templates'
3029
import formWidgets from '../components/rjsf/widgets'
31-
import { useActionDispatch } from '../hooks/ActionHooks'
30+
import { useAction, useActionDispatch } from '../hooks/ActionHooks'
3231
import {
3332
customizeUiSchema,
33+
extractDateTimeFromId,
3434
sentenceCase,
3535
splitActionId,
3636
} from '../utils/helpers'
@@ -48,7 +48,6 @@ export const FormFlow: FC<{
4848
}> = ({ actionId, formType = 'sidebar', onSubmitCallback }) => {
4949
const [changed, setChanged] = useState<Set<string>>(new Set())
5050
const [formValues, setFormValues] = useState<IFormValues | null>(null)
51-
const [actionRunning, setActionRunning] = useState(false)
5251
const [openDialog, setOpenDialog] = useState(false)
5352
const [previousSubmit, setPreviousSubmit] = useState<IFormValues | null>(null)
5453
const apiUrl = useApiUrl()
@@ -57,6 +56,8 @@ export const FormFlow: FC<{
5756
const { open } = useNotification()
5857
const { levels } = splitActionId(actionId)
5958
const dispatch = useActionDispatch()
59+
const { running } = useAction()
60+
const actionRunning = running?.has(actionId)
6061

6162
const queryResult = useOne<components['schemas']['ActionFull']>({
6263
resource: 'actions',
@@ -101,52 +102,52 @@ export const FormFlow: FC<{
101102
}
102103
}
103104

104-
useSubscription({
105-
channel: 'processes',
106-
types: ['send-processes-finished'],
107-
onLiveEvent: ({ payload, type }) => {
108-
if (
109-
actionId === payload?.data?.action &&
110-
type === 'send-processes-finished'
111-
) {
112-
setActionRunning(false)
113-
}
114-
},
115-
})
116-
117105
const handleSubmission = async (formData: IFormValues) => {
118106
if (onSubmitCallback) {
119-
onSubmitCallback(actionId);
107+
onSubmitCallback(actionId)
120108
}
121-
setActionRunning(true)
122-
publish?.({
123-
channel: 'processes',
124-
type: 'get-processes',
125-
payload: { action: actionId },
126-
date: new Date(),
127-
})
128109
try {
129110
const result = await mutateAsync({
130111
url: `${apiUrl}/actions/${actionId}`,
131112
method: 'post',
132113
values: { ...formData, changed: [...changed] },
133-
successNotification: {
134-
message: 'Action successfully created.',
135-
description: 'Success with no errors',
136-
type: 'success',
114+
successNotification: (data) => {
115+
return {
116+
description: `Action started successfully.`,
117+
message: data?.data?.id
118+
? extractDateTimeFromId(data?.data?.id as string)
119+
: '',
120+
type: 'success',
121+
}
137122
},
138-
errorNotification: {
139-
message: 'Error.',
140-
description: 'Something went wrong',
141-
type: 'error',
123+
errorNotification: () => {
124+
return {
125+
message: 'Error.',
126+
description: 'Something went wrong',
127+
type: 'error',
128+
}
142129
},
143130
})
144131

145132
if (result && actionId) {
133+
dispatch?.({
134+
type: 'start-action',
135+
id: actionId,
136+
})
137+
dispatch?.({
138+
type: 'change-process-state',
139+
process: result.data as components['schemas']['ActionRunInfo'],
140+
})
146141
dispatch?.({
147142
type: 'set-process',
148143
process: result.data as components['schemas']['ActionRunInfo'],
149144
})
145+
publish?.({
146+
channel: 'processes',
147+
type: 'get-processes',
148+
payload: { action: actionId },
149+
date: new Date(),
150+
})
150151
publish?.({
151152
channel: 'process',
152153
type: 'get-process',

0 commit comments

Comments
 (0)