diff --git a/.env.development b/.env.development index 9e9f84cf22..8a4494712a 100644 --- a/.env.development +++ b/.env.development @@ -52,3 +52,4 @@ USE_API_CACHE='true' SUBSCRIPTION_LPR='true' PLOTLY_SERVER_URL='http://localhost:8050' AUTH0_SELF_SERVICE_INTEGRATION='true' +FEATURE_SSO_SETTINGS_TAB='true' diff --git a/package-lock.json b/package-lock.json index ea06117091..fe42c17d03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@edx/frontend-enterprise-utils": "3.2.0", "@edx/frontend-platform": "4.0.1", "@edx/paragon": "20.39.2", + "@tanstack/react-query": "^4.35.7", "algoliasearch": "4.8.3", "axios-mock-adapter": "1.19.0", "classnames": "2.2.6", @@ -5553,6 +5554,41 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/query-core": { + "version": "4.35.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.35.7.tgz", + "integrity": "sha512-PgDJtX75ubFS0WCYFM7DqEoJ4QbxU3S5OH3gJSI40xr7UVVax3/J4CM3XUMOTs+EOT5YGEfssi3tfRVGte4DEw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.35.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.35.7.tgz", + "integrity": "sha512-0MankquP/6EOM2ATfEov6ViiKemey5uTbjGlFMX1xGotwNaqC76YKDMJdHumZupPbZcZPWAeoPGEHQmVKIKoOQ==", + "dependencies": { + "@tanstack/query-core": "4.35.7", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@testing-library/dom": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", @@ -22817,6 +22853,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index c358cbf26d..fbd1b8ffac 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@edx/frontend-enterprise-utils": "3.2.0", "@edx/frontend-platform": "4.0.1", "@edx/paragon": "20.39.2", + "@tanstack/react-query": "^4.35.7", "algoliasearch": "4.8.3", "axios-mock-adapter": "1.19.0", "classnames": "2.2.6", diff --git a/src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx b/src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx new file mode 100644 index 0000000000..99a2bb8b52 --- /dev/null +++ b/src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx @@ -0,0 +1,179 @@ +import _ from 'lodash'; +import { + CardGrid, + Skeleton, + useToggle, +} from '@edx/paragon'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import LmsApiService from '../../../data/services/LmsApiService'; +import NewSSOConfigAlerts from './NewSSOConfigAlerts'; +import NewSSOConfigCard from './NewSSOConfigCard'; + +const FRESH_CONFIG_POLLING_INTERVAL = 30000; +const UPDATED_CONFIG_POLLING_INTERVAL = 2000; + +const NewExistingSSOConfigs = ({ + configs, refreshBool, setRefreshBool, enterpriseId, +}) => { + const [inactiveConfigs, setInactiveConfigs] = useState([]); + const [activeConfigs, setActiveConfigs] = useState([]); + const [inProgressConfigs, setInProgressConfigs] = useState([]); + const [untestedConfigs, setUntestedConfigs] = useState([]); + const [liveConfigs, setLiveConfigs] = useState([]); + const [notConfiguredConfigs, setNotConfiguredConfigs] = useState([]); + const [queryForTestedConfigs, setQueryForTestedConfigs] = useState(false); + const [queryForConfiguredConfigs, setQueryForConfiguredConfigs] = useState(false); + const [intervalMs, setIntervalMs] = React.useState(FRESH_CONFIG_POLLING_INTERVAL); + const [loading, setLoading] = useState(false); + const [showAlerts, openAlerts, closeAlerts] = useToggle(false); + + const queryClient = useQueryClient(); + + useQuery({ + queryKey: ['ssoOrchestratorConfigPoll'], + queryFn: async () => { + const res = await LmsApiService.listEnterpriseSsoOrchestrationRecords(enterpriseId); + const inProgress = res.data.filter( + config => (config.submitted_at && !config.configured_at) || (config.configured_at < config.submitted_at), + ); + const untested = res.data.filter(config => !config.validated_at || config.validated_at < config.configured_at); + + if (queryForConfiguredConfigs) { + if (inProgress.length === 0) { + setRefreshBool(!refreshBool); + setQueryForConfiguredConfigs(false); + } + } + + if (queryForTestedConfigs) { + if (untested.length === 0) { + setRefreshBool(!refreshBool); + setQueryForTestedConfigs(false); + } + } + + if (inProgress.length === 0 && untested.length === 0) { + queryClient.invalidateQueries({ queryKey: ['ssoOrchestratorConfigPoll'] }); + } + + return res.data; + }, + refetchInterval: intervalMs, + enabled: queryForTestedConfigs || queryForConfiguredConfigs, + refetchOnWindowFocus: true, + }); + + const renderCards = (gridTitle, configList) => { + if (configList.length > 0) { + return ( +
+ edX is configuring your SSO. This step takes approximately{' '} + {notConfigured.length > 0 ? `five minutes. You will receive an email at ${contactEmail} when the configuration is complete` : 'fifteen seconds'}. +
+
+ Your SSO configuration has completed,
+ and you should have received an email with the following instructions:
+
+ 1. Copy the URL for your learner Portal dashboard below:
+
+ http://courses.edx.org/dashboard?tpa_hint=saml-bestrun-hana
+
+ 2: Launch a new incognito or private window and paste the copied URL into the URL bar to load your
+ learner Portal dashboard.
+
+ 3: When prompted, enter login credentials supported by your IDP to test your connection to edX.
+
+ Return to this window after completing the testing instructions.
+ This window will automatically update when a successful test is detected.
+
+ Great news! Your test was successful and your new SSO integration is live and ready to use. +
+
+ Only one SSO integration is supported at a time.
+
+ To continue updating and editing your SSO integration, select "Cancel" and then
+ "Configure" on the integration card. Creating a new SSO configuration will overwrite and delete
+ your existing SSO configuration.
+
{error?.message}
+{pdError?.message}
+