Skip to content

Commit

Permalink
Merge pull request #400 from alan-turing-institute/main
Browse files Browse the repository at this point in the history
pulling in recent edits
  • Loading branch information
kallewesterling committed Feb 16, 2024
2 parents adda3ea + 280dd31 commit de654ca
Show file tree
Hide file tree
Showing 81 changed files with 3,287 additions and 611 deletions.
28 changes: 9 additions & 19 deletions .github/workflows/ci.yaml → .github/workflows/docs-publish.yaml
@@ -1,4 +1,4 @@
name: ci
name: docs-publish
on:
workflow_dispatch:
pull_request:
Expand All @@ -9,10 +9,6 @@ on:
release:
types:
- published

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
jobs:
Expand All @@ -29,30 +25,24 @@ jobs:
- uses: pre-commit/action@v3.0.0
with:
extra_args: --hook-stage manual --all-files

deploy:
docs:
runs-on: ubuntu-latest
if: github.event.repository.fork == false
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v3
with:
key: mkdocs-material-${{ env.cache_id }}
path: .cache
restore-keys: |
mkdocs-material-
- run:
pip install
git+https://${GH_TOKEN}@github.com/squidfunk/mkdocs-material.git
mkdocs-table-reader-plugin
- name: "Upgrade pip"
run: pip install --upgrade pip
- name: "Install requirements"
run: pip install -r site/requirements.txt
- name: Build and Deploy
working-directory: ./site
run: |
mkdocs build
mkdocs gh-deploy --force
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Expand Up @@ -4,7 +4,7 @@ ci:

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.1.14"
rev: "v0.2.1"
hooks:
- id: ruff
args: ["--fix", "--show-fixes"]
Expand Down
49 changes: 0 additions & 49 deletions HOWTO_reset_the_database.md

This file was deleted.

18 changes: 16 additions & 2 deletions eap_backend/eap_api/view_utils.py
Expand Up @@ -26,13 +26,24 @@
"serializer": AssuranceCaseSerializer,
"model": AssuranceCase,
"children": ["goals"],
"fields": ("name", "description", "lock_uuid", "owner", "color_profile"),
"fields": (
"name",
"description",
"lock_uuid",
"owner",
"color_profile",
),
},
"goal": {
"serializer": TopLevelNormativeGoalSerializer,
"model": TopLevelNormativeGoal,
"children": ["context", "property_claims", "strategies"],
"fields": ("name", "short_description", "long_description", "keywords"),
"fields": (
"name",
"short_description",
"long_description",
"keywords",
),
"parent_types": [("assurance_case", False)],
},
"context": {
Expand Down Expand Up @@ -259,6 +270,9 @@ def get_case_permissions(case, user):
"view": if user is a member of a group that has view rights on the case
None otherwise.
"""
if isinstance(case, (int, str)):
case = AssuranceCase.objects.get(pk=int(case))

if (not case.owner) or (case.owner == user):
# case has no owner - anyone can view it, or user is owner
return "manage"
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/Theming.jsx
@@ -1,11 +1,12 @@
import * as React from "react";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import { CssBaseline } from "@mui/material";
import configData from "./config.json";

function Theming({ children }) {
const theme = createTheme({
typography: {
fontFamily: '"Plus Jakarta Sans", sans-serif',
fontFamily: configData.styling.mainFont,
fontSize: 16,
h1: {
fontSize: "2.625rem",
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/components/CaseAccessibilityModal.jsx
Expand Up @@ -18,6 +18,23 @@ import configData from "../config.json";
import { Wheelchair } from "./common/Icons.jsx";
import ErrorMessage from "./common/ErrorMessage.jsx";

/**
* CaseAccessibilityModal provides a modal dialog for editing the accessibility options,
* specifically the colour profile, of an assurance case diagram. Users can select from
* predefined colour schemes configured in `config.json` to enhance diagram accessibility.
*
* @param {Object} props The component props.
* @param {boolean} props.isOpen Indicates if the modal is open.
* @param {Function} props.onClose Function to call when closing the modal.
* @param {Function} props.onSuccess Function to call upon successful update of the case.
* @param {string} props.caseId The ID of the case being edited.
* @param {string} props.currentColour The current colour profile of the case diagram.
*
* This component utilizes the `editCase` API to submit the selected colour profile update,
* handling loading states, success, and error feedback within the modal dialog. The colour
* selection is made through a radio button group, offering a user-friendly way to enhance
* diagram accessibility for users with visual impairments.
*/
function CaseAccessibilityModal({
isOpen,
onClose,
Expand Down
36 changes: 29 additions & 7 deletions frontend/src/components/CaseContainer.js
Expand Up @@ -15,6 +15,14 @@ import { ColumnFlow, RowFlow } from "./common/Layout.jsx";
import { Add, Subtract, Target } from "./common/Icons.jsx";
import ErrorMessage from "./common/ErrorMessage.jsx";

/**
* CaseContainer serves as the main component for displaying and interacting with an assurance case.
* It integrates various components such as MermaidChart for visual representation, ItemEditor for editing case items,
* and CaseTopBar for additional case management functions. This component handles loading of case data,
* user authentication, and provides zoom and pan functionality for the case diagram.
*
* @returns {JSX.Element} Renders the assurance case container with editing capabilities and visualization controls.
*/
function CaseContainer() {
const { caseSlug } = useParams();
const theme = useTheme();
Expand Down Expand Up @@ -120,13 +128,16 @@ function CaseContainer() {
}
}, [assuranceCase]);

/**
* Generates a unique identifier for new elements within the assurance case based on the type and its parents.
* It prefixes the identifier based on the type and ensures uniqueness within the case.
*
* @param {string} type - The type of the element for which the ID is being generated.
* @param {string} parentId - The ID of the parent element.
* @param {string} parentType - The type of the parent element.
* @returns {string} A unique identifier for the new element.
*/
const getIdForNewElement = useCallback(
/**
* @param {string} type
* @param {string} parentId
* @param {string} parentType
* @returns {string}
*/
(type, parentId, parentType) => {
let prefix = configData.navigation[type].db_name
.substring(0, 1)
Expand Down Expand Up @@ -154,6 +165,11 @@ function CaseContainer() {
[assuranceCase, identifiers],
);

/**
* Updates all identifiers within the assurance case to ensure they are unique.
* This function might be necessary when there are changes to the case structure
* or to correct any identifier conflicts.
*/
const updateAllIdentifiers = useCallback(() => {
setIsLoading(true);

Expand Down Expand Up @@ -381,7 +397,13 @@ function CaseContainer() {
);
}

/** @returns {string[]} */
/**
* Generates a list of identifiers from the assurance case. It recursively visits
* each item in the case structure, collecting the names to form a set of identifiers.
*
* @param {Object} assuranceCase - The assurance case object from which to generate the list.
* @returns {string[]} A list of unique identifiers derived from the assurance case items.
*/
function updateIdList(assuranceCase) {
const set = [];
assuranceCase.goals.forEach((goal) => {
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/components/CaseCreator.js
Expand Up @@ -6,6 +6,18 @@ import CaseImporterFlow from "./CaseImporterFlow.jsx";
import ModalDialog from "./common/ModalDialog.jsx";
import useId from "@mui/utils/useId";

/**
* CaseCreator component that toggles between the CaseCreatorFlow and CaseImporterFlow based on user action.
* It presents a modal dialog which either guides the user through creating a new assurance case
* or importing an existing one. The component ensures that a user is logged in before allowing case creation
* or importation.
*
* @param {Object} props - Component props.
* @param {boolean} props.isOpen - Determines if the modal dialog is open.
* @param {Function} props.onClose - Handler called when the modal dialog is requested to close.
* @param {boolean} props.isImport - Flag determining which flow to show: true for import, false for creation.
* @returns {JSX.Element|null} The rendered component if the user is logged in, otherwise null.
*/
function CaseCreator({ isOpen, onClose, isImport }) {
const titleId = useId();

Expand Down
15 changes: 14 additions & 1 deletion frontend/src/components/CaseCreatorFlow.jsx
Expand Up @@ -10,10 +10,23 @@ import LoadingSpinner from "./common/LoadingSpinner.jsx";
import { ArrowRight } from "./common/Icons.jsx";
import ErrorMessage from "./common/ErrorMessage.jsx";

// see models.py
/**
* Maximum allowed lengths for title and description,
* see /eap_backend/eap_api/models.py
*/
const titleMaxLength = 200;
const descriptionMaxLength = 1000;


/**
* CaseCreatorFlow component guides the user through the process of creating a new assurance case.
* It consists of two stages: entering basic case details (title and description),
* and selecting a template for the case. Upon completion, the case is posted to the server.
*
* @param {Object} props Component props.
* @param {string} props.titleId A unique ID for the title element, used for accessibility.
* @param {Function} props.onClose Function to call when the user chooses to close the modal.
*/
function CaseCreatorFlow({ titleId, onClose }) {
const [stage, setStage] = useState(0);

Expand Down
47 changes: 45 additions & 2 deletions frontend/src/components/CaseImporterFlow.jsx
Expand Up @@ -17,6 +17,14 @@ import FileInput from "./common/FileInput.jsx";
import { ArrowRight } from "./common/Icons.jsx";
import ErrorMessage from "./common/ErrorMessage.jsx";

/**
* CaseImporterFlow allows users to import an assurance case into the TEA Platform from either a file or a URL.
* It supports importing from SVG files with embedded JSON metadata or directly from JSON files.
*
* @param {Object} props The component props.
* @param {string} props.titleId A unique identifier for the title element, used for accessibility.
* @param {Function} props.onClose A function to call when closing the import modal.
*/
function CaseImporterFlow({ titleId, onClose }) {
const [uploadType, setUploadType] = useState("file");
const [url, setUrl] = useState("");
Expand All @@ -33,6 +41,11 @@ function CaseImporterFlow({ titleId, onClose }) {
const baseURL = `${getBaseURL()}`;
const navigate = useNavigate();

/** Parses SVG text to extract embedded JSON metadata
* @param {string} text The SVG text to parse
* @returns {Promise<Object>} The parsed JSON metadata
* @throws {Error} If the metadata cannot be parsed
*/
const parseSvg = useCallback(async (text) => {
const parser = new DOMParser();
const svgDoc = parser.parseFromString(text, "image/svg+xml");
Expand All @@ -48,8 +61,13 @@ function CaseImporterFlow({ titleId, onClose }) {
}
}, []);

/** Fetches content from a URL and tries to parse it as JSON or SVG
*
* @param {string} url
* @returns {Promise<Object>} The parsed JSON or SVG content
* @throws {Error} If the content cannot be loaded or parsed
*/
const getUrlContent = useCallback(
/** @param {string} url */
async (url) => {
try {
const response = await fetch(url);
Expand All @@ -75,6 +93,13 @@ function CaseImporterFlow({ titleId, onClose }) {
[parseSvg]
);

/**
* Posts the JSON representation of a case to the backend
*
* @param {Object} json The case JSON to post
* @returns {Promise<void>}
* @throws {Error} If the JSON cannot be posted
*/
const postCaseJSON = useCallback(
(json) => {
const requestOptions = {
Expand Down Expand Up @@ -108,6 +133,13 @@ function CaseImporterFlow({ titleId, onClose }) {
[token, baseURL, navigate]
);

/**
* Handles form submission for importing a case
*
* @param {Event} e The form submission event
* @returns {void}
* @throws {Error} If the form submission fails
*/
const onSubmit = useCallback(
(e) => {
e.preventDefault();
Expand All @@ -133,10 +165,21 @@ function CaseImporterFlow({ titleId, onClose }) {
[uploadType, url, fileJson, getUrlContent, postCaseJSON]
);

/**
* Handles changes in the selected upload type (file or URL)
* @param {Event} e The change event
* @returns {void}
* @throws {Error} If the change event fails
*/
const onTypeChange = useCallback((e) => {
setUploadType(e.target.value);
}, []);

/**
* Processes the selected file to extract JSON data.
*
* @returns {void}
*/
useEffect(() => {
if (!file) {
setFileJson(null);
Expand Down Expand Up @@ -200,7 +243,7 @@ function CaseImporterFlow({ titleId, onClose }) {
control={<Radio />}
label="File upload"
/>
<FormControlLabel value="url" control={<Radio />} label="Url upload" />
<FormControlLabel value="url" control={<Radio />} label="URL upload" />
</RadioGroup>
{uploadType === "file" ? (
<FileInput
Expand Down

0 comments on commit de654ca

Please sign in to comment.