Skip to content

Commit

Permalink
feat: implementing sso orchestrator existing configs page
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-sheehan-edx committed Oct 12, 2023
1 parent 6a47c98 commit d6119b3
Show file tree
Hide file tree
Showing 11 changed files with 871 additions and 31 deletions.
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -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'
44 changes: 44 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
179 changes: 179 additions & 0 deletions src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx
Original file line number Diff line number Diff line change
@@ -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);

Check warning on line 31 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L20-L31

Added lines #L20 - L31 were not covered by tests

const queryClient = useQueryClient();

Check warning on line 33 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L33

Added line #L33 was not covered by tests

useQuery({

Check warning on line 35 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L35

Added line #L35 was not covered by tests
queryKey: ['ssoOrchestratorConfigPoll'],
queryFn: async () => {
const res = await LmsApiService.listEnterpriseSsoOrchestrationRecords(enterpriseId);
const inProgress = res.data.filter(

Check warning on line 39 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L37-L39

Added lines #L37 - L39 were not covered by tests
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);

Check warning on line 47 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L46-L47

Added lines #L46 - L47 were not covered by tests
}
}

if (queryForTestedConfigs) {
if (untested.length === 0) {
setRefreshBool(!refreshBool);
setQueryForTestedConfigs(false);

Check warning on line 54 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L53-L54

Added lines #L53 - L54 were not covered by tests
}
}

if (inProgress.length === 0 && untested.length === 0) {
queryClient.invalidateQueries({ queryKey: ['ssoOrchestratorConfigPoll'] });

Check warning on line 59 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L59

Added line #L59 was not covered by tests
}

return res.data;

Check warning on line 62 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L62

Added line #L62 was not covered by tests
},
refetchInterval: intervalMs,
enabled: queryForTestedConfigs || queryForConfiguredConfigs,
refetchOnWindowFocus: true,
});

const renderCards = (gridTitle, configList) => {

Check warning on line 69 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L69

Added line #L69 was not covered by tests
if (configList.length > 0) {
return (

Check warning on line 71 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L71

Added line #L71 was not covered by tests
<div>
<h3 className="mb-4.5">{gridTitle}</h3>
<CardGrid
className="mb-2 mr-3"
columnSizes={{
xs: 9,
s: 9,
m: 9,
l: 9,
xl: 9,
}}
>
{configList.map((config) => (
<NewSSOConfigCard

Check warning on line 85 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L85

Added line #L85 was not covered by tests
config={config}
setLoading={setLoading}
setRefreshBool={setRefreshBool}
refreshBool={refreshBool}
/>
))}
</CardGrid>
</div>
);
}
return null;

Check warning on line 96 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L96

Added line #L96 was not covered by tests
};

useEffect(() => {
const [active, inactive] = _.partition(configs, config => config.active);
const inProgress = configs.filter(

Check warning on line 101 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L99-L101

Added lines #L99 - L101 were not covered by tests
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(

Check warning on line 105 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L104-L105

Added lines #L104 - L105 were not covered by tests
config => (config.validated_at && config.active && config.validated_at > config.configured_at),
);
const notConfigured = configs.filter(config => !config.configured_at);

Check warning on line 108 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L108

Added line #L108 was not covered by tests

if (live.length >= 1) {
setLiveConfigs(live);
openAlerts();

Check warning on line 112 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L111-L112

Added lines #L111 - L112 were not covered by tests
}

setUntestedConfigs(untested);

Check warning on line 115 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L115

Added line #L115 was not covered by tests
if (untested.length >= 1) {
setQueryForTestedConfigs(true);
openAlerts();

Check warning on line 118 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L117-L118

Added lines #L117 - L118 were not covered by tests
}
setInProgressConfigs(inProgress);

Check warning on line 120 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L120

Added line #L120 was not covered by tests
if (inProgress.length >= 1) {
const beenConfigured = inProgress.filter(config => config.configured_at);

Check warning on line 122 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L122

Added line #L122 was not covered by tests
if (beenConfigured.length >= 1) {
setIntervalMs(UPDATED_CONFIG_POLLING_INTERVAL);

Check warning on line 124 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L124

Added line #L124 was not covered by tests
}
setQueryForConfiguredConfigs(true);
openAlerts();

Check warning on line 127 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L126-L127

Added lines #L126 - L127 were not covered by tests
}

if (notConfigured.length >= 1) {
setNotConfiguredConfigs(notConfigured);

Check warning on line 131 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L131

Added line #L131 was not covered by tests
}

setActiveConfigs(active);
setInactiveConfigs(inactive);
setLoading(false);

Check warning on line 136 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L134-L136

Added lines #L134 - L136 were not covered by tests
}, [configs, refreshBool, openAlerts]);

return (

Check warning on line 139 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L139

Added line #L139 was not covered by tests
<>
{!loading && (
<>

Check warning on line 142 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L142

Added line #L142 was not covered by tests
{showAlerts && (
<NewSSOConfigAlerts

Check warning on line 144 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L144

Added line #L144 was not covered by tests
liveConfigs={liveConfigs}
inProgressConfigs={inProgressConfigs}
untestedConfigs={untestedConfigs}
notConfigured={notConfiguredConfigs}
closeAlerts={closeAlerts}
/>
)}
{renderCards('Active', activeConfigs)}
{renderCards('Inactive', inactiveConfigs)}
</>
)}
{loading && (
<div>

Check warning on line 157 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L157

Added line #L157 was not covered by tests
<Skeleton />
<Skeleton />
<Skeleton />
<Skeleton />
</div>
)}
</>
);
};

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);
90 changes: 90 additions & 0 deletions src/components/settings/SettingsSSOTab/NewSSOConfigAlerts.jsx
Original file line number Diff line number Diff line change
@@ -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 && (
<Alert
variant="warning"
icon={Warning}
className="ml-0 w-75"
dismissible
onClose={closeAlerts}
>
<Alert.Heading>Your SSO Integration is in progress</Alert.Heading>
<p>
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'}.
</p>
</Alert>
)}
{untestedConfigs.length >= 1 && inProgressConfigs.length === 0 && (
<Alert
variant="warning"
icon={Warning}
className="ml-0 w-75"
onClose={closeAlerts}
dismissible
>
<Alert.Heading>You need to test your SSO connection</Alert.Heading>
<p>
Your SSO configuration has completed,
and you should have received an email with the following instructions:<br />
<br />
1. Copy the URL for your learner Portal dashboard below:<br />
<br />
&emsp; http://courses.edx.org/dashboard?tpa_hint=saml-bestrun-hana<br />
<br />
2: Launch a new incognito or private window and paste the copied URL into the URL bar to load your
learner Portal dashboard.<br />
<br />
3: When prompted, enter login credentials supported by your IDP to test your connection to edX.<br />
<br />
Return to this window after completing the testing instructions.
This window will automatically update when a successful test is detected.<br />
</p>
</Alert>
)}
{liveConfigs.length >= 1 && inProgressConfigs.length === 0 && untestedConfigs.length === 0 && (
<Alert
variant="success"
className="ml-0 w-75"
icon={CheckCircle}
onClose={closeAlerts}
dismissible
>
<Alert.Heading>Your SSO integration is live!</Alert.Heading>
<p>
Great news! Your test was successful and your new SSO integration is live and ready to use.
</p>
</Alert>
)}
</>
);

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);
Loading

0 comments on commit d6119b3

Please sign in to comment.