Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/actions/create-native-bundle/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ runs:
with:
args: unzip -qq project.zip
- name: "Rename extracted directory"
run: mv Native-Mobile-Resources-mx-version-10 Native-Mobile-Resources-mx10
run: mv Native-Mobile-Resources-mx-version-10 NMR-mx10
shell: bash
- name: "Extract deployment package"
uses: montudor/action-zip@v1.0.0
with:
args: unzip -qq ${{ inputs.mda-file }} -d Native-Mobile-Resources-mx10/deployment
args: unzip -qq ${{ inputs.mda-file }} -d NMR-mx10/deployment
- name: "Create bundle for ${{ inputs.platform }}"
run: |
mkdir -p ${{ inputs.platform }}/assets
cd Native-Mobile-Resources-mx10/deployment/native && \
cd NMR-mx10/deployment/native && \
/tmp/mxbuild/modeler/tools/node/linux-x64/node \
/tmp/mxbuild/modeler/tools/node/node_modules/react-native/cli.js \
bundle --verbose --platform ${{ inputs.platform }} --dev false \
Expand Down
47 changes: 37 additions & 10 deletions .github/scripts/determine-widget-scope.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ input_workspace="$2"
before_commit="$3"
current_commit="$4"

# List of all native widgets
# Dynamically discover all native widgets from the packages/pluggableWidgets directory
# This ensures we don't miss any widgets when new ones are added
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Expand Down Expand Up @@ -43,39 +44,65 @@ if [ "$event_name" == "pull_request" ]; then
selected_workspaces=$(echo $selected_workspaces | xargs)

# Build the final scope and widgets output
# Note: widgets output is used for both BUILDING and the widget TEST MATRIX
# When only JS actions change, widgets_to_test is empty (no widget tests needed)
# but we still need to build all widgets for the test project
if [[ -n "$selected_workspaces" ]] && [[ "$js_actions_changed" == "true" ]]; then
# Both widgets and JS actions changed
# Convert space-separated widget names to JSON array format
widget_array=$(echo "$selected_workspaces" | sed 's/ /","/g')
echo "scope=--all --include '$selected_workspaces mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT
echo "widgets=[\"$widget_array\",\"mobile-resources-native\",\"nanoflow-actions-native\"]" >> $GITHUB_OUTPUT
echo "widgets=[\"$widget_array\"]" >> $GITHUB_OUTPUT
echo "widgets_to_test=[\"$widget_array\"]" >> $GITHUB_OUTPUT
echo "js_actions_changed=true" >> $GITHUB_OUTPUT
elif [[ -n "$selected_workspaces" ]] && [[ "$js_actions_changed" == "false" ]]; then
# Only widgets changed
widget_array=$(echo "$selected_workspaces" | sed 's/ /","/g')
echo "scope=--all --include '$selected_workspaces'" >> $GITHUB_OUTPUT
echo "widgets=[\"$widget_array\"]" >> $GITHUB_OUTPUT
echo "widgets_to_test=[\"$widget_array\"]" >> $GITHUB_OUTPUT
echo "js_actions_changed=false" >> $GITHUB_OUTPUT
elif [[ -z "$selected_workspaces" ]] && [[ "$js_actions_changed" == "true" ]]; then
# Only JS actions changed
echo "scope=--all --include 'mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT
echo "widgets=[\"mobile-resources-native\",\"nanoflow-actions-native\"]" >> $GITHUB_OUTPUT
# Only JS actions changed - need to build ALL widgets because JS action tests
# require the full test project with all widgets to function properly
# But widget tests should NOT run (empty widgets_to_test)
echo "scope=--all --include '*-native mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT
echo "widgets=${all_widgets}" >> $GITHUB_OUTPUT
echo "widgets_to_test=[]" >> $GITHUB_OUTPUT
echo "js_actions_changed=true" >> $GITHUB_OUTPUT
else
# No specific changes detected in widgets or JS actions, run everything
echo "scope=--all --include '*-native mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT
echo "widgets=${all_widgets_and_js}" >> $GITHUB_OUTPUT
echo "widgets=${all_widgets}" >> $GITHUB_OUTPUT
echo "widgets_to_test=${all_widgets}" >> $GITHUB_OUTPUT
echo "js_actions_changed=true" >> $GITHUB_OUTPUT
fi
else
if [ -n "$input_workspace" ] && [ "$input_workspace" != "*-native" ] && [ "$input_workspace" != "js-actions" ]; then
# Specific widget(s) selected
selected_workspaces=$(echo "$input_workspace" | sed 's/,/ /g')
echo "scope=--all --include '${selected_workspaces}'" >> $GITHUB_OUTPUT
echo "widgets=[\"$input_workspace\"]" >> $GITHUB_OUTPUT
echo "widgets_to_test=[\"$input_workspace\"]" >> $GITHUB_OUTPUT
echo "js_actions_changed=false" >> $GITHUB_OUTPUT
elif [ "$input_workspace" == "js-actions" ]; then
echo "scope=--all --include 'mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT
echo "widgets=[\"mobile-resources-native\",\"nanoflow-actions-native\"]" >> $GITHUB_OUTPUT
# JS actions selected - need to build ALL widgets because JS action tests
# require the full test project with all widgets to function properly
# But widget tests should NOT run (empty widgets_to_test)
echo "scope=--all --include '*-native mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT
echo "widgets=${all_widgets}" >> $GITHUB_OUTPUT
echo "widgets_to_test=[]" >> $GITHUB_OUTPUT
echo "js_actions_changed=true" >> $GITHUB_OUTPUT
else
# All widgets (*-native) or default - run everything
echo "scope=--all --include '*-native mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT
echo "widgets=${all_widgets_and_js}" >> $GITHUB_OUTPUT
echo "widgets=${all_widgets}" >> $GITHUB_OUTPUT
echo "widgets_to_test=${all_widgets}" >> $GITHUB_OUTPUT
echo "js_actions_changed=true" >> $GITHUB_OUTPUT
fi
fi

echo "Determined scope: $(cat $GITHUB_OUTPUT | grep scope= | cut -d= -f2)"
echo "Widgets: $(cat $GITHUB_OUTPUT | grep widgets= | cut -d= -f2)"
echo "Determined scope: $(cat $GITHUB_OUTPUT | grep 'scope=' | cut -d= -f2)"
echo "Widgets to build: $(cat $GITHUB_OUTPUT | grep '^widgets=' | cut -d= -f2)"
echo "Widgets to test: $(cat $GITHUB_OUTPUT | grep 'widgets_to_test=' | cut -d= -f2)"
echo "JS actions changed: $(cat $GITHUB_OUTPUT | grep 'js_actions_changed=' | cut -d= -f2)"
59 changes: 30 additions & 29 deletions .github/workflows/NativePipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ jobs:
outputs:
scope: ${{ steps.scope.outputs.scope }}
widgets: ${{ steps.scope.outputs.widgets }}
widgets_to_test: ${{ steps.scope.outputs.widgets_to_test }}
js_actions_changed: ${{ steps.scope.outputs.js_actions_changed }}
steps:
- name: "Check out code"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
Expand All @@ -101,7 +103,9 @@ jobs:
- name: "Debug Scope Output"
run: |
echo "Scope is: ${{ steps.scope.outputs.scope }}"
echo "Widgets or js actions are: ${{ steps.scope.outputs.widgets }}"
echo "Widgets to build: ${{ steps.scope.outputs.widgets }}"
echo "Widgets to test: ${{ steps.scope.outputs.widgets_to_test }}"
echo "JS actions changed: ${{ steps.scope.outputs.js_actions_changed }}"
mendix-version:
runs-on: ubuntu-24.04
outputs:
Expand Down Expand Up @@ -214,23 +218,20 @@ jobs:
run: yarn install --immutable
- name: "Force rebuild resources"
run: |
# Build JS actions if needed
if [ "${{ github.event.inputs.workspace }}" = "js-actions" ] || \
# Build JS actions if needed (when js_actions_changed is true OR when workspace explicitly includes them)
if [ "${{ needs.scope.outputs.js_actions_changed }}" = "true" ] || \
[ "${{ github.event.inputs.workspace }}" = "js-actions" ] || \
[ "${{ github.event.inputs.workspace }}" = "*-native" ] || \
[ "${{ github.event_name }}" = "schedule" ]; then
yarn workspace mobile-resources-native run build
yarn workspace nanoflow-actions-native run build
fi

# Build widgets if needed (any specific widget, *-native, or nightly)
if [ "${{ github.event.inputs.workspace }}" != "js-actions" ] || \
[ "${{ github.event.inputs.workspace }}" = "*-native" ] || \
[ "${{ github.event_name }}" = "schedule" ]; then
# Build widgets from scope
widgets=$(echo '${{ needs.scope.outputs.widgets }}' | jq -r '.[]')
for w in $widgets; do
yarn workspace $w run build
done
fi
- name: "Unit test"
run: yarn workspaces foreach ${{ needs.scope.outputs.scope }} run test
- name: "Upload JS actions resources artifact"
Expand Down Expand Up @@ -269,8 +270,8 @@ jobs:
uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28 # v1.0.0
with:
args: unzip -qq project.zip -d .
- name: "Rename extracted directory" # Doing this since mxbuild fails with - path too long
run: mv Native-Mobile-Resources-mx-version-10 Native-Mobile-Resources-mx10
- name: "Rename test project directory"
run: mv Native-Mobile-Resources-mx-version-10 NMR-mx10
- name: "Download resources artifact"
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
Expand All @@ -284,28 +285,28 @@ jobs:
run: |
if compgen -G 'resources/pluggableWidgets/**/dist/*/*.mpk' > /dev/null; then
for oldPath in resources/pluggableWidgets/**/dist/*/*.mpk; do
newPath=Native-Mobile-Resources-mx10/widgets/$(basename $oldPath)
newPath=NMR-mx10/widgets/$(basename $oldPath)
mv -f $oldPath $newPath
done
mx update-widgets --loose-version-check Native-Mobile-Resources-mx10/NativeComponentsTestProject.mpr
mx update-widgets --loose-version-check NMR-mx10/NativeComponentsTestProject.mpr
fi
- name: "Move mobile-resources"
shell: bash
run: |
if compgen -G 'resources/jsActions/mobile-resources-native/dist/*' > /dev/null; then
rm -rf Native-Mobile-Resources-mx10/javascriptsource/nativemobileresources/actions/node_modules
mv -f resources/jsActions/mobile-resources-native/dist/* Native-Mobile-Resources-mx10/javascriptsource/nativemobileresources/actions/
rm -rf NMR-mx10/javascriptsource/nativemobileresources/actions/node_modules
mv -f resources/jsActions/mobile-resources-native/dist/* NMR-mx10/javascriptsource/nativemobileresources/actions/
fi
- name: "Move nanoflow-actions"
shell: bash
run: |
if compgen -G 'resources/jsActions/nanoflow-actions-native/dist/*' > /dev/null; then
rm -rf Native-Mobile-Resources-mx10/javascriptsource/nanoflowcommons/actions/node_modules
mv -f resources/jsActions/nanoflow-actions-native/dist/* Native-Mobile-Resources-mx10/javascriptsource/nanoflowcommons/actions/
rm -rf NMR-mx10/javascriptsource/nanoflowcommons/actions/node_modules
mv -f resources/jsActions/nanoflow-actions-native/dist/* NMR-mx10/javascriptsource/nanoflowcommons/actions/
fi
- name: "Force rebuild test project"
run: |
mxbuild -o automation.mda --loose-version-check Native-Mobile-Resources-mx10/NativeComponentsTestProject.mpr
mxbuild -o automation.mda --loose-version-check NMR-mx10/NativeComponentsTestProject.mpr
- name: "Upload MDA"
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 #v4
with:
Expand Down Expand Up @@ -490,14 +491,14 @@ jobs:

android-widget-tests:
needs: [scope, mendix-version, project, android-app]
# Run if project succeeds and either android-app succeeds OR we're using custom artifacts (android-app was skipped)
if: ${{ (github.event.inputs.workspace != 'js-actions' || github.event_name == 'schedule') && always() && needs.project.result == 'success' && (needs.android-app.result == 'success' || needs.android-app.result == 'skipped') }}
# Run if widgets need testing (widgets_to_test is not empty) and project succeeds
if: ${{ needs.scope.outputs.widgets_to_test != '[]' && always() && needs.project.result == 'success' && (needs.android-app.result == 'success' || needs.android-app.result == 'skipped') }}
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
max-parallel: 5
matrix:
widget: ${{ fromJson(needs.scope.outputs.widgets) }}
widget: ${{ fromJson(needs.scope.outputs.widgets_to_test) }}
fail-fast: false
steps:
- name: "Check out code"
Expand Down Expand Up @@ -566,14 +567,14 @@ jobs:

ios-widget-tests:
needs: [scope, mendix-version, project, ios-app]
# Run if project succeeds and either ios-app succeeds OR we're using custom artifacts (ios-app was skipped)
if: ${{ (github.event.inputs.workspace != 'js-actions' || github.event_name == 'schedule') && always() && needs.project.result == 'success' && (needs.ios-app.result == 'success' || needs.ios-app.result == 'skipped') }}
# Run if widgets need testing (widgets_to_test is not empty) and project succeeds
if: ${{ needs.scope.outputs.widgets_to_test != '[]' && always() && needs.project.result == 'success' && (needs.ios-app.result == 'success' || needs.ios-app.result == 'skipped') }}
runs-on: macos-15
timeout-minutes: 60
strategy:
max-parallel: 5
matrix:
widget: ${{ fromJson(needs.scope.outputs.widgets) }}
widget: ${{ fromJson(needs.scope.outputs.widgets_to_test) }}
fail-fast: false
steps:
- name: "Force cleanup workspace"
Expand Down Expand Up @@ -662,8 +663,8 @@ jobs:

android-js-tests:
needs: [scope, mendix-version, project, android-app]
# Run if project succeeds and either android-app succeeds OR we're using custom artifacts (android-app was skipped)
if: ${{ (github.event.inputs.workspace == '*-native' || github.event_name == 'schedule' || github.event.inputs.workspace == 'js-actions' || contains(needs.scope.outputs.widgets, 'mobile-resources-native') || contains(needs.scope.outputs.widgets, 'nanoflow-actions-native')) && always() && needs.project.result == 'success' && (needs.android-app.result == 'success' || needs.android-app.result == 'skipped') }}
# Run if JS actions changed and project succeeds and either android-app succeeds OR we're using custom artifacts (android-app was skipped)
if: ${{ needs.scope.outputs.js_actions_changed == 'true' && always() && needs.project.result == 'success' && (needs.android-app.result == 'success' || needs.android-app.result == 'skipped') }}
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
Expand Down Expand Up @@ -732,8 +733,8 @@ jobs:

ios-js-tests:
needs: [scope, mendix-version, project, ios-app]
# Run if project succeeds and either ios-app succeeds OR we're using custom artifacts (ios-app was skipped)
if: ${{ (github.event.inputs.workspace == '*-native' || github.event_name == 'schedule' || github.event.inputs.workspace == 'js-actions' || contains(needs.scope.outputs.widgets, 'mobile-resources-native') || contains(needs.scope.outputs.widgets, 'nanoflow-actions-native')) && always() && needs.project.result == 'success' && (needs.ios-app.result == 'success' || needs.ios-app.result == 'skipped') }}
# Run if JS actions changed and project succeeds and either ios-app succeeds OR we're using custom artifacts (ios-app was skipped)
if: ${{ needs.scope.outputs.js_actions_changed == 'true' && always() && needs.project.result == 'success' && (needs.ios-app.result == 'success' || needs.ios-app.result == 'skipped') }}
runs-on: macos-15
timeout-minutes: 90
steps:
Expand Down Expand Up @@ -830,7 +831,7 @@ jobs:

- name: "Download Android screenshots"
run: |
widgets=$(echo '${{ needs.scope.outputs.widgets }}' | jq -r '.[]')
widgets=$(echo '${{ needs.scope.outputs.widgets_to_test }}' | jq -r '.[]')
mkdir -p images/actual/android/
for widget in $widgets; do
echo "Downloading android-screenshots-${widget}"
Expand All @@ -844,7 +845,7 @@ jobs:

- name: "Download iOS screenshots"
run: |
widgets=$(echo '${{ needs.scope.outputs.widgets }}' | jq -r '.[]')
widgets=$(echo '${{ needs.scope.outputs.widgets_to_test }}' | jq -r '.[]')
mkdir -p images/actual/ios/
for widget in $widgets; do
echo "Downloading ios-screenshots-${widget}"
Expand Down
2 changes: 1 addition & 1 deletion configs/e2e/mendix-versions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"latest": "10.24.4.77222"
"latest": "10.24.14.90436"
}
5 changes: 4 additions & 1 deletion packages/jsActions/nanoflow-actions-native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Fixed

- We've fixed a synchronization issue with Base64 images generated by the Signature widget.

## [5.2.0] Nanoflow Commons - 2026-1-23

- We've migrated from using @react-native-community/geolocation to react-native-permissions for handling location permissions.
-

## [5.1.6] Nanoflow Commons - 2025-12-05

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ appId: "${APP_ID}"
id: "container1"
- inputText: "11"
- tapOn: "Show confirmation "
- assertVisible: "Yolo"
- extendedWaitUntil:
visible: "Yolo"
timeout: 10000
- assertVisible: "11"
- tapOn: "OK"
- assertVisible: "Confirmed!"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// - the code between BEGIN EXTRA CODE and END EXTRA CODE
// Other code you write will be lost the next time you deploy the project.
import { Base64 } from "js-base64";
import RNBlobUtil from "react-native-blob-util";

// BEGIN EXTRA CODE
// END EXTRA CODE
Expand All @@ -16,7 +17,7 @@ import { Base64 } from "js-base64";
* @param {MxObject} image
* @returns {Promise.<boolean>}
*/
export function Base64DecodeToImage(base64: string, image: mendix.lib.MxObject): Promise<boolean> {
export async function Base64DecodeToImage(base64: string, image: mendix.lib.MxObject): Promise<boolean> {
// BEGIN USER CODE

if (!base64) {
Expand All @@ -26,8 +27,57 @@ export function Base64DecodeToImage(base64: string, image: mendix.lib.MxObject):
throw new Error("image should not be null");
}

const blob = new Blob([Base64.toUint8Array(base64)], { type: "image/png" });
// Native platform
if (navigator && navigator.product === "ReactNative") {
try {
// Remove data URI prefix if present (e.g., "data:image/png;base64,")
let cleanBase64 = base64;
if (base64.includes(",")) {
cleanBase64 = base64.split(",")[1];
}

// Remove any whitespace/newlines
cleanBase64 = cleanBase64.replace(/\s/g, "");

// Validate base64 format
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(cleanBase64)) {
throw new Error("Invalid base64 format");
}

// Create a temporary file path
const tempPath = `${RNBlobUtil.fs.dirs.CacheDir}/temp_image_${Date.now()}.png`;

// Write Base64 data to a temporary file
await RNBlobUtil.fs.writeFile(tempPath, cleanBase64, "base64");

// Fetch the file as a blob
const res = await fetch(`file://${tempPath}`);
const blob = await res.blob();

return new Promise((resolve, reject) => {
mx.data.saveDocument(
image.getGuid(),
"camera image",
{},
blob,
() => {
RNBlobUtil.fs.unlink(tempPath).catch(() => {});
resolve(true);
},
error => {
RNBlobUtil.fs.unlink(tempPath).catch(() => {});
reject(error);
}
);
});
} catch (error) {
console.error("Failed to decode base64 to image:", error);
return false;
}
}

// Other platforms
const blob = new Blob([Base64.toUint8Array(base64)], { type: "image/png" });
return new Promise((resolve, reject) => {
mx.data.saveDocument(image.getGuid(), "camera image", {}, blob, () => resolve(true), reject);
});
Expand Down
Loading