From 3617df3fa32d0163d89d0daf1ff407d7e7390766 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Mon, 16 Dec 2024 10:14:34 -0400 Subject: [PATCH 1/3] Prepare device farm artifacts --- .../workflows/bitbar-prepare-artifacts.yaml | 111 +++++++++++++++++ .github/workflows/bitbar-results.yaml | 104 ++++++++++++++++ .github/workflows/bitbar-run.yaml | 113 ++++++++++++++++++ .github/workflows/ci.yaml | 38 ++++++ .../davinci/DavinciAndroidTest.kt | 2 +- 5 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/bitbar-prepare-artifacts.yaml create mode 100644 .github/workflows/bitbar-results.yaml create mode 100644 .github/workflows/bitbar-run.yaml diff --git a/.github/workflows/bitbar-prepare-artifacts.yaml b/.github/workflows/bitbar-prepare-artifacts.yaml new file mode 100644 index 0000000..5f41c7d --- /dev/null +++ b/.github/workflows/bitbar-prepare-artifacts.yaml @@ -0,0 +1,111 @@ +# +# Copyright (c) 2024 Ping Identity. All rights reserved. +# +# This software may be modified and distributed under the terms +# of the MIT license. See the LICENSE file for details. +# + +name: Prepare BitBar Artifacts +on: + workflow_call: + secrets: + SIGNING_KEYSTORE: + description: 'Needed for signing the apk artifacts' + required: true + SIGNING_ALIAS: + description: 'Needed for signing the apk artifacts' + required: true + SIGNING_KEYSTORE_PASSWORD: + description: 'Needed for signing the apk artifacts' + required: true + SIGNING_KEY_PASSWORD: + description: 'Needed for signing the apk artifacts' + required: true + SLACK_WEBHOOK: + description: Slack Notifier Incoming Webhook + required: true +jobs: + prepare-device-farm-artifacts: + runs-on: macos-latest + + steps: + # Clone the repo + - name: Clone the repository + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{github.event.pull_request.head.repo.full_name}} + fetch-depth: 0 + + # Setup JDK and cache and restore dependencies. + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + + # Build apk files + - name: Prepare device farm artifacts + run: ./gradlew assembleDebugAndroidTest --stacktrace --no-daemon + + # List the available build tools versions see https://github.com/r0adkll/sign-android-release/issues/84 + - name: List build tools versions + run: ls /Users/runner/Library/Android/sdk/build-tools/ + + # Sign app-debug-androidTest.apk + - name: Sign app-debug-androidTest.apk + uses: r0adkll/sign-android-release@v1 + with: + releaseDirectory: samples/app/build/outputs/apk/androidTest/debug + signingKeyBase64: ${{ secrets.SIGNING_KEYSTORE }} + alias: ${{ secrets.SIGNING_ALIAS }} + keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} + keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} + env: + BUILD_TOOLS_VERSION: "35.0.0" + + # Sign davinci-debug-androidTest.apk + - name: Sign davinci-debug-androidTest.apk + uses: r0adkll/sign-android-release@v1 + with: + releaseDirectory: davinci/build/outputs/apk/androidTest/debug + signingKeyBase64: ${{ secrets.SIGNING_KEYSTORE }} + alias: ${{ secrets.SIGNING_ALIAS }} + keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} + keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} + env: + BUILD_TOOLS_VERSION: "35.0.0" + + # Publish the signed APKs as build artifacts + - name: Publish app-debug-androidTest.apk + uses: actions/upload-artifact@v4 + if: success() + with: + name: app-debug-androidTest-signed.apk + path: samples/app/build/outputs/apk/androidTest/debug/app-debug-androidTest-signed.apk + + - name: Publish davinci-debug-androidTest.apk + uses: actions/upload-artifact@v4 + if: success() + with: + name: davinci-debug-androidTest-signed.apk + path: davinci/build/outputs/apk/androidTest/debug/davinci-debug-androidTest-signed.apk + + # Send slack notification ONLY if any of the steps above fail + - name: Send slack notification + uses: 8398a7/action-slack@v3 + with: + status: custom + fields: all + custom_payload: | + { + attachments: [{ + title: ':no_entry: Failed to prepare BitBar test artifacts', + color: 'danger', + text: `\nWorkflow: ${process.env.AS_WORKFLOW} -> ${process.env.AS_JOB}\nPull request: ${process.env.AS_PULL_REQUEST}\nCommit: ${process.env.AS_COMMIT} by ${process.env.AS_AUTHOR}`, + }] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + if: failure() \ No newline at end of file diff --git a/.github/workflows/bitbar-results.yaml b/.github/workflows/bitbar-results.yaml new file mode 100644 index 0000000..e743df1 --- /dev/null +++ b/.github/workflows/bitbar-results.yaml @@ -0,0 +1,104 @@ +# +# Copyright (c) 2024 Ping Identity. All rights reserved. +# +# This software may be modified and distributed under the terms +# of the MIT license. See the LICENSE file for details. +# + +name: BitBar Test Results + +on: + workflow_call: + inputs: + bitbar-project-id: + description: BitBar Project ID + type: string + required: true + bitbar-run-id: + description: BitBar Run ID + type: string + required: true + outputs: + bitbar-run-url: + description: "The BitBar run URL" + value: ${{ jobs.bitbar-run.outputs.bitbar_run_url }} + secrets: + BITBAR_API_KEY: + description: BitBar API Key + required: true + SLACK_WEBHOOK: + description: Slack Notifier Incoming Webhook + required: true + +jobs: + bitbar-results: + runs-on: ubuntu-latest + steps: + - name: "Workflow inputs:" + run: | + echo "Project ID - ${{ inputs.bitbar-project-id }}" + echo "Run ID - ${{ inputs.bitbar-run-id }}" + + - name: Wait for BitBar test run to finish... + timeout-minutes: 60 + run: | + ( + until [ "$(curl -s -u ${{ secrets.BITBAR_API_KEY }}: https://cloud.bitbar.com/api/me/projects/${{ inputs.bitbar-project-id }}/runs/${{ inputs.bitbar-run-id }} | jq -r '.state')" == "FINISHED" ]; + do + echo "Waiting for BitBar Results. Sleeping for 10 seconds..." + sleep 10 + done + ) + echo "BITBAR_TEST_RUN_RESULT=$(curl -s -u ${{ secrets.BITBAR_API_KEY }}: https://cloud.bitbar.com/api/me/projects/${{ inputs.bitbar-project-id }}/runs/${{ inputs.bitbar-run-id }})" >> $GITHUB_ENV + + # Get the outcome json of the test run. + - name: Parse test run outcome json + run: | + echo ${{ env.BITBAR_TEST_RUN_RESULT }} + echo "===========================================" + echo "projectName: $(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq '.projectName')" + echo "displayName: $(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq '.displayName')" + echo "executedTestCaseCount: $(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq '.executedTestCaseCount')" + echo "successfulTestCaseCount: $(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq '.successfulTestCaseCount')" + echo "failedTestCaseCount: $(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq '.failedTestCaseCount')" + echo "runningDeviceCount: $(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq '.runningDeviceCount')" + echo "totalDeviceCount: $(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq '.totalDeviceCount')" + echo "===========================================" + echo "BITBAR_PROJECT_NAME=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.projectName')" >> $GITHUB_ENV + echo "BITBAR_RUN_DISPLAY_NAME=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.displayName')" >> $GITHUB_ENV + echo "BITBAR_RUN_NUMBER=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.number')" >> $GITHUB_ENV + echo "BITBAR_EXECUTED_TESTS_COUNT=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.executedTestCaseCount')" >> $GITHUB_ENV + echo "BITBAR_SUCCESS_TESTS_COUNT=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.successfulTestCaseCount')" >> $GITHUB_ENV + echo "BITBAR_FAILED_TESTS_COUNT=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.failedTestCaseCount')" >> $GITHUB_ENV + echo "BITBAR_SUCCESS_RATIO=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.successRatio')" >> $GITHUB_ENV + echo "BITBAR_DEVICE_COUNT=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.deviceCount')" >> $GITHUB_ENV + echo "BITBAR_DEVICE_GROUP_NAME=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.config.usedDeviceGroupName')" >> $GITHUB_ENV + echo "BITBAR_TEST_RUN_URL=$(echo '${{ env.BITBAR_TEST_RUN_RESULT }}' | jq -r '.uiLink')" >> $GITHUB_ENV + + # Check for failures and set the outcome of the workflow + - name: Set job status + run: | + if [[ ${{env.BITBAR_FAILED_TESTS_COUNT}} != '0' ]]; then + exit 1 + else + exit 0 + fi + + # Send slack notification with result status + - name: Send slack notification + uses: 8398a7/action-slack@v3 + with: + status: custom + fields: all + custom_payload: | + { + attachments: [{ + title: 'BitBar ${{ env.BITBAR_PROJECT_NAME }} - #${{ env.BITBAR_RUN_NUMBER }}', + title_link: '${{ env.BITBAR_TEST_RUN_URL }}', + color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning', + text: `\nTest summary: ${{ job.status }} in ${process.env.AS_TOOK}\nPassed: ${{ env.BITBAR_SUCCESS_TESTS_COUNT }}, Failed: ${{ env.BITBAR_FAILED_TESTS_COUNT }}\nDevice group: ${{ env.BITBAR_DEVICE_GROUP_NAME }}, Number of devices: ${{ env.BITBAR_DEVICE_COUNT }}\n\nWorkflow: ${process.env.AS_WORKFLOW} -> ${process.env.AS_JOB}\nPull request: ${process.env.AS_PULL_REQUEST}\nCommit: ${process.env.AS_COMMIT} by ${process.env.AS_AUTHOR}\nMessage: ${process.env.AS_MESSAGE}`, + }] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + if: always() \ No newline at end of file diff --git a/.github/workflows/bitbar-run.yaml b/.github/workflows/bitbar-run.yaml new file mode 100644 index 0000000..596ece8 --- /dev/null +++ b/.github/workflows/bitbar-run.yaml @@ -0,0 +1,113 @@ +# +# Copyright (c) 2024 Ping Identity. All rights reserved. +# +# This software may be modified and distributed under the terms +# of the MIT license. See the LICENSE file for details. +# + +name: Run Tests in BitBar +on: + workflow_call: + inputs: + bitbar-project-id: + description: BitBar project id + type: string + bitbar-device-group-id: + description: The device group id to run tests against + type: string + bitbar-os-type: + description: OS Type + type: string + default: ANDROID + bitbar-framework-id: + description: The framework id + type: string + default: 252 + outputs: + bitbar-run-id: + description: The newly created run id in BitBar + value: ${{ jobs.bitbar-run.outputs.bitbar_run_id }} + secrets: + BITBAR_API_KEY: + description: BitBar API Key + required: true + SLACK_WEBHOOK: + description: Slack Notifier Incoming Webhook + required: true +jobs: + bitbar-run: + runs-on: ubuntu-latest + outputs: + bitbar_run_id: ${{ steps.bitbar_run_id.outputs.bitbar_run_id }} + + steps: + # Get the test artifacts prepared in previous step + - name: Get the app-debug-androidTest-signed.apk BitBar artifact + uses: actions/download-artifact@v4 + with: + name: app-debug-androidTest-signed.apk + + - name: Get the davinci-debug-androidTest-signed.apk BitBar artifact + uses: actions/download-artifact@v4 + with: + name: davinci-debug-androidTest-signed.apk + + - name: Unzip app-debug-androidTest-signed.apk and davinci-debug-androidTest-signed.apk + run: | + unzip -o app-debug-androidTest-signed.apk + unzip -o davinci-debug-androidTest-signed.apk + + - name: Upload app-debug-androidTest-signed.apk to BitBar + run: | + echo "BITBAR_APP_FILE_ID=$(curl -X POST -u ${{ secrets.BITBAR_API_KEY }}: https://cloud.bitbar.com/api/me/files -F "file=@app-debug-androidTest-signed.apk" | jq '.id')" >> $GITHUB_ENV + + - name: Upload davinci-debug-androidTest-signed.apk to BitBar + run: | + echo "BITBAR_TEST_FILE_ID=$(curl -X POST -u ${{ secrets.BITBAR_API_KEY }}: https://cloud.bitbar.com/api/me/files -F "file=@davinci-debug-androidTest-signed.apk" | jq '.id')" >> $GITHUB_ENV + + - name: Prepare BitBar run configuration file + run: | + ( + echo "{" + echo "\"osType\":\"${{ inputs.bitbar-os-type }}\"," + echo "\"projectId\":${{ inputs.bitbar-project-id }}," + echo "\"frameworkId\":${{ inputs.bitbar-framework-id }}," + echo "\"deviceGroupId\":${{ inputs.bitbar-device-group-id }}," + echo "\"files\":[" + echo " {\"id\":${{ env.BITBAR_APP_FILE_ID }}, \"action\": \"INSTALL\"}," + echo " {\"id\":${{ env.BITBAR_TEST_FILE_ID }}, \"action\": \"RUN_TEST\"}" + echo "]" + echo "}" + ) > bitbar-run-configuration.txt + + - name: Display bitbar-run-configuration.txt + run: | + cat bitbar-run-configuration.txt + + # Start the test run + - name: Start a test run + run: | + echo "BITBAR_TEST_RUN_ID=$(curl -H 'Content-Type: application/json' -u ${{ secrets.BITBAR_API_KEY }}: https://cloud.bitbar.com/api/me/runs --data-binary @bitbar-run-configuration.txt | jq '.id')" >> $GITHUB_ENV + + # Set bitbar_run_id as output of the workflow. This is needed for the next workflow to continue + - name: Set the bitbar_run_id output + id: bitbar_run_id + run: echo "::set-output name=bitbar_run_id::${{ env.BITBAR_TEST_RUN_ID }}" + + # Send slack notification ONLY if any of the steps above fail + - name: Send slack notification + uses: 8398a7/action-slack@v3 + with: + status: custom + fields: all + custom_payload: | + { + attachments: [{ + title: ':no_entry: Failed to start BitBar test run!', + color: 'danger', + text: `\nWorkflow: ${process.env.AS_WORKFLOW} -> ${process.env.AS_JOB}\nPull request: ${process.env.AS_PULL_REQUEST}\nCommit: ${process.env.AS_COMMIT} by ${process.env.AS_AUTHOR}`, + }] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + if: failure() \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b1080e8..47b57f7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -56,6 +56,44 @@ jobs: runs-on: ubuntu-latest testrail-run-id: ${{needs.create-testrail-run.outputs.testrail-run-id}} + # Build and sign BitBar test artifacts (app-debug-androidTest-signed.apk and davinci-debug-androidTest-signed.apk) + # Skip this step for PRs created by dependabot + bitbar-prepare-artifacts: + name: Prepare device farm artifacts + uses: ./.github/workflows/bitbar-prepare-artifacts.yaml + if: ${{ github.actor != 'dependabot[bot]' }} + needs: build-and-test + secrets: + SIGNING_KEYSTORE: ${{ secrets.SIGNING_KEYSTORE }} + SIGNING_ALIAS: ${{ secrets.SIGNING_ALIAS }} + SIGNING_KEYSTORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} + SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + + # Execute e2e test cases in BitBar. The workflow outputs the newly created run id. + bitbar-run: + name: Run tests in BitBar + uses: ./.github/workflows/bitbar-run.yaml + needs: bitbar-prepare-artifacts + secrets: + BITBAR_API_KEY: ${{ secrets.BITBAR_API_KEY }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + with: + bitbar-project-id: ${{ vars.BITBAR_PROJECT_ID }} + bitbar-device-group-id: ${{ vars.BITBAR_DEVICE_GROUP_ID }} + + # Wait for BitBar test run to finish and publish results + bitbar-results: + name: BitBar test results + uses: ./.github/workflows/bitbar-results.yaml + needs: bitbar-run + secrets: + BITBAR_API_KEY: ${{ secrets.BITBAR_API_KEY }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + with: + bitbar-project-id: ${{ vars.BITBAR_PROJECT_ID }} + bitbar-run-id: ${{ needs.bitbar-run.outputs.bitbar-run-id }} + # Run Mend CLI Scan mend-cli-scan: name: Mend CLI Scan diff --git a/davinci/src/androidTest/kotlin/com/pingidentity/davinci/DavinciAndroidTest.kt b/davinci/src/androidTest/kotlin/com/pingidentity/davinci/DavinciAndroidTest.kt index 9bc8646..02f0b73 100644 --- a/davinci/src/androidTest/kotlin/com/pingidentity/davinci/DavinciAndroidTest.kt +++ b/davinci/src/androidTest/kotlin/com/pingidentity/davinci/DavinciAndroidTest.kt @@ -607,7 +607,7 @@ class DavinciAndroidTest { } @TestRailCase(24629) - @Test(timeout = 20000) + @Test(timeout = 60000) fun accountLocked() = runBlocking { // Register a test user... val newUser = userFname + System.currentTimeMillis() + "@example.com" From ffcea6320be8d65c1ba035edf8dcc7432c4d7a34 Mon Sep 17 00:00:00 2001 From: Andy Witrisna Date: Thu, 19 Dec 2024 12:11:43 -0800 Subject: [PATCH 2/3] SDKS-3616: Prevent side effects on the Global Logger when configuring the DaVinci Logger. --- .../kotlin/com/pingidentity/orchestrate/WorkflowConfig.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/foundation/orchestrate/src/main/kotlin/com/pingidentity/orchestrate/WorkflowConfig.kt b/foundation/orchestrate/src/main/kotlin/com/pingidentity/orchestrate/WorkflowConfig.kt index afe5990..414dc0c 100644 --- a/foundation/orchestrate/src/main/kotlin/com/pingidentity/orchestrate/WorkflowConfig.kt +++ b/foundation/orchestrate/src/main/kotlin/com/pingidentity/orchestrate/WorkflowConfig.kt @@ -41,10 +41,6 @@ open class WorkflowConfig { // Logger for the log, default is None var logger = Logger.logger - set(value) { - field = value - Logger.logger = value - } // HTTP client for the engine lateinit var httpClient: HttpClient From 7854c67abcac93b384d9f986e6ec8619662445d8 Mon Sep 17 00:00:00 2001 From: Andy Witrisna Date: Tue, 7 Jan 2025 18:57:01 -0800 Subject: [PATCH 3/3] SDKS-3596 DaVinci Form Implementation - Require and Regex Validation - Provide function to access ErrorNode with validation error (e.g Password policy not met) - Handle Multi and single select collector, checkbox, dropdown, radio, combobox - New Collectors, LABEL, SingleSelect, MultiSelect - Handle default formData, populate default value from server. --- davinci/CONCEPT.md | 134 +++++++++++ davinci/README.md | 196 ++++++++++++--- davinci/build.gradle.kts | 4 +- davinci/images/davinciSequence.png | Bin 105217 -> 0 bytes .../pingidentity/davinci/CollectorRegistry.kt | 17 +- .../kotlin/com/pingidentity/davinci/Json.kt | 16 ++ .../davinci/collector/Collectors.kt | 84 ++++--- .../davinci/collector/FieldCollector.kt | 21 +- .../davinci/collector/FlowCollector.kt | 6 +- .../pingidentity/davinci/collector/Form.kt | 14 +- .../davinci/collector/LabelCollector.kt | 30 +++ .../davinci/collector/MultiSelectCollector.kt | 58 +++++ .../pingidentity/davinci/collector/Option.kt | 34 +++ .../davinci/collector/PasswordCollector.kt | 60 ++++- .../davinci/collector/PasswordPolicy.kt | 81 +++++++ .../collector/SingleSelectCollector.kt | 28 +++ .../davinci/collector/SingleValueCollector.kt | 34 +++ .../davinci/collector/SubmitCollector.kt | 6 +- .../davinci/collector/TextCollector.kt | 6 +- .../davinci/collector/ValidatedCollector.kt | 55 +++++ .../davinci/collector/ValidationError.kt | 26 ++ .../pingidentity/davinci/module/Connector.kt | 4 +- .../pingidentity/davinci/module/ErrorNode.kt | 112 +++++++++ .../davinci/CollectorRegistryTest.kt | 23 +- .../pingidentity/davinci/DaVinciErrorTest.kt | 78 +++++- .../com/pingidentity/davinci/DaVinciTest.kt | 131 +++++++++- .../davinci/FieldCollectorTest.kt | 9 +- .../pingidentity/davinci/FlowCollectorTest.kt | 33 ++- .../davinci/LabelCollectorTest.kt | 37 +++ .../davinci/MultiSelectCollectorTest.kt | 78 ++++++ .../davinci/PasswordCollectorTest.kt | 130 +++++++++- .../davinci/PasswordPolicyTest.kt | 101 ++++++++ .../davinci/SingleSelectCollectorTest.kt | 56 +++++ .../pingidentity/davinci/TextCollectorTest.kt | 18 +- .../davinci/ValidatedCollectorTest.kt | 79 ++++++ .../resources/PasswordValidationError.json | 48 ++++ .../test/resources/ResponseWithBasicType.json | 225 ++++++++++++++++++ .../pingidentity/davinci/plugin/Collector.kt | 11 +- .../kotlin/com/pingidentity/test/Utils.kt | 17 ++ .../com/pingidentity/samples/app/Alert.kt | 43 +++- .../com/pingidentity/samples/app/AppDrawer.kt | 2 +- .../pingidentity/samples/app/AppNavHost.kt | 2 +- .../samples/app/LogoutViewModel.kt | 2 +- .../com/pingidentity/samples/app/Setting.kt | 2 +- .../java/com/pingidentity/samples/app/User.kt | 2 +- .../centralize/CentralizeLoginViewModel.kt | 2 +- .../samples/app/davinci/DaVinci.kt | 21 +- .../samples/app/davinci/DaVinciViewModel.kt | 2 +- .../samples/app/davinci/collector/CheckBox.kt | 69 ++++++ .../samples/app/davinci/collector/ComboBox.kt | 105 ++++++++ .../app/davinci/collector/ContinueNode.kt | 22 +- .../samples/app/davinci/collector/Dropdown.kt | 75 ++++++ .../app/davinci/collector/ErrorMessage.kt | 49 ++++ .../app/davinci/collector/FlowButton.kt | 41 +++- .../samples/app/davinci/collector/Label.kt | 39 +++ .../samples/app/davinci/collector/Password.kt | 96 +++++++- .../samples/app/davinci/collector/Radio.kt | 59 +++++ .../samples/app/davinci/collector/Text.kt | 26 +- .../com/pingidentity/samples/app/env/Env.kt | 2 +- .../samples/app/env/EnvViewModel.kt | 14 +- 60 files changed, 2636 insertions(+), 139 deletions(-) create mode 100644 davinci/CONCEPT.md delete mode 100644 davinci/images/davinciSequence.png create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/Json.kt create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/collector/LabelCollector.kt create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/collector/MultiSelectCollector.kt create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/collector/Option.kt create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/collector/PasswordPolicy.kt create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/collector/SingleSelectCollector.kt create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/collector/SingleValueCollector.kt create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/collector/ValidatedCollector.kt create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/collector/ValidationError.kt create mode 100644 davinci/src/main/kotlin/com/pingidentity/davinci/module/ErrorNode.kt create mode 100644 davinci/src/test/kotlin/com/pingidentity/davinci/LabelCollectorTest.kt create mode 100644 davinci/src/test/kotlin/com/pingidentity/davinci/MultiSelectCollectorTest.kt create mode 100644 davinci/src/test/kotlin/com/pingidentity/davinci/PasswordPolicyTest.kt create mode 100644 davinci/src/test/kotlin/com/pingidentity/davinci/SingleSelectCollectorTest.kt create mode 100644 davinci/src/test/kotlin/com/pingidentity/davinci/ValidatedCollectorTest.kt create mode 100644 davinci/src/test/resources/PasswordValidationError.json create mode 100644 davinci/src/test/resources/ResponseWithBasicType.json create mode 100644 foundation/testrail/src/main/kotlin/com/pingidentity/test/Utils.kt create mode 100644 samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/CheckBox.kt create mode 100644 samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/ComboBox.kt create mode 100644 samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/Dropdown.kt create mode 100644 samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/ErrorMessage.kt create mode 100644 samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/Label.kt create mode 100644 samples/app/src/main/java/com/pingidentity/samples/app/davinci/collector/Radio.kt diff --git a/davinci/CONCEPT.md b/davinci/CONCEPT.md new file mode 100644 index 0000000..791d307 --- /dev/null +++ b/davinci/CONCEPT.md @@ -0,0 +1,134 @@ +

+ + Logo + +


+

+ +# Design Concept + +## Mapping Field Types to Collectors + +DaVinci employs a factory-based approach to dynamically create and initialize collectors for different field types in a +form. This allows us to handle various field types such as text fields, password fields, and buttons, each with its own +specific behavior, validation, and data collection logic. + +The `CollectorFactory` is a factory class that maps field types to their corresponding collectors. To register a new +collector, you provide the field type and the constructor reference of the collector. Using the constructor reference, +the `CollectorFactory` can dynamically create collectors when parsing the DaVinci Response JSON. + +``` +CollectorFactory.register(, ) +``` + +For example: + +```kotlin +// Map Password Type to PasswordCollector +CollectorFactory.register("PASSWORD", ::PasswordCollector) + +CollectorFactory.register("SUBMIT_BUTTON", ::SubmitCollector) + +// Allow to map multiple Field Type to the same Collector +CollectorFactory.register("FLOW_BUTTON", ::FlowCollector) +CollectorFactory.register("FLOW_LINK", ::FlowCollector) +``` + +## How Collectors Are Created and Initialized + +DaVinci Response JSON: + +```json +{ + "form": { + "components": { + "fields": [ + { + "type": "TEXT", + "key": "user.username", + "label": "Username", + "required": true, + "validation": { + "regex": "^[^@]+@[^@]+\\.[^@]+$", + "errorMessage": "Must be alphanumeric" + } + }, + { + "type": "PASSWORD", + "key": "password", + "label": "Password", + "required": true + }, + ... + ] + } + } +} +``` + +```mermaid +sequenceDiagram + Form ->> CollectorFactory: collector(fields) + loop ForEach Field in Fields + CollectorFactory ->> Collector: create() + CollectorFactory ->> Collector: init(field) + Collector ->> Collector: populate the instance properties with field Json + end + CollectorFactory ->> Form: collectors +``` + +## How Collectors Populate Default Values + +The Collector populates the default value from the `formData` JSON: + +```json +{ + "formData": { + "value": { + "user.username": "", + "password": "", + "dropdown-field": "", + "combobox-field": [], + "radio-field": "", + "checkbox-field": [] + } + } +} +``` + +```mermaid +sequenceDiagram + loop ForEach Collector in Collectors + Form ->> Collector: getKey() + alt key in formData + Form ->> Collector: init(formData[key]) + Collector ->> Collector: "populate the default value with formData + end + end +``` + +## How Collectors Access the ConnectorNode + +By default, the `Collector` is self-contained and does not have direct access to the `ConnectorNode`. It is responsible for +handling data collection and validation specific to the field it represents. However, in certain cases, a collector may +need to access the `ConnectorNode` - for example, to retrieve global data such as the `passwordPolicy` from the root JSON to +validate a password. + +To enable this access, a collector can implement the `ConnectorNodeAware` interface. This interface includes the +`connectorNode` property, allowing the collector to retrieve the `ConnectorNode` when needed. Once a collector is created, +the `ConnectorNode` is injected into it, granting access to global data for tasks like validation or other cross-field +operations. + +```kotlin +class PasswordCollector : ContinueNodeAware { + override lateinit var continueNode: ContinueNode +} +``` +```mermaid +sequenceDiagram + loop ForEach Collector in ContinueNode.collectors + alt Collector is ContinueNodeAware + CollectorFactory ->> Collector: setContinueNode(continueNode) + end + end +``` \ No newline at end of file diff --git a/davinci/README.md b/davinci/README.md index 48cbe3d..c35ce62 100644 --- a/davinci/README.md +++ b/davinci/README.md @@ -13,7 +13,27 @@ DaVinci is a powerful and flexible library for Authentication and Authorization. extensible. It provides a simple API for navigating the authentication flow and handling the various states that can occur during the authentication process. - +```mermaid +sequenceDiagram + Developer ->> DaVinci: Create DaVinci instance + DaVinci ->> Developer: DaVinci + Developer ->> DaVinci: start() + DaVinci ->> PingOne DaVinci Server: /authorize + PingOne DaVinci Server ->> PingOne DaVinci Server: Launch DaVinci Flow + PingOne DaVinci Server ->> DaVinci: Node with Form + DaVinci ->> Developer: Node + Developer ->> Developer: Gather credentials + Developer ->> DaVinci: next() + DaVinci ->> PingOne DaVinci Server: /continue + PingOne DaVinci Server ->> DaVinci: authorization code + DaVinci ->> PingOne DaVinci Server: /token + PingOne DaVinci Server ->> DaVinci: access token + DaVinci ->> DaVinci: persist access token + DaVinci ->> Developer: Access Token +``` + +You can find more information about PingOne +DaVinci [here](https://docs.pingidentity.com/davinci/davinci_introduction.html). ## Add dependency to your project @@ -77,46 +97,150 @@ when (node) { } ``` -| Node Type | Description | -|-------------|:-----------------------------------------------------------------------------------------------------------| -| ContinueNode | In the middle of the flow, call ```node.next``` to move to next Node in the flow | -| ErrorNode | Bad Request from the server, e.g Invalid Password, OTP, username ```node.message``` for the error message | -| FailureNode | Unexpected Error, e.g Network, parsing ```node.cause``` to retrieve the cause of the error | -| SuccessNode | Authentication successful ```node.session``` to retrieve the session | +| Node Type | Description | +|--------------|:---------------------------------------------------------------------------------------------------------------------| +| ContinueNode | In the middle of the flow, call ```node.next``` to move to next Node in the flow | +| ErrorNode | Bad request from the server, e.g., invalid password, OTP, or username. Use ```node.message``` for the error message. | +| FailureNode | Unexpected error, e.g., network issues. Use node.cause to retrieve the cause of the error. | +| SuccessNode | Successful authentication. Use ```node.session``` to retrieve the session. | ### Provide Input -For `ContinueNode` Node, you can access list of Collector with `node.collectors()` and provide input to -the `Collector`. -Currently, there are, `TextCollector`, `PasswordCollector`, `SubmitCollector`, `FlowCollector`, but more will be added in the future, such as `Fido`, -`SocialLoginCollector`, etc... +For a `ContinueNode`, you can access the list of collectors using `node.collectors()` and provide input to the desired +`Collector`. +Currently, the available collectors include `TextCollector`, `PasswordCollector`, `SubmitCollector`, `FlowCollector`, +`LabelCollector`, `MultiSelectCollector`, and `SingleSelectCollector`. Additional collectors, such as `Fido` and +`IdpCollector`, will be added in the future. To access the collectors, you can use the following code: ```kotlin node.collectors.forEach { - when(it) { - is TextCollector -> it.value = "My First Name" - is PasswordCollector -> it.value = "My Password" - is SubmitCollector -> it.value = "click me" - is FlowCollector -> it.value = "Forgot Password" - } + when (it) { + is TextCollector -> it.value = "My First Name" + is PasswordCollector -> it.value = "My Password" + is SubmitCollector -> it.value = "click me" + is FlowCollector -> it.value = "Forgot Password" + ... } +} + +// Move to next Node, and repeat the flow until it reaches `SuccessNode` or `ErrorNode` +val next = node.next() +``` + +Each `Collector` has its own function. + +#### TextCollector (TEXT) + +```kotlin +textCollector.label //To access the label +textCollector.key //To access the key attribute +textCollector.type //To access the type attribute +textCollector.required //To access the required attribute +textCollector.valiation //To access the validation attribute + +textCollector.validate() //To validate the field's input value using both required and regex constraints. +textCollector.value = "My First Name" //To set the value +``` + +#### PasswordCollector (PASSWORD, PASSWORD_VERIFY) + +`PasswordCollector` has the same attributes as `TextCollector`, plus the following functions + +```kotlin +passwordCollector.passwordPolicy() //Retrieve the password policy +passwordCollector.validate() //To validate the field input value against the password policy + +passwordCollector.type == "PASSWORD_VERIFY" // Check if the type is "PASSWORD_VERIFY". +``` + +#### SubmitCollector (SUBMIT_BUTTON) + +```kotlin +submitCollector.label //To access the label +submitCollector.key //To access the key attribute +submitCollector.type //To access the type attribute +submitCollector.value = "submit" //To set the value +``` + +#### FlowCollector (FLOW_BUTTON, FLOW_LINK) + +`FlowCollector` has the same attributes as `SubmitCollector` + +```kotlin +flowCollector.type == "FLOW_LINK" // Check if the type is "FLOW_LINK". Note that developers may choose to display flow collectors as link or button. +``` + +#### LabelCollector (LABEL) - // Move to next Node, and repeat the flow until it reaches `SuccessNode` or `ErrorNode` - val next = node.next() +```kotlin +labelCollector.content //To access the Content +``` + +#### MultiSelectCollector (COMBOBOX, CHECKBOX) + +```kotlin +multiSelectCollector.label //To access the label +multiSelectCollector.key //To access the key attribute +multiSelectCollector.type //To access the type attribute +multiSelectCollector.required //To access the required attribute +multiSelectCollector.options //To access the options attribute + +multiSelectCollector.value.add("option1") //To add the value +``` + +#### SingleSelectCollector (DROPDOWN, RADIO) + +```kotlin +singleSelectCollector.label //To access the label +singleSelectCollector.key //To access the key attribute +singleSelectCollector.type //To access the type attribute +singleSelectCollector.required //To access the required attribute +singleSelectCollector.options //To access the options attribute + +singleSelectCollector.value = "option1" //To set the value ``` +### Collector Validation + +Collectors have a `validate()` function to validate the input value. The `validate()` function will return `Success` +or `List` + +For example, to validate the `TextCollector` input value, you can use the following code: + +```kotlin +val result: List = textCollector.validate() +``` + +| ValidationError | Description | +|-----------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| InvalidLength | Indicates that the password length is outside the valid range. The InvalidLength error includes the required minimum and maximum lengths. | +| UniqueCharacter | Indicates that the number of unique characters is less than the required minUniqueCharacters specified in the the policy. The UniqueCharacter error contains the required minimum unique character count. | +| MaxRepeat | Indicates that the maxRepeatedCharacters policy requirement is not met. The MaxRepeat error specifies the maximum allowed repetitions of a character. | +| MinCharacters | Indicates that the minCharacters password policy requirement is not met. | +| Required | Indicates that the input value has not been supplied, but is required. | +| Regex | Indicates that the input value does not match the required pattern. The Regex error contains the required regular expression. | + ### Error Handling -For `FailureNode`, you can retrieve the cause of the error by using `node.cause()`. The `cause` is a `Throwable` object, -when receiving an error, you cannot continue the Flow, you may want to display a generic message to user, and report -the issue to the Support team. -The Error may include Network issue, parsing issue, API Error (Server response other that 2xx and 400) and other unexpected issues. +`FailureNode` and `ErrorNode` handle errors differently in the flow. A `FailureNode` represents an unrecoverable error +that +prevents the flow from continuing, whereas an `ErrorNode` allows the flow to continue and provides an error message for +the user. -For `ErrorNode`, you can retrieve the error message by using `node.message()`, and the raw json response with `node.input`. -The `message` is a `String` object, when receiving a failure, you can continue the Flow with previous `ContinueNode` Node, but you may want to display the error message to the user. -e.g "Username/Password is incorrect", "OTP is invalid", etc... +For a `FailureNode`, you can retrieve the cause of the error using `node.cause()`. The cause is a `Throwable` object. +When an +error occurs, the flow cannot continue, and you may want to display a generic message to the user and report the issue +to the support team. Possible errors include network issues, parsing problems, API errors (e.g., server responses in the +5xx range), and other unexpected issues. + +For an `ErrorNode`, you can retrieve the error message using `node.message()` and access the raw JSON response with +node.input. + +The `message` is a `String` object. When a failure occurs, you can continue the flow with the previous `ContinueNode`, +but you +may want to display the error message to the user (e.g., "Username/Password is incorrect", "OTP is invalid", etc.). ```kotlin val node = daVinci.start() // Start the flow @@ -126,6 +250,16 @@ when (node) { is ContinueNode -> {} is ErrorNode -> { node.message() // Retrieve the cause of the error + node.details().forEach { // Retrieve the details of the error + it.rawResponse.let { rawResponse -> + rawResponse.details?.forEach { detail -> + val msg = detail.message + detail.innerError?.errors?.forEach { (key, value) -> + val innerError = "$key: $value" + } + } + } + } } is FailureNode -> { node.cause() // Retrieve the error message @@ -154,11 +288,13 @@ when (node.id()) { } ``` -Other than `id`, you can also use `node.name` to retrieve the name of the Node, `node.description` to retrieve the description of the Node. +Other than `id`, you can also use `node.name` to retrieve the name of the Node, `node.description` to retrieve the +description of the Node. ### Work with Jetpack Composable ViewModel + ```kotlin // Define State that listen by the View var state = MutableStateFlow(Empty) @@ -182,6 +318,7 @@ fun next(node: ContinueNode) { ``` View + ```kotlin when (val node = state.node) { is ContinueNode -> {} @@ -192,6 +329,7 @@ when (val node = state.node) { ``` ### Post Authentication + After authenticate with DaVinci, the user session will be stored in the storage. To retrieve the existing session, you can use the following code: @@ -207,6 +345,4 @@ user?.let { it.userinfo() it.logout() } - - -``` +``` \ No newline at end of file diff --git a/davinci/build.gradle.kts b/davinci/build.gradle.kts index 289f94f..debde3a 100644 --- a/davinci/build.gradle.kts +++ b/davinci/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Ping Identity. All rights reserved. + * Copyright (c) 2024 - 2025 Ping Identity. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -11,6 +11,7 @@ plugins { id("com.pingidentity.convention.jacoco") alias(libs.plugins.androidLibrary) alias(libs.plugins.kotlinAndroid) + alias(libs.plugins.kotlinSerialization) } description = "DaVinci library" @@ -49,6 +50,7 @@ dependencies { testImplementation(project(":foundation:testrail")) testImplementation(libs.kotlin.test) + testImplementation(libs.mockk) testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.ktor.client.mock) diff --git a/davinci/images/davinciSequence.png b/davinci/images/davinciSequence.png deleted file mode 100644 index 916cad40f09f843db63dad490e44138cebd3bacc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105217 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≷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 diff --git a/davinci/src/main/kotlin/com/pingidentity/davinci/CollectorRegistry.kt b/davinci/src/main/kotlin/com/pingidentity/davinci/CollectorRegistry.kt index 8196e36..7865ede 100644 --- a/davinci/src/main/kotlin/com/pingidentity/davinci/CollectorRegistry.kt +++ b/davinci/src/main/kotlin/com/pingidentity/davinci/CollectorRegistry.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Ping Identity. All rights reserved. + * Copyright (c) 2024 - 2025 Ping Identity. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -9,7 +9,10 @@ package com.pingidentity.davinci import com.pingidentity.android.ModuleInitializer import com.pingidentity.davinci.collector.FlowCollector +import com.pingidentity.davinci.collector.LabelCollector +import com.pingidentity.davinci.collector.MultiSelectCollector import com.pingidentity.davinci.collector.PasswordCollector +import com.pingidentity.davinci.collector.SingleSelectCollector import com.pingidentity.davinci.collector.SubmitCollector import com.pingidentity.davinci.collector.TextCollector import com.pingidentity.davinci.plugin.CollectorFactory @@ -31,11 +34,23 @@ internal class CollectorRegistry : ModuleInitializer() { // Register PasswordCollector with the CollectorFactory CollectorFactory.register("PASSWORD", ::PasswordCollector) + CollectorFactory.register("PASSWORD_VERIFY", ::PasswordCollector) // Register SubmitCollector with the CollectorFactory CollectorFactory.register("SUBMIT_BUTTON", ::SubmitCollector) // Register FlowCollector with the CollectorFactory CollectorFactory.register("FLOW_BUTTON", ::FlowCollector) + + // Register FlowCollector with the CollectorFactory + CollectorFactory.register("FLOW_LINK", ::FlowCollector) + + // Register LabelCollector with the CollectorFactory + CollectorFactory.register("LABEL", ::LabelCollector) + + CollectorFactory.register("DROPDOWN", ::SingleSelectCollector) + CollectorFactory.register("RADIO", ::SingleSelectCollector) + CollectorFactory.register("COMBOBOX", ::MultiSelectCollector) + CollectorFactory.register("CHECKBOX", ::MultiSelectCollector) } } \ No newline at end of file diff --git a/davinci/src/main/kotlin/com/pingidentity/davinci/Json.kt b/davinci/src/main/kotlin/com/pingidentity/davinci/Json.kt new file mode 100644 index 0000000..676394a --- /dev/null +++ b/davinci/src/main/kotlin/com/pingidentity/davinci/Json.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Ping Identity. All rights reserved. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +package com.pingidentity.davinci + +import kotlinx.serialization.json.Json + +internal val json: Json = + Json { + ignoreUnknownKeys = true + encodeDefaults = true + } diff --git a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/Collectors.kt b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/Collectors.kt index b2c209c..a73ca5e 100644 --- a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/Collectors.kt +++ b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/Collectors.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Ping Identity. All rights reserved. + * Copyright (c) 2024 - 2025 Ping Identity. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -11,22 +11,17 @@ import com.pingidentity.davinci.plugin.Collectors import com.pingidentity.davinci.plugin.RequestAdapter import com.pingidentity.orchestrate.FlowContext import com.pingidentity.orchestrate.Request +import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonObject internal fun Collectors.eventType(): String? { forEach { when (it) { - is SubmitCollector -> { - if (it.value.isNotEmpty()) { - return it.value - } - } - - is FlowCollector -> { - if (it.value.isNotEmpty()) { + is SubmitCollector, is FlowCollector -> { + if ((it as SingleValueCollector).value.isNotEmpty()) { return it.value } } @@ -56,21 +51,14 @@ internal fun Collectors.request(context: FlowContext, request: Request): Request * This function takes a list of collectors and represents it as a JSON object. It iterates over the list of collectors, * adding each collector's key and value to the JSON object if the collector's value is not empty. * - * @param collectors The list of collectors to represent as a JSON object. * @return A JSON object representing the list of collectors. */ internal fun Collectors.asJson(): JsonObject { return buildJsonObject { forEach { when (it) { - is SubmitCollector -> { - if (it.value.isNotEmpty()) { - put("actionKey", it.key) - } - } - - is FlowCollector -> { - if (it.value.isNotEmpty()) { + is SubmitCollector, is FlowCollector -> { + if ((it as SingleValueCollector).value.isNotEmpty()) { put("actionKey", it.key) } } @@ -78,21 +66,63 @@ internal fun Collectors.asJson(): JsonObject { else -> {} } } - putJsonObject("formData") { - forEach { - when (it) { - is TextCollector -> { - if (it.value.isNotEmpty()) put(it.key, it.value) + val map = mutableMapOf() + forEach { + when (it) { + is TextCollector, is PasswordCollector -> { + if ((it as SingleValueCollector).value.isNotEmpty()) { + toMap(map, it.key, it.value) } + } - is PasswordCollector -> { - if (it.value.isNotEmpty()) put(it.key, it.value) + is SingleSelectCollector -> { + if (it.value.isNotEmpty()) { + toMap(map, it.key, it.value) } + } - else -> {} + is MultiSelectCollector -> { + if (it.value.isNotEmpty()) { + toMap(map, it.key, it.value) + } } } } + put("formData", mapToJsonObject(map)) + } +} + +@Suppress("UNCHECKED_CAST") +private fun toMap(map: MutableMap, key: String, value: Any) { + var currentMap = map + + //TODO Remove this when the nested key is removed. + val keys = key.split(".") + + for (i in keys.indices) { + val part = keys[i] + if (i == keys.size - 1) { + currentMap[part] = value + } else { + currentMap = + currentMap.getOrPut(part) { mutableMapOf() } as MutableMap + } } } +//TODO Remove this when the nested key is removed. +@Suppress("UNCHECKED_CAST") +fun mapToJsonObject(map: Map): JsonObject { + return JsonObject(map.mapValues { (_, value) -> + when (value) { + is Map<*, *> -> mapToJsonObject(value as Map) // Recursive for nested maps + is List<*> -> JsonArray(value.map { JsonPrimitive(it.toString()) }) // Convert List to JsonArray + is String -> JsonPrimitive(value) + is Boolean -> JsonPrimitive(value) + is Number -> JsonPrimitive(value) + else -> { + JsonPrimitive(value.toString()) + } + } + }) +} \ No newline at end of file diff --git a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/FieldCollector.kt b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/FieldCollector.kt index ce18b58..f9b11da 100644 --- a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/FieldCollector.kt +++ b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/FieldCollector.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Ping Identity. All rights reserved. + * Copyright (c) 2024 - 2025 Ping Identity. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -14,23 +14,34 @@ import kotlinx.serialization.json.jsonPrimitive /** * Abstract class representing a fields from the form. * + * @property type The type of the field collector. * @property key The key of the field collector. * @property label The label of the field collector. - * @property value The value of the field collector. * */ abstract class FieldCollector : Collector { + var type = "" + private set var key = "" + private set var label = "" - open var value: String = "" + private set /** * Function to initialize the field collector. * @param input The input JSON object to parse. */ override fun init(input: JsonObject) { + type = input["type"]?.jsonPrimitive?.content ?: "" key = input["key"]?.jsonPrimitive?.content ?: "" label = input["label"]?.jsonPrimitive?.content ?: "" - } -} \ No newline at end of file +} + +/** + * Data class representing the validation of the field collector. + * + * @property regex The regex of the validation. + * @property errorMessage The error message of the validation. + */ +data class Validation(val regex: Regex, val errorMessage: String) \ No newline at end of file diff --git a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/FlowCollector.kt b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/FlowCollector.kt index 15bc616..673ac6c 100644 --- a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/FlowCollector.kt +++ b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/FlowCollector.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Ping Identity. All rights reserved. + * Copyright (c) 2024 - 2025 Ping Identity. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -8,11 +8,11 @@ package com.pingidentity.davinci.collector /** - * Class representing a FLOW_BUTTON Type. + * Class representing a FLOW_BUTTON, FLOW_LINK Type. * * This class inherits from the FieldCollector class and implements the Collector interface. * It is used to collect data in a flow. * * @constructor Creates a new FlowCollector with the given input. */ -class FlowCollector : FieldCollector() \ No newline at end of file +class FlowCollector : SingleValueCollector() \ No newline at end of file diff --git a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/Form.kt b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/Form.kt index be09fdf..b130ce0 100644 --- a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/Form.kt +++ b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/Form.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 PingIdentity. All rights reserved. + * Copyright (c) 2024 - 2025 Ping Identity. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -34,11 +34,17 @@ internal object Form { json: JsonObject, ): Collectors { val collectors = mutableListOf() - json["form"]?.jsonObject?.let { - it["components"]?.jsonObject?.get("fields")?.jsonArray?.let { array -> - collectors.addAll(CollectorFactory.collector(array)) + json["form"]?.jsonObject?.get("components")?.jsonObject?.get("fields")?.jsonArray?.let { array -> + collectors.addAll(CollectorFactory.collector(array)) + } + + //Populate values for collectors + json["formData"]?.jsonObject?.get("value")?.jsonObject?.let { value -> + collectors.filterIsInstance().forEach { collector -> + value[collector.key]?.let(collector::init) } } + return collectors } diff --git a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/LabelCollector.kt b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/LabelCollector.kt new file mode 100644 index 0000000..83f0a61 --- /dev/null +++ b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/LabelCollector.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Ping Identity. All rights reserved. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +package com.pingidentity.davinci.collector + +import com.pingidentity.davinci.plugin.Collector +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonPrimitive + +/** + * Class representing a LABEL type. + * + * This class inherits from the [Collector] class. It is used to display a label on the form. + * + * @constructor Creates a new LabelCollector. + */ +class LabelCollector : Collector { + + var content = "" + private set + + + override fun init(input: JsonObject) { + content = input["content"]?.jsonPrimitive?.content ?: "" + } +} \ No newline at end of file diff --git a/davinci/src/main/kotlin/com/pingidentity/davinci/collector/MultiSelectCollector.kt b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/MultiSelectCollector.kt new file mode 100644 index 0000000..fb5231c --- /dev/null +++ b/davinci/src/main/kotlin/com/pingidentity/davinci/collector/MultiSelectCollector.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Ping Identity. All rights reserved. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +package com.pingidentity.davinci.collector + +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.jsonPrimitive + +/** + * Class representing CHECKBOX, COMBOBOX type with MULTI_SELECT inputType. + * + * This class inherits from the FieldCollector class and implements the Collector interface. + * It is used to collect multiple values from a list of options. + * + * @constructor Creates a new MultiSelectCollector with the given input. + */ +open class MultiSelectCollector : FieldCollector() { + lateinit var options: List