Skip to content

Commit cb3af18

Browse files
Merge branch 'main' into MMQA-1785-tier-4-rpc-mocks
2 parents 68444a3 + f3a256d commit cb3af18

156 files changed

Lines changed: 10847 additions & 1528 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
name: CI Status Gate
2+
description: Evaluate required CI job results and fail on unexpected skips or failed jobs.
3+
4+
inputs:
5+
needs-json:
6+
description: JSON representation of the calling job's needs context.
7+
required: true
8+
requirement-context-json:
9+
description: JSON representation of get-requirements outputs.
10+
required: true
11+
e2e-job-regex:
12+
description: Regex matching E2E build/test jobs whose skipped result is allowed. Failed or cancelled E2E jobs still fail.
13+
required: false
14+
default: '^e2e-'
15+
event-name:
16+
description: GitHub event name for the current workflow run.
17+
required: true
18+
is-fork:
19+
description: Whether the current pull request originates from a fork. When true, skipped jobs are treated as allowed skips.
20+
required: false
21+
default: 'false'
22+
23+
runs:
24+
using: composite
25+
steps:
26+
- name: Evaluate CI status
27+
shell: bash
28+
env:
29+
NEEDS_JSON: ${{ inputs.needs-json }}
30+
REQUIREMENT_CONTEXT_JSON: ${{ inputs.requirement-context-json }}
31+
E2E_JOB_REGEX: ${{ inputs.e2e-job-regex }}
32+
EVENT_NAME: ${{ inputs.event-name }}
33+
IS_FORK: ${{ inputs.is-fork }}
34+
run: |
35+
set -euo pipefail
36+
37+
get_requirement() {
38+
local key="$1"
39+
jq -nr --arg key "$key" 'env.REQUIREMENT_CONTEXT_JSON | fromjson | .[$key] // "false"'
40+
}
41+
42+
sanitize_markdown_cell() {
43+
local value="$1"
44+
value="${value//$'\n'/ }"
45+
value="${value//|/\\|}"
46+
printf '%s' "$value"
47+
}
48+
49+
add_summary_row() {
50+
local job_name result decision reason
51+
job_name="$(sanitize_markdown_cell "$1")"
52+
result="$(sanitize_markdown_cell "$2")"
53+
decision="$(sanitize_markdown_cell "$3")"
54+
reason="$(sanitize_markdown_cell "$4")"
55+
56+
printf '| `%s` | `%s` | %s | %s |\n' \
57+
"$job_name" "$result" "$decision" "$reason" >> "$summary_file"
58+
}
59+
60+
mark_failure() {
61+
local message="$1"
62+
failed="true"
63+
echo "::error::$message"
64+
}
65+
66+
validate_json_type() {
67+
local variable_name="$1"
68+
local expected_type="$2"
69+
70+
if ! jq -en --arg variable_name "$variable_name" --arg expected_type "$expected_type" \
71+
'(env[$variable_name] | fromjson | type) == $expected_type' >/dev/null 2>&1; then
72+
echo "::error::$variable_name is not a valid JSON $expected_type"
73+
exit 1
74+
fi
75+
}
76+
77+
require_requirement_key() {
78+
local key="$1"
79+
80+
if ! jq -en --arg key "$key" \
81+
'env.REQUIREMENT_CONTEXT_JSON | fromjson | .[$key] != null' >/dev/null 2>&1; then
82+
echo "::error::REQUIREMENT_CONTEXT_JSON is missing or null for required key: $key"
83+
exit 1
84+
fi
85+
}
86+
87+
validate_json_type NEEDS_JSON object
88+
validate_json_type REQUIREMENT_CONTEXT_JSON object
89+
90+
for required_key in skip_everything block_merge_for_e2e_readiness; do
91+
require_requirement_key "$required_key"
92+
done
93+
94+
skip_everything="$(get_requirement skip_everything)"
95+
block_merge_for_e2e_readiness="$(get_requirement block_merge_for_e2e_readiness)"
96+
97+
if [[ "$block_merge_for_e2e_readiness" == "true" ]]; then
98+
echo "::error::The 'pr-not-ready-for-e2e' label is still applied. Remove it to trigger E2E tests before merging."
99+
exit 1
100+
fi
101+
102+
if [[ "$skip_everything" == "true" ]]; then
103+
echo "skip_everything=true; treating all jobs as passed"
104+
exit 0
105+
fi
106+
107+
failed="false"
108+
summary_file="$(mktemp)"
109+
trap 'if [[ -n "${GITHUB_STEP_SUMMARY:-}" && -f "$summary_file" ]]; then cat "$summary_file" >> "$GITHUB_STEP_SUMMARY"; fi; rm -f "$summary_file"' EXIT
110+
job_count=0
111+
112+
{
113+
echo "### CI Status Gate"
114+
echo
115+
echo "| Job | Result | Decision | Reason |"
116+
echo "| --- | --- | --- | --- |"
117+
} >> "$summary_file"
118+
119+
while IFS=$'\t' read -r job_name result; do
120+
job_count=$((job_count + 1))
121+
122+
case "$result" in
123+
success)
124+
add_summary_row "$job_name" "$result" "pass" "job succeeded"
125+
;;
126+
failure|cancelled)
127+
mark_failure "$job_name finished with result: $result"
128+
add_summary_row "$job_name" "$result" "fail" "job did not complete successfully"
129+
;;
130+
skipped)
131+
if [[ "$job_name" =~ $E2E_JOB_REGEX ]]; then
132+
add_summary_row "$job_name" "$result" "pass" "skipped E2E jobs are allowed"
133+
elif [[ "$EVENT_NAME" == "merge_group" ]]; then
134+
add_summary_row "$job_name" "$result" "pass" "merge queue skip is allowed"
135+
elif [[ "$IS_FORK" == "true" ]]; then
136+
add_summary_row "$job_name" "$result" "pass" "fork-only skip is allowed"
137+
else
138+
mark_failure "$job_name was skipped unexpectedly"
139+
add_summary_row "$job_name" "$result" "fail" "skip was not expected"
140+
fi
141+
;;
142+
*)
143+
mark_failure "$job_name has unknown result: $result"
144+
add_summary_row "$job_name" "$result" "fail" "job result is unknown"
145+
;;
146+
esac
147+
done < <(jq -nr 'env.NEEDS_JSON | fromjson | to_entries[] | [.key, (.value.result // "")] | @tsv')
148+
149+
if [[ "$job_count" -eq 0 ]]; then
150+
echo "::error::NEEDS_JSON does not contain any jobs"
151+
exit 1
152+
fi
153+
154+
if [[ "$failed" == "true" ]]; then
155+
exit 1
156+
fi
157+
158+
echo "All required jobs passed"

.github/actions/setup-e2e-env/action.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,9 @@ runs:
283283
node_modules
284284
.yarn/install-state.gz
285285
key: ${{ inputs.cache-prefix }}-yarn-${{ inputs.platform }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
286+
restore-keys: |
287+
${{ inputs.cache-prefix }}-yarn-${{ inputs.platform }}-${{ runner.os }}-
288+
continue-on-error: true
286289

287290
- name: Install JavaScript dependencies with retry
288291
id: yarn-install
@@ -387,19 +390,19 @@ runs:
387390
${{ runner.os }}-cocoapods-specs-
388391
continue-on-error: true
389392

390-
- name: Clear CocoaPods trunk to prevent stale specs
391-
if: ${{ inputs.platform == 'ios' }}
392-
run: pod repo remove trunk || true
393-
shell: bash
394-
395393
- name: Install CocoaPods via bundler
396394
if: ${{ inputs.platform == 'ios'}}
397395
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
398396
with:
399397
timeout_minutes: 15
400-
max_attempts: 2
401-
retry_wait_seconds: 30
398+
max_attempts: 3
399+
retry_wait_seconds: 60
400+
on_retry_command: |
401+
echo "::warning::CocoaPods install failed, retrying after trunk cleanup..."
402+
pod repo remove trunk || true
402403
command: cd ios && bundle exec pod install --repo-update
404+
env:
405+
COCOAPODS_DISABLE_STATS: 'true'
403406

404407
- name: Install applesimutils
405408
if: ${{ inputs.platform == 'ios' }}

.github/workflows/ci.yml

Lines changed: 24 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -937,113 +937,41 @@ jobs:
937937
fi
938938
fi
939939
940-
all-jobs-pass:
941-
name: All jobs pass
942-
runs-on: ubuntu-latest
943-
if: ${{ !cancelled() }}
944-
needs:
945-
[
946-
check-diff,
947-
dedupe,
948-
scripts,
949-
unit-tests,
950-
component-view-tests,
951-
check-workflows,
952-
js-bundle-size-check,
953-
sonar-cloud-quality-gate-status,
954-
]
955-
outputs:
956-
ALL_JOBS_PASSED: ${{ steps.jobs-passed-status.outputs.ALL_JOBS_PASSED }}
957-
steps:
958-
- name: Set jobs passed status
959-
id: jobs-passed-status
960-
env:
961-
NEEDS_CONTEXT: ${{ toJSON(needs) }}
962-
EVENT_NAME: ${{ github.event_name }}
963-
IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
964-
run: |
965-
# Check results of all required jobs dynamically
966-
# On merge_group events, "skipped" is acceptable (some jobs intentionally skip)
967-
# On fork PRs, "skipped" is acceptable (secret-dependent jobs are intentionally skipped)
968-
# On other events (push to main), all jobs must succeed
969-
970-
FAILED="false"
971-
972-
while read -r job_name result; do
973-
if [[ "$result" == "failure" ]] || [[ "$result" == "cancelled" ]]; then
974-
echo "::error::Job '$job_name' failed with result: $result"
975-
FAILED="true"
976-
elif [[ "$result" == "skipped" ]]; then
977-
if [[ "$EVENT_NAME" == "merge_group" ]] || [[ "$IS_FORK" == "true" ]]; then
978-
echo "Job '$job_name' was skipped (OK for merge_group events and fork PRs)"
979-
else
980-
echo "::error::Job '$job_name' was unexpectedly skipped on $EVENT_NAME event"
981-
FAILED="true"
982-
fi
983-
else
984-
echo "Job '$job_name' passed"
985-
fi
986-
done < <(echo "$NEEDS_CONTEXT" | jq -r 'to_entries[] | "\(.key) \(.value.result)"')
987-
988-
if [[ "$FAILED" == "true" ]]; then
989-
echo "Some required jobs failed"
990-
exit 1
991-
fi
992-
993-
echo "ALL_JOBS_PASSED=true" >> "$GITHUB_OUTPUT"
994-
995940
check-all-jobs-pass:
996941
name: Check all jobs pass
997-
if: ${{ !cancelled() }}
942+
# Run the aggregate gate even when optional dependencies are skipped.
943+
# The composite action decides which skipped jobs are acceptable.
944+
if: ${{ always() && !cancelled() }}
998945
runs-on: ubuntu-latest
999946
needs:
1000947
- get_requirements
1001-
- all-jobs-pass
948+
- check-diff
949+
- dedupe
950+
- scripts
951+
- unit-tests
952+
- component-view-tests
953+
- check-workflows
954+
- js-bundle-size-check
955+
- sonar-cloud-quality-gate-status
1002956
- build-android-apks
1003957
- build-ios-apps
1004958
- e2e-smoke-tests-android
1005959
- e2e-smoke-tests-ios
1006-
env:
1007-
SKIPPED: ${{ needs.get_requirements.outputs.skip_everything == 'true' }}
1008960
steps:
1009-
- name: Block merge while pr-not-ready-for-e2e label is applied
1010-
if: ${{ needs.get_requirements.outputs.block_merge_for_e2e_readiness == 'true' }}
1011-
run: |
1012-
echo "::error::The 'pr-not-ready-for-e2e' label is still applied. Remove it to trigger E2E tests before merging."
1013-
exit 1
1014-
- run: |
1015-
# If the merge queue was skipped, consider all jobs as passed
1016-
if [[ "$SKIPPED" == "true" ]]; then
1017-
echo "Merge queue skipped, considering all jobs as passed"
1018-
exit 0
1019-
fi
1020-
1021-
# Check if all non-E2E jobs passed
1022-
if [[ "${{ needs.all-jobs-pass.outputs.ALL_JOBS_PASSED }}" != "true" ]]; then
1023-
echo "Non-E2E jobs failed"
1024-
exit 1
1025-
fi
1026-
1027-
# Check E2E build + smoke results only if E2E should have run.
1028-
# 'skipped' is acceptable — covers merge_group, fork PRs, ignorable-only changes,
1029-
# platform-only PRs, and AI selection returning zero tags.
1030-
# 'failure'/'cancelled' on any of build or smoke must block merge.
1031-
if [[ "${{ needs.get_requirements.outputs.skip_e2e }}" != "true" ]]; then
1032-
for entry in \
1033-
"build-android-apks:${{ needs.build-android-apks.result }}" \
1034-
"e2e-smoke-tests-android:${{ needs.e2e-smoke-tests-android.result }}" \
1035-
"build-ios-apps:${{ needs.build-ios-apps.result }}" \
1036-
"e2e-smoke-tests-ios:${{ needs.e2e-smoke-tests-ios.result }}"; do
1037-
name="${entry%%:*}"
1038-
result="${entry#*:}"
1039-
if [[ "$result" == "failure" ]] || [[ "$result" == "cancelled" ]]; then
1040-
echo "::error::Required E2E job '$name' did not succeed (result: $result)"
1041-
exit 1
1042-
fi
1043-
done
1044-
fi
961+
- uses: actions/checkout@v6
962+
with:
963+
fetch-depth: 1
964+
sparse-checkout: |
965+
.github/actions/ci-status-gate
1045966
1046-
echo "All required jobs passed"
967+
- name: Evaluate CI status
968+
uses: ./.github/actions/ci-status-gate
969+
with:
970+
needs-json: ${{ toJSON(needs) }}
971+
requirement-context-json: ${{ toJSON(needs.get_requirements.outputs) }}
972+
e2e-job-regex: '^(build-android-apks|build-ios-apps|e2e-smoke-tests-android|e2e-smoke-tests-ios)$'
973+
event-name: ${{ github.event_name }}
974+
is-fork: ${{ github.event.pull_request.head.repo.fork == true }}
1047975

1048976
log-merge-group-failure:
1049977
name: Log merge group failure

0 commit comments

Comments
 (0)