diff --git a/src/pages-and-resources/PagesAndResources.jsx b/src/pages-and-resources/PagesAndResources.jsx index 663843e24..c99e1409d 100644 --- a/src/pages-and-resources/PagesAndResources.jsx +++ b/src/pages-and-resources/PagesAndResources.jsx @@ -40,7 +40,7 @@ function PagesAndResources({ courseId, intl }) { return (
-
+

{intl.formatMessage(messages.heading)}

{isInvalidConsumerKey && ( - - {errors.consumerKey} - + + {errors.consumerKey} + )} @@ -80,9 +85,9 @@ function LtiConfigForm({ value={values.consumerSecret} /> {isInvalidConsumerSecret && ( - - {errors.consumerSecret} - + + {errors.consumerSecret} + )} @@ -93,11 +98,36 @@ function LtiConfigForm({ value={values.launchUrl} /> {isInvalidLaunchUrl && ( - - {errors.launchUrl} - + + {errors.launchUrl} + )} + {appConfig.piiSharing && ( + <> + + {intl.formatMessage(messages.piiSharing)} + + + + + + + )} @@ -120,6 +150,9 @@ LtiConfigForm.propTypes = { consumerKey: PropTypes.string, consumerSecret: PropTypes.string, launchUrl: PropTypes.string, + piiSharing: PropTypes.bool.isRequired, + piiShareUsername: PropTypes.bool.isRequired, + piiShareEmail: PropTypes.bool.isRequired, }), intl: intlShape.isRequired, onSubmit: PropTypes.func.isRequired, diff --git a/src/pages-and-resources/discussions/app-config-form/apps/lti/messages.js b/src/pages-and-resources/discussions/app-config-form/apps/lti/messages.js index e69d5828b..a48fc18f5 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/lti/messages.js +++ b/src/pages-and-resources/discussions/app-config-form/apps/lti/messages.js @@ -39,6 +39,20 @@ const messages = defineMessages({ defaultMessage: 'Launch URL is a required field', description: 'Tells the user that the Launch URL field is required and must have a value.', }, + piiSharing: { + id: 'authoring.discussions.piiSharing', + defaultMessage: 'Optionally share a user\'s username and/or email with the LTI provider:', + }, + piiShareUsername: { + id: 'authoring.discussions.piiShareUsername', + defaultMessage: 'Share username', + description: 'Label for the Share Username field.', + }, + piiShareEmail: { + id: 'authoring.discussions.piiShareEmail', + defaultMessage: 'Share email', + description: 'Label for the Share Email field.', + }, contact: { id: 'authoring.discussions.appDocInstructions.contact', defaultMessage: 'Contact: {link}', diff --git a/src/pages-and-resources/discussions/data/api.js b/src/pages-and-resources/discussions/data/api.js index d31695cd2..feb20e6c3 100644 --- a/src/pages-and-resources/discussions/data/api.js +++ b/src/pages-and-resources/discussions/data/api.js @@ -11,6 +11,9 @@ function normalizeLtiConfig(data) { consumerKey: data.lti_1p1_client_key, consumerSecret: data.lti_1p1_client_secret, launchUrl: data.lti_1p1_launch_url, + piiSharing: 'pii_share_username' in data || 'pii_share_email' in data, + piiShareUsername: data.pii_share_username, + piiShareEmail: data.pii_share_email, }; } @@ -118,6 +121,12 @@ function denormalizeData(courseId, appId, data) { if (data.launchUrl) { ltiConfiguration.lti_1p1_launch_url = data.launchUrl; } + if ('piiShareUsername' in data) { + ltiConfiguration.pii_share_username = data.piiShareUsername; + } + if ('piiShareEmail' in data) { + ltiConfiguration.pii_share_email = data.piiShareEmail; + } if (Object.keys(ltiConfiguration).length > 0) { // Only add this in if we're sending LTI fields. diff --git a/src/pages-and-resources/discussions/data/redux.test.js b/src/pages-and-resources/discussions/data/redux.test.js index 4f8aab8ed..960d470d8 100644 --- a/src/pages-and-resources/discussions/data/redux.test.js +++ b/src/pages-and-resources/discussions/data/redux.test.js @@ -166,6 +166,45 @@ describe('Data layer integration tests', () => { consumerKey: 'client_key_123', consumerSecret: 'client_secret_123', launchUrl: 'https://localhost/example', + piiSharing: false, + piiShareUsername: undefined, + piiShareEmail: undefined, + }); + }); + + test('successfully loads an LTI configuration with PII Sharing', async () => { + axiosMock.onGet(getAppsUrl(courseId)).reply(200, { + ...piazzaApiResponse, + lti_configuration: { + ...piazzaApiResponse.lti_configuration, + pii_share_username: true, + pii_share_email: false, + }, + }); + + await executeThunk(fetchApps(courseId), store.dispatch); + + expect(store.getState().discussions).toEqual({ + appIds: ['legacy', 'piazza'], + featureIds, + activeAppId: 'piazza', + selectedAppId: null, + status: LOADED, + saveStatus: SAVED, + hasValidationError: false, + discussionTopicIds: [], + }); + expect(store.getState().models.apps.legacy).toEqual(legacyApp); + expect(store.getState().models.apps.piazza).toEqual(piazzaApp); + expect(store.getState().models.features).toEqual(featuresState); + expect(store.getState().models.appConfigs.piazza).toEqual({ + id: 'piazza', + consumerKey: 'client_key_123', + consumerSecret: 'client_secret_123', + launchUrl: 'https://localhost/example', + piiSharing: true, + piiShareUsername: true, + piiShareEmail: false, }); }); @@ -329,6 +368,9 @@ describe('Data layer integration tests', () => { consumerKey: 'new_consumer_key', consumerSecret: 'new_consumer_secret', launchUrl: 'https://localhost/new_launch_url', + piiSharing: false, + piiShareUsername: undefined, + piiShareEmail: undefined, }); });