Skip to content

Commit d71ed14

Browse files
authored
Merge pull request #24 from moda20/Improvement/adding-missing-filtering-and-selection-features
2 parents 81888f5 + cb87539 commit d71ed14

13 files changed

Lines changed: 1413 additions & 301 deletions

File tree

.github/workflows/dev-ci-cd.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: CI/CD Pipeline
2+
3+
on:
4+
push:
5+
branches:
6+
- dev
7+
8+
jobs:
9+
build-and-push:
10+
permissions: write-all
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v3
15+
16+
- name: Log in to GHCR
17+
uses: docker/login-action@v2
18+
with:
19+
registry: ghcr.io
20+
username: ${{ github.actor }}
21+
password: ${{ secrets.GITHUB_TOKEN }}
22+
23+
- name: Set up Docker Buildx
24+
uses: docker/setup-buildx-action@v3
25+
26+
- name: Build and push Docker image
27+
uses: docker/build-push-action@v6
28+
with:
29+
context: .
30+
file: ./DOCKERFILE
31+
push: true
32+
tags: |
33+
ghcr.io/${{ github.repository_owner }}/scheduler_ui:dev
34+
ghcr.io/${{ github.repository_owner }}/scheduler_ui:${{ github.sha }}
35+
platforms: linux/amd64,linux/arm64

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
"@radix-ui/react-popover": "^1.1.6",
3434
"@radix-ui/react-scroll-area": "^1.2.2",
3535
"@radix-ui/react-select": "^2.1.4",
36-
"@radix-ui/react-separator": "^1.1.0",
37-
"@radix-ui/react-slot": "^1.2.3",
36+
"@radix-ui/react-separator": "^1.1.8",
37+
"@radix-ui/react-slot": "^1.2.4",
3838
"@radix-ui/react-switch": "^1.2.6",
3939
"@radix-ui/react-tabs": "^1.1.3",
4040
"@radix-ui/react-toast": "^1.2.2",
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
import * as React from "react"
2+
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"
3+
import {
4+
Card,
5+
CardContent,
6+
CardDescription,
7+
CardHeader,
8+
CardTitle,
9+
} from "@/components/ui/card"
10+
import type { ChartConfig } from "@/components/ui/chart"
11+
import {
12+
ChartContainer,
13+
ChartLegend,
14+
ChartLegendContent,
15+
ChartTooltip,
16+
ChartTooltipContent,
17+
} from "@/components/ui/chart"
18+
import jobsService from "@/services/JobsService"
19+
import { useCallback, useEffect, useState } from "react"
20+
import {
21+
DatePickerWithPresets,
22+
defaultDateRange,
23+
} from "@/components/ui/date-picker-presets"
24+
import type { DateRange } from "react-day-picker"
25+
import WithError from "@/components/custom/dashboard/WithError"
26+
import { Button } from "@/components/ui/button"
27+
import { FileWarningIcon, LucideListRestart, SearchIcon } from "lucide-react"
28+
import { getEventStats } from "@/services/components/eventsService"
29+
import { Input } from "@/components/ui/input"
30+
import { ButtonGroup } from "@/components/ui/buttonGroup"
31+
import {
32+
InputGroup,
33+
InputGroupAddon,
34+
InputGroupButton,
35+
InputGroupInput,
36+
} from "@/components/ui/input-group"
37+
import {
38+
Tooltip,
39+
TooltipContent,
40+
TooltipTrigger,
41+
} from "@/components/ui/tooltip"
42+
import { JobEventsTableFull } from "@/components/custom/dashboard/JobEventsTableFull"
43+
const chartConfig = {
44+
events: {
45+
label: "All events count",
46+
color: "hsl(var(--chart-4))",
47+
},
48+
errors: {
49+
label: "Errors count",
50+
color: "hsl(var(--destructive))",
51+
},
52+
warnings: {
53+
label: "Warnings count",
54+
color: "hsl(var(--chart-3))",
55+
},
56+
info: {
57+
label: "Info count",
58+
color: "hsl(var(--chart-1))",
59+
},
60+
} satisfies ChartConfig
61+
62+
export interface AllEventStatsProps {
63+
dateRange?: DateRange | undefined
64+
period?: number
65+
stats?: Array<any>
66+
}
67+
68+
export function AllEventStats(props: AllEventStatsProps) {
69+
const [stats, setStats] = React.useState(props.stats ?? [])
70+
const [dateRange, setDateRange] = useState<DateRange | undefined>({
71+
from: defaultDateRange.from,
72+
to: defaultDateRange.to,
73+
})
74+
const [period, setPeriod] = useState(props.period ?? 60)
75+
76+
const [activeTab, setActiveTab] = useState("chart")
77+
78+
const [hasError, setHasError] = useState(false)
79+
80+
const ErrorComponent = React.memo(() => (
81+
<div className=" flex">
82+
<div className="m-auto flex flex-col gap-4 justify-center items-center text-sm ">
83+
<span className="flex gap-2">
84+
<FileWarningIcon /> <p>Error getting event stats</p>
85+
</span>
86+
<Button
87+
variant="default"
88+
className="btn-sm btn-rounded text-sm px-2 w-[130px]"
89+
onClick={() => {
90+
getStats()
91+
}}
92+
>
93+
<LucideListRestart /> Try again
94+
</Button>
95+
</div>
96+
</div>
97+
))
98+
99+
const getStats = () => {
100+
getEventStats(period, dateRange)
101+
.then(data => {
102+
setStats(data as Array<any>)
103+
})
104+
.catch(err => {
105+
setHasError(true)
106+
})
107+
}
108+
109+
useEffect(() => {
110+
getStats()
111+
}, [dateRange])
112+
113+
const updatePeriod = (period: DateRange | undefined) => {
114+
setDateRange(period)
115+
}
116+
117+
const setChunkPeriod = useCallback(
118+
(event: any) => {
119+
setPeriod(Number(event.target.value))
120+
if (event.key === "Enter") {
121+
getStats()
122+
}
123+
},
124+
[period],
125+
)
126+
127+
const setActiveTabProxy = useCallback((tab: string) => {
128+
return () => setActiveTab(tab)
129+
}, [])
130+
131+
return (
132+
<Card className="border-border">
133+
<CardHeader className="flex items-center gap-2 space-y-0 border-b py-4 px-4 sm:flex-row text-foreground bg-background border-border rounded-t-xl">
134+
<div className="grid flex-1 gap-1 text-center sm:text-left">
135+
<CardTitle>
136+
{activeTab === "table"
137+
? "Top unhandled events"
138+
: "Events over time"}
139+
</CardTitle>
140+
<CardDescription>
141+
{activeTab === "table"
142+
? "Showing the top jobs with unhandled events by type"
143+
: "Showing recurrent stats for all emitted events"}
144+
</CardDescription>
145+
</div>
146+
<div className="flex gap-2 items-center">
147+
<ButtonGroup>
148+
<Button
149+
data-active={activeTab === "table"}
150+
className="data-[active=true]:bg-orange-50 data-[active=true]:text-orange-700 dark:data-[active=true]:bg-orange-900 dark:data-[active=true]:text-orange-50"
151+
variant={"outline"}
152+
onClick={setActiveTabProxy("table")}
153+
>
154+
Table
155+
</Button>
156+
<Button
157+
data-active={activeTab === "chart"}
158+
className="data-[active=true]:bg-orange-50 data-[active=true]:text-orange-700 dark:data-[active=true]:bg-orange-900 dark:data-[active=true]:text-orange-50"
159+
variant={"outline"}
160+
onClick={setActiveTabProxy("chart")}
161+
>
162+
Chart
163+
</Button>
164+
</ButtonGroup>
165+
<InputGroup>
166+
<InputGroupAddon>Period (min)</InputGroupAddon>
167+
<InputGroupInput
168+
placeholder={"set graph x axis period"}
169+
value={period}
170+
onInput={setChunkPeriod}
171+
type="number"
172+
min={1}
173+
onKeyDown={setChunkPeriod}
174+
disabled={activeTab !== "chart"}
175+
/>
176+
<InputGroupAddon align="inline-end">
177+
<Tooltip>
178+
<TooltipTrigger asChild>
179+
<InputGroupButton
180+
onClick={getStats}
181+
size="icon-xs"
182+
className="data-[active=true]:bg-orange-100 data-[active=true]:text-orange-700 dark:data-[active=true]:bg-orange-800 dark:data-[active=true]:text-orange-100"
183+
>
184+
<SearchIcon />
185+
</InputGroupButton>
186+
</TooltipTrigger>
187+
<TooltipContent>Search</TooltipContent>
188+
</Tooltip>
189+
</InputGroupAddon>
190+
</InputGroup>
191+
<DatePickerWithPresets
192+
onChange={updatePeriod}
193+
defaultValue={dateRange}
194+
/>
195+
</div>
196+
</CardHeader>
197+
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6 relative">
198+
<div
199+
className={`transition-opacity duration-300 ease-in-out ${
200+
activeTab === "chart"
201+
? "opacity-100"
202+
: "absolute px-2 pt-4 sm:px-6 sm:pt-6 inset-0 opacity-0 pointer-events-none"
203+
}`}
204+
>
205+
<WithError errorCpt={<ErrorComponent />} hasError={hasError}>
206+
<ChartContainer
207+
config={chartConfig}
208+
className="aspect-auto h-[350px] w-full"
209+
>
210+
<AreaChart data={stats}>
211+
<defs>
212+
<linearGradient
213+
id="filleAllEvents"
214+
x1="0"
215+
y1="0"
216+
x2="0"
217+
y2="1"
218+
>
219+
<stop
220+
offset="5%"
221+
stopColor="var(--color-events)"
222+
stopOpacity={0.8}
223+
/>
224+
<stop
225+
offset="95%"
226+
stopColor="var(--color-events)"
227+
stopOpacity={0.1}
228+
/>
229+
</linearGradient>
230+
<linearGradient id="fillErrors" x1="0" y1="0" x2="0" y2="1">
231+
<stop
232+
offset="5%"
233+
stopColor="var(--color-errors)"
234+
stopOpacity={0.8}
235+
/>
236+
<stop
237+
offset="95%"
238+
stopColor="var(--color-errors)"
239+
stopOpacity={0.1}
240+
/>
241+
</linearGradient>
242+
<linearGradient id="fillWarnings" x1="0" y1="0" x2="0" y2="1">
243+
<stop
244+
offset="95%"
245+
stopColor="transparent"
246+
stopOpacity={0.8}
247+
/>
248+
</linearGradient>
249+
</defs>
250+
<CartesianGrid vertical={false} />
251+
<XAxis
252+
dataKey="period"
253+
tickLine={false}
254+
axisLine={false}
255+
tickMargin={8}
256+
minTickGap={32}
257+
tickFormatter={value => {
258+
const date = new Date(value)
259+
return date.toLocaleDateString("en-US", {
260+
month: "short",
261+
day: "numeric",
262+
minute: "numeric",
263+
hour: "numeric",
264+
})
265+
}}
266+
/>
267+
<YAxis
268+
yAxisId="main"
269+
domain={["dataMin", "auto"]}
270+
orientation="left"
271+
tickLine={false}
272+
axisLine={false}
273+
tickMargin={12}
274+
minTickGap={32}
275+
amplitude={200}
276+
allowDataOverflow={true}
277+
/>
278+
<ChartTooltip
279+
cursor={false}
280+
content={
281+
<ChartTooltipContent
282+
ItemLabelClassName="gap-4"
283+
labelFormatter={value => {
284+
return new Date(value).toLocaleDateString("en-US", {
285+
month: "short",
286+
day: "numeric",
287+
year: "numeric",
288+
})
289+
}}
290+
indicator="dot"
291+
/>
292+
}
293+
/>
294+
<Area
295+
dataKey="events"
296+
type="natural"
297+
fill="url(#filleAllEvents)"
298+
stroke="var(--color-events)"
299+
yAxisId="main"
300+
stackId="a"
301+
/>
302+
<Area
303+
dataKey="errors"
304+
type="natural"
305+
fill="url(#fillErrors)"
306+
stroke="var(--color-errors)"
307+
yAxisId="main"
308+
/>
309+
<Area
310+
dataKey="warnings"
311+
type="natural"
312+
stroke="var(--color-warnings)"
313+
fill="url(#fillWarnings)"
314+
yAxisId="main"
315+
/>
316+
<Area
317+
dataKey="info"
318+
type="natural"
319+
stroke="var(--color-info)"
320+
fill="url(#FillTotalRuns)"
321+
yAxisId="main"
322+
/>
323+
<ChartLegend content={<ChartLegendContent />} />
324+
</AreaChart>
325+
</ChartContainer>
326+
</WithError>
327+
</div>
328+
<div
329+
className={`transition-opacity duration-300 ease-in-out ${
330+
activeTab === "table"
331+
? "opacity-100"
332+
: "absolute px-2 pt-4 sm:px-6 sm:pt-6 inset-0 opacity-0 pointer-events-none"
333+
}`}
334+
>
335+
<JobEventsTableFull />
336+
</div>
337+
</CardContent>
338+
</Card>
339+
)
340+
}

0 commit comments

Comments
 (0)