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..2765df53a6
--- /dev/null
+++ b/src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx
@@ -0,0 +1,196 @@
+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: ['todos'],
+ 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: ['todos'] });
+ }
+
+ return res.data;
+ },
+ // Refetch the data every second
+ refetchInterval: intervalMs,
+ enabled: queryForTestedConfigs || queryForConfiguredConfigs,
+ refetchOnWindowFocus: true,
+ });
+
+ useEffect(() => {
+ const inactive = configs.filter(config => config.active === false);
+ const active = configs.filter(config => config.active === true);
+ const inProgress = configs.filter(
+ config => (config.submitted_at && !config.configured_at) || (config.configured_at < config.submitted_at),
+ );
+ const untested = configs.filter(config => !config.validated_at);
+ const live = configs.filter(
+ config => (config.validated_at && config.active && config.validated_at > config.configured_at),
+ );
+ const notConfigured = configs.filter(config => !config.configured_at);
+
+ if (live.length >= 1) {
+ setLiveConfigs(live);
+ openAlerts();
+ }
+
+ setUntestedConfigs(untested);
+ if (untested.length >= 1) {
+ setQueryForTestedConfigs(true);
+ openAlerts();
+ }
+ setInProgressConfigs(inProgress);
+ if (inProgress.length >= 1) {
+ const beenConfigured = inProgress.filter(config => config.configured_at);
+ if (beenConfigured.length >= 1) {
+ setIntervalMs(UPDATED_CONFIG_POLLING_INTERVAL);
+ }
+ setQueryForConfiguredConfigs(true);
+ openAlerts();
+ }
+
+ if (notConfigured.length >= 1) {
+ setNotConfiguredConfigs(notConfigured);
+ }
+
+ setActiveConfigs(active);
+ setInactiveConfigs(inactive);
+ setLoading(false);
+ }, [configs, refreshBool, openAlerts]);
+
+ return (
+ <>
+ {!loading && (
+ <>
+ {showAlerts && (
+
+ )}
+ {activeConfigs.length > 0 && (
+
+
Active
+
+ {activeConfigs.map((config) => (
+
+ ))}
+
+
+ )}
+ {inactiveConfigs.length > 0 && (
+
+
Inactive
+
+ {inactiveConfigs.map((config) => (
+
+ ))}
+
+
+ )}
+ >
+ )}
+ {loading && (
+
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+NewExistingSSOConfigs.propTypes = {
+ configs: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
+ refreshBool: PropTypes.bool.isRequired,
+ setRefreshBool: PropTypes.func.isRequired,
+ enterpriseId: PropTypes.string.isRequired,
+};
+
+const mapStateToProps = state => ({
+ enterpriseId: state.portalConfiguration.enterpriseId,
+});
+
+export default connect(mapStateToProps)(NewExistingSSOConfigs);
diff --git a/src/components/settings/SettingsSSOTab/NewSSOConfigAlerts.jsx b/src/components/settings/SettingsSSOTab/NewSSOConfigAlerts.jsx
new file mode 100644
index 0000000000..6bffe32f63
--- /dev/null
+++ b/src/components/settings/SettingsSSOTab/NewSSOConfigAlerts.jsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import {
+ CheckCircle, Warning,
+} from '@edx/paragon/icons';
+import { Alert } from '@edx/paragon';
+
+const NewSSOConfigAlerts = ({
+ inProgressConfigs,
+ untestedConfigs,
+ liveConfigs,
+ notConfigured,
+ contactEmail,
+ closeAlerts,
+}) => (
+ <>
+ {inProgressConfigs.length >= 1 && (
+
+ Your SSO Integration is in progress
+
+ 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'}.
+
+
+ )}
+ {untestedConfigs.length >= 1 && inProgressConfigs.length === 0 && (
+
+ You need to test your SSO connection
+
+ 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.
+
+
+ )}
+ {liveConfigs.length >= 1 && inProgressConfigs.length === 0 && untestedConfigs.length === 0 && (
+
+ Your SSO integration is live!
+
+ Great news! Your test was successful and your new SSO integration is live and ready to use.
+
+
+ )}
+ >
+);
+
+NewSSOConfigAlerts.propTypes = {
+ inProgressConfigs: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
+ untestedConfigs: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
+ liveConfigs: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
+ notConfigured: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
+ closeAlerts: PropTypes.func.isRequired,
+ contactEmail: PropTypes.string.isRequired,
+};
+
+const mapStateToProps = state => ({
+ contactEmail: state.portalConfiguration.contactEmail,
+});
+
+export default connect(mapStateToProps)(NewSSOConfigAlerts);
diff --git a/src/components/settings/SettingsSSOTab/NewSSOConfigCard.jsx b/src/components/settings/SettingsSSOTab/NewSSOConfigCard.jsx
new file mode 100644
index 0000000000..e9d58fd522
--- /dev/null
+++ b/src/components/settings/SettingsSSOTab/NewSSOConfigCard.jsx
@@ -0,0 +1,176 @@
+import React, { useContext } from 'react';
+import {
+ Card, Badge, Button, Dropdown, IconButton, Icon, Tooltip, OverlayTrigger,
+} from '@edx/paragon';
+import PropTypes from 'prop-types';
+import {
+ Key, KeyOff, MoreVert,
+} from '@edx/paragon/icons';
+import { SSOConfigContext } from './SSOConfigContext';
+import LmsApiService from '../../../data/services/LmsApiService';
+
+const NewSSOConfigCard = ({
+ config,
+ setLoading,
+ setRefreshBool,
+ refreshBool,
+}) => {
+ const { setProviderConfig } = useContext(SSOConfigContext);
+
+ const convertToReadableDate = (date) => {
+ const dateObj = new Date(date);
+ const options = { year: 'numeric', month: 'short', day: 'numeric' };
+ return new Intl.DateTimeFormat('en-US', options).format(dateObj);
+ };
+
+ const onDeleteClick = (deletedConfig) => {
+ setLoading(true);
+ LmsApiService.deleteEnterpriseSsoOrchestrationRecord(deletedConfig.uuid).then(() => {
+ setRefreshBool(!refreshBool);
+ });
+ };
+
+ const onDisableClick = (disabledConfig) => {
+ setLoading(true);
+ LmsApiService.updateEnterpriseSsoOrchestrationRecord({ active: false }, disabledConfig.uuid).then(() => {
+ setRefreshBool(!refreshBool);
+ });
+ };
+
+ const onEnableClick = (enabledConfig) => {
+ setLoading(true);
+ LmsApiService.updateEnterpriseSsoOrchestrationRecord({ active: true }, enabledConfig.uuid).then(() => {
+ setRefreshBool(!refreshBool);
+ });
+ };
+
+ return (
+
+
+ {config.validated_at && config.active && (
+ The integration is verified and working}
+ >
+
+
+ )}
+ {!config.validated_at && (
+ This integration has not been validated. Please follow the testing instructions to validate your integration.
+ )}
+ >
+
+
+ )}
+ {config.validated_at && !config.active && (
+
+ )}
+ {config.display_name}
+ {(!config.validated_at || config.submitted_at > config.configured_at) && (
+ In-progress
+ )}
+ {config.validated_at && config.submitted_at < config.configured_at && !config.active && (
+ Disabled
+ )}
+ {!config.validated_at && (
+ !config.configured_at || config.submitted_at > config.configured_at) && (
+ !config.active) && (
+ setProviderConfig(config)}
+ variant="outline-primary"
+ >
+ Configure
+
+ )}
+ {config.validated_at && (
+ !config.configured_at || config.submitted_at > config.configured_at) && (
+ !config.active) && (
+ onEnableClick(config)}
+ variant="outline-primary"
+ >
+ Enable
+
+ )}
+
+ )}
+ subtitle={(
+
+ Last modified {convertToReadableDate(config.modified)}
+
+ )}
+ actions={(!config.configured_at || config.submitted_at > config.configured_at) && (
+
+
+
+ {config.validated_at && (
+ setProviderConfig(config)}
+ >
+ Configure
+
+ )}
+ {!config.active && (
+ onDeleteClick(config)}
+ >
+ Delete
+
+ )}
+ {config.active && (
+ onDisableClick(config)}
+ >
+ Disable
+
+ )}
+
+
+ )}
+ />
+
+ );
+};
+
+NewSSOConfigCard.propTypes = {
+ config: PropTypes.shape({
+ uuid: PropTypes.string,
+ display_name: PropTypes.string,
+ active: PropTypes.bool,
+ modified: PropTypes.string,
+ validated_at: PropTypes.string,
+ configured_at: PropTypes.string,
+ submitted_at: PropTypes.string,
+ }).isRequired,
+ setLoading: PropTypes.func.isRequired,
+ setRefreshBool: PropTypes.func.isRequired,
+ refreshBool: PropTypes.bool.isRequired,
+};
+
+export default NewSSOConfigCard;
diff --git a/src/components/settings/SettingsSSOTab/hooks.js b/src/components/settings/SettingsSSOTab/hooks.js
index 04a2002af8..4069de6804 100644
--- a/src/components/settings/SettingsSSOTab/hooks.js
+++ b/src/components/settings/SettingsSSOTab/hooks.js
@@ -10,6 +10,7 @@ import {
updateIdpDirtyState,
} from './data/actions';
import { updateSamlProviderData, deleteSamlProviderData } from './utils';
+import { features } from '../../../config';
const useIdpState = () => {
const {
@@ -179,23 +180,43 @@ const useExistingSSOConfigs = (enterpriseUuid, refreshBool) => {
const [error, setError] = useState(null);
useEffect(() => {
+ const { AUTH0_SELF_SERVICE_INTEGRATION } = features;
if (enterpriseUuid) {
- const fetchConfig = async () => {
- const response = await LmsApiService.getProviderConfig(enterpriseUuid);
- return response.data.results;
- };
- fetchConfig().then(configs => {
- setSsoConfigs(configs);
- setLoading(false);
- }).catch(err => {
- setLoading(false);
- if (err.customAttributes?.httpErrorStatus !== 404) {
- // nothing found is okay for this fetcher.
- setError(err);
- } else {
- setSsoConfigs([]);
- }
- });
+ if (!AUTH0_SELF_SERVICE_INTEGRATION) {
+ const fetchConfig = async () => {
+ const response = await LmsApiService.getProviderConfig(enterpriseUuid);
+ return response.data.results;
+ };
+ fetchConfig().then(configs => {
+ setSsoConfigs(configs);
+ setLoading(false);
+ }).catch(err => {
+ setLoading(false);
+ if (err.customAttributes?.httpErrorStatus !== 404) {
+ // nothing found is okay for this fetcher.
+ setError(err);
+ } else {
+ setSsoConfigs([]);
+ }
+ });
+ } else {
+ const fetchConfig = async () => {
+ const response = await LmsApiService.listEnterpriseSsoOrchestrationRecords(enterpriseUuid);
+ return response.data;
+ };
+ fetchConfig().then(orchestratorConfigs => {
+ setSsoConfigs(orchestratorConfigs);
+ setLoading(false);
+ }).catch(err => {
+ setLoading(false);
+ if (err.customAttributes?.httpErrorStatus !== 404) {
+ // nothing found is okay for this fetcher.
+ setError(err);
+ } else {
+ setSsoConfigs([]);
+ }
+ });
+ }
}
}, [enterpriseUuid, refreshBool]);
diff --git a/src/components/settings/SettingsSSOTab/index.jsx b/src/components/settings/SettingsSSOTab/index.jsx
index ff17bfe254..49b94597f2 100644
--- a/src/components/settings/SettingsSSOTab/index.jsx
+++ b/src/components/settings/SettingsSSOTab/index.jsx
@@ -1,15 +1,18 @@
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
- Alert, Hyperlink, Toast, Skeleton,
+ Alert, ActionRow, Button, Hyperlink, ModalDialog, Toast, Skeleton, useToggle,
} from '@edx/paragon';
-import { WarningFilled } from '@edx/paragon/icons';
+import { Add, WarningFilled } from '@edx/paragon/icons';
import { HELP_CENTER_SAML_LINK } from '../data/constants';
import { useExistingSSOConfigs, useExistingProviderData } from './hooks';
import NoSSOCard from './NoSSOCard';
import ExistingSSOConfigs from './ExistingSSOConfigs';
+import NewExistingSSOConfigs from './NewExistingSSOConfigs';
import NewSSOConfigForm from './NewSSOConfigForm';
import { SSOConfigContext, SSOConfigContextProvider } from './SSOConfigContext';
+import LmsApiService from '../../../data/services/LmsApiService';
+import { features } from '../../../config';
const SettingsSSOTab = ({ enterpriseId, setHasSSOConfig }) => {
const {
@@ -21,39 +24,115 @@ const SettingsSSOTab = ({ enterpriseId, setHasSSOConfig }) => {
const [existingProviderData, pdError, pdIsLoading] = useExistingProviderData(enterpriseId, refreshBool);
const [showNewSSOForm, setShowNewSSOForm] = useState(false);
const [showNoSSOCard, setShowNoSSOCard] = useState(false);
+ const { AUTH0_SELF_SERVICE_INTEGRATION } = features;
+ const [isOpen, open, close] = useToggle(false);
+
+ const newConfigurationButtonOnClick = async () => {
+ for (let i = 0; i < existingConfigs.length; i++) {
+ LmsApiService.updateEnterpriseSsoOrchestrationRecord(
+ { active: false, is_removed: true },
+ existingConfigs[i].uuid,
+ ).then(() => {
+ setRefreshBool(!refreshBool);
+ close();
+ });
+ }
+ };
useEffect(() => {
- let validConfigExists = false;
- existingConfigs.forEach(config => {
- if (config.was_valid_at) {
- validConfigExists = true;
- }
- });
+ if (AUTH0_SELF_SERVICE_INTEGRATION) {
+ let validConfigExists = false;
+ existingConfigs.forEach(config => {
+ if (config.validated_at) {
+ validConfigExists = true;
+ }
+ });
+ setHasSSOConfig(validConfigExists);
+ } else {
+ let validConfigExists = false;
+ existingConfigs.forEach(config => {
+ if (config.was_valid_at) {
+ validConfigExists = true;
+ }
+ });
+ setHasSSOConfig(validConfigExists);
+ }
if (!existingConfigs || existingConfigs?.length < 1) {
setShowNoSSOCard(true);
} else {
setShowNoSSOCard(false);
}
- setHasSSOConfig(validConfigExists);
- }, [existingConfigs, setHasSSOConfig]);
+ }, [AUTH0_SELF_SERVICE_INTEGRATION, existingConfigs, setHasSSOConfig]);
return (
+
+
+
+ Create new SSO configuration?
+
+
+
+
+ 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.
+
+
+
+
+
+ Cancel
+
+
+ Create new SSO
+
+
+
+
-
SAML Configuration
-
- Help Center
-
+ {!AUTH0_SELF_SERVICE_INTEGRATION && (
+
SAML Configuration
+ )}
+ {AUTH0_SELF_SERVICE_INTEGRATION && (
+
Single Sign-On (SSO) Integrations
+ )}
+
+ {existingConfigs?.length > 0 && (providerConfig === null) && AUTH0_SELF_SERVICE_INTEGRATION && (
+
+ New
+
+ )}
+
+ Help Center: Single Sign-On
+
+
{(!isLoading || !pdIsLoading) && (
{/* providerConfig represents the currently selected config to edit/create, if there are
existing configs but no providerConfig then we can safely render the listings page */}
- {existingConfigs?.length > 0 && (providerConfig === null)
+ {existingConfigs?.length > 0 && (providerConfig === null) && (!AUTH0_SELF_SERVICE_INTEGRATION)
&& (
{
setRefreshBool={setRefreshBool}
/>
)}
+ {existingConfigs?.length > 0 && (providerConfig === null) && (AUTH0_SELF_SERVICE_INTEGRATION)
+ && (
+
+ )}
{/* Nothing found so guide user to creation/edit form */}
{showNoSSOCard && }
{/* Since we found a selected providerConfig we know we are in editing mode and can safely
diff --git a/src/components/settings/SettingsTabs.jsx b/src/components/settings/SettingsTabs.jsx
index 8f471233c3..729ac106bc 100644
--- a/src/components/settings/SettingsTabs.jsx
+++ b/src/components/settings/SettingsTabs.jsx
@@ -1,4 +1,8 @@
import React, { useState, useMemo } from 'react';
+import {
+ QueryClient,
+ QueryClientProvider,
+} from '@tanstack/react-query';
import {
Container,
Tabs,
@@ -27,6 +31,12 @@ import SettingsApiCredentialsTab from './SettingsApiCredentialsTab';
import { features } from '../../config';
import { updatePortalConfigurationEvent } from '../../data/actions/portalConfiguration';
+const queryClient = new QueryClient({
+ queries: {
+ retry: true, // optional: you may disable automatic query retries for all queries or on a per-query basis.
+ },
+});
+
const SettingsTabs = ({
enterpriseId,
enterpriseSlug,
@@ -80,10 +90,12 @@ const SettingsTabs = ({
eventKey={SETTINGS_TABS_VALUES.sso}
title={SETTINGS_TAB_LABELS.sso}
>
-
+
+
+
,
);
}
diff --git a/src/data/services/LmsApiService.js b/src/data/services/LmsApiService.js
index 2ed35e8274..b7c1e0aa91 100644
--- a/src/data/services/LmsApiService.js
+++ b/src/data/services/LmsApiService.js
@@ -46,7 +46,7 @@ class LmsApiService {
return LmsApiService.apiClient().get(enterpriseSsoOrchestrationFetchUrl);
}
- static listEnterpriseSsoOrchestration(enterpriseCustomerUuid) {
+ static listEnterpriseSsoOrchestrationRecords(enterpriseCustomerUuid) {
const enterpriseSsoOrchestrationListUrl = `${LmsApiService.enterpriseSsoOrchestrationUrl}`;
if (enterpriseCustomerUuid) {
return LmsApiService.apiClient().get(`${enterpriseSsoOrchestrationListUrl}?enterprise_customer=${enterpriseCustomerUuid}`);