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 6, 2023
1 parent 6a47c98 commit fb3443c
Show file tree
Hide file tree
Showing 11 changed files with 877 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
196 changes: 196 additions & 0 deletions src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
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
},
// Refetch the data every second
refetchInterval: intervalMs,
enabled: queryForTestedConfigs || queryForConfiguredConfigs,
refetchOnWindowFocus: true,
});

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

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

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L70-L72

Added lines #L70 - L72 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 76 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L75-L76

Added lines #L75 - L76 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 79 in src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L79 was not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L82-L83

Added lines #L82 - L83 were not covered by tests
}

setUntestedConfigs(untested);

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L88-L89

Added lines #L88 - L89 were not covered by tests
}
setInProgressConfigs(inProgress);

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx#L97-L98

Added lines #L97 - L98 were not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L102 was not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

return (

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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
liveConfigs={liveConfigs}
inProgressConfigs={inProgressConfigs}
untestedConfigs={untestedConfigs}
notConfigured={notConfiguredConfigs}
closeAlerts={closeAlerts}
/>
)}
{activeConfigs.length > 0 && (
<div>

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
<h3 className="mb-4.5">Active</h3>
<CardGrid
className="mb-2 mr-3"
columnSizes={{
xs: 9,
s: 9,
m: 9,
l: 9,
xl: 9,
}}
>
{activeConfigs.map((config) => (
<NewSSOConfigCard

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L137 was not covered by tests
config={config}
setLoading={setLoading}
setRefreshBool={setRefreshBool}
refreshBool={refreshBool}
/>
))}
</CardGrid>
</div>
)}
{inactiveConfigs.length > 0 && (
<div>

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L161 was not covered by tests
config={config}
setLoading={setLoading}
setRefreshBool={setRefreshBool}
refreshBool={refreshBool}
/>
))}
</CardGrid>
</div>
)}
</>
)}
{loading && (
<div>

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L174 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 fb3443c

Please sign in to comment.