Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cc7ea75
Added hook for timeline data
MuhammadTalha57 Jul 4, 2026
37f899c
Created React component for Timeline
MuhammadTalha57 Jul 4, 2026
a3aa4af
Added tests for Timeline component
MuhammadTalha57 Jul 4, 2026
c17bb02
Updated changelog
MuhammadTalha57 Jul 4, 2026
b96cd7e
Added Tabs in Report to switch between two formats
MuhammadTalha57 Jul 6, 2026
ad1238d
style: fix lint formatting issues
MuhammadTalha57 Jul 7, 2026
e732468
Added sample parallel run envelope
MuhammadTalha57 Jul 7, 2026
80d1353
Merge branch 'main' into enhancement/timeline-report
MuhammadTalha57 Jul 9, 2026
61a3d50
removed old parallel-run file (locally generated)
MuhammadTalha57 Jul 9, 2026
9cb7ea2
Reimplemented Timeline component using vis-timeline
MuhammadTalha57 Jul 9, 2026
be1c03f
Merge branch 'main' into enhancement/timeline-report
MuhammadTalha57 Jul 25, 2026
29e3fed
Added Tooltip
MuhammadTalha57 Jul 25, 2026
3e41b83
Implemented Basic Layout of Timeline
MuhammadTalha57 Jul 26, 2026
e72c7c1
Separated Timeline Axis Component
MuhammadTalha57 Aug 1, 2026
4fea467
Added Panning
MuhammadTalha57 Aug 3, 2026
2fe5996
Fixed Panning and Zoom
MuhammadTalha57 Aug 3, 2026
c860765
Fixed useRef issue
MuhammadTalha57 Aug 4, 2026
d0ea61f
Cleanup
MuhammadTalha57 Aug 4, 2026
dc6960b
Refactored Timeline Bar
MuhammadTalha57 Aug 4, 2026
4c12d23
Refactored useTimeline hook
MuhammadTalha57 Aug 4, 2026
d04d642
Implemented items bucket
MuhammadTalha57 Aug 4, 2026
7915f17
Uninstalled vis-data and vis-timeline
MuhammadTalha57 Aug 4, 2026
e3ff679
Resolved Merge Conflicts
MuhammadTalha57 Aug 4, 2026
f3b3b3c
Fixed formatting
MuhammadTalha57 Aug 4, 2026
7b5de32
Added useMemo for selectedItem and cleanup
MuhammadTalha57 Aug 4, 2026
83423e5
Fixed formatting
MuhammadTalha57 Aug 4, 2026
263cd85
Fixed axis tick and item detail UI
MuhammadTalha57 Aug 4, 2026
2b8a4a0
Removed tooltip overlay arrow
MuhammadTalha57 Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -583,3 +583,7 @@ to rebuild them every time the envelope list is updated. Use this instead of `<W
[4.1.1]: https://github.com/cucumber/cucumber-react/compare/v4.0.0...v4.1.1
[4.0.0]: https://github.com/cucumber/cucumber-react/compare/v3.3.0...v4.0.0
[3.3.0]: https://github.com/cucumber/cucumber-react/compare/v3.2.0...v3.3.0

## [Unreleased]
### Added
- Added `<Timeline/>` component showing scenario execution over time grouped by worker ([#126](https://github.com/cucumber/react-components/issues/126))
36 changes: 36 additions & 0 deletions src/components/app/Report.module.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@use '../../styles/theming';

.layout {
> section:not(:last-child) {
margin-bottom: 1.5em;
Expand All @@ -8,3 +10,37 @@
font-size: 1.25em;
margin: 1em 0;
}

.tabList {
display: flex;
gap: 0.25em;
border-bottom: 1px solid theming.$panelAccentColor;
margin-bottom: 1em;
}

.tab {
padding: 0.5em 1em;
border-bottom: 2px solid transparent;
color: theming.$panelTextColor;
cursor: pointer;
outline: none;

&[data-hovered] {
background-color: theming.$panelBackgroundColor;
}

&[data-selected] {
border-bottom-color: theming.$anchorColor;
color: theming.$anchorColor;
font-weight: 600;
}

&[data-focus-visible] {
outline: 2px solid theming.$anchorColor;
outline-offset: 2px;
}
}

.tabPanel {
outline: none;
}
22 changes: 20 additions & 2 deletions src/components/app/Report.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { FC } from 'react'
import { Tab, TabList, TabPanel, TabPanels, Tabs } from 'react-aria-components'

import { ExecutionSummary } from './ExecutionSummary.js'
import { FilteredDocuments } from './FilteredDocuments.js'
import styles from './Report.module.scss'
import { SearchBar } from './SearchBar.js'
import { TestRunHooks } from './TestRunHooks.js'
import { Timeline } from './Timeline.js'

export const Report: FC = () => {
return (
Expand All @@ -14,8 +16,24 @@ export const Report: FC = () => {
<SearchBar />
</section>
<section>
<h2 className={styles.heading}>Scenarios</h2>
<FilteredDocuments />
<Tabs className={styles.tabs}>
<TabList aria-label="Report views" className={styles.tabList}>
<Tab id="scenarios" className={styles.tab}>
Scenarios
</Tab>
<Tab id="timeline" className={styles.tab}>
Timeline
</Tab>
</TabList>
<TabPanels>
<TabPanel id="scenarios" className={styles.tabPanel}>
<FilteredDocuments />
</TabPanel>
<TabPanel id="timeline" className={styles.tabPanel}>
<Timeline />
</TabPanel>
</TabPanels>
</Tabs>
</section>
<section>
<h2 className={styles.heading}>Hooks</h2>
Expand Down
223 changes: 223 additions & 0 deletions src/components/app/Timeline.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
@use '../../styles/statuses';
@use '../../styles/theming';
@use 'sass:color';

.timelineWrapper {
display: flex;
flex-direction: column;
padding: 1px;
}

.timelineRow {
width: 100%;
display: grid;
grid-template-columns: 1fr minmax(0, 4fr);
gap: 1px;
border: 1px solid theming.$panelAccentColor;
border-top: none;
}

.timelineRow:first-child {
border-top: 1px solid theming.$panelAccentColor;
}

.cell {
height: 3em;
}

.cell:first-child {
border-right: 1px solid theming.$panelAccentColor;
}

.leftCell {
display: flex;
align-items: center;
padding-left: 0.5em;
font-family: inherit;
}

// Timeline Bar Styles

.timelineBarWrapper {
display: flex;
overflow: hidden;
align-items: center;
position: sticky; // Allows setting timelineBar position: absolute
}

.timelineBar {
height: 80%;
border-radius: 0.333em;
border: none;
padding: 0;
box-sizing: border-box;
flex: none;
position: absolute;
}

@each $name, $color in statuses.$statusColors {
.timelineBar[data-status='#{$name}'] {
background-color: color.adjust($color, $alpha: -0.666);
border-color: $color;

&:hover {
outline: 1px solid $color;
}

&.selected {
outline: 1px solid $color
}
}
}

// Tooltip Styles

.icon {
padding-top: 0.35em;
padding-left: 0.5em;
padding-right: 0.5em;
}

// Tooltip card
:global(.react-aria-Tooltip) {
display: flex;
flex-direction: column;
overflow: hidden;
background-color: theming.$panelBackgroundColor;
color: theming.$panelTextColor;
border: 1px solid theming.$panelAccentColor;
border-radius: 0.25em;
padding: 0.4em 0.6em;
font-size: inherit;
font-family: inherit;
align-items: center;
gap: 0.4em;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
max-width: 20em;
margin-bottom: 0.7em;
}

.tooltipBtn {
border: none;
background: none;
}

// Arrow styling
:global(.react-aria-Tooltip) svg {
fill: theming.$exampleNumberColor;
stroke: theming.$panelAccentColor;
stroke-width: 1px;
}

// Axis Styles

.axisUnit {
font-size: 0.65em;
text-transform: uppercase;
letter-spacing: 0.05em;
color: theming.$exampleNumberColor;
opacity: 0.9;
}

.axisRuler {
position: relative;
overflow: hidden;
cursor: grab;
user-select: none;
border: none;
background-color: inherit;
padding: 0;
margin: 0;
}

.axisRuler:active {
cursor: grabbing;
}

.minorTick {
position: absolute;
bottom: 0;
width: 2px;
height: 30%;
background-color: theming.$panelAccentColor;
pointer-events: none;
}

.majorTick {
position: absolute;
bottom: 0;
width: 2px;
height: 55%;
background-color: theming.$exampleNumberColor;
pointer-events: none;
}

.tickLabel {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
font-size: 0.6em;
line-height: 1.5;
white-space: nowrap;
color: theming.$panelTextColor;
opacity: 0.75;
pointer-events: none;
}


// Timeline Bar Detail Styles

.detail {
position: relative;
padding: 1em;
background-color: theming.$panelBackgroundColor;
color: theming.$panelTextColor;
border: 1px solid theming.$panelAccentColor;
border-radius: 0.25em;
}

.detailClose {
position: absolute;
top: 0.5em;
right: 0.5em;
padding: 0.25em;
background: none;
border: none;
cursor: pointer;
color: inherit;
}

.detailTitle {
display: flex;
align-items: center;
gap: 0.4em;
margin: 0 0 0.25em;
font-size: 1.1em;

svg {
height: 1em;
}
}

.detailFeature {
margin: 0 0 0.5em;
opacity: 0.75;
}

.detailMeta {
display: flex;
flex-wrap: wrap;
gap: 1em;
margin: 0.75em 0 0;

dt {
font-size: 0.75em;
text-transform: uppercase;
opacity: 0.75;
}

dd {
margin: 0;
}
}
40 changes: 40 additions & 0 deletions src/components/app/Timeline.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { Envelope } from '@cucumber/messages'
import type { Story } from '@ladle/react'
import examplesTables from '../../../acceptance/examples-tables/examples-tables.js'
import parallel from '../../../acceptance/parallel/parallel.js'
import { EnvelopesProvider } from './EnvelopesProvider.js'
import { InMemorySearchProvider } from './InMemorySearchProvider.js'
import { Timeline } from './Timeline.js'

export default {
title: 'App/Timeline',
}

type TemplateArgs = {
envelopes: readonly Envelope[]
}

const Template: Story<TemplateArgs> = ({ envelopes }) => {
return (
<EnvelopesProvider envelopes={envelopes}>
<InMemorySearchProvider>
<Timeline />
</InMemorySearchProvider>
</EnvelopesProvider>
)
}

export const SingleProcess = Template.bind({})
SingleProcess.args = {
envelopes: examplesTables,
} as TemplateArgs

export const Parallel = Template.bind({})
Parallel.args = {
envelopes: parallel,
} as TemplateArgs

export const NoTestCases = Template.bind({})
NoTestCases.args = {
envelopes: [],
} as TemplateArgs
Loading