From c659e6e6d38f63b56fe4b1386bd1195314d2677a Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:10:52 +0100 Subject: [PATCH 01/21] Add initial project files for MkDocs Tech Radar plugin --- docs/index.md | 3 +++ mkdocs.yml | 8 ++++++++ mkdocs_tech_radar/__init__.py | 7 +++++++ mkdocs_tech_radar/py.typed | 0 pyproject.toml | 23 +++++++++++++++++++++++ 5 files changed, 41 insertions(+) create mode 100644 docs/index.md create mode 100644 mkdocs.yml create mode 100644 mkdocs_tech_radar/__init__.py create mode 100644 mkdocs_tech_radar/py.typed create mode 100644 pyproject.toml diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..798ab9e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,3 @@ +# Test Site + +This is a test site to verify the tech-radar plugin installation. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..39f6681 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,8 @@ +site_name: Tech Radar Test +site_description: Testing the tech-radar plugin + +plugins: + - tech-radar + +nav: + - Home: index.md diff --git a/mkdocs_tech_radar/__init__.py b/mkdocs_tech_radar/__init__.py new file mode 100644 index 0000000..32559a7 --- /dev/null +++ b/mkdocs_tech_radar/__init__.py @@ -0,0 +1,7 @@ +""" +MkDocs Tech Radar Plugin + +A MkDocs plugin to generate a visual Tech Radar as part of your documentation site. +""" + +__version__ = "0.1.0" diff --git a/mkdocs_tech_radar/py.typed b/mkdocs_tech_radar/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8360a48 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "mkdocs-tech-radar" +version = "0.1.0" +description = "A MkDocs plugin to generate a visual Tech Radar." +authors = [ + { name = "thatmlopsguy", email = "your@email.com" } +] +readme = "README.md" +license = { file = "LICENSE" } +requires-python = ">=3.10" +dependencies = [ + "mkdocs>=1.4.0" +] + +[project.optional-dependencies] +dev = [ + "pytest", + "mkdocs", +] + + +[project.entry-points."mkdocs.plugins"] +tech-radar = "mkdocs_tech_radar.plugin:TechRadarPlugin" From 613f42cbd73d6a13e92ed54dbf887d5456261f07 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:19:31 +0100 Subject: [PATCH 02/21] Add .editorconfig and initial tech radar documentation --- .editorconfig | 14 ++++++++++++++ docs/tech-radar.md | 2 ++ mkdocs.yml | 8 +++++--- 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 .editorconfig create mode 100644 docs/tech-radar.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8fd90ba --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# https://editorconfig.org/ + +root = true + +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 + +[*.py] +max_line_length = 80 diff --git a/docs/tech-radar.md b/docs/tech-radar.md new file mode 100644 index 0000000..3731872 --- /dev/null +++ b/docs/tech-radar.md @@ -0,0 +1,2 @@ +# Tech Radar + diff --git a/mkdocs.yml b/mkdocs.yml index 39f6681..b00eda5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,8 +1,10 @@ site_name: Tech Radar Test site_description: Testing the tech-radar plugin -plugins: - - tech-radar - nav: - Home: index.md + - Tech Radar: tech-radar.md + +plugins: + - search + - tech-radar \ No newline at end of file From 9337f16bdb240046999a443a0a58567a33177467 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:01:28 +0100 Subject: [PATCH 03/21] feat: Implement TechRadarPlugin to generate a visual Tech Radar based on Zalando's implementation. - Create radar_inline.html template for inline embedding of the radar. - Add configuration options for the plugin in mkdocs.yml. - Include example JSON data file for technology entries. - Update README.md with detailed usage instructions and features. - Enhance tech-radar.md with an overview of the Tech Radar concept. - Add test for plugin import functionality. --- README.md | 143 +++++- docs/radars/2025-08-20.json | 283 +++++++++++ docs/tech-radar.md | 23 + mkdocs.yml | 25 +- mkdocs_tech_radar/plugin.py | 167 +++++++ mkdocs_tech_radar/templates/radar_inline.html | 456 ++++++++++++++++++ pyproject.toml | 15 +- tests/test_plugin.py | 5 + 8 files changed, 1112 insertions(+), 5 deletions(-) create mode 100644 docs/radars/2025-08-20.json create mode 100644 mkdocs_tech_radar/plugin.py create mode 100644 mkdocs_tech_radar/templates/radar_inline.html create mode 100644 tests/test_plugin.py diff --git a/README.md b/README.md index 6bbba5c..2de45bf 100644 --- a/README.md +++ b/README.md @@ -1 +1,142 @@ -# tech-radar \ No newline at end of file +# MkDocs Tech Radar Plugin + +A MkDocs plugin that generates an interactive Technology Radar based on the [Zalando Tech Radar](https://github.com/zalando/tech-radar) implementation. +This plugin helps engineering teams visualize and track their technology adoption, assessment, and decisions. + +## Features + +- 🎯 **Interactive visualization** with D3.js-powered radar chart +- 🎨 **Zalando Tech Radar design** - proven and widely adopted visual style +- 📊 **Four-ring assessment model** (Adopt, Trial, Assess, Hold) +- 🏗️ **Four-quadrant organization** (Languages & Frameworks, Tools, Platforms, Techniques) +- 🔗 **Clickable entries** with external links support +- 📱 **Responsive design** that works on all devices +- 🎭 **Movement indicators** (moved up ▲, moved down ▼, new ★, no change ⬤) +- 💡 **Hover tooltips** and interactive legend +- 📋 **Configurable** via JSON data files or plugin configuration + +## Installation + +Install the plugin using uv, pip, or your preferred Python package manager: + +```bash +# Using uv +uv pip install mkdocs-tech-radar + +# Using pip +pip install mkdocs-tech-radar +``` + +## Configuration + +Add the plugin to your `mkdocs.yml`: + +```yaml +plugins: + - tech-radar: + data_file: 'radars/2025-08-20.json' # Optional: path to JSON data file + output_file: 'tech-radar.html' # Optional: output file name (default) +``` + +## Usage + +### Method 1: JSON Data File + +Create a JSON file (e.g., `docs/radars/2025-08-20.json`) with your technology entries: + +```json +{ + "title": "Our Technology Radar", + "date": "2025-08-20", + "quadrants": [ + {"name": "Languages & Frameworks"}, + {"name": "Tools"}, + {"name": "Platforms"}, + {"name": "Techniques"} + ], + "rings": [ + {"name": "ADOPT", "color": "#5ba300", "description": "Technologies we have high confidence in"}, + {"name": "TRIAL", "color": "#009eb0", "description": "Worth pursuing with small risks"}, + {"name": "ASSESS", "color": "#c7ba00", "description": "Worth exploring with higher risks"}, + {"name": "HOLD", "color": "#e09b96", "description": "Proceed with caution or avoid"} + ], + "entries": [ + { + "label": "React", + "quadrant": 0, + "ring": 0, + "moved": 0, + "active": true, + "description": "A JavaScript library for building user interfaces", + "link": "https://reactjs.org/" + } + ] +} +``` + +### Method 2: Direct Configuration + +Configure entries directly in `mkdocs.yml`: + +```yaml +plugins: + - tech-radar: + title: "Our Technology Radar" + date: "2024-11" + entries: + - label: "React" + quadrant: 0 # 0=Languages&Frameworks, 1=Tools, 2=Platforms, 3=Techniques + ring: 0 # 0=Adopt, 1=Trial, 2=Assess, 3=Hold + moved: 0 # -1=moved out, 0=no change, 1=moved in, 2=new + active: true + link: "https://reactjs.org/" +``` + +## Data Structure + +### Entry Properties + +- **label** (string): Technology name +- **quadrant** (0-3): Position in radar + - 0: Languages & Frameworks + - 1: Tools + - 2: Platforms + - 3: Techniques +- **ring** (0-3): Assessment level + - 0: ADOPT + - 1: TRIAL + - 2: ASSESS + - 3: HOLD +- **moved** (integer): Movement indicator + - -1: Moved out (▼) + - 0: No change (⬤) + - 1: Moved in (▲) + - 2: New (★) +- **active** (boolean): Whether entry is clickable/interactive +- **link** (string, optional): External URL +- **description** (string, optional): Technology description + +### Customization + +You can customize the radar appearance by modifying the template or providing custom CSS. The radar uses the Zalando color scheme by default: + +- **ADOPT**: #5ba300 (green) +- **TRIAL**: #009eb0 (teal) +- **ASSESS**: #c7ba00 (yellow) +- **HOLD**: #e09b96 (red) + +## Example + +Check out the included example in the `docs/` directory: + +1. Build the documentation: `mkdocs build` +2. Serve locally: `mkdocs serve` +3. View the tech radar at: `http://127.0.0.1:8000/tech-radar.html` + +## Credits + +This plugin is based on the excellent [Zalando Tech Radar](https://github.com/zalando/tech-radar) implementation, which in turn is inspired by [ThoughtWorks Technology Radar](https://www.thoughtworks.com/radar). + +## License + +Apache License 2.0 - see LICENSE file for details. diff --git a/docs/radars/2025-08-20.json b/docs/radars/2025-08-20.json new file mode 100644 index 0000000..fc60f15 --- /dev/null +++ b/docs/radars/2025-08-20.json @@ -0,0 +1,283 @@ +{ + "title": "Our Technology Radar", + "date": "2025-08-20", + "quadrants": [ + {"name": "Languages & Frameworks"}, + {"name": "Tools"}, + {"name": "Platforms"}, + {"name": "Techniques"} + ], + "rings": [ + {"name": "ADOPT", "color": "#5ba300", "description": "Technologies we have high confidence in to serve our purpose, also in large scale. Technologies with a usage culture in our production environment, low risk and recommended to be widely used."}, + {"name": "TRIAL", "color": "#009eb0", "description": "Technologies that we have seen work with success in project work to solve a real problem; first serious usage experience that confirm benefits and can uncover limitations."}, + {"name": "ASSESS", "color": "#c7ba00", "description": "Technologies that are promising and have clear potential value-add for us; technologies worth to invest some research and prototyping efforts in to see if it has impact."}, + {"name": "HOLD", "color": "#e09b96", "description": "Technologies not recommended to be used for new projects. Technologies that we think are not (yet) worth to (further) invest in."} + ], + "entries": [ + { + "label": "React", + "quadrant": 0, + "ring": 0, + "moved": 0, + "active": true, + "description": "A JavaScript library for building user interfaces", + "link": "https://reactjs.org/" + }, + { + "label": "Vue.js", + "quadrant": 0, + "ring": 1, + "moved": 1, + "active": true, + "description": "The Progressive JavaScript Framework", + "link": "https://vuejs.org/" + }, + { + "label": "Angular", + "quadrant": 0, + "ring": 2, + "moved": 0, + "active": true, + "description": "Platform for building mobile and desktop web applications", + "link": "https://angular.io/" + }, + { + "label": "jQuery", + "quadrant": 0, + "ring": 3, + "moved": -1, + "active": true, + "description": "Fast, small, and feature-rich JavaScript library" + }, + { + "label": "TypeScript", + "quadrant": 0, + "ring": 0, + "moved": 0, + "active": true, + "description": "Typed superset of JavaScript that compiles to plain JavaScript", + "link": "https://www.typescriptlang.org/" + }, + { + "label": "Python", + "quadrant": 0, + "ring": 0, + "moved": 0, + "active": true, + "description": "Programming language that lets you work quickly and integrate systems", + "link": "https://www.python.org/" + }, + { + "label": "Rust", + "quadrant": 0, + "ring": 1, + "moved": 1, + "active": true, + "description": "A language empowering everyone to build reliable and efficient software", + "link": "https://www.rust-lang.org/" + }, + { + "label": "Go", + "quadrant": 0, + "ring": 1, + "moved": 0, + "active": true, + "description": "Open source programming language that makes it easy to build simple software", + "link": "https://golang.org/" + }, + { + "label": "Docker", + "quadrant": 1, + "ring": 0, + "moved": 0, + "active": true, + "description": "Platform for developing, shipping, and running applications using containers", + "link": "https://www.docker.com/" + }, + { + "label": "Kubernetes", + "quadrant": 1, + "ring": 0, + "moved": 0, + "active": true, + "description": "Open-source container orchestration system", + "link": "https://kubernetes.io/" + }, + { + "label": "Terraform", + "quadrant": 1, + "ring": 1, + "moved": 1, + "active": true, + "description": "Infrastructure as Code tool for building, changing, and versioning infrastructure", + "link": "https://www.terraform.io/" + }, + { + "label": "Jenkins", + "quadrant": 1, + "ring": 2, + "moved": 0, + "active": true, + "description": "Open source automation server for CI/CD", + "link": "https://www.jenkins.io/" + }, + { + "label": "GitLab CI", + "quadrant": 1, + "ring": 1, + "moved": 1, + "active": true, + "description": "Continuous Integration/Continuous Deployment platform", + "link": "https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/" + }, + { + "label": "Prometheus", + "quadrant": 1, + "ring": 1, + "moved": 0, + "active": true, + "description": "Open-source monitoring system with a dimensional data model", + "link": "https://prometheus.io/" + }, + { + "label": "Grafana", + "quadrant": 1, + "ring": 1, + "moved": 0, + "active": true, + "description": "Open source analytics and interactive visualization web application", + "link": "https://grafana.com/" + }, + { + "label": "AWS", + "quadrant": 2, + "ring": 0, + "moved": 0, + "active": true, + "description": "Amazon Web Services cloud computing platform", + "link": "https://aws.amazon.com/" + }, + { + "label": "Azure", + "quadrant": 2, + "ring": 1, + "moved": 0, + "active": true, + "description": "Microsoft's cloud computing platform", + "link": "https://azure.microsoft.com/" + }, + { + "label": "Google Cloud", + "quadrant": 2, + "ring": 1, + "moved": 0, + "active": true, + "description": "Google's suite of cloud computing services", + "link": "https://cloud.google.com/" + }, + { + "label": "Serverless", + "quadrant": 2, + "ring": 1, + "moved": 1, + "active": true, + "description": "Cloud computing execution model where cloud provider manages server allocation" + }, + { + "label": "Edge Computing", + "quadrant": 2, + "ring": 2, + "moved": 2, + "active": true, + "description": "Distributed computing paradigm that brings computation closer to data sources" + }, + { + "label": "Multi-cloud", + "quadrant": 2, + "ring": 2, + "moved": 0, + "active": true, + "description": "Use of multiple cloud computing services in a single architecture" + }, + { + "label": "Microservices", + "quadrant": 3, + "ring": 0, + "moved": 0, + "active": true, + "description": "Architectural approach for building applications as a collection of loosely coupled services" + }, + { + "label": "DevOps", + "quadrant": 3, + "ring": 0, + "moved": 0, + "active": true, + "description": "Set of practices that combines software development and IT operations" + }, + { + "label": "GitOps", + "quadrant": 3, + "ring": 1, + "moved": 1, + "active": true, + "description": "Operational framework that takes DevOps best practices for application development" + }, + { + "label": "Infrastructure as Code", + "quadrant": 3, + "ring": 0, + "moved": 0, + "active": true, + "description": "Practice of managing and provisioning computing infrastructure through code" + }, + { + "label": "Test-Driven Development", + "quadrant": 3, + "ring": 1, + "moved": 0, + "active": true, + "description": "Software development process relying on software requirements being converted to test cases" + }, + { + "label": "Event Sourcing", + "quadrant": 3, + "ring": 2, + "moved": 0, + "active": true, + "description": "Architectural pattern in which changes to application state are stored as a sequence of events" + }, + { + "label": "Domain-Driven Design", + "quadrant": 3, + "ring": 1, + "moved": 0, + "active": true, + "description": "Approach to software development that centers the development on programming a domain model" + }, + { + "label": "CQRS", + "quadrant": 3, + "ring": 2, + "moved": 0, + "active": true, + "description": "Command Query Responsibility Segregation pattern that separates reads and writes" + }, + { + "label": "Machine Learning", + "quadrant": 3, + "ring": 1, + "moved": 1, + "active": true, + "description": "Type of artificial intelligence that allows software applications to learn from data" + }, + { + "label": "Blockchain", + "quadrant": 3, + "ring": 3, + "moved": -1, + "active": true, + "description": "Distributed ledger technology that maintains a continuously growing list of records" + } + ] +} diff --git a/docs/tech-radar.md b/docs/tech-radar.md index 3731872..66e899e 100644 --- a/docs/tech-radar.md +++ b/docs/tech-radar.md @@ -1,2 +1,25 @@ # Tech Radar +Our technology radar helps us track the technologies we use and evaluate emerging trends in our development ecosystem. + + + +## About the Tech Radar + +The tech radar is a visual tool that categorizes technologies into four rings: + +- **ADOPT**: Technologies we have high confidence in and recommend for widespread use +- **TRIAL**: Technologies worth pursuing with known success cases but some risk +- **ASSESS**: Promising technologies worth exploring with higher risks +- **HOLD**: Technologies to proceed with caution or avoid for new projects + +Technologies are also organized into four quadrants: + +- **Languages & Frameworks**: Programming languages and development frameworks +- **Tools**: Development and operational tools +- **Platforms**: Infrastructure and platform technologies +- **Techniques**: Methods, processes, and architectural patterns + +The radar visualization is based on the [Zalando Tech Radar](https://github.com/zalando/tech-radar) implementation. + +More info [Thoughtworks Technology Radar](https://www.thoughtworks.com/radar). diff --git a/mkdocs.yml b/mkdocs.yml index b00eda5..0f9de10 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,6 +5,29 @@ nav: - Home: index.md - Tech Radar: tech-radar.md +theme: + name: "material" + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: black + accent: amber + toggle: + icon: material/toggle-switch + name: Switch to light mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: amber + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + plugins: - search - - tech-radar \ No newline at end of file + - tech-radar: + data_file: 'radars/2025-08-20.json' + output_file: 'tech-radar.html' + inline_mode: true + target_page: 'tech-radar.md' + diff --git a/mkdocs_tech_radar/plugin.py b/mkdocs_tech_radar/plugin.py new file mode 100644 index 0000000..4879ae9 --- /dev/null +++ b/mkdocs_tech_radar/plugin.py @@ -0,0 +1,167 @@ +import os +import json +from mkdocs.plugins import BasePlugin +from mkdocs.config import config_options +from jinja2 import Environment, FileSystemLoader + +class TechRadarPlugin(BasePlugin): + """MkDocs plugin to generate a visual Tech Radar based on Zalando's implementation.""" + + config_scheme = ( + ('title', config_options.Type(str, default='Technology Radar')), + ('date', config_options.Type(str, default='')), + ('quadrants', config_options.Type(list, default=[ + {'name': 'Languages & Frameworks'}, + {'name': 'Tools'}, + {'name': 'Platforms'}, + {'name': 'Techniques'} + ])), + ('rings', config_options.Type(list, default=[ + {'name': 'ADOPT', 'color': '#5ba300', 'description': 'We have high confidence in these technologies'}, + {'name': 'TRIAL', 'color': '#009eb0', 'description': 'Worth pursuing with small risks'}, + {'name': 'ASSESS', 'color': '#c7ba00', 'description': 'Worth exploring with higher risks'}, + {'name': 'HOLD', 'color': '#e09b96', 'description': 'Proceed with caution or avoid'} + ])), + ('entries', config_options.Type(list, default=[])), + ('data_file', config_options.Type(str, default='')), + ('output_file', config_options.Type(str, default='tech-radar.html')), + ('inline_mode', config_options.Type(bool, default=True)), + ('target_page', config_options.Type(str, default='tech-radar.md')), + ) + + def __init__(self): + super().__init__() + self.radar_content = None + + def on_config(self, config): + """Plugin initialization logic.""" + # Load entries from data file if specified + if self.config['data_file']: + data_file_path = os.path.join(config['docs_dir'], self.config['data_file']) + if os.path.exists(data_file_path): + try: + with open(data_file_path, 'r', encoding='utf-8') as f: + data = json.load(f) + if 'entries' in data: + self.config['entries'] = data['entries'] + # Update other config from file if present + for key in ['title', 'date', 'quadrants', 'rings']: + if key in data: + self.config[key] = data[key] + except Exception as e: + print(f"Warning: Could not load data file {data_file_path}: {e}") + + # Generate radar content for inline mode + if self.config['inline_mode']: + self._generate_radar_content() + + return config + + def _generate_radar_content(self): + """Generate the radar HTML content for inline embedding.""" + # Get the template directory + template_dir = os.path.join(os.path.dirname(__file__), 'templates') + + # Setup Jinja2 environment + env = Environment(loader=FileSystemLoader(template_dir)) + + try: + template = env.get_template('radar_inline.html') + except Exception as e: + print(f"Error loading template: {e}") + self.radar_content = f"

Error loading radar template: {e}

" + return + + # Prepare template context + context = { + 'title': self.config['title'], + 'date': self.config['date'], + 'quadrants': self.config['quadrants'], + 'rings': self.config['rings'], + 'entries': json.dumps(self.config['entries']) if self.config['entries'] else None, + } + + # Render the template + self.radar_content = template.render(context) + + def on_page_markdown(self, markdown, page, config, files): + """Process markdown content to inject tech radar.""" + print(f"Processing page: {page.file.src_path}, inline_mode: {self.config['inline_mode']}, target: {self.config['target_page']}") + + if not self.config['inline_mode']: + return markdown + + # Check if this is the target page for the tech radar + if page.file.src_path == self.config['target_page']: + print("Found target page, injecting radar content") + # Look for the radar placeholder or inject after a specific marker + radar_placeholder = '' + view_section = '## View the Tech Radar' + + if radar_placeholder in markdown: + # Replace placeholder with radar content + markdown = markdown.replace(radar_placeholder, self.radar_content or '') + print("Replaced placeholder with radar content") + elif view_section in markdown: + # Replace the "View the Tech Radar" section with the actual radar + lines = markdown.split('\n') + new_lines = [] + skip_section = False + + for line in lines: + if line.startswith('## View the Tech Radar'): + # Replace with radar visualization + new_lines.append('## Interactive Tech Radar') + new_lines.append('') + new_lines.append('
') + new_lines.append(self.radar_content or '') + new_lines.append('
') + new_lines.append('') + skip_section = True + print("Replacing 'View the Tech Radar' section") + elif line.startswith('## ') and skip_section: + # Found next section, stop skipping + skip_section = False + new_lines.append(line) + elif not skip_section: + new_lines.append(line) + + markdown = '\n'.join(new_lines) + else: + # Add radar at the end if no specific location found + markdown += f'\n\n## Interactive Tech Radar\n\n
\n{self.radar_content or ""}\n
\n' + print("Added radar at the end") + + return markdown + + def on_post_build(self, config): + """Generate the tech radar HTML file after the build (for non-inline mode).""" + if self.config['inline_mode']: + print("Tech Radar embedded inline in documentation") + return config + + # Original separate file generation logic + template_dir = os.path.join(os.path.dirname(__file__), 'templates') + env = Environment(loader=FileSystemLoader(template_dir)) + template = env.get_template('radar.html') + + context = { + 'title': self.config['title'], + 'date': self.config['date'], + 'quadrants': self.config['quadrants'], + 'rings': self.config['rings'], + 'entries': json.dumps(self.config['entries']) if self.config['entries'] else None, + } + + html_content = template.render(context) + output_path = os.path.join(config['site_dir'], self.config['output_file']) + os.makedirs(os.path.dirname(output_path), exist_ok=True) + + with open(output_path, 'w', encoding='utf-8') as f: + f.write(html_content) + + print(f"Tech Radar generated at: {self.config['output_file']}") + return config + + + diff --git a/mkdocs_tech_radar/templates/radar_inline.html b/mkdocs_tech_radar/templates/radar_inline.html new file mode 100644 index 0000000..175f5f4 --- /dev/null +++ b/mkdocs_tech_radar/templates/radar_inline.html @@ -0,0 +1,456 @@ +
+ + +
+

{{ title }}

+ {% if date %}
{{ date }}
{% endif %} +
+ +
+ + + + +
+ +
+ +
+
+ + + diff --git a/pyproject.toml b/pyproject.toml index 8360a48..5877889 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,10 +6,12 @@ authors = [ { name = "thatmlopsguy", email = "your@email.com" } ] readme = "README.md" -license = { file = "LICENSE" } +license = "Apache-2.0" requires-python = ">=3.10" dependencies = [ - "mkdocs>=1.4.0" + "mkdocs>=1.4.0", + "jinja2>=3.0.0", + "mkdocs-material>=9.6.17", ] [project.optional-dependencies] @@ -18,6 +20,13 @@ dev = [ "mkdocs", ] - [project.entry-points."mkdocs.plugins"] tech-radar = "mkdocs_tech_radar.plugin:TechRadarPlugin" + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +include = ["mkdocs_tech_radar*"] +exclude = ["site*", "tests*"] diff --git a/tests/test_plugin.py b/tests/test_plugin.py new file mode 100644 index 0000000..c3b298d --- /dev/null +++ b/tests/test_plugin.py @@ -0,0 +1,5 @@ +def test_plugin_loads(): + try: + from mkdocs_tech_radar.plugin import TechRadarPlugin + except ImportError: + assert False, "TechRadarPlugin could not be imported" From 3df510b453ea94fca9e44ba7bc2826e4a7ee6521 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:04:44 +0100 Subject: [PATCH 04/21] docs: Add Movement Indicators section to Tech Radar documentation --- docs/tech-radar.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/tech-radar.md b/docs/tech-radar.md index 66e899e..6143ead 100644 --- a/docs/tech-radar.md +++ b/docs/tech-radar.md @@ -20,6 +20,15 @@ Technologies are also organized into four quadrants: - **Platforms**: Infrastructure and platform technologies - **Techniques**: Methods, processes, and architectural patterns +## Movement Indicators + +Each technology on the radar includes a visual indicator showing its movement trend: + +- **▼ Moved out (-1)**: Technology has moved to an outer ring (less recommended) +- **⬤ No change (0)**: Technology position remains stable +- **▲ Moved in (1)**: Technology has moved to an inner ring (more recommended) +- **★ New (2)**: Technology is newly added to the radar + The radar visualization is based on the [Zalando Tech Radar](https://github.com/zalando/tech-radar) implementation. More info [Thoughtworks Technology Radar](https://www.thoughtworks.com/radar). From 71340a28d77193127ef0a79ede68e67fe758c949 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:06:52 +0100 Subject: [PATCH 05/21] refactor: Remove Movement Indicators section from Tech Radar documentation --- docs/tech-radar.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/tech-radar.md b/docs/tech-radar.md index 6143ead..d7aa93c 100644 --- a/docs/tech-radar.md +++ b/docs/tech-radar.md @@ -20,8 +20,6 @@ Technologies are also organized into four quadrants: - **Platforms**: Infrastructure and platform technologies - **Techniques**: Methods, processes, and architectural patterns -## Movement Indicators - Each technology on the radar includes a visual indicator showing its movement trend: - **▼ Moved out (-1)**: Technology has moved to an outer ring (less recommended) From c1cab94e31ed0eba233223fec7274d8b15ec99d8 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:25:36 +0100 Subject: [PATCH 06/21] feat: refactor Tech Radar Plugin to Support Multiple Radar Files and Improve UI - Modified mkdocs.yml to change plugin configuration, including title and radars folder. - Enhanced mkdocs_tech_radar/plugin.py to load multiple radar JSON files. - Updated radar_inline.html template to include dark mode support, improved styling, and a legend for movement indicators. - Enhanced JavaScript logic for rendering the radar and handling tooltip interactions. - Added functionality to generate technology lists by quadrant with clickable links for each technology. --- README.md | 165 ++++- docs/radars/2025-08-20.json | 4 +- docs/radars/2025-08-21.json | 282 ++++++++ docs/tech-radar.md | 7 - mkdocs.yml | 6 +- mkdocs_tech_radar/plugin.py | 181 +++-- mkdocs_tech_radar/templates/radar_inline.html | 657 ++++++++++++------ 7 files changed, 933 insertions(+), 369 deletions(-) create mode 100644 docs/radars/2025-08-21.json diff --git a/README.md b/README.md index 2de45bf..5cc665e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MkDocs Tech Radar Plugin A MkDocs plugin that generates an interactive Technology Radar based on the [Zalando Tech Radar](https://github.com/zalando/tech-radar) implementation. -This plugin helps engineering teams visualize and track their technology adoption, assessment, and decisions. +This plugin helps engineering teams visualize and track their technology adoption, assessment, and decisions across multiple radar versions. ## Features @@ -9,11 +9,11 @@ This plugin helps engineering teams visualize and track their technology adoptio - 🎨 **Zalando Tech Radar design** - proven and widely adopted visual style - 📊 **Four-ring assessment model** (Adopt, Trial, Assess, Hold) - 🏗️ **Four-quadrant organization** (Languages & Frameworks, Tools, Platforms, Techniques) -- 🔗 **Clickable entries** with external links support -- 📱 **Responsive design** that works on all devices - 🎭 **Movement indicators** (moved up ▲, moved down ▼, new ★, no change ⬤) - 💡 **Hover tooltips** and interactive legend -- 📋 **Configurable** via JSON data files or plugin configuration +- ️ **Multiple radar versions** - supports a folder of radar files with dropdown selector +- 🌙 **Dark mode support** - automatically adapts to MkDocs Material theme +- 📋 **Inline embedding** - radar is embedded directly in your documentation pages ## Installation @@ -34,19 +34,35 @@ Add the plugin to your `mkdocs.yml`: ```yaml plugins: - tech-radar: - data_file: 'radars/2025-08-20.json' # Optional: path to JSON data file - output_file: 'tech-radar.html' # Optional: output file name (default) + radars_folder: 'radars' # Optional: folder containing radar JSON files (default: 'radars') + target_page: 'tech-radar.md' # Optional: page to embed radar in (default: 'tech-radar.md') + title_name: 'Our Tech Radar' # Required: title for the radar (default: 'Technology Radar') ``` ## Usage -### Method 1: JSON Data File +### Creating Multiple Radar Files -Create a JSON file (e.g., `docs/radars/2025-08-20.json`) with your technology entries: +Create multiple JSON files in a `docs/radars/` folder for different radar versions: + +```text +docs/ + radars/ + 2025-08-20.json + 2025-08-21.json + 2025-09-01.json +``` + +The plugin will: + +- Automatically load all JSON files from the `radars_folder` +- Display a dropdown selector for switching between radar versions +- Default to the newest version (based on filename sorting) + +Example radar file (`docs/radars/2025-08-20.json`): ```json { - "title": "Our Technology Radar", "date": "2025-08-20", "quadrants": [ {"name": "Languages & Frameworks"}, @@ -74,28 +90,77 @@ Create a JSON file (e.g., `docs/radars/2025-08-20.json`) with your technology en } ``` -### Method 2: Direct Configuration +### Adding Radar to Your Documentation -Configure entries directly in `mkdocs.yml`: +Add the radar placeholder in your target markdown page: -```yaml -plugins: - - tech-radar: - title: "Our Technology Radar" - date: "2024-11" - entries: - - label: "React" - quadrant: 0 # 0=Languages&Frameworks, 1=Tools, 2=Platforms, 3=Techniques - ring: 0 # 0=Adopt, 1=Trial, 2=Assess, 3=Hold - moved: 0 # -1=moved out, 0=no change, 1=moved in, 2=new - active: true - link: "https://reactjs.org/" +```markdown +# Tech Radar + +Our technology radar helps us track the technologies we use and evaluate emerging trends. + + + +Additional content can go here after the radar. ``` -## Data Structure +The `` placeholder will be replaced with the interactive radar visualization. + +### Title Configuration + +- **Required setting**: The radar title is configured exclusively through the `title_name` option in mkdocs.yml +- **No JSON titles**: The `title` field is no longer required in radar JSON files and will be ignored if present +- **Consistent branding**: All radar versions display the same title as configured in mkdocs.yml + +### Multiple Radar Versions + +The plugin provides these features for managing multiple radar versions: + +#### Version Selection + +- **Dropdown menu**: Appears automatically when multiple radar files are detected +- **Date-based sorting**: Files are sorted by filename (assuming YYYY-MM-DD format) +- **Auto-selection**: Newest version is selected by default +- **Dynamic switching**: Users can switch between radar versions without page reload + +#### File Naming Convention + +Use date-based naming for optimal sorting: + +- `2025-08-20.json` - August 20, 2025 version +- `2025-09-01.json` - September 1, 2025 version +- `2025-12-15.json` - December 15, 2025 version + +#### Recommended Workflow + +1. Create a new radar file for each release/quarter +2. Copy the previous version as a starting point +3. Update entries with new technologies and movement indicators +4. The plugin automatically detects and makes it available + +## Configuration Options + +| Option | Type | Default | Description | +|-----------------|--------|-------------------|--------------------------------------------------| +| `radars_folder` | string | `'radars'` | Folder containing radar JSON files | +| `target_page` | string | `'tech-radar.md'` | Page to embed the radar in | +| `title_name` | string | `'Technology Radar'` | Title for the radar (configured in mkdocs.yml) | + +## Radar File Structure + +Each radar JSON file must contain these required fields: + +### Required Fields + +- **date** (string): Radar date (e.g., "2025-08-20") +- **quadrants** (array): Four quadrant definitions +- **rings** (array): Four ring definitions with colors +- **entries** (array): Technology entries ### Entry Properties +Each entry in the `entries` array must have: + - **label** (string): Technology name - **quadrant** (0-3): Position in radar - 0: Languages & Frameworks @@ -116,22 +181,64 @@ plugins: - **link** (string, optional): External URL - **description** (string, optional): Technology description -### Customization +### Example Complete Radar File -You can customize the radar appearance by modifying the template or providing custom CSS. The radar uses the Zalando color scheme by default: +```json +{ + "date": "2025-08-20", + "quadrants": [ + {"name": "Languages & Frameworks"}, + {"name": "Tools"}, + {"name": "Platforms"}, + {"name": "Techniques"} + ], + "rings": [ + {"name": "ADOPT", "color": "#5ba300", "description": "Technologies we have high confidence in"}, + {"name": "TRIAL", "color": "#009eb0", "description": "Worth pursuing with small risks"}, + {"name": "ASSESS", "color": "#c7ba00", "description": "Worth exploring with higher risks"}, + {"name": "HOLD", "color": "#e09b96", "description": "Proceed with caution or avoid"} + ], + "entries": [ + { + "label": "React", + "quadrant": 0, + "ring": 0, + "moved": 0, + "active": true, + "description": "A JavaScript library for building user interfaces", + "link": "https://reactjs.org/" + }, + { + "label": "Vue.js", + "quadrant": 0, + "ring": 1, + "moved": 1, + "active": true, + "description": "Progressive JavaScript framework", + "link": "https://vuejs.org/" + } + ] +} +``` + +## Customization + +You can customize the radar appearance by modifying the radar files. The radar uses the Zalando color scheme by default: - **ADOPT**: #5ba300 (green) - **TRIAL**: #009eb0 (teal) - **ASSESS**: #c7ba00 (yellow) - **HOLD**: #e09b96 (red) +You can change these colors by modifying the `rings` array in your radar JSON files. + ## Example Check out the included example in the `docs/` directory: -1. Build the documentation: `mkdocs build` -2. Serve locally: `mkdocs serve` -3. View the tech radar at: `http://127.0.0.1:8000/tech-radar.html` +1. Build the documentation: `uv run mkdocs build` +2. Serve locally: `uv run mkdocs serve` +3. View the tech radar at: `http://127.0.0.1:8000/tech-radar/` ## Credits diff --git a/docs/radars/2025-08-20.json b/docs/radars/2025-08-20.json index fc60f15..b086d09 100644 --- a/docs/radars/2025-08-20.json +++ b/docs/radars/2025-08-20.json @@ -1,5 +1,4 @@ { - "title": "Our Technology Radar", "date": "2025-08-20", "quadrants": [ {"name": "Languages & Frameworks"}, @@ -221,7 +220,8 @@ "ring": 1, "moved": 1, "active": true, - "description": "Operational framework that takes DevOps best practices for application development" + "description": "Operational framework that takes DevOps best practices for application development", + "link": "https://opengitops.dev/" }, { "label": "Infrastructure as Code", diff --git a/docs/radars/2025-08-21.json b/docs/radars/2025-08-21.json new file mode 100644 index 0000000..53c22c0 --- /dev/null +++ b/docs/radars/2025-08-21.json @@ -0,0 +1,282 @@ +{ + "date": "2025-08-21", + "quadrants": [ + {"name": "Languages & Frameworks"}, + {"name": "Tools"}, + {"name": "Platforms"}, + {"name": "Techniques"} + ], + "rings": [ + {"name": "ADOPT", "color": "#5ba300", "description": "Technologies we have high confidence in to serve our purpose, also in large scale. Technologies with a usage culture in our production environment, low risk and recommended to be widely used."}, + {"name": "TRIAL", "color": "#009eb0", "description": "Technologies that we have seen work with success in project work to solve a real problem; first serious usage experience that confirm benefits and can uncover limitations."}, + {"name": "ASSESS", "color": "#c7ba00", "description": "Technologies that are promising and have clear potential value-add for us; technologies worth to invest some research and prototyping efforts in to see if it has impact."}, + {"name": "HOLD", "color": "#e09b96", "description": "Technologies not recommended to be used for new projects. Technologies that we think are not (yet) worth to (further) invest in."} + ], + "entries": [ + { + "label": "React", + "quadrant": 0, + "ring": 0, + "moved": 0, + "active": true, + "description": "A JavaScript library for building user interfaces", + "link": "https://reactjs.org/" + }, + { + "label": "Vue.js", + "quadrant": 0, + "ring": 1, + "moved": 1, + "active": true, + "description": "The Progressive JavaScript Framework", + "link": "https://vuejs.org/" + }, + { + "label": "Angular", + "quadrant": 0, + "ring": 2, + "moved": 0, + "active": true, + "description": "Platform for building mobile and desktop web applications", + "link": "https://angular.io/" + }, + { + "label": "jQuery", + "quadrant": 0, + "ring": 3, + "moved": -1, + "active": true, + "description": "Fast, small, and feature-rich JavaScript library" + }, + { + "label": "TypeScript", + "quadrant": 0, + "ring": 0, + "moved": 0, + "active": true, + "description": "Typed superset of JavaScript that compiles to plain JavaScript", + "link": "https://www.typescriptlang.org/" + }, + { + "label": "Python", + "quadrant": 0, + "ring": 0, + "moved": 0, + "active": true, + "description": "Programming language that lets you work quickly and integrate systems", + "link": "https://www.python.org/" + }, + { + "label": "Rust", + "quadrant": 0, + "ring": 1, + "moved": 1, + "active": true, + "description": "A language empowering everyone to build reliable and efficient software", + "link": "https://www.rust-lang.org/" + }, + { + "label": "Go", + "quadrant": 0, + "ring": 1, + "moved": 0, + "active": true, + "description": "Open source programming language that makes it easy to build simple software", + "link": "https://golang.org/" + }, + { + "label": "Docker", + "quadrant": 1, + "ring": 0, + "moved": 0, + "active": true, + "description": "Platform for developing, shipping, and running applications using containers", + "link": "https://www.docker.com/" + }, + { + "label": "Kubernetes", + "quadrant": 1, + "ring": 0, + "moved": 0, + "active": true, + "description": "Open-source container orchestration system", + "link": "https://kubernetes.io/" + }, + { + "label": "Terraform", + "quadrant": 1, + "ring": 1, + "moved": 1, + "active": true, + "description": "Infrastructure as Code tool for building, changing, and versioning infrastructure", + "link": "https://www.terraform.io/" + }, + { + "label": "Jenkins", + "quadrant": 1, + "ring": 2, + "moved": 0, + "active": true, + "description": "Open source automation server for CI/CD", + "link": "https://www.jenkins.io/" + }, + { + "label": "GitLab CI", + "quadrant": 1, + "ring": 1, + "moved": 1, + "active": true, + "description": "Continuous Integration/Continuous Deployment platform", + "link": "https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/" + }, + { + "label": "Prometheus", + "quadrant": 1, + "ring": 1, + "moved": 0, + "active": true, + "description": "Open-source monitoring system with a dimensional data model", + "link": "https://prometheus.io/" + }, + { + "label": "Grafana", + "quadrant": 1, + "ring": 1, + "moved": 0, + "active": true, + "description": "Open source analytics and interactive visualization web application", + "link": "https://grafana.com/" + }, + { + "label": "AWS", + "quadrant": 2, + "ring": 0, + "moved": 0, + "active": true, + "description": "Amazon Web Services cloud computing platform", + "link": "https://aws.amazon.com/" + }, + { + "label": "Azure", + "quadrant": 2, + "ring": 1, + "moved": 0, + "active": true, + "description": "Microsoft's cloud computing platform", + "link": "https://azure.microsoft.com/" + }, + { + "label": "Google Cloud", + "quadrant": 2, + "ring": 1, + "moved": 0, + "active": true, + "description": "Google's suite of cloud computing services", + "link": "https://cloud.google.com/" + }, + { + "label": "Serverless", + "quadrant": 2, + "ring": 1, + "moved": 1, + "active": true, + "description": "Cloud computing execution model where cloud provider manages server allocation" + }, + { + "label": "Edge Computing", + "quadrant": 2, + "ring": 2, + "moved": 2, + "active": true, + "description": "Distributed computing paradigm that brings computation closer to data sources" + }, + { + "label": "Multi-cloud", + "quadrant": 2, + "ring": 2, + "moved": 0, + "active": true, + "description": "Use of multiple cloud computing services in a single architecture" + }, + { + "label": "Microservices", + "quadrant": 3, + "ring": 0, + "moved": 0, + "active": true, + "description": "Architectural approach for building applications as a collection of loosely coupled services" + }, + { + "label": "DevOps", + "quadrant": 3, + "ring": 0, + "moved": 0, + "active": true, + "description": "Set of practices that combines software development and IT operations" + }, + { + "label": "GitOps", + "quadrant": 3, + "ring": 1, + "moved": 1, + "active": true, + "description": "Operational framework that takes DevOps best practices for application development" + }, + { + "label": "Infrastructure as Code", + "quadrant": 3, + "ring": 0, + "moved": 0, + "active": true, + "description": "Practice of managing and provisioning computing infrastructure through code" + }, + { + "label": "Test-Driven Development", + "quadrant": 3, + "ring": 1, + "moved": 0, + "active": true, + "description": "Software development process relying on software requirements being converted to test cases" + }, + { + "label": "Event Sourcing", + "quadrant": 3, + "ring": 2, + "moved": 0, + "active": true, + "description": "Architectural pattern in which changes to application state are stored as a sequence of events" + }, + { + "label": "Domain-Driven Design", + "quadrant": 3, + "ring": 1, + "moved": 0, + "active": true, + "description": "Approach to software development that centers the development on programming a domain model" + }, + { + "label": "CQRS", + "quadrant": 3, + "ring": 2, + "moved": 0, + "active": true, + "description": "Command Query Responsibility Segregation pattern that separates reads and writes" + }, + { + "label": "Machine Learning", + "quadrant": 3, + "ring": 1, + "moved": 1, + "active": true, + "description": "Type of artificial intelligence that allows software applications to learn from data" + }, + { + "label": "Blockchain", + "quadrant": 3, + "ring": 3, + "moved": -1, + "active": true, + "description": "Distributed ledger technology that maintains a continuously growing list of records" + } + ] +} diff --git a/docs/tech-radar.md b/docs/tech-radar.md index d7aa93c..66e899e 100644 --- a/docs/tech-radar.md +++ b/docs/tech-radar.md @@ -20,13 +20,6 @@ Technologies are also organized into four quadrants: - **Platforms**: Infrastructure and platform technologies - **Techniques**: Methods, processes, and architectural patterns -Each technology on the radar includes a visual indicator showing its movement trend: - -- **▼ Moved out (-1)**: Technology has moved to an outer ring (less recommended) -- **⬤ No change (0)**: Technology position remains stable -- **▲ Moved in (1)**: Technology has moved to an inner ring (more recommended) -- **★ New (2)**: Technology is newly added to the radar - The radar visualization is based on the [Zalando Tech Radar](https://github.com/zalando/tech-radar) implementation. More info [Thoughtworks Technology Radar](https://www.thoughtworks.com/radar). diff --git a/mkdocs.yml b/mkdocs.yml index 0f9de10..a9061b8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,8 +26,8 @@ theme: plugins: - search - tech-radar: - data_file: 'radars/2025-08-20.json' - output_file: 'tech-radar.html' - inline_mode: true + title_name: 'Our Custom Mkdocs Technology Radar' + radars_folder: 'radars' target_page: 'tech-radar.md' + diff --git a/mkdocs_tech_radar/plugin.py b/mkdocs_tech_radar/plugin.py index 4879ae9..2725899 100644 --- a/mkdocs_tech_radar/plugin.py +++ b/mkdocs_tech_radar/plugin.py @@ -5,60 +5,87 @@ from jinja2 import Environment, FileSystemLoader class TechRadarPlugin(BasePlugin): - """MkDocs plugin to generate a visual Tech Radar based on Zalando's implementation.""" + """MkDocs plugin to generate a visual Tech Radar from multiple radar files.""" config_scheme = ( - ('title', config_options.Type(str, default='Technology Radar')), - ('date', config_options.Type(str, default='')), - ('quadrants', config_options.Type(list, default=[ - {'name': 'Languages & Frameworks'}, - {'name': 'Tools'}, - {'name': 'Platforms'}, - {'name': 'Techniques'} - ])), - ('rings', config_options.Type(list, default=[ - {'name': 'ADOPT', 'color': '#5ba300', 'description': 'We have high confidence in these technologies'}, - {'name': 'TRIAL', 'color': '#009eb0', 'description': 'Worth pursuing with small risks'}, - {'name': 'ASSESS', 'color': '#c7ba00', 'description': 'Worth exploring with higher risks'}, - {'name': 'HOLD', 'color': '#e09b96', 'description': 'Proceed with caution or avoid'} - ])), - ('entries', config_options.Type(list, default=[])), - ('data_file', config_options.Type(str, default='')), - ('output_file', config_options.Type(str, default='tech-radar.html')), - ('inline_mode', config_options.Type(bool, default=True)), + ('radars_folder', config_options.Type(str, default='radars')), ('target_page', config_options.Type(str, default='tech-radar.md')), + ('title_name', config_options.Type(str, default='Technology Radar')), ) def __init__(self): super().__init__() self.radar_content = None + self.radar_files = [] + self.default_radar = None def on_config(self, config): """Plugin initialization logic.""" - # Load entries from data file if specified - if self.config['data_file']: - data_file_path = os.path.join(config['docs_dir'], self.config['data_file']) - if os.path.exists(data_file_path): - try: - with open(data_file_path, 'r', encoding='utf-8') as f: - data = json.load(f) - if 'entries' in data: - self.config['entries'] = data['entries'] - # Update other config from file if present - for key in ['title', 'date', 'quadrants', 'rings']: - if key in data: - self.config[key] = data[key] - except Exception as e: - print(f"Warning: Could not load data file {data_file_path}: {e}") - - # Generate radar content for inline mode - if self.config['inline_mode']: - self._generate_radar_content() + # Load all radar files from the radars folder + self._load_radar_files(config) + + # Generate radar content for inline embedding + self._generate_radar_content() return config + def _load_radar_files(self, config): + """Load all radar JSON files from the radars folder.""" + radars_path = os.path.join(config['docs_dir'], self.config['radars_folder']) + + if not os.path.exists(radars_path): + raise ValueError(f"Radars folder not found at {radars_path}. Please create the folder and add radar JSON files.") + + # Find all JSON files in the radars folder + json_files = [] + for file in os.listdir(radars_path): + if file.endswith('.json'): + json_files.append(file) + + if not json_files: + raise ValueError(f"No JSON files found in radars folder {radars_path}. Please add at least one radar JSON file.") + + # Load and parse each radar file + radar_data = [] + for file in json_files: + file_path = os.path.join(radars_path, file) + try: + with open(file_path, 'r', encoding='utf-8') as f: + data = json.load(f) + # Validate required fields (title is no longer required since we use title_name from config) + required_fields = ['date', 'quadrants', 'rings', 'entries'] + for field in required_fields: + if field not in data: + raise ValueError(f"Missing required field '{field}' in {file}") + + # Extract filename without extension as the key + key = os.path.splitext(file)[0] + radar_data.append({ + 'key': key, + 'filename': file, + 'data': data + }) + except Exception as e: + print(f"Error loading radar file {file_path}: {e}") + raise + + # Sort by key (assuming date format YYYY-MM-DD) - newest first + radar_data.sort(key=lambda x: x['key'], reverse=True) + + self.radar_files = radar_data + + # Set default radar (newest one) + if radar_data: + self.default_radar = radar_data[0]['key'] + + print(f"Loaded {len(radar_data)} radar files. Default: {self.default_radar}") + def _generate_radar_content(self): """Generate the radar HTML content for inline embedding.""" + if not self.radar_files: + self.radar_content = "

No radar files found. Please add radar JSON files to the radars folder.

" + return + # Get the template directory template_dir = os.path.join(os.path.dirname(__file__), 'templates') @@ -72,13 +99,22 @@ def _generate_radar_content(self): self.radar_content = f"

Error loading radar template: {e}

" return + # Get default radar data + default_data = self.radar_files[0]['data'] + + # Use title from configuration only + title = self.config['title_name'] + # Prepare template context context = { - 'title': self.config['title'], - 'date': self.config['date'], - 'quadrants': self.config['quadrants'], - 'rings': self.config['rings'], - 'entries': json.dumps(self.config['entries']) if self.config['entries'] else None, + 'title': title, + 'date': default_data['date'], + 'quadrants': default_data['quadrants'], + 'rings': default_data['rings'], + 'entries': json.dumps(default_data['entries']), + 'radar_files': self.radar_files, + 'default_radar': self.default_radar, + 'radar_files_json': json.dumps(self.radar_files), } # Render the template @@ -86,47 +122,18 @@ def _generate_radar_content(self): def on_page_markdown(self, markdown, page, config, files): """Process markdown content to inject tech radar.""" - print(f"Processing page: {page.file.src_path}, inline_mode: {self.config['inline_mode']}, target: {self.config['target_page']}") - - if not self.config['inline_mode']: - return markdown + print(f"Processing page: {page.file.src_path}, target: {self.config['target_page']}") # Check if this is the target page for the tech radar if page.file.src_path == self.config['target_page']: print("Found target page, injecting radar content") # Look for the radar placeholder or inject after a specific marker radar_placeholder = '' - view_section = '## View the Tech Radar' if radar_placeholder in markdown: # Replace placeholder with radar content markdown = markdown.replace(radar_placeholder, self.radar_content or '') print("Replaced placeholder with radar content") - elif view_section in markdown: - # Replace the "View the Tech Radar" section with the actual radar - lines = markdown.split('\n') - new_lines = [] - skip_section = False - - for line in lines: - if line.startswith('## View the Tech Radar'): - # Replace with radar visualization - new_lines.append('## Interactive Tech Radar') - new_lines.append('') - new_lines.append('
') - new_lines.append(self.radar_content or '') - new_lines.append('
') - new_lines.append('') - skip_section = True - print("Replacing 'View the Tech Radar' section") - elif line.startswith('## ') and skip_section: - # Found next section, stop skipping - skip_section = False - new_lines.append(line) - elif not skip_section: - new_lines.append(line) - - markdown = '\n'.join(new_lines) else: # Add radar at the end if no specific location found markdown += f'\n\n## Interactive Tech Radar\n\n
\n{self.radar_content or ""}\n
\n' @@ -135,32 +142,8 @@ def on_page_markdown(self, markdown, page, config, files): return markdown def on_post_build(self, config): - """Generate the tech radar HTML file after the build (for non-inline mode).""" - if self.config['inline_mode']: - print("Tech Radar embedded inline in documentation") - return config - - # Original separate file generation logic - template_dir = os.path.join(os.path.dirname(__file__), 'templates') - env = Environment(loader=FileSystemLoader(template_dir)) - template = env.get_template('radar.html') - - context = { - 'title': self.config['title'], - 'date': self.config['date'], - 'quadrants': self.config['quadrants'], - 'rings': self.config['rings'], - 'entries': json.dumps(self.config['entries']) if self.config['entries'] else None, - } - - html_content = template.render(context) - output_path = os.path.join(config['site_dir'], self.config['output_file']) - os.makedirs(os.path.dirname(output_path), exist_ok=True) - - with open(output_path, 'w', encoding='utf-8') as f: - f.write(html_content) - - print(f"Tech Radar generated at: {self.config['output_file']}") + """Post-build processing.""" + print("Tech Radar embedded inline in documentation") return config diff --git a/mkdocs_tech_radar/templates/radar_inline.html b/mkdocs_tech_radar/templates/radar_inline.html index 175f5f4..2273514 100644 --- a/mkdocs_tech_radar/templates/radar_inline.html +++ b/mkdocs_tech_radar/templates/radar_inline.html @@ -1,5 +1,37 @@

{{ title }}

- {% if date %}
{{ date }}
{% endif %}
+ {% if radar_files and radar_files|length > 1 %} +
+ + +
+ {% endif %} +
+
+
+ + Moved out +
+
+ + No change +
+
+ + Moved in +
+
+ + New +
+
@@ -141,15 +275,57 @@

{{ title }}

From 1b88fd45586d634f5c9a67c1d9a370f5fa4cc209 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:49:26 +0100 Subject: [PATCH 07/21] feat: update Tech Radar configuration and improve plugin functionality --- .gitignore | 4 +- .pre-commit-config.yaml | 24 ++++++++++ docs/radars/2025-08-20.json | 2 +- docs/radars/2025-08-21.json | 2 +- mkdocs.yml | 2 +- mkdocs_tech_radar/plugin.py | 81 +++++++++++++++++--------------- pyproject.toml | 94 ++++++++++++++++++++++++++++++++++++- 7 files changed, 163 insertions(+), 46 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.gitignore b/.gitignore index cb0f8dc..5934afe 100644 --- a/.gitignore +++ b/.gitignore @@ -182,9 +182,9 @@ cython_debug/ .abstra/ # Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, +# and can be added to the global gitignore or merged into this file. However, if you prefer, # you could uncomment the following to ignore the entire vscode folder # .vscode/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..03689e4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-merge-conflict + - id: check-added-large-files + - id: trailing-whitespace + args: ["--markdown-linebreak-ext=md"] + - id: detect-private-key + - repo: https://github.com/commitizen-tools/commitizen + rev: v4.8.3 + hooks: + - id: commitizen + stages: [commit-msg] + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.9 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format diff --git a/docs/radars/2025-08-20.json b/docs/radars/2025-08-20.json index b086d09..645386f 100644 --- a/docs/radars/2025-08-20.json +++ b/docs/radars/2025-08-20.json @@ -1,7 +1,7 @@ { "date": "2025-08-20", "quadrants": [ - {"name": "Languages & Frameworks"}, + {"name": "Frameworks"}, {"name": "Tools"}, {"name": "Platforms"}, {"name": "Techniques"} diff --git a/docs/radars/2025-08-21.json b/docs/radars/2025-08-21.json index 53c22c0..ec4d3ca 100644 --- a/docs/radars/2025-08-21.json +++ b/docs/radars/2025-08-21.json @@ -1,7 +1,7 @@ { "date": "2025-08-21", "quadrants": [ - {"name": "Languages & Frameworks"}, + {"name": "Frameworks"}, {"name": "Tools"}, {"name": "Platforms"}, {"name": "Techniques"} diff --git a/mkdocs.yml b/mkdocs.yml index a9061b8..897dd1d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,7 +26,7 @@ theme: plugins: - search - tech-radar: - title_name: 'Our Custom Mkdocs Technology Radar' + title_name: '' radars_folder: 'radars' target_page: 'tech-radar.md' diff --git a/mkdocs_tech_radar/plugin.py b/mkdocs_tech_radar/plugin.py index 2725899..fb5d8fb 100644 --- a/mkdocs_tech_radar/plugin.py +++ b/mkdocs_tech_radar/plugin.py @@ -1,16 +1,18 @@ -import os import json -from mkdocs.plugins import BasePlugin -from mkdocs.config import config_options +import os + from jinja2 import Environment, FileSystemLoader +from mkdocs.config import config_options +from mkdocs.plugins import BasePlugin + class TechRadarPlugin(BasePlugin): """MkDocs plugin to generate a visual Tech Radar from multiple radar files.""" config_scheme = ( - ('radars_folder', config_options.Type(str, default='radars')), - ('target_page', config_options.Type(str, default='tech-radar.md')), - ('title_name', config_options.Type(str, default='Technology Radar')), + ("radars_folder", config_options.Type(str, default="radars")), + ("target_page", config_options.Type(str, default="tech-radar.md")), + ("title_name", config_options.Type(str, default="Technology Radar")), ) def __init__(self): @@ -31,52 +33,54 @@ def on_config(self, config): def _load_radar_files(self, config): """Load all radar JSON files from the radars folder.""" - radars_path = os.path.join(config['docs_dir'], self.config['radars_folder']) + radars_path = os.path.join(config["docs_dir"], self.config["radars_folder"]) if not os.path.exists(radars_path): - raise ValueError(f"Radars folder not found at {radars_path}. Please create the folder and add radar JSON files.") + raise ValueError( + f"Radars folder not found at {radars_path}. Please create the folder and add radar JSON files." + ) # Find all JSON files in the radars folder json_files = [] for file in os.listdir(radars_path): - if file.endswith('.json'): + if file.endswith(".json"): json_files.append(file) if not json_files: - raise ValueError(f"No JSON files found in radars folder {radars_path}. Please add at least one radar JSON file.") + raise ValueError( + f"No JSON files found in radars folder {radars_path}. Please add at least one radar JSON file." + ) # Load and parse each radar file radar_data = [] for file in json_files: file_path = os.path.join(radars_path, file) try: - with open(file_path, 'r', encoding='utf-8') as f: + with open(file_path, encoding="utf-8") as f: data = json.load(f) # Validate required fields (title is no longer required since we use title_name from config) - required_fields = ['date', 'quadrants', 'rings', 'entries'] + required_fields = ["date", "quadrants", "rings", "entries"] for field in required_fields: if field not in data: - raise ValueError(f"Missing required field '{field}' in {file}") + raise ValueError( + f"Missing required field '{field}' in {file}" + ) # Extract filename without extension as the key key = os.path.splitext(file)[0] - radar_data.append({ - 'key': key, - 'filename': file, - 'data': data - }) + radar_data.append({"key": key, "filename": file, "data": data}) except Exception as e: print(f"Error loading radar file {file_path}: {e}") raise # Sort by key (assuming date format YYYY-MM-DD) - newest first - radar_data.sort(key=lambda x: x['key'], reverse=True) + radar_data.sort(key=lambda x: x["key"], reverse=True) self.radar_files = radar_data # Set default radar (newest one) if radar_data: - self.default_radar = radar_data[0]['key'] + self.default_radar = radar_data[0]["key"] print(f"Loaded {len(radar_data)} radar files. Default: {self.default_radar}") @@ -87,34 +91,34 @@ def _generate_radar_content(self): return # Get the template directory - template_dir = os.path.join(os.path.dirname(__file__), 'templates') + template_dir = os.path.join(os.path.dirname(__file__), "templates") # Setup Jinja2 environment env = Environment(loader=FileSystemLoader(template_dir)) try: - template = env.get_template('radar_inline.html') + template = env.get_template("radar_inline.html") except Exception as e: print(f"Error loading template: {e}") self.radar_content = f"

Error loading radar template: {e}

" return # Get default radar data - default_data = self.radar_files[0]['data'] + default_data = self.radar_files[0]["data"] # Use title from configuration only - title = self.config['title_name'] + title = self.config["title_name"] # Prepare template context context = { - 'title': title, - 'date': default_data['date'], - 'quadrants': default_data['quadrants'], - 'rings': default_data['rings'], - 'entries': json.dumps(default_data['entries']), - 'radar_files': self.radar_files, - 'default_radar': self.default_radar, - 'radar_files_json': json.dumps(self.radar_files), + "title": title, + "date": default_data["date"], + "quadrants": default_data["quadrants"], + "rings": default_data["rings"], + "entries": json.dumps(default_data["entries"]), + "radar_files": self.radar_files, + "default_radar": self.default_radar, + "radar_files_json": json.dumps(self.radar_files), } # Render the template @@ -122,17 +126,19 @@ def _generate_radar_content(self): def on_page_markdown(self, markdown, page, config, files): """Process markdown content to inject tech radar.""" - print(f"Processing page: {page.file.src_path}, target: {self.config['target_page']}") + print( + f"Processing page: {page.file.src_path}, target: {self.config['target_page']}" + ) # Check if this is the target page for the tech radar - if page.file.src_path == self.config['target_page']: + if page.file.src_path == self.config["target_page"]: print("Found target page, injecting radar content") # Look for the radar placeholder or inject after a specific marker - radar_placeholder = '' + radar_placeholder = "" if radar_placeholder in markdown: # Replace placeholder with radar content - markdown = markdown.replace(radar_placeholder, self.radar_content or '') + markdown = markdown.replace(radar_placeholder, self.radar_content or "") print("Replaced placeholder with radar content") else: # Add radar at the end if no specific location found @@ -145,6 +151,3 @@ def on_post_build(self, config): """Post-build processing.""" print("Tech Radar embedded inline in documentation") return config - - - diff --git a/pyproject.toml b/pyproject.toml index 5877889..12e2be5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,13 +11,11 @@ requires-python = ">=3.10" dependencies = [ "mkdocs>=1.4.0", "jinja2>=3.0.0", - "mkdocs-material>=9.6.17", ] [project.optional-dependencies] dev = [ "pytest", - "mkdocs", ] [project.entry-points."mkdocs.plugins"] @@ -30,3 +28,95 @@ build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] include = ["mkdocs_tech_radar*"] exclude = ["site*", "tests*"] + +[dependency-groups] +dev = [ + "prek>=0.1.1", + "ruff>=0.12.9", +] + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.10 +target-version = "py310" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = [ + "E4", + "E7", + "E9", + "F", + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "PIE", # flake8-pie + "SIM", # flake8-simplify + "RET", # flake8-return + "PTH", # flake8-use-pathlib +] + +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +docstring-code-format = false + +# Set the line length limit used for formatting and linting. +docstring-code-line-length = "dynamic" From 49a5b2bbb1a0d5127fa6e176a47e352459df871a Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:50:54 +0100 Subject: [PATCH 08/21] feat: add commitizen as a development dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 12e2be5..b32f17b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ exclude = ["site*", "tests*"] [dependency-groups] dev = [ + "commitizen>=4.8.3", "prek>=0.1.1", "ruff>=0.12.9", ] From 0504d915c52ab5292adb11239ef2935f7a543609 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:10:19 +0100 Subject: [PATCH 09/21] refactor: improve README and plugin code structure; enhance test for plugin loading --- README.md | 27 +++++++++--------------- mkdocs_tech_radar/plugin.py | 21 +++++++++---------- pyproject.toml | 42 +++++++++++++++++++++++++++++++++++++ tests/test_plugin.py | 9 ++++++-- 4 files changed, 69 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 5cc665e..f86bd73 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,11 @@ This plugin helps engineering teams visualize and track their technology adoptio ## Features - 🎯 **Interactive visualization** with D3.js-powered radar chart -- 🎨 **Zalando Tech Radar design** - proven and widely adopted visual style - 📊 **Four-ring assessment model** (Adopt, Trial, Assess, Hold) -- 🏗️ **Four-quadrant organization** (Languages & Frameworks, Tools, Platforms, Techniques) +- 🏗️ **Four-quadrant organization** (Frameworks, Tools, Platforms, Techniques) - 🎭 **Movement indicators** (moved up ▲, moved down ▼, new ★, no change ⬤) - 💡 **Hover tooltips** and interactive legend -- ️ **Multiple radar versions** - supports a folder of radar files with dropdown selector +- ️🎨 **Multiple radar versions** - supports a folder of radar files with dropdown selector - 🌙 **Dark mode support** - automatically adapts to MkDocs Material theme - 📋 **Inline embedding** - radar is embedded directly in your documentation pages @@ -34,9 +33,9 @@ Add the plugin to your `mkdocs.yml`: ```yaml plugins: - tech-radar: - radars_folder: 'radars' # Optional: folder containing radar JSON files (default: 'radars') - target_page: 'tech-radar.md' # Optional: page to embed radar in (default: 'tech-radar.md') - title_name: 'Our Tech Radar' # Required: title for the radar (default: 'Technology Radar') + title_name: 'Our Tech Radar' # Optional: title for the radar (default: 'Technology Radar') + radars_folder: 'radars' # Optional: folder containing radar JSON files (default: 'radars') + target_page: 'tech-radar.md' # Optional: page to embed radar in (default: 'tech-radar.md') ``` ## Usage @@ -106,12 +105,6 @@ Additional content can go here after the radar. The `` placeholder will be replaced with the interactive radar visualization. -### Title Configuration - -- **Required setting**: The radar title is configured exclusively through the `title_name` option in mkdocs.yml -- **No JSON titles**: The `title` field is no longer required in radar JSON files and will be ignored if present -- **Consistent branding**: All radar versions display the same title as configured in mkdocs.yml - ### Multiple Radar Versions The plugin provides these features for managing multiple radar versions: @@ -140,11 +133,11 @@ Use date-based naming for optimal sorting: ## Configuration Options -| Option | Type | Default | Description | -|-----------------|--------|-------------------|--------------------------------------------------| -| `radars_folder` | string | `'radars'` | Folder containing radar JSON files | -| `target_page` | string | `'tech-radar.md'` | Page to embed the radar in | -| `title_name` | string | `'Technology Radar'` | Title for the radar (configured in mkdocs.yml) | +| Option | Type | Default | Description | +|-----------------|--------|----------------------|------------------------------------| +| `title_name` | string | `'Technology Radar'` | Title for the radar | +| `target_page` | string | `'tech-radar.md'` | Page to embed the radar in | +| `radars_folder` | string | `'radars'` | Folder containing radar JSON files | ## Radar File Structure diff --git a/mkdocs_tech_radar/plugin.py b/mkdocs_tech_radar/plugin.py index fb5d8fb..3dcd67e 100644 --- a/mkdocs_tech_radar/plugin.py +++ b/mkdocs_tech_radar/plugin.py @@ -1,5 +1,5 @@ import json -import os +from pathlib import Path from jinja2 import Environment, FileSystemLoader from mkdocs.config import config_options @@ -32,19 +32,18 @@ def on_config(self, config): return config def _load_radar_files(self, config): - """Load all radar JSON files from the radars folder.""" - radars_path = os.path.join(config["docs_dir"], self.config["radars_folder"]) + radars_path = Path(config["docs_dir"]) / self.config["radars_folder"] - if not os.path.exists(radars_path): + if not Path(radars_path).exists(): raise ValueError( f"Radars folder not found at {radars_path}. Please create the folder and add radar JSON files." ) # Find all JSON files in the radars folder json_files = [] - for file in os.listdir(radars_path): - if file.endswith(".json"): - json_files.append(file) + for file_path in Path(radars_path).iterdir(): + if file_path.is_file() and file_path.suffix == ".json": + json_files.append(file_path.name) if not json_files: raise ValueError( @@ -54,9 +53,9 @@ def _load_radar_files(self, config): # Load and parse each radar file radar_data = [] for file in json_files: - file_path = os.path.join(radars_path, file) + file_path = Path(radars_path) / file try: - with open(file_path, encoding="utf-8") as f: + with Path.open(file_path, encoding="utf-8") as f: data = json.load(f) # Validate required fields (title is no longer required since we use title_name from config) required_fields = ["date", "quadrants", "rings", "entries"] @@ -67,7 +66,7 @@ def _load_radar_files(self, config): ) # Extract filename without extension as the key - key = os.path.splitext(file)[0] + key = Path(file).stem radar_data.append({"key": key, "filename": file, "data": data}) except Exception as e: print(f"Error loading radar file {file_path}: {e}") @@ -91,7 +90,7 @@ def _generate_radar_content(self): return # Get the template directory - template_dir = os.path.join(os.path.dirname(__file__), "templates") + template_dir = Path(__file__).parent / "templates" # Setup Jinja2 environment env = Environment(loader=FileSystemLoader(template_dir)) diff --git a/pyproject.toml b/pyproject.toml index b32f17b..e23d5b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -121,3 +121,45 @@ docstring-code-format = false # Set the line length limit used for formatting and linting. docstring-code-line-length = "dynamic" + +[tool.pytest.ini_options] +# Test discovery +testpaths = ["tests"] +python_files = ["test_*.py", "*_test.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] + +# Output options +addopts = [ + "-v", # verbose output + "--tb=short", # shorter traceback format + "--strict-markers", # strict marker usage + "--strict-config", # strict configuration +] + +# Markers for organizing tests +markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "integration: marks tests as integration tests", + "unit: marks tests as unit tests", +] + +# Minimum version requirement +minversion = "6.0" + +# Paths to ignore during test collection +norecursedirs = [ + ".*", + "build", + "dist", + "*.egg", + "venv", + ".venv", + "node_modules", +] + +# Filter warnings +filterwarnings = [ + "ignore::UserWarning", + "ignore::DeprecationWarning", +] diff --git a/tests/test_plugin.py b/tests/test_plugin.py index c3b298d..69337fe 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,5 +1,10 @@ def test_plugin_loads(): try: from mkdocs_tech_radar.plugin import TechRadarPlugin - except ImportError: - assert False, "TechRadarPlugin could not be imported" + + # Verify the plugin class exists and can be instantiated + assert TechRadarPlugin is not None + plugin = TechRadarPlugin() + assert plugin is not None + except ImportError as e: + raise AssertionError("TechRadarPlugin could not be imported") from e From 0b3e73b2fef8aac2eb56c4dc706bcb3ebfb0322a Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:07:24 +0100 Subject: [PATCH 10/21] docs: update index.md to clarify mkdocs configuration limitations --- docs/index.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/index.md b/docs/index.md index 798ab9e..abfc817 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,9 @@ # Test Site This is a test site to verify the tech-radar plugin installation. + + + + From f8d8aed4fd1897af93ee5da919b5b563bdafbdbb Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:08:08 +0100 Subject: [PATCH 11/21] feat: enhance radar legend with filtering functionality and improve item styling --- mkdocs_tech_radar/templates/radar_inline.html | 129 ++++++++++++++++-- 1 file changed, 119 insertions(+), 10 deletions(-) diff --git a/mkdocs_tech_radar/templates/radar_inline.html b/mkdocs_tech_radar/templates/radar_inline.html index 2273514..0900e86 100644 --- a/mkdocs_tech_radar/templates/radar_inline.html +++ b/mkdocs_tech_radar/templates/radar_inline.html @@ -54,13 +54,14 @@ transform: translateX(-50%); background: var(--radar-bg); border: 1px solid var(--radar-border); - border-radius: 6px; - padding: 8px 12px; + border-radius: 8px; + padding: 8px 16px; display: flex; - gap: 15px; - font-size: 12px; + gap: 12px; + font-size: 11px; color: var(--text-primary); box-shadow: 0 2px 4px rgba(0,0,0,0.1); + white-space: nowrap; } [data-md-color-scheme="slate"] .radar-legend-inline { @@ -70,11 +71,39 @@ .legend-item-inline { display: flex; align-items: center; - gap: 3px; + gap: 4px; + cursor: pointer; + padding: 6px 8px; + border-radius: 4px; + transition: background-color 0.2s ease; + white-space: nowrap; + } + + .legend-item-inline:hover { + background-color: rgba(128, 128, 128, 0.1); + } + + .legend-item-inline.active { + background-color: rgba(0, 123, 204, 0.2); + border: 1px solid rgba(0, 123, 204, 0.5); } .legend-icon-inline { - font-size: 14px; + font-size: 12px; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + min-width: 12px; + } + + .entry.highlighted { + stroke-width: 4px !important; + filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.8)); + } + + .entry.dimmed { + opacity: 0.3; } .quadrant text { @@ -248,19 +277,19 @@

{{ title }}

-
+
Moved out
-
+
No change
-
+
Moved in
-
+
New
@@ -313,8 +342,15 @@

{{ title }}

config = allRadarData[radarKey]; config.title = originalTitle; // Restore the title from mkdocs.yml currentRadar = radarKey; + + // Reset filter state when switching radars + currentFilter = null; + const legendItems = document.querySelectorAll('.legend-item-inline'); + legendItems.forEach(li => li.classList.remove('active')); + renderRadar(); generateTechLists(); + setupLegendFiltering(); } } @@ -573,6 +609,78 @@

{{ title }}

console.log("Tech Radar rendered with", config.entries.length, "entries"); } + // Legend filtering functionality + let currentFilter = null; + + function setupLegendFiltering() { + const legendItems = document.querySelectorAll('.legend-item-inline'); + + // Remove existing event listeners by cloning and replacing nodes + legendItems.forEach(item => { + const newItem = item.cloneNode(true); + item.parentNode.replaceChild(newItem, item); + }); + + // Get fresh references to the legend items + const newLegendItems = document.querySelectorAll('.legend-item-inline'); + + newLegendItems.forEach(item => { + item.addEventListener('click', function() { + const movement = parseInt(this.getAttribute('data-movement')); + + // Toggle filter if clicking the same item + if (currentFilter === movement) { + currentFilter = null; + newLegendItems.forEach(li => li.classList.remove('active')); + highlightEntries(null); + } else { + currentFilter = movement; + newLegendItems.forEach(li => li.classList.remove('active')); + this.classList.add('active'); + highlightEntries(movement); + } + }); + }); + } + + function highlightEntries(filterMovement) { + const entries = svg.selectAll('.entry'); + + if (filterMovement === null) { + // Remove all highlighting + entries.classed('highlighted', false); + entries.classed('dimmed', false); + // Reset stroke to original ring color + entries.each(function(d) { + const entry = d3.select(this); + const ring = config.rings[d.ring]; + const shape = entry.select('polygon, circle'); + shape.attr('stroke', '#fff'); + }); + } else { + // Apply highlighting based on filter + entries.each(function(d) { + const entry = d3.select(this); + const ring = config.rings[d.ring]; + const shape = entry.select('polygon, circle'); + + if (d.moved === filterMovement) { + entry.classed('highlighted', true); + entry.classed('dimmed', false); + // Set stroke to ring color with enhanced thickness + shape.attr('stroke', ring.color); + shape.attr('stroke-width', 4); + } else { + entry.classed('highlighted', false); + entry.classed('dimmed', true); + // Reset to normal stroke + shape.attr('stroke', '#fff'); + shape.attr('stroke-width', 2); + } + }); + } + } + // Generate technology lists by quadrant function generateTechLists() { const techListsContainer = document.getElementById('tech-lists'); @@ -649,6 +757,7 @@

{{ title }}

// Initial render renderRadar(); generateTechLists(); + setupLegendFiltering(); console.log("Tech Radar initialized with", config.entries.length, "entries"); })(); From 8d23631600f23a8a7fafe22297aef9458c984678 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:03:22 +0100 Subject: [PATCH 12/21] feat: enhance tooltip functionality and improve styling in radar visualization --- docs/tech-radar.md | 2 +- mkdocs.yml | 38 ++--- mkdocs_tech_radar/templates/radar_inline.html | 135 +++++++++++------- pyproject.toml | 4 + 4 files changed, 112 insertions(+), 67 deletions(-) diff --git a/docs/tech-radar.md b/docs/tech-radar.md index 66e899e..df540c0 100644 --- a/docs/tech-radar.md +++ b/docs/tech-radar.md @@ -1,6 +1,6 @@ # Tech Radar -Our technology radar helps us track the technologies we use and evaluate emerging trends in our development ecosystem. +The technology radar helps us track the technologies we use and evaluate emerging trends in our development ecosystem. diff --git a/mkdocs.yml b/mkdocs.yml index 897dd1d..c8c9e03 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,27 +1,31 @@ site_name: Tech Radar Test site_description: Testing the tech-radar plugin +site_url: "https://thatmlopsguy.github.io/mkdocs-tech-radar/" +repo_url: "https://github.com/thatmlopsguy/mkdocs-tech-radar" +repo_name: "mkdocs-tech-radar" +copyright: Copyright © 2025 That MLops Guy nav: - Home: index.md - Tech Radar: tech-radar.md -theme: - name: "material" - palette: - - media: "(prefers-color-scheme: light)" - scheme: default - primary: black - accent: amber - toggle: - icon: material/toggle-switch - name: Switch to light mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: black - accent: amber - toggle: - icon: material/toggle-switch-off-outline - name: Switch to dark mode +# theme: +# name: "material" +# palette: +# - media: "(prefers-color-scheme: light)" +# scheme: default +# primary: black +# accent: amber +# toggle: +# icon: material/toggle-switch +# name: Switch to light mode +# - media: "(prefers-color-scheme: dark)" +# scheme: slate +# primary: black +# accent: amber +# toggle: +# icon: material/toggle-switch-off-outline +# name: Switch to dark mode plugins: - search diff --git a/mkdocs_tech_radar/templates/radar_inline.html b/mkdocs_tech_radar/templates/radar_inline.html index 0900e86..109c78f 100644 --- a/mkdocs_tech_radar/templates/radar_inline.html +++ b/mkdocs_tech_radar/templates/radar_inline.html @@ -39,6 +39,12 @@ margin: 0 auto; } + /* Ensure tooltip container for default theme compatibility */ + #tech-radar { + position: relative; + isolation: isolate; + } + .radar-svg { width: 100%; height: auto; @@ -140,16 +146,65 @@ .tooltip { position: absolute; - background: var(--tooltip-bg); - color: var(--tooltip-text); + background: #000; + color: #fff; padding: 8px 12px; border-radius: 4px; font-family: Arial, sans-serif; font-size: 12px; pointer-events: none; - z-index: 1000; + z-index: 999999; + max-width: 250px; + word-wrap: break-word; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + border: 1px solid #333; + display: none; + white-space: nowrap; + } + + /* Tooltip styles */ + .radar-tooltip { + position: absolute; + background: rgba(0, 0, 0, 0.9); + color: white; + padding: 8px 12px; + border-radius: 4px; + font-size: 12px; + line-height: 1.4; + pointer-events: none; + z-index: 10000; + box-shadow: 0 2px 8px rgba(0,0,0,0.3); + max-width: 300px; + word-wrap: break-word; + white-space: normal; + opacity: 0; + transition: opacity 0.2s ease; + } + + .radar-tooltip.visible { + opacity: 1; + } + + /* CSS-only tooltip fallback */ + .entry { + position: relative; + } + + .entry:hover::after { + content: attr(data-tooltip); + position: fixed; + background: rgba(0, 0, 0, 0.9); + color: white; + padding: 8px 12px; + border-radius: 4px; + font-family: Arial, sans-serif; + font-size: 12px; + z-index: 999999; max-width: 250px; word-wrap: break-word; + white-space: pre-line; + pointer-events: none; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } .radar-title { @@ -275,7 +330,8 @@

{{ title }}

- + +
@@ -370,7 +426,6 @@

{{ title }}

// Select the SVG element const svg = d3.select("#tech-radar .radar-svg"); - const tooltip = d3.select("#tooltip"); // Function to render the radar function renderRadar() { @@ -549,7 +604,32 @@

{{ title }}

}; // Create the shape for this entry - createShape(d.moved, pos.x, pos.y, ring.color); + const shape = createShape(d.moved, pos.x, pos.y, ring.color); + + // Add tooltip functionality using custom div + const ringInfo = config.rings[d.ring]; + const quadrant = config.quadrants[d.quadrant]; + const titleText = `${d.label}
${quadrant.name} - ${ringInfo.name}${d.description ? '
' + d.description : ''}`; + + // Get tooltip element + const tooltip = document.getElementById('radar-tooltip'); + + // Add mouse event handlers + shape.on('mouseenter', function(event) { + tooltip.innerHTML = titleText; + tooltip.classList.add('visible'); + + // Position tooltip near cursor + const rect = svg.node().getBoundingClientRect(); + const mouseX = event.clientX - rect.left; + const mouseY = event.clientY - rect.top; + + tooltip.style.left = (mouseX + 10) + 'px'; + tooltip.style.top = (mouseY - 40) + 'px'; + }) + .on('mouseleave', function() { + tooltip.classList.remove('visible'); + }); // Add number label on top of the shape group.append("text") @@ -561,49 +641,6 @@

{{ title }}

.attr("font-weight", "bold") .attr("text-anchor", "middle") .attr("pointer-events", "none"); - - // Hover effects - group - .on("mouseover", function(event, d) { - const ring = config.rings[d.ring]; - const quadrant = config.quadrants[d.quadrant]; - const entryPos = d.calculatedPos; - - // Get the container position to calculate relative positioning - const containerRect = document.querySelector('.radar-container').getBoundingClientRect(); - const svgRect = document.querySelector('.radar-svg').getBoundingClientRect(); - - // Calculate tooltip position relative to the point - const tooltipX = svgRect.left + entryPos.x * (svgRect.width / width) - containerRect.left; - const tooltipY = svgRect.top + entryPos.y * (svgRect.height / height) - containerRect.top; - - tooltip - .style("display", "block") - .html(` - ${d.label}
- ${quadrant.name} - ${ring.name}
- ${d.description || ''} - ${d.movement ? `
${d.movement}` : ''} - `) - .style("left", (tooltipX + 15) + "px") - .style("top", (tooltipY - 35) + "px"); - }) - .on("mousemove", function(event, d) { - // Keep tooltip position fixed to the point, don't follow mouse - const containerRect = document.querySelector('.radar-container').getBoundingClientRect(); - const svgRect = document.querySelector('.radar-svg').getBoundingClientRect(); - const entryPos = d.calculatedPos; - - const tooltipX = svgRect.left + entryPos.x * (svgRect.width / width) - containerRect.left; - const tooltipY = svgRect.top + entryPos.y * (svgRect.height / height) - containerRect.top; - - tooltip - .style("left", (tooltipX + 15) + "px") - .style("top", (tooltipY - 35) + "px"); - }) - .on("mouseout", function() { - tooltip.style("display", "none"); - }); }); console.log("Tech Radar rendered with", config.entries.length, "entries"); diff --git a/pyproject.toml b/pyproject.toml index e23d5b2..b850187 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ authors = [ ] readme = "README.md" license = "Apache-2.0" +keywords = ["mkdocs", "mkdocs-plugin", "documentation", "tech-radar"] requires-python = ">=3.10" dependencies = [ "mkdocs>=1.4.0", @@ -29,6 +30,9 @@ build-backend = "setuptools.build_meta" include = ["mkdocs_tech_radar*"] exclude = ["site*", "tests*"] +[tool.setuptools.package-data] +mkdocs_tech_radar = ["templates/*"] + [dependency-groups] dev = [ "commitizen>=4.8.3", From 6dfce8b8ce0e9a24621cb73b06835f7aea95507d Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:15:28 +0100 Subject: [PATCH 13/21] feat: add Makefile for project management and enhance dev dependencies in pyproject.toml --- Makefile | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 + 2 files changed, 112 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8b5ee20 --- /dev/null +++ b/Makefile @@ -0,0 +1,110 @@ +.PHONY: help install dev test clean build serve lint format check docs + +##@ General +help: ## Show this help + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage: \033[36m\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-26s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Environment setup +install: ## Install dependencies using uv + uv sync + +dev: ## Install development dependencies + uv sync --dev + +##@ Development +serve: ## Start the MkDocs development server + uv run mkdocs serve + +serve-custom: ## Start the MkDocs development server on custom port + uv run mkdocs serve --dev-addr 127.0.0.1:8001 + +build: ## Build the MkDocs site + uv run mkdocs build + +##@ Package management +install-pkg: ## Install the package in editable mode + uv pip install --editable . + +reinstall: ## Uninstall and reinstall the package + uv pip uninstall mkdocs-tech-radar -q && uv pip install --editable . -q + +##@ Testing +test: ## Run tests (installs dev dependencies if needed) + @uv sync --dev --quiet + uv run pytest + +test-verbose: ## Run tests with verbose output + @uv sync --dev --quiet + uv run pytest -v + +test-coverage: ## Run tests with coverage report + @uv sync --dev --quiet + uv run pytest --cov=mkdocs_tech_radar --cov-report=html --cov-report=term + +##@ Code quality +lint: ## Run linting with ruff + uv run ruff check . + +lint-fix: ## Run linting and fix auto-fixable issues + uv run ruff check . --fix + +format: ## Format code with ruff + uv run ruff format . + +format-check: ## Check code formatting without making changes + uv run ruff format . --check + +check: ## Run all quality checks (lint + format check + test) + @uv sync --dev --quiet + uv run ruff check . + uv run ruff format . --check + uv run pytest + +##@ Documentation +docs-build: ## Build documentation + uv run mkdocs build + +docs-deploy: ## Deploy documentation to GitHub Pages + uv run mkdocs gh-deploy + +##@ Cleanup +clean: ## Remove build artifacts and cache + rm -rf build/ + rm -rf dist/ + rm -rf site/ + rm -rf *.egg-info/ + find . -type d -name __pycache__ -exec rm -rf {} + + find . -type f -name "*.pyc" -delete + +clean-venv: ## Remove virtual environment + rm -rf .venv + +##@ Package building and publishing +build-package: ## Build the package + uv build + +publish-test: ## Publish to TestPyPI + uv publish --repository testpypi + +publish: ## Publish to PyPI + uv publish + +##@ Dependency management +update: ## Update dependencies + uv sync --upgrade + +lock: ## Update lock file + uv lock + +add: ## Add a new dependency (use: make add PKG=package_name) + uv add $(PKG) + +add-dev: ## Add a new development dependency (use: make add-dev PKG=package_name) + uv add --dev $(PKG) + +remove: ## Remove a dependency (use: make remove PKG=package_name) + uv remove $(PKG) + +##@ Utility +show: ## Show installed packages + uv pip list diff --git a/pyproject.toml b/pyproject.toml index b850187..463e503 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ mkdocs_tech_radar = ["templates/*"] dev = [ "commitizen>=4.8.3", "prek>=0.1.1", + "pytest>=8.4.1", + "pytest-cov>=6.2.1", "ruff>=0.12.9", ] From 6b6dff39527eabd66c9d23b13428300b449b7419 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:27:51 +0100 Subject: [PATCH 14/21] docs: streamline README by removing redundant features section and enhancing clarity on radar file structure --- README.md | 95 ++++++++++++++----------------------------------------- 1 file changed, 23 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index f86bd73..eec16f5 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,14 @@ # MkDocs Tech Radar Plugin A MkDocs plugin that generates an interactive Technology Radar based on the [Zalando Tech Radar](https://github.com/zalando/tech-radar) implementation. -This plugin helps engineering teams visualize and track their technology adoption, assessment, and decisions across multiple radar versions. -## Features +This plugin helps engineering teams visualize and track their technology adoption, assessment, and decisions. -- 🎯 **Interactive visualization** with D3.js-powered radar chart -- 📊 **Four-ring assessment model** (Adopt, Trial, Assess, Hold) -- 🏗️ **Four-quadrant organization** (Frameworks, Tools, Platforms, Techniques) -- 🎭 **Movement indicators** (moved up ▲, moved down ▼, new ★, no change ⬤) -- 💡 **Hover tooltips** and interactive legend -- ️🎨 **Multiple radar versions** - supports a folder of radar files with dropdown selector -- 🌙 **Dark mode support** - automatically adapts to MkDocs Material theme -- 📋 **Inline embedding** - radar is embedded directly in your documentation pages +See [live demo](https://thatmlopsguy.github.io/mkdocs-tech-radar) ## Installation -Install the plugin using uv, pip, or your preferred Python package manager: +Install the plugin using `uv`, `pip`, or your preferred Python package manager: ```bash # Using uv @@ -30,6 +22,16 @@ pip install mkdocs-tech-radar Add the plugin to your `mkdocs.yml`: +### Parameters + +| Option | Type | Default | Description | +|-----------------|--------|----------------------|------------------------------------| +| `title_name` | string | `'Technology Radar'` | Title for the radar | +| `target_page` | string | `'tech-radar.md'` | Page to embed the radar in | +| `radars_folder` | string | `'radars'` | Folder containing radar JSON files | + +### Example: + ```yaml plugins: - tech-radar: @@ -40,9 +42,7 @@ plugins: ## Usage -### Creating Multiple Radar Files - -Create multiple JSON files in a `docs/radars/` folder for different radar versions: +Create one or multiple JSON files in a `docs/radars/` folder for different radar versions: ```text docs/ @@ -52,43 +52,18 @@ docs/ 2025-09-01.json ``` +**Note:** Use date-based naming for optimal sorting: + +- `2025-08-20.json` - August 20, 2025 version +- `2025-09-01.json` - September 1, 2025 version +- `2025-12-15.json` - December 15, 2025 version + The plugin will: - Automatically load all JSON files from the `radars_folder` - Display a dropdown selector for switching between radar versions - Default to the newest version (based on filename sorting) -Example radar file (`docs/radars/2025-08-20.json`): - -```json -{ - "date": "2025-08-20", - "quadrants": [ - {"name": "Languages & Frameworks"}, - {"name": "Tools"}, - {"name": "Platforms"}, - {"name": "Techniques"} - ], - "rings": [ - {"name": "ADOPT", "color": "#5ba300", "description": "Technologies we have high confidence in"}, - {"name": "TRIAL", "color": "#009eb0", "description": "Worth pursuing with small risks"}, - {"name": "ASSESS", "color": "#c7ba00", "description": "Worth exploring with higher risks"}, - {"name": "HOLD", "color": "#e09b96", "description": "Proceed with caution or avoid"} - ], - "entries": [ - { - "label": "React", - "quadrant": 0, - "ring": 0, - "moved": 0, - "active": true, - "description": "A JavaScript library for building user interfaces", - "link": "https://reactjs.org/" - } - ] -} -``` - ### Adding Radar to Your Documentation Add the radar placeholder in your target markdown page: @@ -105,39 +80,13 @@ Additional content can go here after the radar. The `` placeholder will be replaced with the interactive radar visualization. -### Multiple Radar Versions - -The plugin provides these features for managing multiple radar versions: - -#### Version Selection - -- **Dropdown menu**: Appears automatically when multiple radar files are detected -- **Date-based sorting**: Files are sorted by filename (assuming YYYY-MM-DD format) -- **Auto-selection**: Newest version is selected by default -- **Dynamic switching**: Users can switch between radar versions without page reload - -#### File Naming Convention - -Use date-based naming for optimal sorting: - -- `2025-08-20.json` - August 20, 2025 version -- `2025-09-01.json` - September 1, 2025 version -- `2025-12-15.json` - December 15, 2025 version - #### Recommended Workflow 1. Create a new radar file for each release/quarter 2. Copy the previous version as a starting point 3. Update entries with new technologies and movement indicators 4. The plugin automatically detects and makes it available - -## Configuration Options - -| Option | Type | Default | Description | -|-----------------|--------|----------------------|------------------------------------| -| `title_name` | string | `'Technology Radar'` | Title for the radar | -| `target_page` | string | `'tech-radar.md'` | Page to embed the radar in | -| `radars_folder` | string | `'radars'` | Folder containing radar JSON files | +5. Commit the changes to your version control system ## Radar File Structure @@ -176,6 +125,8 @@ Each entry in the `entries` array must have: ### Example Complete Radar File +Example radar file (`docs/radars/2025-08-20.json`): + ```json { "date": "2025-08-20", From 0ff374f0a0f8d1eb912f0cad2a5b7accbe1ff8c2 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:37:26 +0100 Subject: [PATCH 15/21] feat: add GitHub Actions workflow for documentation build and deployment; update Python version requirement in pyproject.toml --- .github/workflows/docs.yml | 80 ++++++++++++++++++++++++++++++++++++++ .vscode/settings.json | 5 +++ pyproject.toml | 2 +- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docs.yml create mode 100644 .vscode/settings.json diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..e86eb85 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,80 @@ +name: docs + +on: + push: + branches: + - main + - dev + paths: + - 'docs/**' + - 'mkdocs.yml' + - 'mkdocs_tech_radar/**' + - 'pyproject.toml' + + pull_request: + branches: + - main + paths: + - 'docs/**' + - 'mkdocs.yml' + - 'mkdocs_tech_radar/**' + - 'pyproject.toml' + +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: 3.12 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: "latest" + + - name: Install dependencies and plugin + run: | + uv pip install --system --editable . + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + + - name: Build documentation + run: | + uv run mkdocs build --config-file mkdocs.yml + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./site + + deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/dev' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 + + diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5ca369d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "pyproject" + ] +} diff --git a/pyproject.toml b/pyproject.toml index 463e503..13af7d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [ readme = "README.md" license = "Apache-2.0" keywords = ["mkdocs", "mkdocs-plugin", "documentation", "tech-radar"] -requires-python = ">=3.10" +requires-python = ">=3.12" dependencies = [ "mkdocs>=1.4.0", "jinja2>=3.0.0", From 8f8cb6c19b5331385b9b47c15e3f60a6b5583923 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:43:31 +0100 Subject: [PATCH 16/21] fix: update deploy action version to v4 in GitHub Actions workflow; add documentation badge to README --- .github/workflows/docs.yml | 2 +- README.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e86eb85..43bc5f0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -75,6 +75,6 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index eec16f5..a3af4cd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # MkDocs Tech Radar Plugin +[![docs](https://github.com/thatmlopsguy/mkdocs-tech-radar/actions/workflows/docs.yml/badge.svg)](https://github.com/thatmlopsguy/mkdocs-tech-radar/actions/workflows/docs.yml) + A MkDocs plugin that generates an interactive Technology Radar based on the [Zalando Tech Radar](https://github.com/zalando/tech-radar) implementation. This plugin helps engineering teams visualize and track their technology adoption, assessment, and decisions. From 45a61f5ec78109d2c2a91146f73ff20c331461d1 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:46:10 +0100 Subject: [PATCH 17/21] feat: enable manual triggering of documentation workflow with workflow_dispatch --- .github/workflows/docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 43bc5f0..3c520db 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,6 +1,7 @@ name: docs on: + workflow_dispatch: push: branches: - main From 1831df012cb2f25ce759e0d28a99f33987733191 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:47:44 +0100 Subject: [PATCH 18/21] fix: comment out conditional deployment for dev branch in GitHub Actions workflow --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3c520db..ff1ee59 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -67,7 +67,7 @@ jobs: path: ./site deploy: - if: github.event_name == 'push' && github.ref == 'refs/heads/dev' + # if: github.event_name == 'push' && github.ref == 'refs/heads/dev' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} From fb4dee7eda5069d6c6591ce286622cdfe1ec379b Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:57:22 +0100 Subject: [PATCH 19/21] fix: update concurrency settings to cancel in-progress deployments and remove conditional deployment for dev branch --- .github/workflows/docs.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ff1ee59..9c23e25 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -27,10 +27,10 @@ permissions: id-token: write # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +# Cancel in-progress deployments when a new one is triggered to prevent stuck deployments. concurrency: group: "pages" - cancel-in-progress: false + cancel-in-progress: true jobs: build: @@ -67,7 +67,6 @@ jobs: path: ./site deploy: - # if: github.event_name == 'push' && github.ref == 'refs/heads/dev' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} From e8dddf46d358466ec6cf80aa654ed16a5708cd43 Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:01:55 +0100 Subject: [PATCH 20/21] fix: update site name and description in mkdocs configuration; add Tech Radar navigation instruction in index --- docs/index.md | 2 ++ mkdocs.yml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index abfc817..d671431 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,3 +7,5 @@ This will not work, because the mkdocs config only check in the `target_page`. --> + +Go to the Tech Radar tab to see the interactive technology radar. diff --git a/mkdocs.yml b/mkdocs.yml index c8c9e03..c58b8e2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ -site_name: Tech Radar Test -site_description: Testing the tech-radar plugin +site_name: Tech Radar Live Demo +site_description: Demo of the tech-radar mkdocs plugin site_url: "https://thatmlopsguy.github.io/mkdocs-tech-radar/" repo_url: "https://github.com/thatmlopsguy/mkdocs-tech-radar" repo_name: "mkdocs-tech-radar" From 0bdf663da0283cae95bfb20f3663decc99ecb45f Mon Sep 17 00:00:00 2001 From: thatmlopsguy <165834479+thatmlopsguy@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:11:48 +0100 Subject: [PATCH 21/21] fix: update GitHub Actions workflow to remove dev branch from push triggers; update action versions and improve README formatting --- .github/workflows/docs.yml | 13 ++++--------- README.md | 8 +++----- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9c23e25..b9c924e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,11 +1,9 @@ name: docs on: - workflow_dispatch: push: branches: - main - - dev paths: - 'docs/**' - 'mkdocs.yml' @@ -40,12 +38,8 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: 3.12 - - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0 with: version: "latest" @@ -55,18 +49,19 @@ jobs: - name: Setup Pages id: pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - name: Build documentation run: | uv run mkdocs build --config-file mkdocs.yml - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: path: ./site deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/README.md b/README.md index a3af4cd..7886f01 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A MkDocs plugin that generates an interactive Technology Radar based on the [Zal This plugin helps engineering teams visualize and track their technology adoption, assessment, and decisions. -See [live demo](https://thatmlopsguy.github.io/mkdocs-tech-radar) +**See [live demo](https://thatmlopsguy.github.io/mkdocs-tech-radar)** ## Installation @@ -32,7 +32,7 @@ Add the plugin to your `mkdocs.yml`: | `target_page` | string | `'tech-radar.md'` | Page to embed the radar in | | `radars_folder` | string | `'radars'` | Folder containing radar JSON files | -### Example: +### Example ```yaml plugins: @@ -73,7 +73,7 @@ Add the radar placeholder in your target markdown page: ```markdown # Tech Radar -Our technology radar helps us track the technologies we use and evaluate emerging trends. +The technology radar helps us track the technologies we use and evaluate emerging trends. @@ -178,8 +178,6 @@ You can customize the radar appearance by modifying the radar files. The radar u You can change these colors by modifying the `rings` array in your radar JSON files. -## Example - Check out the included example in the `docs/` directory: 1. Build the documentation: `uv run mkdocs build`