Skip to content

Commit

Permalink
feat: Add new updated qa visualiser
Browse files Browse the repository at this point in the history
  • Loading branch information
katie-gardner committed Nov 24, 2024
1 parent cf8ba9f commit 39f766b
Show file tree
Hide file tree
Showing 15 changed files with 449 additions and 2 deletions.
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ $RECYCLE.BIN/
*.msp

# Windows shortcuts
*.lnk
*.lnk

# Draw.io files
*.bkp
Expand All @@ -502,4 +502,8 @@ build/
contentful/migration-scripts/config.json
contentful/migration-scripts/contentful-export-*.json

.sonarqube/
.sonarqube/

# Secrets
.env
*.env
1 change: 1 addition & 0 deletions qa-visualiser/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PLANTECH_API_KEY=<API_KEY>
1 change: 1 addition & 0 deletions qa-visualiser/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
28 changes: 28 additions & 0 deletions qa-visualiser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# QA visualiser

Python project for creating visualisations of the different question pathways for each topic, to support QA testing.

## Local setup

To run the qa-visualiser locally:
1. Install [uv](https://github.com/astral-sh/uv)
2. Setup your .env file by copying .env.example and adding the relevant keys
3. create and activate a virtual environment by running
```bash
cd qa-visualiser
uv venv
source .venv/bin/activate
```
4. Run the tool to generate visualisations with
```bash
uv run main.py
```

## Linting and formatting

Linting and formatting can be done with the commands:
```bash
uv run ruff check
uv run ruff check --fix # fix linting errors automatically where possible
uv run ruff format
```
11 changes: 11 additions & 0 deletions qa-visualiser/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from src.fetch_sections import fetch_sections
from src.generate_visualisations import process_sections


def main():
sections = fetch_sections()
process_sections(sections)


if __name__ == "__main__":
main()
19 changes: 19 additions & 0 deletions qa-visualiser/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[project]
name = "qa-visualiser"
version = "0.1.0"
description = "Tool for creating questionnaire flow visualisations for Plan Tech"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"graphviz>=0.20.3",
"python-dotenv>=1.0.1",
"requests>=2.32.3",
]

[tool.ruff.lint]
extend-select = ["I"]

[tool.uv]
dev-dependencies = [
"ruff>=0.8.0",
]
Empty file added qa-visualiser/src/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions qa-visualiser/src/fetch_sections.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import os

import requests
from dotenv import load_dotenv

from src.models import Section

load_dotenv()


def fetch_sections() -> list[Section]:
token = os.getenv("PLANTECH_API_KEY")
data = requests.get(
"https://localhost:8080/api/cms/sections",
headers={
"Accept": "application/json",
"Authorization": f"Bearer {token}",
},
)
sections: list[Section] = data.json()
return sections
88 changes: 88 additions & 0 deletions qa-visualiser/src/generate_visualisations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import textwrap
from pathlib import Path

from graphviz import Digraph

from src.models import Section


def _wrap_text(text: str, max_length: int) -> str:
"""Wrap lines such that no line exceeds max_length, splitting on whitespace where possible."""
return textwrap.fill(
text,
width=max_length,
break_long_words=True,
break_on_hyphens=True,
)


def _create_blank_digraph() -> Digraph:
"""Blank graph with styling options"""
return Digraph(
format="png",
graph_attr={
"rankdir": "LR",
"beautify": "true",
},
edge_attr={
"arrowhead": "vee",
"arrowsize": "0.5",
},
)


def _create_questionnaire_flowchart(section: Section) -> Digraph:
tree = _create_blank_digraph()

tree.node(
"end",
"Check Answers",
shape="box",
style="filled",
fillcolor="lightblue:white",
width="2",
gradient="300",
)

for question in section["questions"]:
current_question = question["sys"]["id"]
question_text = _wrap_text(question["text"], 20)

tree.node(
current_question,
question_text,
shape="box",
style="filled",
fillcolor="grey:white",
width="2",
gradient="200",
)

for answer in question["answers"]:
answer_text = _wrap_text(answer["text"], 20)

if next_question := answer["nextQuestion"]:
next_question_id = next_question["sys"]["id"]
tree.node(
next_question_id,
"Missing Content",
shape="diamond",
style="filled",
fillcolor="red:white",
width="2",
)
tree.edge(current_question, next_question_id, label=answer_text)
else:
tree.edge(current_question, "end", label=answer_text)

return tree


def process_sections(sections: list[Section]) -> None:
png_folder = Path("visualisations")
png_folder.mkdir(exist_ok=True)

for section in sections:
output_file = Path(png_folder, section["name"])
flowchart = _create_questionnaire_flowchart(section)
flowchart.render(output_file, cleanup=True)
29 changes: 29 additions & 0 deletions qa-visualiser/src/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import annotations

from typing import TypedDict


class SystemDetails(TypedDict):
id: str


class Answer(TypedDict):
sys: SystemDetails
text: str
nextQuestion: Question | None


class Question(TypedDict):
answers: list[Answer]
helpText: str | None
slug: str
sys: SystemDetails
text: str


class Section(TypedDict):
name: str
questions: list[Question]
firstQuestionId: str
interstitialPage: dict | None
sys: SystemDetails
Loading

0 comments on commit 39f766b

Please sign in to comment.