Skip to content

Commit e86d5e4

Browse files
committed
Merge branch 'master' into CLXR-347-IgniteAI-refactor
2 parents ffe60d6 + b777ed4 commit e86d5e4

410 files changed

Lines changed: 22241 additions & 4545 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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Import Translations
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
import-translations:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Generate GitHub App token
11+
id: app-token
12+
uses: actions/create-github-app-token@v1
13+
with:
14+
app-id: ${{ secrets.GH_APP_ID }}
15+
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
16+
owner: ${{ github.repository_owner }}
17+
repositories: canvas-android
18+
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
token: ${{ steps.app-token.outputs.token }}
24+
25+
- name: Configure git
26+
run: |
27+
git config user.name "inst-danger"
28+
git config user.email "ios@instructure.com"
29+
30+
- name: Set up Ruby
31+
uses: ruby/setup-ruby@v1
32+
with:
33+
ruby-version: '3.2'
34+
35+
- name: Import translations from S3
36+
env:
37+
AWS_ACCESS_KEY_ID: ${{ secrets.TRANSLATIONS_AWS_ACCESS_KEY_ID }}
38+
AWS_SECRET_ACCESS_KEY: ${{ secrets.TRANSLATIONS_AWS_SECRET_ACCESS_KEY }}
39+
run: ruby translations/import.rb
40+
41+
- name: Create pull request
42+
env:
43+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
run: |
45+
BRANCH=$(git branch --show-current)
46+
gh pr create --base master --title "Update translations" --body "Automated translation import" --head "$BRANCH"

apps/parent/src/androidTest/java/com/instructure/parentapp/ui/e2e/compose/AlertsE2ETest.kt

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,4 +442,172 @@ class AlertsE2ETest : ParentComposeTest() {
442442
alertsPage.assertAlertRead("Course grade: 90.0% in ${course.courseCode}")
443443
}
444444

445+
@E2E
446+
@Test
447+
@TestMetaData(Priority.MANDATORY, FeatureCategory.ALERTS, TestCategory.E2E)
448+
fun testAlertSettingsMarkBelowAboveDependencyE2E() {
449+
450+
Log.d(PREPARATION_TAG, "Seeding data.")
451+
val data = seedData(students = 1, courses = 1, teachers = 1, parents = 1)
452+
val parent = data.parentsList[0]
453+
val student = data.studentsList[0]
454+
455+
Log.d(STEP_TAG, "Login with user: '${parent.name}', login id: '${parent.loginId}'.")
456+
tokenLogin(parent)
457+
dashboardPage.waitForRender()
458+
459+
Log.d(STEP_TAG, "Open the Left Side Navigation Drawer menu.")
460+
dashboardPage.openLeftSideMenu()
461+
462+
Log.d(STEP_TAG, "Open the Manage Students Page.")
463+
leftSideNavigationDrawerPage.clickManageStudents()
464+
465+
Log.d(STEP_TAG, "Open the Student Alert Settings Page.")
466+
manageStudentsPage.clickStudent(student.shortName)
467+
468+
val assignmentGradeBelow = "Assignment grade below"
469+
val assignmentGradeAbove = "Assignment grade above"
470+
Log.d(STEP_TAG, "Set '$assignmentGradeBelow' threshold to 30%.")
471+
studentAlertSettingsPage.setThreshold(AlertType.ASSIGNMENT_GRADE_LOW, "30")
472+
473+
Log.d(ASSERTION_TAG, "Assert that '$assignmentGradeBelow' threshold is set to '30%'.")
474+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.ASSIGNMENT_GRADE_LOW, "30%")
475+
476+
Log.d(STEP_TAG, "Open the '$assignmentGradeAbove' dialog and enter '20' (below the low threshold of 30).")
477+
studentAlertSettingsPage.clickThreshold(AlertType.ASSIGNMENT_GRADE_HIGH)
478+
studentAlertSettingsPage.enterThreshold("20")
479+
480+
Log.d(ASSERTION_TAG, "Assert that an error is shown because 20 is not above the '$assignmentGradeBelow' threshold of 30.")
481+
studentAlertSettingsPage.assertThresholdDialogError()
482+
483+
Log.d(STEP_TAG, "Change the '$assignmentGradeAbove' threshold value to '70'.")
484+
studentAlertSettingsPage.enterThreshold("70")
485+
486+
Log.d(ASSERTION_TAG, "Assert that there is no error for '$assignmentGradeAbove' value 70.")
487+
studentAlertSettingsPage.assertThresholdDialogNotError()
488+
489+
Log.d(STEP_TAG, "Save the '$assignmentGradeAbove' threshold.")
490+
studentAlertSettingsPage.tapThresholdSaveButton()
491+
492+
Log.d(ASSERTION_TAG, "Assert that '$assignmentGradeAbove' threshold is saved as '70%'.")
493+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.ASSIGNMENT_GRADE_HIGH, "70%")
494+
495+
Log.d(STEP_TAG, "Open the '$assignmentGradeBelow' dialog and enter '80' (above the '$assignmentGradeAbove' threshold of 70).")
496+
studentAlertSettingsPage.clickThreshold(AlertType.ASSIGNMENT_GRADE_LOW)
497+
studentAlertSettingsPage.enterThreshold("80")
498+
499+
Log.d(ASSERTION_TAG, "Assert that an error is shown because 80 is not below the '$assignmentGradeAbove' threshold of 70.")
500+
studentAlertSettingsPage.assertThresholdDialogError()
501+
502+
Log.d(STEP_TAG, "Change the '$assignmentGradeBelow' threshold value to '50'.")
503+
studentAlertSettingsPage.enterThreshold("50")
504+
505+
Log.d(ASSERTION_TAG, "Assert that there is no error for '$assignmentGradeBelow' value 50.")
506+
studentAlertSettingsPage.assertThresholdDialogNotError()
507+
508+
Log.d(STEP_TAG, "Save the '$assignmentGradeBelow' threshold.")
509+
studentAlertSettingsPage.tapThresholdSaveButton()
510+
511+
Log.d(ASSERTION_TAG, "Assert that '$assignmentGradeBelow' threshold is saved as '50%'.")
512+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.ASSIGNMENT_GRADE_LOW, "50%")
513+
514+
Log.d(STEP_TAG, "Set '$assignmentGradeBelow' to Never so the '$assignmentGradeAbove' threshold has no lower bound.")
515+
studentAlertSettingsPage.clickThreshold(AlertType.ASSIGNMENT_GRADE_LOW)
516+
studentAlertSettingsPage.tapThresholdNeverButton()
517+
518+
Log.d(ASSERTION_TAG, "Assert that '$assignmentGradeBelow' threshold is set to 'Never'.")
519+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.ASSIGNMENT_GRADE_LOW, "Never")
520+
521+
Log.d(STEP_TAG, "Open the '$assignmentGradeAbove' dialog and enter '100' (boundary value that is not allowed).")
522+
studentAlertSettingsPage.clickThreshold(AlertType.ASSIGNMENT_GRADE_HIGH)
523+
studentAlertSettingsPage.enterThreshold("100")
524+
525+
Log.d(ASSERTION_TAG, "Assert that an error is shown because 100 is not a valid '$assignmentGradeAbove' threshold (max is exclusive).")
526+
studentAlertSettingsPage.assertThresholdDialogError()
527+
528+
Log.d(STEP_TAG, "Change the '$assignmentGradeAbove' threshold value to '99' (the maximum valid value).")
529+
studentAlertSettingsPage.enterThreshold("99")
530+
531+
Log.d(ASSERTION_TAG, "Assert that there is no error for '$assignmentGradeAbove' value 99.")
532+
studentAlertSettingsPage.assertThresholdDialogNotError()
533+
534+
Log.d(STEP_TAG, "Save the '$assignmentGradeAbove' threshold.")
535+
studentAlertSettingsPage.tapThresholdSaveButton()
536+
537+
Log.d(ASSERTION_TAG, "Assert that '$assignmentGradeAbove' threshold is saved as '99%'.")
538+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.ASSIGNMENT_GRADE_HIGH, "99%")
539+
540+
val courseGradeBelow = "Course grade below"
541+
val courseGradeAbove = "Course grade above"
542+
Log.d(STEP_TAG, "Set '$courseGradeBelow' threshold to 30%.")
543+
studentAlertSettingsPage.setThreshold(AlertType.COURSE_GRADE_LOW, "30")
544+
545+
Log.d(ASSERTION_TAG, "Assert that '$courseGradeBelow' threshold is set to '30%'.")
546+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.COURSE_GRADE_LOW, "30%")
547+
548+
Log.d(STEP_TAG, "Open the '$courseGradeAbove' dialog and enter '20' (below the low threshold of 30).")
549+
studentAlertSettingsPage.clickThreshold(AlertType.COURSE_GRADE_HIGH)
550+
studentAlertSettingsPage.enterThreshold("20")
551+
552+
Log.d(ASSERTION_TAG, "Assert that an error is shown because 20 is not above the '$courseGradeBelow' threshold of 30.")
553+
studentAlertSettingsPage.assertThresholdDialogError()
554+
555+
Log.d(STEP_TAG, "Change the '$courseGradeAbove' threshold value to '70'.")
556+
studentAlertSettingsPage.enterThreshold("70")
557+
558+
Log.d(ASSERTION_TAG, "Assert that there is no error for '$courseGradeAbove' value 70.")
559+
studentAlertSettingsPage.assertThresholdDialogNotError()
560+
561+
Log.d(STEP_TAG, "Save the '$courseGradeAbove' threshold.")
562+
studentAlertSettingsPage.tapThresholdSaveButton()
563+
564+
Log.d(ASSERTION_TAG, "Assert that '$courseGradeAbove' threshold is saved as '70%'.")
565+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.COURSE_GRADE_HIGH, "70%")
566+
567+
Log.d(STEP_TAG, "Open the '$courseGradeBelow' dialog and enter '80' (above the '$courseGradeAbove' threshold of 70).")
568+
studentAlertSettingsPage.clickThreshold(AlertType.COURSE_GRADE_LOW)
569+
studentAlertSettingsPage.enterThreshold("80")
570+
571+
Log.d(ASSERTION_TAG, "Assert that an error is shown because 80 is not below the '$courseGradeAbove' threshold of 70.")
572+
studentAlertSettingsPage.assertThresholdDialogError()
573+
574+
Log.d(STEP_TAG, "Change the '$courseGradeBelow' threshold value to '50'.")
575+
studentAlertSettingsPage.enterThreshold("50")
576+
577+
Log.d(ASSERTION_TAG, "Assert that there is no error for '$courseGradeBelow' value 50.")
578+
studentAlertSettingsPage.assertThresholdDialogNotError()
579+
580+
Log.d(STEP_TAG, "Save the '$courseGradeBelow' threshold.")
581+
studentAlertSettingsPage.tapThresholdSaveButton()
582+
583+
Log.d(ASSERTION_TAG, "Assert that '$courseGradeBelow' threshold is saved as '50%'.")
584+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.COURSE_GRADE_LOW, "50%")
585+
586+
Log.d(STEP_TAG, "Set '$courseGradeBelow' to Never so the '$courseGradeAbove' threshold has no lower bound.")
587+
studentAlertSettingsPage.clickThreshold(AlertType.COURSE_GRADE_LOW)
588+
studentAlertSettingsPage.tapThresholdNeverButton()
589+
590+
Log.d(ASSERTION_TAG, "Assert that '$courseGradeBelow' threshold is set to 'Never'.")
591+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.COURSE_GRADE_LOW, "Never")
592+
593+
Log.d(STEP_TAG, "Open the '$courseGradeAbove' dialog and enter '100' (boundary value that is not allowed).")
594+
studentAlertSettingsPage.clickThreshold(AlertType.COURSE_GRADE_HIGH)
595+
studentAlertSettingsPage.enterThreshold("100")
596+
597+
Log.d(ASSERTION_TAG, "Assert that an error is shown because 100 is not a valid '$courseGradeAbove' threshold (max is exclusive).")
598+
studentAlertSettingsPage.assertThresholdDialogError()
599+
600+
Log.d(STEP_TAG, "Change the '$courseGradeAbove' threshold value to '99' (the maximum valid value).")
601+
studentAlertSettingsPage.enterThreshold("99")
602+
603+
Log.d(ASSERTION_TAG, "Assert that there is no error for '$courseGradeAbove' value 99.")
604+
studentAlertSettingsPage.assertThresholdDialogNotError()
605+
606+
Log.d(STEP_TAG, "Save the '$courseGradeAbove' threshold.")
607+
studentAlertSettingsPage.tapThresholdSaveButton()
608+
609+
Log.d(ASSERTION_TAG, "Assert that '$courseGradeAbove' threshold is saved as '99%'.")
610+
studentAlertSettingsPage.assertPercentageThreshold(AlertType.COURSE_GRADE_HIGH, "99%")
611+
}
612+
445613
}

apps/parent/src/androidTest/java/com/instructure/parentapp/ui/e2e/compose/LoginE2ETest.kt

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,15 @@ class LoginE2ETest : ParentComposeTest() {
243243

244244
@E2E
245245
@Test
246+
@Stub("MBL-19866")
246247
@TestMetaData(Priority.IMPORTANT, FeatureCategory.LOGIN, TestCategory.E2E)
247248
fun testInvalidAndEmptyLoginCredentialsE2E() {
248249

249250
val INVALID_USERNAME = "invalidusercred@test.com"
250251
val INVALID_PASSWORD = "invalidpw"
251-
val INVALID_CREDENTIALS_ERROR_MESSAGE = "Please verify your username or password and try again. Trouble logging in? Check out our Login FAQs."
252-
val NO_PASSWORD_GIVEN_ERROR_MESSAGE = "No password was given"
252+
val INVALID_CREDENTIALS_ERROR_MESSAGE = "Please verify your username or password and try again."
253+
val NO_EMAIL_GIVEN_ERROR_MESSAGE = "Please enter your email."
254+
val NO_PASSWORD_GIVEN_ERROR_MESSAGE = "Please enter your password."
253255
val DOMAIN = "mobileqa.beta"
254256

255257
Log.d(STEP_TAG, "Click 'Find My School' button.")
@@ -261,58 +263,31 @@ class LoginE2ETest : ParentComposeTest() {
261263
Log.d(STEP_TAG, "Click on 'Next' button on the Toolbar.")
262264
loginFindSchoolPage.clickToolbarNextMenuItem()
263265

266+
/* Somehow React does not recognize the invalid credentials, need to be fixed in follow-up ticket
264267
Log.d(STEP_TAG, "Try to login with invalid, non-existing credentials ('$INVALID_USERNAME', '$INVALID_PASSWORD').")
265268
loginSignInPage.loginAs(INVALID_USERNAME, INVALID_PASSWORD)
266269
267270
Log.d(ASSERTION_TAG, "Assert that the invalid credentials error message is displayed.")
268-
loginSignInPage.assertLoginErrorMessage(INVALID_CREDENTIALS_ERROR_MESSAGE)
269-
271+
loginSignInPage.assertLoginEmailErrorMessage(INVALID_CREDENTIALS_ERROR_MESSAGE) // Invalid credentials error message will be displayed within the email error message holder on the login page.
272+
*/
270273
Log.d(STEP_TAG, "Try to login with no credentials typed in either of the username and password field.")
271274
loginSignInPage.loginAs(EMPTY_STRING, EMPTY_STRING)
272275

273-
Log.d(ASSERTION_TAG, "Assert that the no password was given error message is displayed.")
274-
loginSignInPage.assertLoginErrorMessage(NO_PASSWORD_GIVEN_ERROR_MESSAGE)
276+
Log.d(ASSERTION_TAG, "Assert that the no email and no password error messages are displayed.")
277+
loginSignInPage.assertLoginEmailErrorMessage(NO_EMAIL_GIVEN_ERROR_MESSAGE)
278+
loginSignInPage.assertLoginPasswordErrorMessage(NO_PASSWORD_GIVEN_ERROR_MESSAGE)
275279

276280
Log.d(STEP_TAG, "Try to login with leaving only the password field empty.")
277281
loginSignInPage.loginAs(INVALID_USERNAME, EMPTY_STRING)
278282

279283
Log.d(ASSERTION_TAG, "Assert that the no password was given error message is displayed.")
280-
loginSignInPage.assertLoginErrorMessage(NO_PASSWORD_GIVEN_ERROR_MESSAGE)
284+
loginSignInPage.assertLoginEmailErrorMessage(NO_PASSWORD_GIVEN_ERROR_MESSAGE)
281285

282286
Log.d(STEP_TAG, "Try to login with leaving only the username field empty.")
283287
loginSignInPage.loginAs(EMPTY_STRING, INVALID_PASSWORD)
284288

285-
Log.d(ASSERTION_TAG, "Assert that the invalid credentials error message is displayed.")
286-
loginSignInPage.assertLoginErrorMessage(INVALID_CREDENTIALS_ERROR_MESSAGE)
287-
}
288-
289-
private fun loginWithUser(user: CanvasUserApiModel, lastSchoolSaved: Boolean = false) {
290-
291-
Thread.sleep(5100) //Need to wait > 5 seconds before each login attempt because of new 'too many attempts' login policy on web.
292-
293-
if (lastSchoolSaved) {
294-
Log.d(STEP_TAG, "Click 'Find Another School' button.")
295-
loginLandingPage.clickFindAnotherSchoolButton()
296-
} else {
297-
Log.d(STEP_TAG, "Click 'Find My School' button.")
298-
loginLandingPage.clickFindMySchoolButton()
299-
}
300-
301-
Log.d(STEP_TAG, "Enter domain: '${user.domain}'.")
302-
loginFindSchoolPage.enterDomain(user.domain)
303-
304-
Log.d(STEP_TAG, "Click on 'Next' button on the Toolbar.")
305-
loginFindSchoolPage.clickToolbarNextMenuItem()
306-
loginSignInPage.loginAs(user)
307-
}
308-
309-
private fun loginWithLastSavedSchool(user: CanvasUserApiModel) {
310-
311-
Log.d(STEP_TAG, "Click on last saved school's button.")
312-
loginLandingPage.clickOnLastSavedSchoolButton()
313-
314-
Log.d(STEP_TAG, "Login with '${user.name}' user.")
315-
loginSignInPage.loginAs(user)
289+
Log.d(ASSERTION_TAG, "Assert that the no email error message is displayed.")
290+
loginSignInPage.assertLoginEmailErrorMessage(NO_EMAIL_GIVEN_ERROR_MESSAGE) // Invalid credentials error message will be displayed within the email error message holder on the login page.
316291
}
317292

318293
@E2E
@@ -401,7 +376,6 @@ class LoginE2ETest : ParentComposeTest() {
401376
}
402377

403378
@Test
404-
@Stub("Stubbed because there was some change on 7th or 8th of July, 2025 and on the CI it loads an invalid URL page, however the test runs locally.")
405379
@E2E
406380
@TestMetaData(Priority.IMPORTANT, FeatureCategory.LOGIN, TestCategory.E2E, SecondaryFeatureCategory.CANVAS_NETWORK)
407381
fun testCanvasNetworkSignInPageE2E() {
@@ -482,4 +456,32 @@ class LoginE2ETest : ParentComposeTest() {
482456
}
483457
}
484458

459+
private fun loginWithUser(user: CanvasUserApiModel, lastSchoolSaved: Boolean = false) {
460+
461+
Thread.sleep(5100) //Need to wait > 5 seconds before each login attempt because of new 'too many attempts' login policy on web.
462+
463+
if (lastSchoolSaved) {
464+
Log.d(STEP_TAG, "Click 'Find Another School' button.")
465+
loginLandingPage.clickFindAnotherSchoolButton()
466+
} else {
467+
Log.d(STEP_TAG, "Click 'Find My School' button.")
468+
loginLandingPage.clickFindMySchoolButton()
469+
}
470+
471+
Log.d(STEP_TAG, "Enter domain: '${user.domain}'.")
472+
loginFindSchoolPage.enterDomain(user.domain)
473+
474+
Log.d(STEP_TAG, "Click on 'Next' button on the Toolbar.")
475+
loginFindSchoolPage.clickToolbarNextMenuItem()
476+
loginSignInPage.loginAs(user)
477+
}
478+
479+
private fun loginWithLastSavedSchool(user: CanvasUserApiModel) {
480+
481+
Log.d(STEP_TAG, "Click on last saved school's button.")
482+
loginLandingPage.clickOnLastSavedSchoolButton()
483+
484+
Log.d(STEP_TAG, "Login with '${user.name}' user.")
485+
loginSignInPage.loginAs(user)
486+
}
485487
}

apps/parent/src/main/java/com/instructure/parentapp/features/inbox/list/ParentInboxRouter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ParentInboxRouter(
3737
) : InboxRouter {
3838

3939
override fun openConversation(conversation: Conversation, scope: InboxApi.Scope) {
40-
navigation.navigate(activity, navigation.inboxDetailsRoute(conversation.id, conversation.workflowState == Conversation.WorkflowState.UNREAD))
40+
navigation.navigate(activity, navigation.inboxDetailsRoute(conversation.id, conversation.workflowState == Conversation.WorkflowState.UNREAD, scope))
4141
}
4242

4343
override fun attachNavigationIcon(toolbar: Toolbar) {

0 commit comments

Comments
 (0)