diff --git a/.github/workflows/module.yaml b/.github/workflows/module.yaml index 28f0419..1bed84f 100644 --- a/.github/workflows/module.yaml +++ b/.github/workflows/module.yaml @@ -52,6 +52,9 @@ jobs: - uses: go-task/setup-task@v2 + - name: Install Drupal site + run: task drupal:site:install + - name: Extract translations run: task translations:extract diff --git a/CHANGELOG.md b/CHANGELOG.md index 16666ae..584f079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0] - 2026-09-28 +* [PR-11](https://github.com/itk-dev/os2forms_f2/pull/11) + * Cleaned up identification of cases and matters + * Set default matter type ("Internal") + * Updated user manual * [PR-10](https://github.com/itk-dev/os2forms_f2/pull/10) Updated user manual * [PR-8](https://github.com/itk-dev/os2forms_f2/pull/8) diff --git a/README.md b/README.md index 8af8a7b..a23dcc9 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ changes). ## Testing ``` shell -task drupal:os2forms:install +task drupal:site:install ``` ## User manual @@ -154,7 +154,7 @@ F2_MATTER_IDENTIFIER='903' With these in place, we can now generate images: ``` shell -task drupal:os2forms:install --yes +task drupal:site:install --yes task playwright:install --yes task user-manual:images:generate ``` @@ -177,12 +177,25 @@ task user-manual:images:generate to quickly check that everything works as expected, but before committing anything you must run ``` shell -task drupal:os2forms:install --yes +task drupal:site:reset --yes task user-manual:images:generate ``` -reset Drupal before generating the images. +to reset Drupal before generating the images. > [!TIP] > See [Run a Playwright command](https://addons.ddev.com/addons/julienloizelet/ddev-playwright#run-a-playwright-command) > for other cool tricks supported by DDEV and the Playwright add-on. + +We use [php-vcr](https://github.com/php-vcr/php-vcr) to record and replay interactions with the F2 API during testing +and store cassettes in `tests/vcr-cassettes`. + +> [!NOTE] +> php-vcr has some redaction issues (cf. ) so we're not yet ready to +> publish cassettes and Git ignore the cassettes for now. + +--- + +``` shell +ddev dotenv set .env.local --os2forms-f2-test=value +``` diff --git a/Taskfile.yml b/Taskfile.yml index 0b42d89..0692f1d 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -141,13 +141,9 @@ tasks: translations:extract: desc: Extract module translations + deps: + - drupal:site:is-installed cmds: - # Patch https://github.com/OS2Forms/os2forms/blob/develop/config/optional/pathauto.pattern.os2forms_wf_webforms.yml - - | - ddev exec sed --in-place 's/id: node_type$/id: entity_bundle:node/' */modules/contrib/os2forms/config/optional/pathauto.pattern.os2forms_wf_webforms.yml - - ddev drush --yes site:install minimal - - ddev drush --yes pm:install drupal_translation_extractor - - ddev drush --yes pm:install os2forms_f2 - for: - da cmd: >- @@ -156,6 +152,11 @@ tasks: --require-context=os2forms_f2 module:os2forms_f2 --sort=asc --clean --debug --finder-do-not-ignore-vcs-ignored + # Show any changes + - task: translations:git:diff + ignore_error: true + # Restore if only some metadata is changed + - task: translations:git:restore translations:git:diff: desc: "`git diff` all translation files ignoring timestamp changes" @@ -180,19 +181,35 @@ tasks: to check. cmd: git restore '*.po' - drupal:os2forms:install: - desc: Install OS2Forms F2 module and configure Drupal for testing - prompt: Really reset Drupal site? + drupal:site:is-installed: + internal: true + preconditions: + # https://drupal.stackexchange.com/a/318384 + - sh: '[ $(ddev drush status --field=bootstrap) = "Successful" ]' + msg: | + It seems that Drupal has not been installed. Run + + task drupal:site:install + + before running this task. + + drupal:site:install: + desc: Installs Drupal site, enables OS2Forms F2 module and configures Drupal for development and testing + prompt: Really install (and reset) Drupal site? cmds: - ddev drush --yes site:install - ddev drush --yes user:password admin password - - ddev drush --yes pm:uninstall update + - ddev drush --yes pm:uninstall update announcements_feed # Patch https://github.com/OS2Forms/os2forms/blob/develop/config/optional/pathauto.pattern.os2forms_wf_webforms.yml - | ddev exec sed --in-place 's/id: node_type$/id: entity_bundle:node/' */modules/contrib/os2forms/config/optional/pathauto.pattern.os2forms_wf_webforms.yml - - ddev drush --yes pm:install os2forms_f2 os2forms_f2_examples os2forms_test_playwright + - ddev drush --yes pm:install os2forms_f2 os2forms_f2_examples os2forms_f2_test drupal_translation_extractor + + # Some defaults + - ddev drush --yes config:set os2forms_f2.settings f2_api.log_level debug + - ddev drush --yes config:set os2forms_f2.settings general.queue os2forms_f2 # Gin theme - ddev drush --yes theme:install gin @@ -213,14 +230,24 @@ tasks: # Import our module translations - ddev drush --yes locale:import da modules/custom/os2forms_f2/translations/os2forms_f2.da.po - - task: drupal:os2forms:configure + - task: drupal:site:reset - drupal:os2forms:configure: + drupal:site:reset: decs: Create settings for os2forms_f2 module + prompt: Really reset Drupal site? cmds: - # Some defaults - - ddev drush --yes config:set os2forms_f2.settings f2_api.log_level debug - - ddev drush --yes config:set os2forms_f2.settings general.queue os2forms_f2 + - ddev drush --yes config:import --partial --source modules/custom/os2forms_f2/modules/os2forms_f2_examples/config/install + - ddev drush --yes config:import --partial --source modules/custom/os2forms_f2/modules/os2forms_f2_test/config/install + - >- + ddev drush --yes sql:query " + TRUNCATE advancedqueue; + TRUNCATE webform_submission_log; + TRUNCATE webform_submission_data; + TRUNCATE webform_submission; + TRUNCATE os2forms_f2_f2_item; + ALTER TABLE webform_submission AUTO_INCREMENT = 1; + UPDATE webform SET next_serial = 1; + " playwright:install: desc: Install Playwright stuff @@ -228,6 +255,9 @@ tasks: user-manual:images:generate: desc: Generate user manual images + deps: + - drupal:site:is-installed + - playwright:install cmds: - cmd: ddev playwright test user-manual-images.spec.ts {{.CLI_ARGS}} # We want to postprocess any updated images even if something fails @@ -237,6 +267,7 @@ tasks: user-manual:images:trim: desc: "Trim docs images" + silent: true vars: BORDER_WIDTH: 4 # Trim and add border @@ -256,6 +287,7 @@ tasks: user-manual:images:restore: desc: "Restore docs images that are not visually modified" + silent: true cmd: cmd: | # https://stackoverflow.com/a/949391 diff --git a/composer.json b/composer.json index 8161b38..340a19f 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "mglaman/composer-drupal-lenient": "^1.0", "mglaman/phpstan-drupal": "^2.2", "palantirnet/drupal-rector": "^1.1", + "php-vcr/php-vcr": "^1.12", "phpstan/extension-installer": "^1.4", "phpstan/phpstan": "2.3.3", "phpstan/phpstan-deprecation-rules": "^2.0" @@ -67,6 +68,11 @@ "drupal/events_logging", "drupal/switch_page_theme" ] + }, + "patches": { + "php-vcr/php-vcr": { + "Undefined array key 2097198": "patches/php-vcr/php-vcr/curl-helper.patch" + } } } } diff --git a/docs/user-manual/da/form-builder.md b/docs/user-manual/da/form-builder.md index 2b4db94..028764f 100644 --- a/docs/user-manual/da/form-builder.md +++ b/docs/user-manual/da/form-builder.md @@ -24,10 +24,12 @@ på yderligere muligheder i behandlerindstillingerne. Som bekendt ligger dokumenter i F2 på en akt, og for at arkivere på en sag opretter behandleren derfor indledningsvist en akt som dokumentet kan knyttes til. For at arkivere på en sag skal man angive et id der entydigt udpeger en sag i F2. -Det vil være oplagt at bruge et sags-nummer +Det vil være oplagt at bruge et sagsnummer. ![Arkivér på sag](./images/generated/form-builder/webform-handler-add-archive-on-case.png) +Akttypen er som standard “Intern" og det vil formentlig aldrig blive aktuelt at ændre den til noget andet. + ![Udfyldt "Arkivér på sag"](./images/generated/form-builder/webform-handler-add-archive-on-case-filled.png) Når man gemmer tjekker systemet at sags-id'et entydigt identificerer en sag i F2: @@ -44,12 +46,24 @@ Hvis man, som antydet, redigerer en F2-behandler kan man se detaljer om hvilken ![Detaljer om sag](./images/generated/form-builder/webform-handler-archive-on-case-case-details.png) +### Indsendelse af formular + +Når en bruger indsender en besvarelse af en formular med en F2-behandler, så oprettes et job i F2-job i køen: + +![Resultatloggen efter indsendelse](./images/generated/webform-submission/submission-log.png) + +Når jobbet i køen er blevet behandlet kan man se hvad der faktisk er sket: + +![Resultatloggen efter behandling af kø](./images/generated/webform-submission/submission-log-after-queue-processed.png) + ## Arkivering på akt -Når man vil arkivere på en (eksisterende) akt skal man angive et id, fx akt-nummer, der entydigt udpeger akten i F2. +Når man vil arkivere på en (eksisterende) akt skal man angive et id, fx aktnummer, der entydigt udpeger akten i F2. ![Arkivér på akt](./images/generated/form-builder/webform-handler-add-archive-on-matter.png) +![Udfyldt "Arkivér på akt"](./images/generated/form-builder/webform-handler-add-archive-on-matter-filled.png) + Når man gemmer tjekker systemet at akt-id'et entydigt identificerer en akt i F2: ![Ugyldigt sags-id](./images/generated/form-builder/webform-handler-add-archive-on-matter-filled-invalid-matter-identifier.png) diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-advanced.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-advanced.png index 796a6bc..a49e962 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-advanced.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-advanced.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled.png index c381b97..d9e4a36 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case.png index 9b9d70c..6904da2 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-invalid-matter-identifier.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-invalid-matter-identifier.png index 65c1621..6cb2325 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-invalid-matter-identifier.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-invalid-matter-identifier.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled.png index 663df58..defbbac 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter.png index 5283ad1..411055d 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings-filled.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings-filled.png index be6173e..98de64e 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings-filled.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings-filled.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general.png index 2e02c50..ea840d6 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-added-2.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-added-2.png index 6d4e344..ec7b088 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-added-2.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-added-2.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case-details.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case-details.png index dbe95fa..13bc0e4 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case-details.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case-details.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case.png index fac18b5..4ce0635 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case.png index d6fe39a..16ba8f3 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter-details.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter-details.png index 142f988..15d688d 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter-details.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter-details.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter.png index 28bb95c..15d688d 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter.png index da06ea7..01e97e0 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter.png and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handlers.png b/docs/user-manual/da/images/generated/form-builder/webform-handlers.png index 75aaebd..f1bc941 100644 Binary files a/docs/user-manual/da/images/generated/form-builder/webform-handlers.png and b/docs/user-manual/da/images/generated/form-builder/webform-handlers.png differ diff --git a/docs/user-manual/da/images/generated/webform-submission/form-empty.png b/docs/user-manual/da/images/generated/webform-submission/form-empty.png new file mode 100644 index 0000000..d1ac1d5 Binary files /dev/null and b/docs/user-manual/da/images/generated/webform-submission/form-empty.png differ diff --git a/docs/user-manual/da/images/generated/webform-submission/form-filled.png b/docs/user-manual/da/images/generated/webform-submission/form-filled.png new file mode 100644 index 0000000..b4b72cb Binary files /dev/null and b/docs/user-manual/da/images/generated/webform-submission/form-filled.png differ diff --git a/docs/user-manual/da/images/generated/webform-submission/form-submitted.png b/docs/user-manual/da/images/generated/webform-submission/form-submitted.png new file mode 100644 index 0000000..e2eb144 Binary files /dev/null and b/docs/user-manual/da/images/generated/webform-submission/form-submitted.png differ diff --git a/docs/user-manual/da/images/generated/webform-submission/submission-log-after-queue-processed.png b/docs/user-manual/da/images/generated/webform-submission/submission-log-after-queue-processed.png new file mode 100644 index 0000000..43cb844 Binary files /dev/null and b/docs/user-manual/da/images/generated/webform-submission/submission-log-after-queue-processed.png differ diff --git a/docs/user-manual/da/images/generated/webform-submission/submission-log.png b/docs/user-manual/da/images/generated/webform-submission/submission-log.png new file mode 100644 index 0000000..7ae2168 Binary files /dev/null and b/docs/user-manual/da/images/generated/webform-submission/submission-log.png differ diff --git a/docs/user-manual/da/webform-submission.md b/docs/user-manual/da/webform-submission.md new file mode 100644 index 0000000..767d457 --- /dev/null +++ b/docs/user-manual/da/webform-submission.md @@ -0,0 +1,7 @@ +# Formularbesvarelse + +![En tom formular](./images/generated/webform-submission/form-empty.png) +![En udfyldt formular](./images/generated/webform-submission/form-filled.png) +![Besvarelse indsendt](./images/generated/webform-submission/form-submitted.png) +![Resultatloggen efter indsendelse](./images/generated/webform-submission/submission-log.png) +![Resultatloggen efter behandling af kø](./images/generated/webform-submission/submission-log-after-queue-processed.png) diff --git a/modules/os2forms_f2_examples/config/install/webform.webform.os2forms_f2_ex_000.yml b/modules/os2forms_f2_examples/config/install/webform.webform.os2forms_f2_ex_000.yml index 2df0929..b89ee7a 100644 --- a/modules/os2forms_f2_examples/config/install/webform.webform.os2forms_f2_ex_000.yml +++ b/modules/os2forms_f2_examples/config/install/webform.webform.os2forms_f2_ex_000.yml @@ -13,7 +13,7 @@ uid: 0 template: false archive: false id: os2forms_f2_ex_000 -title: F2 +title: F2 (archive on case) description: null categories: { } elements: |- @@ -212,5 +212,25 @@ access: roles: { } users: { } permissions: { } -handlers: { } +handlers: + f2_archive_on_case: + id: os2forms_f2_f2 + handler_id: f2_archive_on_case + label: 'F2 (arkivér på sag)' + notes: '' + status: true + conditions: { } + weight: 1 + settings: + archive: + attachment_element: attachment + document_title: '[webform_submission:label] ([site:url]) – [current-date:html_datetime]' + archive_target: case + archive_target_case: + case_identifier: '2026 - 1234567890' + matter_title: 'Matter: [webform_submission:label] ([site:url]) – [current-date:html_datetime]' + matter_type: Internal + additional: + states: + - completed variants: { } diff --git a/modules/os2forms_f2_examples/config/install/webform.webform.os2forms_f2_ex_001.yml b/modules/os2forms_f2_examples/config/install/webform.webform.os2forms_f2_ex_001.yml index 2aaf858..8b09a70 100644 --- a/modules/os2forms_f2_examples/config/install/webform.webform.os2forms_f2_ex_001.yml +++ b/modules/os2forms_f2_examples/config/install/webform.webform.os2forms_f2_ex_001.yml @@ -13,7 +13,7 @@ uid: 0 template: false archive: false id: os2forms_f2_ex_001 -title: F2 +title: F2 (archive on matter) description: null categories: { } elements: |- @@ -231,24 +231,4 @@ handlers: additional: states: - completed - f2_archive_on_case: - id: os2forms_f2_f2 - handler_id: f2_archive_on_case - label: 'F2 (arkivér på sag)' - notes: '' - status: true - conditions: { } - weight: 1 - settings: - archive: - attachment_element: attachment - document_title: '[webform_submission:label] ([site:url]) – [current-date:html_datetime]' - archive_target: case - archive_target_case: - case_identifier: '2026 - 1234567890' - matter_title: 'Matter: [webform_submission:label] ([site:url]) – [current-date:html_datetime]' - matter_type: Internal - additional: - states: - - completed variants: { } diff --git a/modules/os2forms_f2_test/config/install/webform.webform.os2forms_f2_test_000.yml b/modules/os2forms_f2_test/config/install/webform.webform.os2forms_f2_test_000.yml new file mode 100644 index 0000000..afbdb4a --- /dev/null +++ b/modules/os2forms_f2_test/config/install/webform.webform.os2forms_f2_test_000.yml @@ -0,0 +1,216 @@ +langcode: da +status: open +dependencies: + module: + - os2forms_f2 + enforced: + module: + - os2forms_f2_test +weight: 0 +open: null +close: null +uid: 0 +template: false +archive: false +id: os2forms_f2_test_000 +title: F2 +description: null +categories: { } +elements: |- + name: + '#type': textfield + '#title': Name + '#required': true + attachment: + '#type': os2forms_attachment + '#title': attachment + '#export_type': pdf + '#digital_signature': 0 + '#excluded_elements': { } + '#exclude_empty': 0 + '#exclude_empty_checkbox': 0 +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: '' + ajax_effect: '' + ajax_speed: null + page: true + page_submit_path: '' + page_confirm_path: '' + page_theme_name: '' + form_title: both + form_submit_once: false + form_open_message: '' + form_close_message: '' + form_exception_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + form_attributes: { } + form_method: '' + form_action: '' + share: false + share_node: false + share_theme_name: '' + share_title: true + share_page_body_attributes: { } + submission_label: '' + submission_exception_message: '' + submission_locked_message: '' + submission_log: false + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: '' + wizard_prev_button_label: '' + wizard_next_button_label: '' + wizard_toggle: false + wizard_toggle_show_label: '' + wizard_toggle_hide_label: '' + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + draft_pending_single_message: '' + draft_pending_multiple_message: '' + confirmation_type: page + confirmation_url: '' + confirmation_title: '' + confirmation_message: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } +variants: { } diff --git a/modules/os2forms_f2_test/config/install/webform.webform.os2forms_f2_test_001.yml b/modules/os2forms_f2_test/config/install/webform.webform.os2forms_f2_test_001.yml new file mode 100644 index 0000000..1a9e6f5 --- /dev/null +++ b/modules/os2forms_f2_test/config/install/webform.webform.os2forms_f2_test_001.yml @@ -0,0 +1,216 @@ +langcode: da +status: open +dependencies: + module: + - os2forms_f2 + enforced: + module: + - os2forms_f2_test +weight: 0 +open: null +close: null +uid: 0 +template: false +archive: false +id: os2forms_f2_test_001 +title: F2 +description: null +categories: { } +elements: |- + name: + '#type': textfield + '#title': Name + '#required': true + attachment: + '#type': os2forms_attachment + '#title': attachment + '#export_type': pdf + '#digital_signature': 0 + '#excluded_elements': { } + '#exclude_empty': 0 + '#exclude_empty_checkbox': 0 +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: '' + ajax_effect: '' + ajax_speed: null + page: true + page_submit_path: '' + page_confirm_path: '' + page_theme_name: '' + form_title: both + form_submit_once: false + form_open_message: '' + form_close_message: '' + form_exception_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + form_attributes: { } + form_method: '' + form_action: '' + share: false + share_node: false + share_theme_name: '' + share_title: true + share_page_body_attributes: { } + submission_label: '' + submission_exception_message: '' + submission_locked_message: '' + submission_log: false + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: '' + wizard_prev_button_label: '' + wizard_next_button_label: '' + wizard_toggle: false + wizard_toggle_show_label: '' + wizard_toggle_hide_label: '' + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + draft_pending_single_message: '' + draft_pending_multiple_message: '' + confirmation_type: page + confirmation_url: '' + confirmation_title: '' + confirmation_message: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: all + purge_days: 30 + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } +variants: { } diff --git a/modules/os2forms_f2_test/os2forms_f2_test.info.yml b/modules/os2forms_f2_test/os2forms_f2_test.info.yml new file mode 100644 index 0000000..f529986 --- /dev/null +++ b/modules/os2forms_f2_test/os2forms_f2_test.info.yml @@ -0,0 +1,8 @@ +name: "F2 test" +type: module +description: "OS2Forms: F2 test" +package: OS2Forms +core_version_requirement: ^11 +dependencies: + - os2forms_f2:os2forms_f2 + - os2forms_test_playwright:os2forms_test_playwright diff --git a/modules/os2forms_test_playwright/README.md b/modules/os2forms_test_playwright/README.md index 4ba8111..2c55014 100644 --- a/modules/os2forms_test_playwright/README.md +++ b/modules/os2forms_test_playwright/README.md @@ -47,3 +47,14 @@ Files, Public : sites/default/files Files, Temp : /tmp Drupal config : sites/default/files/sync ``` + +Control [php-cvr](https://php-vcr.github.io/php-vcr/): + +@todo + +``` shell +# Insert cassette and start recording +$ ddev exec curl http://localhost/os2forms-test-playwright/vcr/record --json '{"cassette": "my_cassette"}' +# Eject cassette +$ ddev exec curl http://localhost/os2forms-test-playwright/vcr/eject --request POST +``` diff --git a/modules/os2forms_test_playwright/os2forms_test_playwright.routing.yml b/modules/os2forms_test_playwright/os2forms_test_playwright.routing.yml index 7c9263e..e97b2fd 100644 --- a/modules/os2forms_test_playwright/os2forms_test_playwright.routing.yml +++ b/modules/os2forms_test_playwright/os2forms_test_playwright.routing.yml @@ -6,3 +6,13 @@ os2forms_test_playwright.exec: _permission: "access content" methods: POST no_cache: TRUE + +os2forms_test_playwright.vcr: + path: "/os2forms-test-playwright/vcr/{command}" + defaults: + _controller: '\Drupal\os2forms_test_playwright\Controller\VcrController' + argument: null + requirements: + _permission: "access content" + methods: POST + no_cache: TRUE diff --git a/modules/os2forms_test_playwright/os2forms_test_playwright.services.yml b/modules/os2forms_test_playwright/os2forms_test_playwright.services.yml index d4abfbb..9b9e8f4 100644 --- a/modules/os2forms_test_playwright/os2forms_test_playwright.services.yml +++ b/modules/os2forms_test_playwright/os2forms_test_playwright.services.yml @@ -5,3 +5,7 @@ services: logger.channel.os2forms_test_playwright: parent: logger.channel_base arguments: ["os2forms_test_playwright"] + + Drupal\os2forms_test_playwright\VcrOperator: + tags: + - { name: event_subscriber } diff --git a/modules/os2forms_test_playwright/src/Controller/ExecController.php b/modules/os2forms_test_playwright/src/Controller/ExecController.php index 81cca34..043fcad 100644 --- a/modules/os2forms_test_playwright/src/Controller/ExecController.php +++ b/modules/os2forms_test_playwright/src/Controller/ExecController.php @@ -45,7 +45,7 @@ public function __invoke(Request $request, string $command): Response { $this->logger->debug('Running command @command @arguments', [ '@command' => $command, - '@arguments' => $arguments, + '@arguments' => json_encode($arguments), ]); $response = new StreamedResponse(headers: [ diff --git a/modules/os2forms_test_playwright/src/Controller/VcrController.php b/modules/os2forms_test_playwright/src/Controller/VcrController.php new file mode 100644 index 0000000..2c44696 --- /dev/null +++ b/modules/os2forms_test_playwright/src/Controller/VcrController.php @@ -0,0 +1,121 @@ +toArray(); + } + catch (\Throwable $throwable) { + return $this->sendBadRequestHttpException($throwable->getMessage()); + } + + try { + $configuration = $this->operator->configure($arguments); + } + catch (CassetteException $exception) { + return $this->sendBadRequestHttpException($exception->getMessage()); + } + + $this->logger->debug('VCR configuration: @configuration', [ + '@configuration' => json_encode($configuration), + ]); + return new Response(sprintf('VCR configured: %s', json_encode($configuration)), headers: [ + 'content-type' => 'text/plain', + ]); + + case 'record': + try { + $arguments = $request->toArray(); + } + catch (\Throwable $throwable) { + return $this->sendBadRequestHttpException($throwable->getMessage()); + } + + try { + $cassette = $arguments['cassette'] ?? NULL; + if (!is_string($cassette)) { + throw new CassetteException('Missing cassette'); + } + if (!preg_match('/^[a-z][a-z0-9_-]+$/', $cassette)) { + throw new CassetteException('Invalid cassette (must match /^[a-z][a-z0-9_-]+$/)'); + } + + $cassette = $this->operator->insertCassette($cassette); + } + catch (CassetteException $exception) { + return $this->sendBadRequestHttpException($exception->getMessage()); + } + $this->logger->debug('VCR: Cassette "@cassette" inserted.', ['@cassette' => $cassette]); + + return new Response(sprintf('Cassette "%s" inserted.', $cassette), headers: [ + 'content-type' => 'text/plain', + ]); + + case 'eject': + $cassette = $this->operator->ejectCassette(); + + $this->logger->debug('VCR: Cassette "@cassette" ejected.', ['@cassette' => $cassette]); + + return new Response( + NULL !== $cassette + ? sprintf('Cassette "%s" ejected.', $cassette) + : 'No cassetted inserted.', + headers: [ + 'content-type' => 'text/plain', + ] + ); + + default: + return $this->sendBadRequestHttpException(sprintf('command not found: %s', $command)); + } + } + + /** + * Create bad request exception. + */ + private function sendBadRequestHttpException(string $message): Response { + $exception = new BadRequestHttpException($message); + $this->logger->error('VCR error: @message', [ + '@message' => $exception->getMessage(), + 'exception' => $exception, + ]); + + return new Response( + $exception->getMessage(), + status: $exception->getStatusCode(), + headers: [ + 'content-type' => 'text/plain', + ] + ); + } + +} diff --git a/modules/os2forms_test_playwright/src/Exception/CassetteException.php b/modules/os2forms_test_playwright/src/Exception/CassetteException.php new file mode 100644 index 0000000..de5c40e --- /dev/null +++ b/modules/os2forms_test_playwright/src/Exception/CassetteException.php @@ -0,0 +1,10 @@ + $configuration + * The configuration. + */ + public function configure(array $configuration): array { + $conf = $this->state->get(self::CONFIGURATION); + if (!is_array($conf)) { + $conf = []; + } + + foreach ($configuration as $name => $value) { + switch ($name) { + case self::CONFIGURATION_CASSETTE_PATH: + $conf[$name] = $value; + break; + + default: + throw new CassetteException(sprintf('Unknown configuration name: %s', $name)); + } + } + + $this->state->set(self::CONFIGURATION, $conf); + + return $this->validateConfiguration(); + } + + /** + * Insert cassette. + */ + public function insertCassette(string $cassette): string { + if (!preg_match('/^[a-z][a-z0-9_-]+$/', $cassette)) { + throw new CassetteException('Invalid cassette (must match /^[a-z][a-z0-9_-]+$/)'); + } + + $insertedCassette = $this->getInsertedCassette(); + if (NULL !== $insertedCassette) { + throw new CassetteException('Cassette already Inserted'); + } + $this->state->set(self::CASSETTE, $cassette); + + return $this->getInsertedCassette(); + } + + /** + * Eject cassette. + */ + public function ejectCassette(): ?string { + $cassette = $this->state->get(self::CASSETTE); + $this->state->delete(self::CASSETTE); + + return $cassette; + } + + /** + * Get inserted cassette. + */ + private function getInsertedCassette(): ?string { + return $this->state->get(self::CASSETTE); + } + + /** + * Start recording of a cassette has been inserted. + */ + private function recordIfCassetteInserted(): void { + $request = $this->requestStack->getCurrentRequest(); + if (str_starts_with($request->getRequestUri(), '/os2forms-test-playwright/vcr/')) { + return; + } + $cassette = $this->getInsertedCassette(); + if (NULL !== $cassette) { + $configuration = $this->validateConfiguration(); + VCR::configure() + ->setCassettePath($configuration[self::CONFIGURATION_CASSETTE_PATH]); + VCR::turnOn(); + VCR::insertCassette($cassette); + $this->addRedaction(); + } + } + + /** + * Add response redaction. + */ + private function addRedaction(): void { + // Always redact authorization and host headers + // https://php-vcr.github.io/php-vcr/howto/filter-sensitive-data#redact-a-header + VCR::getEventDispatcher()->addListener( + VCREvents::VCR_BEFORE_RECORD, + function (BeforeRecordEvent $event) { + $request = $event->getRequest(); + if ($request->hasHeader('Authorization')) { + $request->setHeader('Authorization', 'REDACTED'); + } + if ($request->hasHeader('Host')) { + $request->setHeader('Host', 'REDACTED'); + } + } + ); + } + + /** + * Kernel request listener. + */ + public function onKernelRequest(RequestEvent $event): void { + $this->recordIfCassetteInserted(); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + KernelEvents::REQUEST => ['onKernelRequest'], + ]; + } + + /** + * Validate configuration. + */ + private function validateConfiguration(): array { + $configuration = $this->state->get(self::CONFIGURATION); + $value = $configuration[self::CONFIGURATION_CASSETTE_PATH]; + if (NULL === $value) { + throw new CassetteException(sprintf('Configuration "%s" not set', self::CONFIGURATION_CASSETTE_PATH)); + } + + return $configuration; + } + +} diff --git a/patches/php-vcr/php-vcr/curl-helper.patch b/patches/php-vcr/php-vcr/curl-helper.patch new file mode 100644 index 0000000..a08a285 --- /dev/null +++ b/patches/php-vcr/php-vcr/curl-helper.patch @@ -0,0 +1,13 @@ +diff --git a/src/VCR/Util/CurlHelper.php b/src/VCR/Util/CurlHelper.php +index 7817d3d..ad701a6 100644 +--- a/src/VCR/Util/CurlHelper.php ++++ b/src/VCR/Util/CurlHelper.php +@@ -210,7 +210,7 @@ class CurlHelper + $info = $response->getCurlInfo(self::$curlInfoList[\CURLINFO_CERTINFO]) ?? []; + break; + default: +- $info = $response->getCurlInfo(self::$curlInfoList[$option]); ++ $info = $response->getCurlInfo(self::$curlInfoList[$option] ?? null); + break; + } + diff --git a/phpstan.neon b/phpstan.neon index 4068c18..0e8f037 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,8 +3,7 @@ parameters: paths: - src ignoreErrors: - - message: '#Constructor of class Drupal\\os2forms_f2\\Exception\\F2NonUniqueCaseException has an unused parameter \$message.#' - path: src/Exception/F2NonUniqueCaseException.php + - message: '#Constructor of class Drupal\\os2forms_f2\\Exception\\F2NonUnique(Case|Matter)Exception has an unused parameter \$message.#' - message: '#Method Drupal\\os2forms_f2\\.+\(\) return type has no value type specified in iterable type array.#' - message: '#Method Drupal\\os2forms_f2\\.+\(\) has parameter \$.+ with no value type specified in iterable type array.#' - message: '#Function .+\(\) return type has no value type specified in iterable type array.#' diff --git a/src/Exception/F2NonUniqueMatterException.php b/src/Exception/F2NonUniqueMatterException.php new file mode 100644 index 0000000..7f217fd --- /dev/null +++ b/src/Exception/F2NonUniqueMatterException.php @@ -0,0 +1,22 @@ +identifier); + parent::__construct($message, $code, $previous); + } + + /** + * Get matter identifier. + */ + public function getIdentifier(): string { + return $this->identifier; + } + +} diff --git a/src/Helper/F2Helper.php b/src/Helper/F2Helper.php index f9897e5..ed47ecf 100644 --- a/src/Helper/F2Helper.php +++ b/src/Helper/F2Helper.php @@ -5,6 +5,7 @@ use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\os2forms_f2\Exception\F2Exception; use Drupal\os2forms_f2\Exception\F2NonUniqueCaseException; +use Drupal\os2forms_f2\Exception\F2NonUniqueMatterException; use Drupal\os2forms_f2\Settings; use ItkDev\F2ApiClient\ApiClient; use ItkDev\F2ApiClient\Model\CaseFile; @@ -66,10 +67,16 @@ public function pingApi(): void { */ public function fetchCaseByIdentifier(string $identifier): CaseFile { $client = $this->client(); - if (filter_var($identifier, FILTER_VALIDATE_INT)) { - return $client->caseById((int) $identifier); + + // First, we try to get a case by number (which is a string) + try { + return $client->caseByCaseNumber($identifier); + } + catch (\Throwable) { + // Ignore any errors. } + // Search for case. $cases = $client->caseSearch($identifier); if (1 !== count($cases)) { throw new F2NonUniqueCaseException($identifier); @@ -85,20 +92,31 @@ public function fetchCaseByIdentifier(string $identifier): CaseFile { public function fetchMatterByIdentifier(string $identifier): Matter { $client = $this->client(); - // @todo Both matter ID and number are integers … - // @code - // if (filter_var($identifier, FILTER_VALIDATE_INT)) { - // return $client->matterById((int) $identifier); - // } - // @endcode - return $client->matterByMatterNumber($identifier); + // First, we try to get a matter by number (which is an integer) + if (filter_var($identifier, FILTER_VALIDATE_INT)) { + try { + return $client->matterByMatterNumber($identifier); + } + catch (\Throwable) { + // Ignore any errors. + } + } + + // Search for matter. + $matters = $client->matterSearch($identifier); + if (1 !== count($matters)) { + throw new F2NonUniqueMatterException($identifier); + } + $matter = reset($matters); + + return $client->matterById($this->getId($matter->id)); } /** * Get integer ID from F2 item URL. */ private function getId(string $url): int { - if (preg_match('@/cases/(?\d+)$@', $url, $matches)) { + if (preg_match('@/(?\d+)$@', $url, $matches)) { return (int) $matches['id']; } diff --git a/src/Helper/WebformHelperF2.php b/src/Helper/WebformHelperF2.php index e835004..6a70868 100644 --- a/src/Helper/WebformHelperF2.php +++ b/src/Helper/WebformHelperF2.php @@ -130,7 +130,9 @@ public function createJob(WebformSubmissionInterface $webformSubmission, Webform $queue = $this->loadQueue(); $queue->enqueueJob($job); $context['@queue'] = $queue->id(); - $this->notice('F2 job added to the queue @queue.', $context); + $this->notice('F2 job (archive on @archive_target) added to the queue @queue.', $context + [ + '@archive_target' => $handlerSettings->archive?->archiveTarget?->value ?? '', + ]); return $job; } diff --git a/src/Plugin/WebformHandler/WebformHandlerAdditionalStatesTrait.php b/src/Plugin/WebformHandler/WebformHandlerAdditionalStatesTrait.php index b89728e..2c59396 100644 --- a/src/Plugin/WebformHandler/WebformHandlerAdditionalStatesTrait.php +++ b/src/Plugin/WebformHandler/WebformHandlerAdditionalStatesTrait.php @@ -87,7 +87,6 @@ private function additionalStatesBuildConfigurationForm(array &$form, FormStateI WebformSubmissionInterface::STATE_CONVERTED => $this->t('anonymous submission is converted to authenticated.', options: ['context' => 'os2forms_f2']), WebformSubmissionInterface::STATE_COMPLETED => $this->t('submission is completed.', options: ['context' => 'os2forms_f2']), WebformSubmissionInterface::STATE_UPDATED => $this->t('submission is updated.', options: ['context' => 'os2forms_f2']), - WebformSubmissionInterface::STATE_DELETED => $this->t('submission is deleted.', options: ['context' => 'os2forms_f2']), WebformSubmissionInterface::STATE_LOCKED => $this->t('submission is locked.', options: ['context' => 'os2forms_f2']), ], '#access' => !$resultsDisabled, diff --git a/src/Plugin/WebformHandler/WebformHandlerF2.php b/src/Plugin/WebformHandler/WebformHandlerF2.php index 4243595..44446b7 100644 --- a/src/Plugin/WebformHandler/WebformHandlerF2.php +++ b/src/Plugin/WebformHandler/WebformHandlerF2.php @@ -7,6 +7,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; use Drupal\os2forms_f2\Exception\F2NonUniqueCaseException; +use Drupal\os2forms_f2\Exception\F2NonUniqueMatterException; use Drupal\os2forms_f2\Helper\F2Helper; use Drupal\os2forms_f2\Helper\WebformHelperF2; use Drupal\os2forms_f2\Settings; @@ -159,7 +160,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ArchiveTargetCase::CASE_IDENTIFIER => [ '#type' => 'textfield', '#title' => $this->t('Case identifier', options: ['context' => 'os2forms_f2']), - '#description' => $this->t('Case number or ID that uniquely identifies a single case.', options: ['context' => 'os2forms_f2']), + '#description' => $this->t('Case number or search terms that uniquely identify a single case.', options: ['context' => 'os2forms_f2']), '#default_value' => $settings->archiveTargetCase?->caseIdentifier, '#states' => [ @@ -203,7 +204,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ], '#title' => $this->t('Matter type', options: ['context' => 'os2forms_f2']), '#description' => $this->t('The type of the matter that will be created for the document.', options: ['context' => 'os2forms_f2']), - '#default_value' => $settings->archiveTargetCase?->matterType, + '#default_value' => $settings->archiveTargetCase->matterType ?? Matter::TYPE_INTERNAL, '#states' => [ 'required' => [ @@ -238,7 +239,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ArchiveTargetMatter::MATTER_IDENTIFIER => [ '#type' => 'textfield', '#title' => $this->t('Matter identifier', options: ['context' => 'os2forms_f2']), - '#description' => $this->t('Matter number or ID that uniquely identifies a single matter.', options: ['context' => 'os2forms_f2']), + '#description' => $this->t('Matter number or search terms that uniquely identify a single matter.', options: ['context' => 'os2forms_f2']), '#default_value' => $settings->archiveTargetMatter?->matterIdentifier, '#states' => [ @@ -365,6 +366,11 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form try { $this->f2->fetchMatterByIdentifier($matterIdentifier); } + catch (F2NonUniqueMatterException) { + $setError($key, t('Identifier "@matter_identifier" does not uniquely identify a matter.', [ + '@matter_identifier' => $matterIdentifier, + ], options: ['context' => 'os2forms_f2'])); + } catch (\Throwable $throwable) { $setError($key, t('Cannot get matter by identifier "@matter_identifier" (@message).', [ '@matter_identifier' => $matterIdentifier, diff --git a/tests/Playwright/tests/user-manual-images.spec.ts b/tests/Playwright/tests/user-manual-images.spec.ts index aa428f5..251a7b0 100644 --- a/tests/Playwright/tests/user-manual-images.spec.ts +++ b/tests/Playwright/tests/user-manual-images.spec.ts @@ -11,10 +11,19 @@ dotenv.config({ path: path.resolve(__dirname, "../.env.local") }); const submitSelector = 'input[data-drupal-selector="edit-submit"]'; test("user manual images – administrator (da)", async ({ page }) => { - const locale = "da"; const context = "administrator"; + const locale = "da"; let screenshotCounter = 0; + // @todo Do something about this code duplication! + + const goto = async (url: string) => { + url = `/${locale}/${url}`; + console.debug(` +going to ${url} …`); + return page.goto(url); + }; + const screenshot = async (name: string, selector: string = "main") => { const filePath = path.join( `/docs/user-manual/${locale}/images/generated/`, @@ -22,7 +31,11 @@ test("user manual images – administrator (da)", async ({ page }) => { // String(screenshotCounter++).padStart(3, "0") + "-" + name + ".png", ); - console.debug(filePath); + const pagePath = page.url().replace(process.env.BASEURL, ""); + console.debug(` +screenshooting ${selector} + on ${pagePath} + into ${filePath}`); await page.locator(selector).screenshot({ path: filePath }); }; @@ -33,24 +46,26 @@ test("user manual images – administrator (da)", async ({ page }) => { }); }; - const goto = async (url: string) => { - url = `/${locale}/${url}`; - console.debug(`going to ${url} …`); - return page.goto(url); - }; - - const redact = async (selector: string, length: int = 8) => { - return page.evaluate( - ({ selector, length }) => { - const element = document.querySelector(selector); - element.value = "•".repeat(length); + const vcrRecord = async (cassette: string) => { + await page.request.post("/os2forms-test-playwright/vcr/configure", { + data: { + cassette_path: "../tests/vcr-cassettes/", + }, + timeout: 4000, + }); - // https://css-tricks.com/fun-with-blurred-text/ - // element.style.color = 'transparent'; - // element.style.textShadow ='0 0 5px rgba(0,0,0,0.5)'; + return page.request.post("/os2forms-test-playwright/vcr/record", { + data: { + cassette, }, - { selector, length }, - ); + timeout: 4000, + }); + }; + + const vcrEject = async (cassette: string) => { + return page.request.post("/os2forms-test-playwright/vcr/eject", { + timeout: 4000, + }); }; await goto("admin/os2forms_f2/settings"); @@ -72,11 +87,7 @@ test("user manual images – administrator (da)", async ({ page }) => { await page.locator('[name="f2_api[uri]"]').focus(); await page.locator('[name="f2_api[uri]"]').blur(); - await redact('[name="f2_api[username]"]', 16); - await redact('[name="f2_api[secret]"]', 16); - await screenshot("f2-settings-filled"); - return; await page.locator(submitSelector).click(); await screenshot("f2-settings-saved"); @@ -86,6 +97,8 @@ test("user manual images – administrator (da)", async ({ page }) => { .click(); await screenshot("f2-settings-api-pinged-error", ".messages__wrapper"); + await vcrRecord(context + "-f2-settings"); + // Refill with proper values await page.locator('[name="f2_api[uri]"]').fill(process.env.F2_API_URI); await page @@ -103,13 +116,17 @@ test("user manual images – administrator (da)", async ({ page }) => { .locator('input[data-drupal-selector="edit-action-ping-api"]') .click(); await screenshot("f2-settings-api-pinged-success", ".messages__wrapper"); + + await vcrEject(); }); test("user manual images – form-builder (da)", async ({ page }) => { - const locale = "da"; const context = "form-builder"; + const locale = "da"; let screenshotCounter = 0; + // @todo Do something about this code duplication! + const goto = async (url: string) => { url = `/${locale}/${url}`; console.debug(` @@ -139,11 +156,35 @@ screenshooting ${selector} }); }; + const vcrRecord = async (cassette: string) => { + await page.request.post("/os2forms-test-playwright/vcr/configure", { + data: { + cassette_path: "../tests/vcr-cassettes/", + }, + timeout: 4000, + }); + + return page.request.post("/os2forms-test-playwright/vcr/record", { + data: { + cassette, + }, + timeout: 4000, + }); + }; + + const vcrEject = async (cassette: string) => { + return page.request.post("/os2forms-test-playwright/vcr/eject", { + timeout: 4000, + }); + }; + + await vcrRecord(context + "-add-handler"); + // Add handler - await goto("admin/structure/webform/manage/os2forms_f2_ex_000/handlers"); + await goto("admin/structure/webform/manage/os2forms_f2_test_000/handlers"); await page .locator( - '[data-drupal-link-system-path="admin/structure/webform/manage/os2forms_f2_ex_000/handlers/add"]', + '[data-drupal-link-system-path="admin/structure/webform/manage/os2forms_f2_test_000/handlers/add"]', ) .click(); await page.locator(".form-element--type-search").fill("F2"); @@ -152,7 +193,7 @@ screenshooting ${selector} await screenshot("webform-handler-select-f2", ".webform-ui-dialog"); await goto( - "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/add/os2forms_f2_f2", + "admin/structure/webform/manage/os2forms_f2_test_000/handlers/add/os2forms_f2_f2", ); await screenshot("webform-handler-add-general", "#webform-handler-form"); await screenshot( @@ -162,7 +203,7 @@ screenshooting ${selector} // Archive on case await goto( - "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/add/os2forms_f2_f2", + "admin/structure/webform/manage/os2forms_f2_test_000/handlers/add/os2forms_f2_f2", ); await page.locator('[name="label"]').fill("F2 – arkivér på sag"); @@ -231,20 +272,20 @@ screenshooting ${selector} ".messages__wrapper", ); - await goto("admin/structure/webform/manage/os2forms_f2_ex_000/handlers"); + await goto("admin/structure/webform/manage/os2forms_f2_test_000/handlers"); await screenshot("webform-handler-added", ".page-wrapper"); await goto( - "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/f2_arkiver_paa_sag/edit", + "admin/structure/webform/manage/os2forms_f2_test_000/handlers/f2_arkiver_paa_sag/edit", ); await screenshot( "webform-handler-archive-on-case-case-details", - // "#edit-settings-os2forms-f2-f2-archive-archive-target-case", + "#edit-settings-os2forms-f2-f2-archive-archive-target-case", ); // Archive on matter await goto( - "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/add/os2forms_f2_f2", + "admin/structure/webform/manage/os2forms_f2_test_001/handlers/add/os2forms_f2_f2", ); await page.locator('[name="label"]').fill("F2 – arkivér på akt"); @@ -300,21 +341,19 @@ screenshooting ${selector} ".messages__wrapper", ); - await goto("admin/structure/webform/manage/os2forms_f2_ex_000/handlers"); + await goto("admin/structure/webform/manage/os2forms_f2_test_001/handlers"); await screenshot("webform-handler-added-2", ".page-wrapper"); await goto( - "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/f2_arkiver_paa_akt/edit", + "admin/structure/webform/manage/os2forms_f2_test_001/handlers/f2_arkiver_paa_akt/edit", ); await screenshot( "webform-handler-archive-on-matter-matter-details", "#edit-settings-os2forms-f2-f2-archive-archive-target-matter", ); - // Existing handler - await goto( - "admin/structure/webform/manage/os2forms_f2_ex_001/handlers/f2_archive_on_case/edit", + "admin/structure/webform/manage/os2forms_f2_test_000/handlers/f2_arkiver_paa_sag/edit", ); await screenshot( "webform-handler-archive-on-case", @@ -326,7 +365,7 @@ screenshooting ${selector} ); await goto( - "admin/structure/webform/manage/os2forms_f2_ex_001/handlers/f2_archive_on_matter/edit", + "admin/structure/webform/manage/os2forms_f2_test_001/handlers/f2_arkiver_paa_akt/edit", ); await screenshot( "webform-handler-archive-on-matter", @@ -337,15 +376,117 @@ screenshooting ${selector} "#edit-settings-os2forms-f2-f2-archive-archive-target-matter", ); - await goto("admin/structure/webform/manage/os2forms_f2_ex_001/handlers"); + await goto("admin/structure/webform/manage/os2forms_f2_test_000/handlers"); await screenshot("webform-handlers", "#webform-handlers"); await goto( - "admin/structure/webform/manage/os2forms_f2_ex_001/handlers/f2_archive_on_case/edit", + "admin/structure/webform/manage/os2forms_f2_test_000/handlers/f2_arkiver_paa_sag/edit", ); await page.getByRole("tab", { name: "Avanceret" }).click(); await expect(page.getByText("Yderligere indstillinger")).toBeVisible(); await page.getByText("Yderligere indstillinger").click(); await page.getByText("Yderligere indstillinger").blur(); await screenshot("webform-handler-add-advanced", "#webform-handler-form"); + + await vcrEject(); +}); + + +test("webform submission", async ({ page }) => { + const context = "webform-submission"; + const locale = "da"; + let screenshotCounter = 0; + + // @todo Do something about this code duplication! + + const goto = async (url: string) => { + url = `/${locale}/${url}`; + console.debug(` +going to ${url} …`); + return page.goto(url); + }; + + const screenshot = async (name: string, selector: string = "main") => { + const filePath = path.join( + `/docs/user-manual/${locale}/images/generated/`, + context, + // String(screenshotCounter++).padStart(3, "0") + "-" + + name + ".png", + ); + const pagePath = page.url().replace(process.env.BASEURL, ""); + console.debug(` +screenshooting ${selector} + on ${pagePath} + into ${filePath}`); + await page.locator(selector).screenshot({ path: filePath }); + }; + + const drush = async (args: string[]) => { + return page.request.post("/os2forms-test-playwright/exec/drush", { + data: args, + timeout: 4000, + }); + }; + + const vcrRecord = async (cassette: string) => { + await page.request.post("/os2forms-test-playwright/vcr/configure", { + data: { + cassette_path: "../tests/vcr-cassettes/", + }, + timeout: 4000, + }); + + return page.request.post("/os2forms-test-playwright/vcr/record", { + data: { + cassette, + }, + timeout: 4000, + }); + }; + + const vcrEject = async (cassette: string) => { + return page.request.post("/os2forms-test-playwright/vcr/eject", { + timeout: 4000, + }); + }; + + await drush([ + "sql:query", + ` +TRUNCATE advancedqueue; +TRUNCATE webform_submission_log; +TRUNCATE webform_submission_data; +TRUNCATE webform_submission; +TRUNCATE os2forms_f2_f2_item; +ALTER TABLE webform_submission AUTO_INCREMENT = 1; +UPDATE webform SET next_serial = 1; +`, + ]); + + await goto("webform/os2forms_f2_test_000"); + await screenshot("form-empty"); + + await page + .locator('input[data-drupal-selector="edit-name"]') + .fill("Anders And"); + await screenshot("form-filled"); + + await page.locator('input[data-drupal-selector="edit-submit"]').click(); + await screenshot("form-submitted"); + + await goto( + "admin/structure/webform/manage/os2forms_f2_test_000/submission/1/log", + ); + await screenshot("submission-log"); + + await vcrRecord(context); + + await drush(["advancedqueue:queue:process", "os2forms_f2"]); + + await goto( + "admin/structure/webform/manage/os2forms_f2_test_000/submission/1/log", + ); + await screenshot("submission-log-after-queue-processed"); + + await vcrEject(); }); diff --git a/tests/Playwright/tests/user-manual-submission.spec.ts b/tests/Playwright/tests/user-manual-submission.spec.ts deleted file mode 100644 index a4e489c..0000000 --- a/tests/Playwright/tests/user-manual-submission.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { test, expect } from "@playwright/test"; - -// We want run and complete the administrator stuff before running the form-builder stuff. -// https://playwright.dev/docs/test-parallel -test.describe.configure({ mode: "serial" }); - -import dotenv from "dotenv"; -import path from "path"; -import * as child_process from "node:child_process"; -dotenv.config({ path: path.resolve(__dirname, "../.env.local") }); - -test("webform submission", async ({ page }) => { - const locale = "da"; - const context = "webform-submission"; - let screenshotCounter = 0; - - const screenshot = async ( - name: string, - selector: string = ".page-wrapper", - ) => { - const filePath = path.join( - `/docs/user-manual/${locale}/images/generated/`, - context, - String(screenshotCounter++).padStart(3, "0") + "-" + name + ".png", - ); - await page.locator(selector).screenshot({ path: filePath }); - }; - - const drush = async (args: string[]) => { - return page.request.post("/os2forms-test-playwright/exec/drush", { - data: args, - timeout: 4000, - }); - }; - - const goto = async (url: string) => { - url = `/${locale}/${url}`; - console.debug(`going to ${url} …`); - return page.goto(url); - }; - - await drush([ - "sql:query", - [ - "TRUNCATE advancedqueue", - "TRUNCATE webform_submission_log", - "TRUNCATE webform_submission_data", - "TRUNCATE webform_submission", - "ALTER TABLE webform_submission AUTO_INCREMENT = 1", - "UPDATE webform SET next_serial = 1", - ].join(";"), - ]); - - await goto("webform/os2forms_f2_ex_001"); - await screenshot("form-empty"); - - await page - .locator('input[data-drupal-selector="edit-name"]') - .fill("Anders And"); - await screenshot("form-filled"); - - await page.locator('input[data-drupal-selector="edit-submit"]').click(); - await screenshot("form-submitted"); - - await goto( - "admin/structure/webform/manage/os2forms_f2_ex_001/submission/1/log", - ); - await screenshot("submission-log"); - - await drush(["advancedqueue:queue:process", "os2forms_f2"]); - - await goto( - "admin/structure/webform/manage/os2forms_f2_ex_001/submission/1/log", - ); - await screenshot("submission-log-after-queue-processed"); -}); diff --git a/translations/os2forms_f2.da.po b/translations/os2forms_f2.da.po index ff9bd7a..818c062 100644 --- a/translations/os2forms_f2.da.po +++ b/translations/os2forms_f2.da.po @@ -3,8 +3,8 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" -"POT-Creation-Date: 2026-10-27 23:57+0100\n" -"PO-Revision-Date: 2026-10-27 23:57+0100\n" +"POT-Creation-Date: 2026-11-02 12:54+0100\n" +"PO-Revision-Date: 2026-11-02 12:54+0100\n" "Last-Translator: NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" @@ -29,10 +29,6 @@ msgctxt "os2forms_f2" msgid "submission is completed." msgstr "besvarelse afsluttes." -msgctxt "os2forms_f2" -msgid "submission is deleted." -msgstr "besvarelse slettes." - msgctxt "os2forms_f2" msgid "submission is locked." msgstr "besvarelse låses." @@ -90,8 +86,8 @@ msgid "Case identifier" msgstr "Sags-id" msgctxt "os2forms_f2" -msgid "Case number or ID that uniquely identifies a single case." -msgstr "Sagsnummer eller -id der entydigt identificerer en enkelt sag." +msgid "Case number or search terms that uniquely identify a single case." +msgstr "Sagsnummer eller søgetermer der entydigt identificerer en enkelt sag." msgctxt "os2forms_f2" msgid "Configure the OS2Forms F2 module" @@ -149,6 +145,10 @@ msgctxt "os2forms_f2" msgid "Identifier \"@case_identifier\" does not uniquely identify a case." msgstr "Id'et \"@case_identifier\" identificerer ikke entydigt en sag." +msgctxt "os2forms_f2" +msgid "Identifier \"@matter_identifier\" does not uniquely identify a matter." +msgstr "Id'et \"@matter_identifier\" identificerer ikke entydigt en akt." + msgctxt "os2forms_f2" msgid "Inbound" msgstr "Indgående" @@ -178,8 +178,8 @@ msgid "Matter identifier" msgstr "Akt-id" msgctxt "os2forms_f2" -msgid "Matter number or ID that uniquely identifies a single matter." -msgstr "Aktnummer eller -id der entydigt identificerer en enkelt akt." +msgid "Matter number or search terms that uniquely identify a single matter." +msgstr "Aktnummer eller søgetermer der entydigt identificerer en enkelt akt." msgctxt "os2forms_f2" msgid "Matter title"