diff --git a/.github/workflows/unzip_and_process_IG_for_archive.yml b/.github/workflows/unzip_and_process_IG_for_archive.yml new file mode 100644 index 00000000..a41c73f8 --- /dev/null +++ b/.github/workflows/unzip_and_process_IG_for_archive.yml @@ -0,0 +1,91 @@ +name: Unzip and Update IG Version in webpage +# This GitHub Actions workflow automates the process of unzipping a ZIP file from the IG directory, +# extracting the version number from an HTML file, renaming the directory to the version number, +# removing the packages folder, running a custom Python script to update the index, and creating a pull request + +on: + workflow_dispatch: # Ermöglicht die manuelle Auslösung des Workflows + push: + branches: + - gh-pages + - feature/automate-gh* # TODO remove this line after testing + paths: + - IG/isik-*.zip # Trigger auf das Hinzufügen einer ZIP-Datei im IG-Ordner + + +jobs: + unzip-and-process: + runs-on: ubuntu-latest # Verwende die neueste Ubuntu-Umgebung + + steps: + - name: Checkout repository + uses: actions/checkout@v2 # Klone das Repository, um auf den Code zuzugreifen + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' # Installiere die neueste Python-Version + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install beautifulsoup4 # Installiere die Abhängigkeit für HTML-Verarbeitung + + - name: Unzip file + id: unzip + run: | + # Bestimme die ZIP-Datei und + ZIP_FILE=$(ls IG/isik-*.zip) + # wenn keine Datei gefunden wurde dann brech ab und gibt eine Fehlermdelung aus + if [ -z "$ZIP_FILE" ]; then + echo "No ZIP file found in IG directory" + exit 1 + fi + # prüfe ob mehr als eine Datei dem Schema folgt, falls ja, gib eine Fehlermdelung aus + if [ $(ls IG/isik-*.zip | wc -l) -gt 1 ]; then + echo "Multiple ZIP files found in IG directory" + exit 1 + fi + # Entpacke die zip-Datei in einen Versionsordner namens new-IG + unzip "$ZIP_FILE" -d IG/new-IG + # Entferne die ZIP-Datei, um Konflikte zu vermeiden + rm "$ZIP_FILE" + #lese die Versionsnummer aus der HTML-Datei "ImplementationGuide-markdown-Einfuehrung.html" im neuen Ordner + VERSION=$(grep -oP '(?<=Version: )\d+\.\d+\.\d+' IG/new-IG/ImplementationGuide-markdown-Einfuehrung.html) + #falls die Versionsnummer nicht gefunden wird, gebe eine Fehlermeldung aus und exitiere + if [ -z "$VERSION" ]; then + echo "Version number not found in HTML file" + exit 1 + fi + #benenne den Ordner um in die Versionsnummer und gib die Versionsnummer aus + mv IG/new-IG IG/"$VERSION" + + echo "Unzipped version: $VERSION" + # Setze die Versionsnummer als Variable, die später für den python argument input verwendet wird + echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + + - name: Remove packages folder + run: | + rm -rf "IG/$VERSION/packages" # Entferne den packages-Ordner aus dem Versionsordner + + - name: Run Python script + run: | + python scripts/update_index.py "$VERSION" # Führe das benutzerdefinierte Python-Skript mit der Versionsnummer als Argument aus + + - name: Commit changes + run: | + # Konfiguriere Git-Benutzerinformationen für den Commit + git config --local user.name "github-actions" + git config --local user.email "ga-ptdata@gematik.de" + # Füge alle Änderungen hinzu + git add . + git commit -m "AUTOCOMMIT: Processed ZIP file: $ZIP_FILE" # Erstelle den Commit + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + title: 'AUTOPULLREQUEST: Update from IG folder for TC Version: ${{ steps.unzip.outputs.VERSION }}' # Titel des Pull-Requests + body: 'Automated PULL REQUEST Processed new IG-ZIP file for Version: ${{ steps.unzip.outputs.VERSION }}.' # Beschreibung des Pull-Requests + base: gh-pages # Ziel-Branch für den Pull-Request + branch: update-from-zip-${{ steps.unzip.outputs.VERSION }} # Erstelle einen neuen Branch für die Änderungen mit Angabe der Version diff --git a/scripts/test_update_index.py b/scripts/test_update_index.py new file mode 100644 index 00000000..b453c5e2 --- /dev/null +++ b/scripts/test_update_index.py @@ -0,0 +1,312 @@ +import os +import shutil +import subprocess +import unittest +from unittest.mock import mock_open, patch +import datetime +from update_index import create_new_row, get_current_date_str, update_content, update_index_html, validate_version, read_index_html, find_insert_position + +class TestUpdateIndex(unittest.TestCase): + + def test_update_index_with_redundant_version(self): + """ + Test case for the `validate_version` function. + + This test verifies that the `validate_version` function correctly handles a redundant version. + + The test uses a version number that is equal to an existing version number. + + Asserts: + The function prints an error message and returns with status false. + """ + version = "3.0.5" + mock_file_content = """ + + 01.01.2022 + + 3.0.5 + + Technical Correction 3.0.5 + 3.0.5 + + + 01.01.2022 + + 3.0.4 + + Technical Correction 3.0.4 + 3.0.4 + + """ + validation_result = validate_version(version, mock_file_content) + self.assertEqual(validation_result, (False, f"Version {version} already exists in index.html.")) + + def test_validate_version_with_incorrect_format(self, version="4.0", content=""): + """ + Test case for the `validate_version` function. + + This test verifies that the `validate_version` function correctly validates the version format. + + The test uses an incorrect version format that does not match the semver pattern. + + Asserts: + The function prints an error message and exits with status code 1. + """ + with patch("sys.exit") as mock_exit: + content = "" # Assuming content is an empty string for the test case + validate_version(version, content) + + def test_validate_version_with_correct_format(self): + """ + Test case for the `validate_version` function. + + This test verifies that the `validate_version` function correctly validates the version format. + + The test uses a correct version format that matches the semver pattern. + + Asserts: + The function does not print an error message and does not exit. + """ + with patch("sys.exit") as mock_exit: + version = "4.0.11" + validation_result = validate_version(version,content="") + self.assertEqual(validation_result, (True, "Version has no obvious issues.")) + + # TODO add test for validate_version with correct version format for e.g. release candidate like 4.0.0-rc2 + + def test_validate_version_with_redundant_version(self): + """ + Test case for the `validate_version` function. + + This test verifies that the `validate_version` function correctly validates the version number being unequal to an existing version. + + The test uses a version number that is equal to an existing version number. + + Asserts: + The function prints an error message and exits with status code 1. + """ + version = "3.0.5" + content = """ + + 01.01.2022 + + 3.0.5 + + Technical Correction 3.0.5 + 3.0.5 + + + 01.01.2022 + + 3.0.4 + + Technical Correction 3.0.4 + 3.0.4 + + """ + validation_result = validate_version(version, content) + self.assertEqual(validation_result, (False, f"Version {version} already exists in index.html.")) + + def test_find_insert_position_with_no_previous_version(self): + """ + Test case for the `find_insert_position` function. + + This test verifies that the `find_insert_position` function correctly handles the case when no previous version is found in the index.html file. + + The test uses a mock file content with no previous version. + + Asserts: + The function prints an error message and exits with status code 1. + """ + version = "3.0.6" + mock_file_content = "" + position_result = find_insert_position(mock_file_content, version) + self.assertEqual(position_result, (False, "No previous version found in index.html")) + + + def test_find_insert_position_with_correct_previous_version_given_front_position_random_string_of_len_10(self): + """ + Test case for the `find_insert_position` function. + + This test verifies that the `find_insert_position` function correctly finds the insert position when a correct previous version is given. + + The test uses a mock file content with a previous version. + + Asserts: + The function returns the correct insert position. + """ + version = "3.0.6" + mock_file_content = """1234567890 + 01.01.2022 + + 3.0.5 + + Technical Correction 3.0.5 + 3.0.5 + + + 01.01.2022 + + 3.0.4 + + Technical Correction 3.0.4 + 3.0.4 + + """ + position_result, message = find_insert_position(mock_file_content, version) + self.assertEqual(position_result, 10) + + + def test_run_update_html_twice_and_determine_insertion_position(self): + """ + Test case for the `update_index_html` function. + + This test verifies that the `update_index_html` function correctly determines the insertion position for the second automatic update. + + The test uses a mock file content with a previous version. + + Asserts: + The function confirms that the string 3.0.6 is occurring before 3.0.7. + """ + version1= "3.0.6" + version2= "3.0.7" + #open index-html as mock file without hard inline content + #TODO + mock_file_content = """ + 01.01.2022 + + 3.0.5 + + Technical Correction 3.0.5 + 3.0.5 + + + 01.01.2022 + + 3.0.4 + + Technical Correction 3.0.4 + 3.0.4 + """ + + # First update + with patch("builtins.open", mock_open(read_data="mock_file_content")): + mock_file_content = update_index_html(version1, mock_file_content) + # Second update + mock_file_content = update_index_html(version2, mock_file_content) + + # Check order of both versions in the mock file content + self.assertIn(version1, mock_file_content) + self.assertIn(version2, mock_file_content) + self.assertLess(mock_file_content.index(version2), mock_file_content.index(version1)) # version2 (3.0.7) should occur before version1 (3.0.6), since order is descending + + def test_run_update_index_with_redundant_version(self): + """ + Test case for the `update_index_html` function. + + This test verifies that the `update_index_html` function correctly handles a redundant version. + + The test uses a version number that is equal to an existing version number. + + Asserts: + The function prints an error message and does not update the index.html content. + """ + version = "3.0.5" + updated_content = update_index_html(version) + self.assertFalse(updated_content) + + + def test_run_update_content_with_redundant_version_without_content_alteration(self): + """ + Test case for the `update_index_html` function. + + This test verifies that the `update_index_html` function correctly handles a redundant version. + + The test uses a version number that is equal to an existing version number. + + Asserts: + The function prints an error message and does not update the index.html content. + """ + + version = "3.0.5" + mock_file_content = """ +
+
+
+
+
+
+
+
+
+
+ + + +

ISiK Implementierungsleitfäden

+

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """ + updated_content = update_index_html(version, mock_file_content) + self.assertFalse(updated_content) + + + + + + diff --git a/scripts/update_index.py b/scripts/update_index.py new file mode 100644 index 00000000..a18c61c7 --- /dev/null +++ b/scripts/update_index.py @@ -0,0 +1,98 @@ +# This script works only for Technical correction updates, e.g. augmenting from 4.0.1 to 4.0.2. For Major or Minor updates, the script needs to be adjusted to handle the versioning correctly or it should be done manually. +# This script is used in an action to update the index.html file with the latest data from IG-folder Version added to the IG Folder. Alternatively a Version for the update can be provided as an argument. + +# Test update # todo remove + +import os +import sys +import re +import datetime + +# Constants +PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__)) +INDEX_FILE_PATH = os.path.join(PROJECT_ROOT, "index.html") +#TODO Add a pattern for different Projects +HTML_TEMPLATE = """ + + + + + + +""" +VERSION_PATTERN = re.compile( + r'\s*\s*' +) + +def get_current_date_str(): + return datetime.datetime.now().strftime("%d.%m.%Y") + +def create_new_row(version, current_date_str): + return HTML_TEMPLATE.format(current_date=current_date_str, version=version) + +def read_index_html(): + with open(INDEX_FILE_PATH, 'r', encoding='utf-8') as file: + return file.read() + +def write_index_html(content): + with open(INDEX_FILE_PATH, 'w', encoding='utf-8') as file: + file.write(content) + +def validate_version(version, content): + if version is None: + return(False, "No Version provided. Please provide a version as an argument, e.g. 4.0.5 or 4.0.0-rc2 (release candidate)") + # Check if the version is in the correct format + semver_pattern = re.compile(r'^\d+\.\d+\.\d+(-\w+)?$') + if not semver_pattern.match(version): + return(False, "Invalid version format. Please provide a version in the format X.Y.Z or X.Y.Z-rcN (e.g., 4.0.5 or 4.0.0-rc2).") + # Check if the version already exists in the index.html file + if VERSION_PATTERN.search(content) and version in content: + return(False, f"Version {version} already exists in index.html.") + return(True, "Version has no obvious issues.") + + +def find_insert_position(content, version): + matches = list(VERSION_PATTERN.finditer(content)) + if not matches: + return (False, "No previous version found in index.html") + + for match in matches: + if version > match.group(1): + return (match.start(), f"Inserting new version {version} before version {match.group(1)}") + +def update_content(content, new_row, insert_position): + return content[:insert_position] + new_row + content[insert_position:] + +def update_index_html(version=None, content=None): + if content is None: + content = read_index_html() + is_valid, message = validate_version(version, content) + if not is_valid: + print(message) + return False + print(message) + current_date_str = get_current_date_str() + new_row = create_new_row(version, current_date_str) + position_found, message_position = find_insert_position(content, version) # TODO # ISSUE insertion position not determined correctly for second automatic update + if position_found is False: + print(message_position) + return False + updated_content = update_content(content, new_row, position_found) + return(updated_content) + + +if __name__ == "__main__": + if len(sys.argv) > 1: + version = sys.argv[1] + else: + version = None + updated_content = update_index_html(version) + if updated_content is False: + print("Failed to update index.html") + sys.exit(1) + write_index_html(updated_content) + print(f"Successfully updated index.html with version {version}") \ No newline at end of file
DatumNameBeschreibungVersion
+ Basismodul Stufe 4
25.06.2024 + 4.0.0-rc3 + Release Candidate Benehmensherstellung 4.0.0-rc34.0.0-rc3
04.04.2024 + 4.0.0-rc2 + Release Candidate Kommentierung 4.0.0-rc24.0.0-rc2
+ Basismodul Stufe 3
30.04.2024 + 3.0.5 + Technical Correction 3.0.53.0.5
03.04.2024 + 3.0.4 + Technical Correction 3.0.43.0.4
{current_date} + + {version} + + Technical Correction {version}{version}
\d{2}\.\d{2}\.\d{4}\s*\s*\1\s*\s*