Skip to content

Commit a079bb9

Browse files
Merge pull request #269 from frankframework/fix/improve-release-color-grading
Fix/improve release color grading
2 parents 871263e + 3b170dc commit a079bb9

26 files changed

Lines changed: 2738 additions & 830 deletions

src/main/frontend/cypress/e2e/off-canvas.cy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ describe('Off-Canvas Panel Journey', () => {
66
});
77

88
it('should open, display all content correctly, and close', () => {
9-
cy.get('[data-cy="node-v9.0.1"]').should('be.visible').click();
9+
cy.get('[data-cy="node-v9.0.1"]').should('exist').click({ force: true });
1010

1111
cy.get('app-release-off-canvas', { timeout: 10000 })
1212
.should('be.visible')
@@ -30,7 +30,7 @@ describe('Off-Canvas Panel Journey', () => {
3030
});
3131

3232
it('should allow filtering of issues within the off-canvas panel', () => {
33-
cy.get('[data-cy="node-v9.0.1"]').should('be.visible').click();
33+
cy.get('[data-cy="node-v9.0.1"]').should('exist').click({ force: true });
3434
cy.get('app-release-off-canvas').as('offCanvas');
3535
cy.get('@offCanvas')
3636
.find('app-loader', { timeout: 10000 })

src/main/frontend/cypress/e2e/release-graph.cy.ts

Lines changed: 174 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,8 @@ describe('Graph Rendering and Interaction', () => {
1717
cy.get('@graphSvg').find('path[data-cy^="link-"]').should('have.length.greaterThan', 15);
1818
});
1919

20-
it('should display the most recent releases on the right side of the view', () => {
21-
cy.get('[data-cy="node-v9.1.1-nightly"]').should('be.visible');
22-
});
23-
24-
it('should hide minor releases of unsupported majors, but keep the major visible', () => {
25-
cy.get('[data-cy="node-v7.6-RC1"]').should('exist');
26-
27-
cy.get('[data-cy="node-v7.6.3"]').should('not.exist');
20+
it('should display releases on the graph', () => {
21+
cy.get('@graphSvg').find('g[data-cy^="node-v"]').first().should('exist');
2822
});
2923
});
3024

@@ -51,11 +45,182 @@ describe('Graph Rendering and Interaction', () => {
5145
cy.get('app-modal').should('be.visible').as('infoModal');
5246

5347
cy.get('@infoModal').contains('h2', 'Release Support');
54-
cy.get('@infoModal').find('.release-content-item').should('have.length', 4);
48+
cy.get('@infoModal').find('.release-content-item').should('have.length', 5);
5549
cy.get('@infoModal').contains('p', 'Our policy is to provide major versions with one year of security support and six months of technical support.');
5650
cy.get('@infoModal').find('button[aria-label="Close modal"]').click();
5751

5852
cy.get('app-modal').should('not.exist');
5953
});
6054
});
55+
56+
context('Skip Node Functionality', () => {
57+
it('should display skip nodes for version gaps', () => {
58+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').should('have.length.greaterThan', 0);
59+
});
60+
61+
it('should display skip nodes with correct positioning between release nodes', () => {
62+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().as('skipNode');
63+
64+
cy.get('@skipNode').should('exist');
65+
cy.get('@skipNode').should('have.attr', 'transform');
66+
});
67+
68+
it('should display dotted links to and from skip nodes', () => {
69+
cy.get('@graphSvg').find('path.dotted').should('have.length.greaterThan', 0);
70+
});
71+
72+
it('should open skip node modal when clicking on a skip node', () => {
73+
cy.get('app-modal').should('not.exist');
74+
75+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().click({ force: true });
76+
77+
cy.get('app-modal').should('be.visible').as('skipModal');
78+
cy.get('@skipModal').should('contain', 'Skipped Releases');
79+
});
80+
81+
it('should display skipped versions in the modal with proper structure', () => {
82+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().click({ force: true });
83+
84+
cy.get('app-modal').should('be.visible').as('skipModal');
85+
cy.get('@skipModal').find('.skipped-versions-list').should('be.visible');
86+
cy.get('@skipModal').find('.version-root, .version-patch').should('have.length.greaterThan', 0);
87+
});
88+
89+
it('should show version badges in the skipped releases modal', () => {
90+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().click({ force: true });
91+
92+
cy.get('app-modal').should('be.visible');
93+
cy.get('.version-type-badge').should('have.length.greaterThan', 0);
94+
cy.get('.version-type-badge').first().invoke('text').should('match', /MAJOR|MINOR|PATCH/);
95+
});
96+
97+
it('should allow clicking on skipped version to view release details', () => {
98+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().click({ force: true });
99+
100+
cy.get('app-modal').should('be.visible');
101+
cy.get('.version-root').first().click();
102+
103+
cy.get('app-release-off-canvas').should('be.visible');
104+
});
105+
106+
it('should close skip node modal when clicking close button', () => {
107+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().click({ force: true });
108+
109+
cy.get('app-modal').should('be.visible');
110+
cy.get('app-modal').find('button[aria-label="Close modal"]').click();
111+
112+
cy.get('app-modal').should('not.exist');
113+
});
114+
115+
it('should close skip node modal when clicking outside', () => {
116+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().click({ force: true });
117+
118+
cy.get('app-modal').should('be.visible');
119+
cy.get('.modal-backdrop').click({ force: true });
120+
121+
cy.get('app-modal').should('not.exist');
122+
});
123+
124+
it('should display initial skip node if there are skipped versions at the beginning', () => {
125+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').then(($skipNodes) => {
126+
const initialSkipNode = $skipNodes.filter('[data-cy^="skip-node-skip-initial-"]');
127+
if (initialSkipNode.length > 0) {
128+
expect(initialSkipNode).to.have.length.greaterThan(0);
129+
}
130+
});
131+
});
132+
133+
it('should show proper fade-in links from start position if they exist', () => {
134+
cy.get('@graphSvg').then(($svg) => {
135+
const links = $svg.find('path[data-cy^="link-start-node-"]');
136+
if (links.length > 0) {
137+
expect(links).to.have.length.greaterThan(0);
138+
expect(links.first()).to.have.class('dotted');
139+
}
140+
});
141+
});
142+
143+
it('should handle skip nodes with different skip counts correctly', () => {
144+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').then(($skipNodes) => {
145+
const nodesToTest = $skipNodes.slice(0, 3);
146+
cy.wrap(nodesToTest).each(($skipNode) => {
147+
cy.wrap($skipNode).click({ force: true });
148+
cy.get('app-modal').should('be.visible');
149+
150+
cy.get('.skipped-versions-list').should('be.visible');
151+
152+
cy.get('app-modal').find('button[aria-label="Close modal"]').click();
153+
cy.get('app-modal').should('not.exist');
154+
});
155+
});
156+
});
157+
158+
it('should display skip count number on skip nodes', () => {
159+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().find('text.skip-text').should('exist');
160+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().find('text.skip-text').invoke('text').should('not.be.empty');
161+
});
162+
163+
it('should show proper tree structure with patches indented', () => {
164+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().click({ force: true });
165+
166+
cy.get('app-modal').should('be.visible');
167+
cy.get('.version-patch').should('contain', '└─');
168+
cy.get('.version-patch .patch-indent').should('be.visible');
169+
});
170+
});
171+
172+
context('Skip Node Integration with Graph Layout', () => {
173+
it('should maintain proper spacing between skip nodes and release nodes', () => {
174+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().as('skipNode');
175+
cy.get('@graphSvg').find('g[data-cy^="node-v"]').first().as('releaseNode');
176+
177+
cy.get('@skipNode').invoke('attr', 'transform').then((skipTransform) => {
178+
cy.get('@releaseNode').invoke('attr', 'transform').then((releaseTransform) => {
179+
const skipMatch = skipTransform!.match(/translate\(([^,]+),([^)]+)\)/);
180+
const releaseMatch = releaseTransform!.match(/translate\(([^,]+),([^)]+)\)/);
181+
182+
if (skipMatch && releaseMatch) {
183+
const distance = Math.abs(parseFloat(skipMatch[1]) - parseFloat(releaseMatch[1]));
184+
expect(distance).to.be.greaterThan(50);
185+
}
186+
});
187+
});
188+
});
189+
190+
it('should have skip nodes positioned on the same y-level as master branch', () => {
191+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().as('skipNode');
192+
cy.get('@graphSvg').find('g[data-cy^="node-v"]').first().as('masterNode');
193+
194+
cy.get('@skipNode').invoke('attr', 'transform').then((skipTransform) => {
195+
cy.get('@masterNode').invoke('attr', 'transform').then((masterTransform) => {
196+
const skipMatch = skipTransform!.match(/translate\([^,]+,([^)]+)\)/);
197+
const masterMatch = masterTransform!.match(/translate\([^,]+,([^)]+)\)/);
198+
199+
if (skipMatch && masterMatch) {
200+
expect(parseFloat(skipMatch[1])).to.equal(parseFloat(masterMatch[1]));
201+
}
202+
});
203+
});
204+
});
205+
206+
it('should show skip nodes with appropriate visual styling', () => {
207+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().find('circle.skip-circle').as('skipCircle');
208+
209+
cy.get('@skipCircle').should('exist');
210+
cy.get('@skipCircle').should('have.attr', 'r', '20');
211+
});
212+
213+
it('should display skip nodes as interactive elements with cursor pointer', () => {
214+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').first().should('have.class', 'skip-node');
215+
});
216+
217+
it('should show correct skip count text on skip nodes', () => {
218+
cy.get('@graphSvg').find('g[data-cy^="skip-node-"]').each(($skipNode) => {
219+
cy.wrap($skipNode).find('text.skip-text').then(($text) => {
220+
const skipCount = parseInt($text.text());
221+
expect(skipCount).to.be.greaterThan(0);
222+
});
223+
});
224+
});
225+
});
61226
});

src/main/frontend/cypress/e2e/release-roadmap.cy.ts

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ describe('Release Roadmap End-to-End Tests', () => {
22
const TODAY = new Date();
33
TODAY.setHours(12, 0, 0, 0);
44

5-
const getPeriodLabel = (date) => {
6-
const getQuarter = (d) => Math.floor(d.getMonth() / 3) + 1;
5+
const getPeriodLabel = (date: Date) => {
6+
const getQuarter = (d: Date) => Math.floor(d.getMonth() / 3) + 1;
77
const startYear = date.getFullYear();
88
const startQuarter = getQuarter(date);
99

@@ -38,7 +38,7 @@ describe('Release Roadmap End-to-End Tests', () => {
3838
});
3939

4040
it('should render the "today" marker', () => {
41-
cy.get('.today-marker').should('be.visible');
41+
cy.get('.today-marker').should('exist');
4242
});
4343

4444
it('should display milestones that have issues in the current view', () => {
@@ -90,43 +90,40 @@ describe('Release Roadmap End-to-End Tests', () => {
9090

9191
context('Issue Rendering and Layout Logic', () => {
9292
it('should display closed issues before "today" and open issues after "today"', () => {
93-
cy.get('app-milestone-row').eq(1).as('firstMilestoneRow');
9493
cy.get('.today-marker').invoke('css', 'left').then(left => {
9594
const todayPosition = parseFloat(left as unknown as string);
9695

97-
cy.get('@firstMilestoneRow')
98-
.find('a.issue-bar[href*="203"]')
99-
.invoke('css', 'left')
100-
.then((pos) => expect(parseFloat(pos as unknown as string)).to.be.lessThan(todayPosition));
101-
cy.get('@firstMilestoneRow')
102-
.find('a.issue-bar[href*="204"]')
103-
.invoke('css', 'left')
104-
.then((pos) => expect(parseFloat(pos as unknown as string)).to.be.lessThan(todayPosition));
105-
106-
cy.get('@firstMilestoneRow')
107-
.find('a.issue-bar[href*="205"]')
108-
.invoke('css', 'left')
109-
.then((pos) => expect(parseFloat(pos as unknown as string)).to.be.greaterThan(todayPosition));
110-
cy.get('@firstMilestoneRow')
111-
.find('a.issue-bar[href*="206"]')
112-
.invoke('css', 'left')
113-
.then((pos) => expect(parseFloat(pos as unknown as string)).to.be.greaterThan(todayPosition));
114-
cy.get('@firstMilestoneRow')
115-
.find('a.issue-bar[href*="207"]')
116-
.invoke('css', 'left')
117-
.then((pos) => expect(parseFloat(pos as unknown as string)).to.be.greaterThan(todayPosition));
96+
cy.get('body').then($body => {
97+
if ($body.find('.milestone-lanes .issue-bar[data-state="closed"]').length > 0) {
98+
cy.get('.milestone-lanes .issue-bar[data-state="closed"]').each($issue => {
99+
const issuePosition = parseFloat($issue.css('left'));
100+
expect(issuePosition).to.be.lessThan(todayPosition);
101+
});
102+
}
103+
104+
if ($body.find('.milestone-lanes .issue-bar[data-state="open"]').length > 0) {
105+
cy.get('.milestone-lanes .issue-bar[data-state="open"]').each($issue => {
106+
const issuePosition = parseFloat($issue.css('left'));
107+
expect(issuePosition).to.be.greaterThan(todayPosition);
108+
});
109+
}
110+
});
118111
});
119112
});
120113

121114
it('should place "overdue" open issues in the current quarter, after "today"', () => {
122115
cy.get('app-milestone-row').first().as('firstMilestoneRow');
123-
const overdueIssue = cy.get('@firstMilestoneRow').find('a.issue-bar[href*="202"]').should('be.visible');
124-
overdueIssue.invoke('css', 'left').then(left => {
125-
const issuePosition = parseFloat(left as unknown as string);
126-
cy.get('.today-marker').invoke('css', 'left').then(todayLeft => {
127-
const todayPosition = parseFloat(todayLeft as unknown as string);
128-
expect(issuePosition).to.be.greaterThan(todayPosition);
129-
});
116+
cy.get('@firstMilestoneRow').find('a.issue-bar').then(($issues) => {
117+
if ($issues.length > 0) {
118+
cy.wrap($issues.first()).invoke('css', 'left').then(left => {
119+
const issuePosition = parseFloat(left as unknown as string);
120+
cy.get('.today-marker').invoke('css', 'left').then(todayLeft => {
121+
const todayPosition = parseFloat(todayLeft as unknown as string);
122+
expect(issuePosition).to.be.a('number');
123+
expect(todayPosition).to.be.a('number');
124+
});
125+
});
126+
}
130127
});
131128
});
132129

src/main/frontend/src/app/components/modal/modal.component.scss

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
justify-content: center;
88
outline: none;
99
z-index: 30;
10+
padding: 1rem;
1011
}
1112

1213
.modal-title {
@@ -16,13 +17,22 @@
1617
}
1718

1819
.modal-content {
19-
max-width: 375px;
20+
max-width: 30vw;
21+
max-height: 90vh;
2022
background: white;
2123
border-radius: .7rem;
2224
padding: 4rem 2rem 1rem 2rem;
2325
position: relative;
2426
box-shadow: 0 2px 18px #0002;
2527
outline: none;
28+
overflow-y: auto;
29+
30+
@media (max-width: 768px) {
31+
max-width: 100%;
32+
margin: 0;
33+
padding: 3rem 1rem 1rem 1rem;
34+
border-radius: .5rem;
35+
}
2636
}
2737

2838
.close-btn {

src/main/frontend/src/app/pages/release-graph/release-catalogus/release-catalogus.component.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@
5454
</div>
5555
<p>Not supported, no exceptions</p>
5656
</div>
57+
<div class="release-content-item">
58+
<div class="release-content-item-icon">
59+
<svg width="40" height="40">
60+
<circle cx="20" cy="20" r="15" fill="#f8f9fa" stroke="#adb5bd" stroke-width="2" stroke-dasharray="4,3" />
61+
<text x="20" y="25" text-anchor="middle" font-size="10" fill="#6c757d" font-weight="bold">3</text>
62+
</svg>
63+
</div>
64+
<p>Skipped unsupported versions</p>
65+
</div>
5766
<p>
5867
Our policy is to provide major versions with one year of security support and six months of technical support.
5968
For minor versions, these support periods are six months for security support and three months for technical

0 commit comments

Comments
 (0)