Skip to content

Commit

Permalink
feat: ✨ added test execution trend chart (#57)
Browse files Browse the repository at this point in the history
* feat: ✨ added test execution trend chart

* fix: 🐛 fixed time issue and changed time graph to linear

* docs: 📝 updated readme and minor refactoring done
  • Loading branch information
WasiqB authored Oct 16, 2024
1 parent 657266a commit 71a1798
Show file tree
Hide file tree
Showing 15 changed files with 395 additions and 65 deletions.
1 change: 1 addition & 0 deletions .release-it.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"github": {
"release": true,
"tokenRef": "PUSH_TOKEN",
"discussionCategoryName": "Announcements",
"comments": {
"submit": true,
"issue": ":rocket: _This issue has been resolved in v${version}. See [${releaseName}](${releaseUrl}) for release notes._",
Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# 📊 Ultra-Reporter App

**Ultra-Reporter** is your go-to tool for transforming your TestNG (Maven, Java) test results into stunning reports.
[Ultra-Reporter](https://ultra-reporter-app.vercel.app/) is your go-to tool for transforming your TestNG (Maven, Java) test results into stunning reports.

Built on the power of Next.js and Tailwind CSS, this reporter takes your raw test results from TestNG and presents it in a beautiful, easy-to-digest format. Simplify your test reporting with just a one click!

## 📷 Generated Report

![Report page 1](/public/report-1.png)
![Report page 2](/public/report-2.png)
![Report page 3](/public/report-3.png)

## 🚀 Quick start

Checkout this demo video to see how to use Ultra-Reporter:

[![Ultra Reporter App Demo Video](https://img.youtube.com/vi/l2pk7LAq50I/0.jpg)](https://www.youtube.com/watch?v=l2pk7LAq50I)

Following are the steps to get started with Ultra-Reporter:

- Run your TestNG tests using it's default reporter listeners
- ![Step 1](/public/step-1.png)
- ![Step 2](/public/step-2.png)
Expand All @@ -24,7 +31,8 @@ Ultra-Reporter leverages cutting-edge technologies to provide fast, efficient, a
- **[Next.js 14](https://nextjs.org/)**: Dynamic, server-rendered React apps.
- **[TypeScript](https://www.typescriptlang.org/)**: Strongly typed for better reliability and scalability.
- **[Tailwind CSS](https://tailwindcss.com/)**: Beautiful, responsive design without the hassle.
- **[Shadcn/UI](https://ui.shadcn.com/)** & **[Daisy UI](https://daisyui.com/)**: Sleek UI components for a polished user experience.
- **[Shadcn/UI](https://ui.shadcn.com/)**, [Magic UI](https://magicui.design/) & **[Daisy UI](https://daisyui.com/)**: Sleek UI components for a polished user experience.
- **[Vercel](https://vercel.com/)**: Next.js is deployed on Vercel, a platform for static websites and serverless functions.

## ⏱️ What's Next?

Expand All @@ -44,9 +52,7 @@ Feel free to reach out for any queries, collaborations, or feedback!

## 💗 Repo Activity

<a href="https://github.com/WasiqB/ultra-reporter-app/graphs/contributors">
<img src="https://contrib.rocks/image?repo=wasiqb/ultra-reporter-app" />
</a>
[![Repo contributors](https://contrib.rocks/image?repo=wasiqb/ultra-reporter-app)](https://github.com/WasiqB/ultra-reporter-app/graphs/contributors)

![Ultra Reporter App Repo activity](https://repobeats.axiom.co/api/embed/40bf4829da597315850c30a8909fcf40a8b5a00c.svg "Repobeats analytics image")

Expand Down
25 changes: 25 additions & 0 deletions app/(app)/results/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { columns } from '@/components/data-table/columns';
import { PieComponent } from '@/components/charts/pie-chart';
import { NavBar } from '@/components/home/nav-bar';
import { cn } from '@/lib/utils';
import { AreaChartComponent } from '@/components/charts/area-chart';

const chartConfig: ChartConfig = {
total: {
Expand All @@ -47,6 +48,13 @@ const chartConfig: ChartConfig = {
},
};

const barConfig: ChartConfig = {
property: {
label: 'Method',
color: 'hsl(var(--failed))',
},
};

const ResultsPage = (): JSX.Element => {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
Expand All @@ -73,6 +81,7 @@ const ResultsPage = (): JSX.Element => {
totalTests,
chartCountData,
chartPieData,
areaChartData,
} = formattedData || {};

return (
Expand Down Expand Up @@ -175,6 +184,22 @@ const ResultsPage = (): JSX.Element => {
</>
)}
</div>
<div className='grid grid-cols-1 gap-6'>
{isLoading ? (
<>
<Skeleton className='h-[300px] w-full' />
</>
) : (
<>
<AreaChartComponent
title='Test Execution Trends (in seconds)'
description='Displays the test execution time trends'
config={barConfig}
data={areaChartData || []}
/>
</>
)}
</div>
<Card>
<CardHeader>
<CardTitle className='text-xl'>Test Details</CardTitle>
Expand Down
95 changes: 95 additions & 0 deletions components/charts/area-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use client';

import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts';

import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from '@/components/ui/chart';
import { AreaChartData } from '@/types/types';

interface AreaChartProps {
title: string;
description: string;
config: ChartConfig;
data: AreaChartData[];
footer?: string;
subFooter?: string;
}

export function AreaChartComponent({
title,
description,
config,
data,
footer,
subFooter,
}: AreaChartProps): JSX.Element {
return (
<Card>
<CardHeader className='items-center pb-0'>
<CardTitle className='text-xl'>{title}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</CardHeader>
<CardContent className='flex-1 pb-5'>
<ChartContainer config={config}>
<AreaChart
accessibilityLayer
data={data}
margin={{
left: 10,
right: 10,
}}
>
<CartesianGrid vertical={true} />
<XAxis
dataKey='property'
tickLine={false}
axisLine={false}
tickMargin={5}
tickFormatter={(value) => value.slice(0, 5)}
hide
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator='dot' />}
/>
<Area
dataKey='value'
type='linear'
fill='var(--color-property)'
fillOpacity={0.4}
stroke='var(--color-property)'
/>
</AreaChart>
</ChartContainer>
</CardContent>
{footer && (
<CardFooter>
<div className='flex w-full items-start gap-2 text-sm'>
<div className='grid gap-2'>
<div className='flex items-center gap-2 font-medium leading-none'>
{footer}
</div>
{subFooter && (
<div className='flex items-center gap-2 leading-none text-muted-foreground'>
{subFooter}
</div>
)}
</div>
</div>
</CardFooter>
)}
</Card>
);
}
11 changes: 10 additions & 1 deletion components/data-table/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AreaChartData,
ChartData,
FormattedData,
TestException,
Expand All @@ -7,7 +8,7 @@ import {
} from '@/types/types';
import { AlertTriangle, Check, X } from 'lucide-react';
import { format } from 'date-fns';
import { formatDateTime, round } from '@/lib/formatting';
import { formatDateTime, round, toDuration } from '@/lib/formatting';
import { formatDuration } from '../../lib/formatting';

export type TestResultData = {
Expand Down Expand Up @@ -125,6 +126,13 @@ export const getFormattedData = (data: TestResultData[]): FormattedData => {
},
];

const areaChartData: AreaChartData[] = data.map((r) => {
return {
property: `${r.class_name} / ${r.method_name}`,
value: toDuration(r.duration_ms),
};
});

const date =
data.length > 0 ? format(data[0].run_date, 'MMMM d, yyyy') : 'N/A';

Expand All @@ -137,5 +145,6 @@ export const getFormattedData = (data: TestResultData[]): FormattedData => {
totalTests,
chartCountData,
chartPieData,
areaChartData,
};
};
12 changes: 12 additions & 0 deletions components/home/feature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
CircleAlert,
Group,
MousePointerClick,
ParkingCircle,
Table,
Timer,
} from 'lucide-react';

const features = [
Expand All @@ -18,6 +20,11 @@ const features = [
description: 'Generate insightful charts and graphs from your data',
icon: BarChartIcon,
},
{
title: 'Test Execution Trends',
description: 'Analyze your test execution trends over time',
icon: Timer,
},
{
title: 'Detailed Results',
description:
Expand All @@ -39,6 +46,11 @@ const features = [
description: 'View your tests groups in which they are categorized',
icon: Group,
},
{
title: 'View Test Parameters',
description: 'View your tests parameters and their values',
icon: ParkingCircle,
},
];

export const Features = (): JSX.Element => {
Expand Down
2 changes: 1 addition & 1 deletion components/home/nav-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const NavBar = ({

return (
<nav
className={`fixed left-0 right-0 top-0 z-50 transition-all duration-300 ${
className={`left-0 right-0 top-0 z-50 transition-all duration-300 ${
isScrolled
? 'bg-background/80 shadow-md backdrop-blur-md'
: 'bg-transparent'
Expand Down
2 changes: 1 addition & 1 deletion components/home/open-source.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RainbowButton } from '@/components/ui/rainbow-button';

export const OpenSource = (): JSX.Element => {
return (
<section className='mb-10 mt-10 text-center'>
<section className='mb-10 mt-16 text-center'>
<h2 className='mb-4 text-3xl font-bold text-foreground'>
We are proudly Open Source ❤️
</h2>
Expand Down
44 changes: 37 additions & 7 deletions lib/formatting.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
import { parse, format } from 'date-fns';
import { DateTime } from 'luxon';

export const toDuration = (duration: string): number => {
const splitTime = duration.split(' ');
const time = parseFloat(splitTime[0]);
const type = splitTime[1];
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
let result = time;

switch (type) {
case 'm':
result = (result * MINUTE) / SECOND;
break;
case 'h':
result = (result * HOUR) / SECOND;
break;
case 'ms':
result /= SECOND;
break;
case 's':
default:
break;
}

return result;
};

export const formatDuration = (duration: number): string => {
const SECOND = 1000;
const MINUTE = 60 * SECOND;
Expand All @@ -25,32 +52,35 @@ export const formatDateTime = (
const systemZone = Intl.DateTimeFormat().resolvedOptions().timeZone;

const timezoneSplit = dateTimeString.split(' ');
let timezone = systemZone;
let adjustedDateTimeString = dateTimeString;

if (timezoneSplit.length > 1) {
timezone = timezoneSplit.pop() || systemZone;
timezoneSplit.pop();
adjustedDateTimeString = timezoneSplit.join(' ');
}

let dateTime = DateTime.fromISO(adjustedDateTimeString, { zone: timezone });
let dateTime = DateTime.fromISO(adjustedDateTimeString, {
zone: systemZone,
});

if (!dateTime.isValid) {
dateTime = DateTime.fromRFC2822(adjustedDateTimeString, {
zone: timezone,
zone: systemZone,
});
}

if (!dateTime.isValid) {
dateTime = DateTime.fromHTTP(adjustedDateTimeString, { zone: timezone });
dateTime = DateTime.fromHTTP(adjustedDateTimeString, {
zone: systemZone,
});
}

if (!dateTime.isValid) {
throw new Error('Invalid date-time format');
}

const formattedDate = dateTime.toUTC().toFormat('yyyy-MM-dd');
const formattedTime = dateTime.toUTC().toFormat('hh:mm:ss a');
const formattedDate = dateTime.toFormat('yyyy-MM-dd');
const formattedTime = dateTime.toFormat('hh:mm:ss a');

return { date: formattedDate, time: formattedTime };
} catch (error) {
Expand Down
Loading

0 comments on commit 71a1798

Please sign in to comment.