Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6b6af54
feat(calm-hub-ui): Add initial visualisation for patterns and their o…
YoofiTT96 Feb 25, 2026
d70a1a8
feat(calm-hub-ui): Add ability to search nodes
YoofiTT96 Feb 25, 2026
0c50ebd
feat(calm-hub-ui): Remove description from search terms. Fix lint issues
YoofiTT96 Feb 25, 2026
20bef80
feat(calm-hub-ui): Add type dropdown
YoofiTT96 Feb 25, 2026
ee3635f
feat(calm-hub-ui): Add decision modals
YoofiTT96 Feb 26, 2026
9a73ef8
feat(calm-hub-ui): Consolidate diagram section
YoofiTT96 Feb 26, 2026
0422f8a
feat(calm-hub-ui): Refactor DiagramSection to use Drawer and remove P…
YoofiTT96 Feb 27, 2026
e4e441e
feat(calm-hub-ui): Implement sidebar collapse functionality with togg…
YoofiTT96 Feb 27, 2026
2a0d45d
feat(calm-hub-ui): Update README
YoofiTT96 Feb 27, 2026
6919130
Merge branch 'main' into viz-patterns
rocketstack-matt Feb 27, 2026
9a4bca9
feat(calm-hub-ui): Refactor namespace handling and enhance e2e tests
YoofiTT96 Feb 27, 2026
0d5b8c8
Merge branch 'viz-patterns' of https://github.com/YoofiTT96/architect…
YoofiTT96 Feb 27, 2026
c7a70c2
feat(calm-hub-ui): Refactor Drawer pattern selection logic
YoofiTT96 Feb 27, 2026
70e52b4
feat(calm-hub-ui): Refactor conditionals
YoofiTT96 Feb 27, 2026
1d5f631
feat(calm-hub-ui): Extract similarities in Architecture and Pattern G…
YoofiTT96 Feb 27, 2026
b3661fc
Merge branch 'main' into viz-patterns
YoofiTT96 Feb 27, 2026
705f4a1
feat(calm-hub-ui): Import fixes and refactorings
YoofiTT96 Feb 27, 2026
3de5e93
Merge branch 'main' into viz-patterns
YoofiTT96 Feb 27, 2026
6c0f4f1
feat(calm-hub-ui): Update README
YoofiTT96 Feb 28, 2026
1a76a54
feat(calm-hub-ui): Add some color
YoofiTT96 Feb 28, 2026
8bbc2c5
feat(calm-hub-ui): Update README
YoofiTT96 Feb 28, 2026
a594588
feat(calm-hub-ui): Update README
YoofiTT96 Feb 28, 2026
87aa637
feat(calm-hub-ui): Update README
YoofiTT96 Feb 28, 2026
89ebe9b
Merge branch 'main' into viz-patterns
rocketstack-matt Mar 2, 2026
2f13ee0
Merge branch 'main' into viz-patterns
rocketstack-matt Mar 2, 2026
4b48736
feat(calm-hub-ui): Remove redundant id
YoofiTT96 Mar 2, 2026
fc0d496
Merge branch 'main' into viz-patterns
rocketstack-matt Mar 3, 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
135 changes: 81 additions & 54 deletions calm-hub-ui/README.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,114 @@
# Getting Started with Create React App
# 🏛️ CALM Hub UI

This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).

## Available Scripts
https://github.com/user-attachments/assets/931cca52-6dff-4895-8de4-88a291acd41a

In the project directory, you can run:

### `npm start`
Explore, visualize, and manage CALM architecture models through an interactive web interface. Features graph-based visualization, pattern decision exploration.

Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
## ✨ Features

The page will reload when you make changes.\
You may also see any lint errors in the console.
### 🎯 Interactive Architecture Visualization
- **Graph-Based Rendering**: ReactFlow-powered diagrams with automatic layout via Dagre
- **Smart Node Rendering**: Type-specific icons for Actors, Systems, Services, Databases, Networks, and more
- **Group Nodes**: Automatic container grouping for `deployed-in` and `composed-of` relationships
- **Floating Edges**: Bezier-curved edges with labels and badges that avoid node overlap
- **Pan, Zoom & Minimap**: Full interactive controls with fit-to-view

### `npm test`
### 🧩 Pattern Visualization with Decision Support
- **JSON Schema Patterns**: Render architecture patterns with automatic layout
- **Decision Points**: Extract and display `oneOf`/`anyOf` constraints as interactive decision selectors
- **Dynamic Filtering**: Select decision paths to see how the architecture changes
- **Decision Group Nodes**: Visual grouping highlights pattern variants

Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### 🌳 Hub Navigation
- **Tree-Based Browsing**: Collapsible sidebar to explore Namespaces, Architectures, Patterns, Flows, and ADRs
- **Diagram & JSON Views**: Toggle between interactive visualization and raw JSON with syntax highlighting
- **Version Selection**: Browse and switch between architecture revisions
- **Quick Visualize**: Jump from any document into the full Visualizer workspace

### `npm run start-cypress`
### 🔍 Search & Filtering
- **Node Search**: Find nodes by name, unique-id, or node-type
- **Type-Based Filtering**: Filter by node type via dropdown
- **Opacity Dimming**: Matching nodes at full opacity, non-matching dimmed to 15%
- **Edge Filtering**: Edges follow connected node visibility

End to End tests are written with cypress. Cypress can be run in headless and headed modes.
The above command runs in headed mode and allows the developer to locally run through the spec
files and observe the test runs. [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) queries and paradigm for testing feature
heavily in these tests as these help maintainability by organising tests around how the UI is used
and not how its implemented. [Cypress and its best practices](https://docs.cypress.io/app/core-concepts/best-practices) are also used in writing and updating
these tests.
### 📋 Metadata Panel
- **Flows Panel**: View business and data flows with step-by-step transitions
- **Controls Panel**: Hierarchical security and compliance controls
- **Flow Highlighting**: Click a transition to highlight the corresponding relationship in the graph
- **Control Deep-Linking**: Jump from a control to the affected node

You need to set an environment variable VITE_BASE_URL which should be the address where the vite server is being run
A default in .env.example has been added. This can also be set in CI to whatever the intended vite port should be
### 🛡️ Risk & Control Integration
- **Risk-Aware Borders**: Node border color indicates risk level (red, yellow, green)
- **Hover Details**: Tooltips show associated risks and mitigations
- **Control Badges**: Quick visibility into control coverage per node

#### Test Stubbing
The tests are all stubbed to return desired responses. These will need to be maintained in tandem with
calm-hub API. This section can be updated when a different and more reliable integration strategy is devised
### 📝 ADR Rendering
- **Rich Display**: Context, decision drivers, considered options, outcome, and links
- **Markdown Support**: Full markdown rendering within ADR sections
- **Revision Browsing**: Switch between ADR versions
- **Status Badges**: Visual indicators for approved, deprecated, and other statuses

### 🔎 Inspector Sidebar
- **Selection Details**: Click any node or edge to inspect its raw JSON
- **Drag & Drop Upload**: Load CALM JSON files directly into the visualizer

### `npm run build`
## 🚀 Getting Started

Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
### Prerequisites
- Node.js (see `.nvmrc` for the recommended version)
- A running instance of CALM Hub (the backend API)

The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
### Development

See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
```bash
npm install
npm start
```

### `npm run eject`
Open [http://localhost:3000](http://localhost:3000) to view the app in your browser. The page will reload when you make changes.

**Note: this is a one-way operation. Once you `eject`, you can't go back!**
### 🧪 Running Tests

If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
```bash
# Unit tests
npm test

Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
# End-to-end tests (headed mode)
npm run start-cypress
```

You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
E2E tests use [Cypress](https://docs.cypress.io/) with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) queries. All tests are stubbed against the CALM Hub API.

## Learn More
You need to set the environment variable `VITE_BASE_URL` to the address where the Vite dev server is running (see `.env.example` for defaults).

You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
### 📦 Production Build

To learn React, check out the [React documentation](https://reactjs.org/).
```bash
npm run build
```

### Code Splitting
Outputs an optimized production bundle to the `build` folder.

This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
## 🛠️ Tech Stack

### Analyzing the Bundle Size
| Category | Libraries |
|---|---|
| UI Framework | React 19, React Router, TypeScript |
| Visualization | ReactFlow, Dagre, D3 |
| Styling | TailwindCSS, DaisyUI |
| Code Display | Monaco Editor, react-json-view-lite |
| Auth | OpenID Connect (oidc-client) |
| Testing | Vitest, Cypress |
| Build | Vite |

This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
## 🤝 Getting Involved

### Making a Progressive Web App
Architecture as Code was developed as part of the [DevOps Automation Special Interest Group](https://devops.finos.org/) before graduating as a top level project in its own right. Our community Zoom meetups take place on the fourth Tuesday of every month, see [here](https://github.com/finos/architecture-as-code/issues?q=label%3Ameeting) for upcoming and previous meetings. For active contributors we have Office Hours every Thursday, see the [FINOS Event Calendar](http://calendar.finos.org) for meeting details.

This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
Have an idea or feedback? [Raise an issue](https://github.com/finos/architecture-as-code/issues/new/choose) in this repository.

### Advanced Configuration
---

This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)

### Deployment

This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)

### `npm run build` fails to minify

This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
**Contributing**: Issues and PRs welcome! 🎉
4 changes: 2 additions & 2 deletions calm-hub-ui/cypress/e2e/adrs.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const expectedNamespace = "finos"
const expectedNamespace = { name: "finos", description: "FINOS namespace" };
const expectedAdrId = 1;
const expectedAdrRevision = 2;

Expand All @@ -14,7 +14,7 @@ describe('ADR Tests', () => {

it("Displays ADR JSON successfully", () => {
cy.visit("/");
cy.findByText(expectedNamespace).click();
cy.findByText(expectedNamespace.name).click();
cy.findByText(/adrs/i).click();
cy.findByText(/1/i).click();
cy.findByText(expectedAdrRevision).click();
Expand Down
19 changes: 14 additions & 5 deletions calm-hub-ui/cypress/e2e/architectures.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const expectedNamespace = "finos"
const expectedNamespace = { name: "finos", description: "FINOS namespace" };
const expectedArchitectureId = 1;
const expectedArchitectureVersion = "1.0.0";

Expand All @@ -12,15 +12,24 @@ describe('Architecture Tests', () => {
});
})

it("Displays architecture JSON successfully", () => {
it("Displays architecture diagram by default", () => {
cy.visit("/");
cy.findByText(expectedNamespace).click();
cy.findByText(expectedNamespace.name).click();
cy.findByText(/architectures/i).click();
cy.findByText(/1/i).click();
cy.findByText(/1.0.0/i).click();

cy.findByText(/relationship descriptions/i).should("exist");
cy.findByText(/node descriptions/i).should("exist");
cy.get('canvas').should("exist");
cy.findByRole("tab", { name: /diagram/i }).should("exist");
cy.findByRole("tab", { name: /json/i }).should("exist");
})

it("Displays architecture JSON successfully", () => {
cy.visit("/");
cy.findByText(expectedNamespace.name).click();
cy.findByText(/architectures/i).click();
cy.findByText(/1/i).click();
cy.findByText(/1.0.0/i).click();

cy.findByRole("tab", { name: /json/i}).click();

Expand Down
4 changes: 2 additions & 2 deletions calm-hub-ui/cypress/e2e/flows.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const expectedNamespace = "finos"
const expectedNamespace = { name: "finos", description: "FINOS namespace" };
const expectedFlowId = 1;
const expectedFlowVersion = "1.0.0";

Expand All @@ -14,7 +14,7 @@ describe('Flow Tests', () => {

it("Displays flow JSON successfully", () => {
cy.visit("/");
cy.findByText(expectedNamespace).click();
cy.findByText(expectedNamespace.name).click();
cy.findByText(/flows/i).click();
cy.findByText(/1/i).click();
cy.findByText(/1.0.0/i).click();
Expand Down
18 changes: 15 additions & 3 deletions calm-hub-ui/cypress/e2e/home.cy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {namespaces, resourceTypes} from "../fixtures/constants.js";
import {namespaceInfos, namespaces, resourceTypes} from "../fixtures/constants.js";

describe('Home page tests', () => {
beforeEach(() => {
cy.intercept("/calm/namespaces", {"values": namespaces})
cy.intercept("/calm/namespaces", {"values": namespaceInfos})
})

it('Loads initial screen successfully', () => {
Expand All @@ -14,14 +14,26 @@ describe('Home page tests', () => {
resourceTypes.forEach(resourceType => { cy.findByText(resourceType).should("exist"); });
})

context("Sidebar collapse", () => {
it("Collapses and expands the sidebar", () => {
cy.visit('/');
cy.findByText("Explore").should("exist");

cy.findByLabelText("Collapse sidebar").click();
cy.findByText("Explore").should("not.exist");

cy.findByLabelText("Expand sidebar").click();
cy.findByText("Explore").should("exist");
})
})

context("Wide screen tests", () => {
it("Finds navigation items", () => {
cy.viewport('macbook-16')
cy.visit('/');
cy.findByRole("link", { name: "Hub" })
cy.findByRole("link", { name: "Visualizer" })
})

})

context("Collapsed screen tests", () => {
Expand Down
25 changes: 17 additions & 8 deletions calm-hub-ui/cypress/e2e/patterns.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const expectedNamespace = "finos"
const expectedNamespace = { name: "finos", description: "FINOS namespace" };
const expectedPatternId = 1;
const expectedPatternVersion = "1.0.0";

Expand All @@ -12,13 +12,27 @@ describe('Pattern Tests', () => {
});
})

it("Displays pattern JSON successfully", () => {
it("Displays pattern diagram by default", () => {
cy.visit("/");
cy.findByText(expectedNamespace).click();
cy.findByText(expectedNamespace.name).click();
cy.findByText(/patterns/i).click();
cy.findByText(/1/i).click();
cy.findByText(/1.0.0/i).click();

cy.findByRole("tab", { name: /diagram/i }).should("exist");
cy.findByRole("tab", { name: /json/i }).should("exist");
cy.get('canvas').should("exist");
})

it("Switches to JSON tab and displays pattern content", () => {
cy.visit("/");
cy.findByText(expectedNamespace.name).click();
cy.findByText(/patterns/i).click();
cy.findByText(/1/i).click();
cy.findByText(/1.0.0/i).click();

cy.findByRole("tab", { name: /json/i }).click();

cy.fixture('conference-signup-pattern').then(data => {
cy.contains(/\$schema/i).should("exist");
cy.contains(data.$schema).should("exist");
Expand All @@ -32,11 +46,6 @@ describe('Pattern Tests', () => {
cy.contains(/description/i).should("exist");
cy.contains(data.description).should("exist");

cy.contains(/minItems/i).should("exist");
cy.contains(data.properties.nodes.minItems).should("exist");

cy.contains(data.properties.nodes.minItems).should("exist");

cy.contains(/prefixItems/i).should("exist");
});
})
Expand Down
12 changes: 12 additions & 0 deletions calm-hub-ui/cypress/e2e/visualizer.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@ describe("Visualizer page tests", () => {
cy.findByText(/relationship descriptions/i).should("exist");
cy.findByText(/node descriptions/i).should("exist");
})

it("Displays pattern visualization on file upload", () => {
cy.viewport('macbook-16')
cy.visit("/");

cy.findByRole("link", { name: "Visualizer" }).click();

cy.fixture("conference-signup-pattern", null).as('pattern');
cy.get('input[type=file]').selectFile("@pattern", {force: true})

cy.get('canvas').should("exist");
})
})
9 changes: 8 additions & 1 deletion calm-hub-ui/cypress/fixtures/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
export const namespaces = ["finos","workshop","traderx","osff2025", "e2e"];
export const namespaceInfos = [
{ name: "finos", description: "FINOS namespace" },
{ name: "workshop", description: "Workshop namespace" },
{ name: "traderx", description: "TraderX namespace" },
{ name: "osff2025", description: "OSFF 2025 namespace" },
{ name: "e2e", description: "E2E namespace" },
];
export const namespaces = namespaceInfos.map(ns => ns.name);
export const resourceTypes = ['Architectures', 'Patterns', 'Flows', 'ADRs'];
Loading
Loading