Skip to content

Commit

Permalink
feat: New SSO workflow skeleton
Browse files Browse the repository at this point in the history
fix: Make service provider metadata button primary color

feat: Add non-SAP fields to SSO Configure page

feat: Show different SSO metadata inputs based on radio selection
  • Loading branch information
marlonkeating committed Sep 19, 2023
1 parent 4da552c commit 77eeb1b
Show file tree
Hide file tree
Showing 16 changed files with 436 additions and 14 deletions.
3 changes: 2 additions & 1 deletion src/components/forms/FormContextWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type FormWrapperProps<FormConfigData> = FormWorkflowProps<FormConfigData> & { formData: FormConfigData };

const FormContextWrapper = <FormConfigData extends unknown>({
workflowTitle,
formWorkflowConfig,
onClickOut,
formData,
Expand All @@ -32,7 +33,7 @@ const FormContextWrapper = <FormConfigData extends unknown>({
>
<FormWorkflow
{...{
formWorkflowConfig, onClickOut, isStepperOpen, dispatch,
workflowTitle, formWorkflowConfig, onClickOut, isStepperOpen, dispatch,
}}
/>
</FormContextProvider>
Expand Down
7 changes: 5 additions & 2 deletions src/components/forms/FormWorkflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,16 @@ export type FormWorkflowConfig<FormData> = {
};

export type FormWorkflowProps<FormConfigData> = {
workflowTitle: string;
formWorkflowConfig: FormWorkflowConfig<FormConfigData>;
onClickOut: (edited: boolean, msg?: string) => null;
onClickOut: (() => void) | ((edited?: boolean, msg?: string) => null);
dispatch: Dispatch<FormActionArguments>;
isStepperOpen: boolean;
};

// Modal container for multi-step forms
const FormWorkflow = <FormConfigData extends unknown>({
workflowTitle,
formWorkflowConfig,
onClickOut,
isStepperOpen,
Expand Down Expand Up @@ -191,12 +193,13 @@ const FormWorkflow = <FormConfigData extends unknown>({
await step?.saveChanges(formFields as FormConfigData, setFormError);
onClickOut(true, SUBMIT_TOAST_MESSAGE);
}
onClickOut(false, 'No changes saved');
}}
/>

{formWorkflowConfig.steps && (
<FullscreenModal
title="New learning platform integration"
title={workflowTitle}
isOpen={isStepperOpen}
onClose={onCancel}
className="stepper-modal"
Expand Down
3 changes: 3 additions & 0 deletions src/components/forms/ValidatedFormControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import { Form } from '@edx/paragon';
import { setFormFieldAction } from './data/actions';
import { useFormContext } from './FormContext';

// TODO: Get from Paragon
type InheritedParagonControlProps = {
autoFocus?: boolean;
className?: string;
type: string;
as?: string;
maxLength?: number;
floatingLabel?: string;
rows?: number;
};

export type ValidatedFormControlProps = {
Expand Down
3 changes: 2 additions & 1 deletion src/components/forms/ValidatedFormRadio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type ValidatedFormRadioProps = {
fieldInstructions?: string;
label?: string;
options?: string[][];
isInline?: boolean;
} & InheritedParagonRadioProps;

const ValidatedFormRadio = (props: ValidatedFormRadioProps) => {
Expand Down Expand Up @@ -69,7 +70,7 @@ const ValidatedFormRadio = (props: ValidatedFormRadioProps) => {
<Form.RadioSet
name={formRadioProps.id}
onChange={formRadioProps.onChange}
isInline
isInline={formRadioProps.isInline}
>
{createOptions(formRadioProps.options)}
</Form.RadioSet>
Expand Down
1 change: 1 addition & 0 deletions src/components/settings/SettingsLMSTab/LMSConfigPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const LMSConfigPage = ({
return (
<div>
<FormContextWrapper
workflowTitle="New learning platform integration"
formWorkflowConfig={formWorkflowConfig}
onClickOut={handleCloseWorkflow}
formData={existingConfigFormData}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ const SAPConfigEnablePage = () => (
formId={formFieldNames.USER_TYPE}
label="SAP User Type"
options={[['User', 'user'], ['Admin', 'admin']]}
isInline
/>
</Form.Group>
</Form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ function testBlackboardConfigSetup(formData) {
},
},
})}
workflowTitle="New learning platform integration"
onClickOut={mockOnClick}
formData={formData}
isStepperOpen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function testCanvasConfigSetup(formData) {
},
},
})}
workflowTitle="New learning platform integration"
onClickOut={mockOnClick}
formData={formData}
isStepperOpen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ function testDegreedConfigSetup(formData) {
},
},
})}
workflowTitle="New learning platform integration"
onClickOut={mockOnClick}
formData={formData}
isStepperOpen
Expand Down
30 changes: 29 additions & 1 deletion src/components/settings/SettingsSSOTab/NewSSOStepper.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
const NewSSOStepper = () => null;
import React, { useState, useContext } from 'react';
import FormContextWrapper from '../../forms/FormContextWrapper';
import { SSOConfigContext } from './SSOConfigContext';
import SSOFormWorkflowConfig from './SSOFormWorkflowConfig';

const NewSSOStepper = () => {
const {
setProviderConfig,
} = useContext(SSOConfigContext);
const [isStepperOpen, setIsStepperOpen] = useState(true);
const handleCloseWorkflow = () => {
setProviderConfig?.(null);
setIsStepperOpen(false);

Check warning on line 13 in src/components/settings/SettingsSSOTab/NewSSOStepper.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/settings/SettingsSSOTab/NewSSOStepper.jsx#L12-L13

Added lines #L12 - L13 were not covered by tests
};

return (isStepperOpen
&& (
<div>
<FormContextWrapper
workflowTitle="New SSO integration"
formWorkflowConfig={SSOFormWorkflowConfig()}
onClickOut={handleCloseWorkflow}
formData={{}}
isStepperOpen={isStepperOpen}
/>
</div>
)
);
};

export default NewSSOStepper;
53 changes: 53 additions & 0 deletions src/components/settings/SettingsSSOTab/SSOFormWorkflowConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { FormWorkflowStep } from '../../forms/FormWorkflow';
import SSOConfigConnectStep from './steps/NewSSOConfigConnectStep';
import SSOConfigConfigureStep from './steps/NewSSOConfigConfigureStep';
import SSOConfigAuthorizeStep from './steps/NewSSOConfigAuthorizeStep';
import SSOConfigConfirmStep from './steps/NewSSOConfigConfirmStep';

type SSOConfigCamelCase = {};

export const SSOFormWorkflowConfig = () => {
const placeHolderButton = (buttonName?: string) => () => ({
buttonText: buttonName || 'Next',
opensNewWindow: false,
onClick: () => {},
});

const steps: FormWorkflowStep<SSOConfigCamelCase>[] = [
{
index: 0,
formComponent: SSOConfigConnectStep,
validations: [],
stepName: 'Connect',
nextButtonConfig: placeHolderButton(),
}, {
index: 1,
formComponent: SSOConfigConfigureStep,
validations: [],
stepName: 'Configure',
nextButtonConfig: placeHolderButton('Configure'),
}, {
index: 2,
formComponent: SSOConfigAuthorizeStep,
validations: [],
stepName: 'Authorize',
nextButtonConfig: placeHolderButton(),
}, {
index: 3,
formComponent: SSOConfigConfirmStep,
validations: [],
stepName: 'Confirm and Test',
nextButtonConfig: placeHolderButton('Finish'),
},
];

// Start at the first step
const getCurrentStep = () => steps[0];

return {
getCurrentStep,
steps,
};
};

export default SSOFormWorkflowConfig;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import {
Alert, Form, Hyperlink, Button, Row,
} from '@edx/paragon';
import { Info, Download } from '@edx/paragon/icons';

const handleCheck = () => null;

const SSOConfigAuthorizeStep = () => (
<>
<h2>Authorize edX as a Service Provider</h2>
<Alert variant="info" className="mb-4" icon={Info}>
<h3>Action required in a new window</h3>
Return to this window after completing the following steps in a new window to finish configuring your integration.
</Alert>
<hr />
<p>
1. Download the edX Service Provider metadata as an XML file:
</p>

<Row className="justify-content-center mb-4 ">
<Button variant="primary" iconAfter={Download}>edX Service Provider Metadata</Button>
</Row>

<p>
2. <Hyperlink destination="/" target="_blank">Launch a new window</Hyperlink> and upload the XML file to the list of
authorized SAML Service Providers on your Identity Provider&apos;s portal or website.
</p>
<hr />
<p>Return to this window and check the box once complete</p>

<Form.Checkbox className="mt-4" checked={false} onChange={handleCheck}>
I have authorized edX as a Service Provider
</Form.Checkbox>
</>
);

export default SSOConfigAuthorizeStep;
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React from 'react';
import {
Form, Container,
} from '@edx/paragon';

import ValidatedFormControl from '../../../forms/ValidatedFormControl';

const SSOConfigConfigureStep = () => {
const renderNonSAPFields = () => (
<>
<h3>Enter user attributes</h3>
<p>
Please enter the SAML user attributes from your Identity Provider.
All attributes are space and case sensitive.
</p>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyOAuthUserID"
type="text"
floatingLabel="User ID"
fieldInstructions="URN of the SAML attribute that edX can use as a unique, persistent user ID."
/>
</Form.Group>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyOAuthFullName"
type="text"
floatingLabel="Full Name"
fieldInstructions="URN of SAML attribute containing the user's full name."
/>
</Form.Group>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyOAuthFirstName"
type="text"
floatingLabel="First Name"
fieldInstructions="URN of SAML attribute containing the user's first name."
/>
</Form.Group>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyOAuth "
type="text"
floatingLabel="Last Name"
fieldInstructions="URN of SAML attribute containing the user's last name."
/>
</Form.Group>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyOAuth "
type="text"
floatingLabel="Email Address"
fieldInstructions="URN of SAML attribute containing the user's email address[es]."
/>
</Form.Group>
</>
);
const renderSAPFields = () => (
<>
<h3>Enable learner account auto-registration</h3>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyOAuthRootURL"
type="text"
floatingLabel="OAuth Root URL"
fieldInstructions="The URL hostname is what you see upon login to SuccessFactors BizX system, typically aligned with the IDP Entity ID."
/>
</Form.Group>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyAPIRootURL"
type="text"
floatingLabel="API Root URL"
fieldInstructions="The BizX OData API service hostname, typically aligned with the IDP entity ID."
/>
</Form.Group>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyCompanyID"
type="text"
floatingLabel="Company ID"
fieldInstructions="The BizX company profile identifier for your tenant."
/>
</Form.Group>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyPrivateKey"
type="text"
as="textarea"
rows={4}
floatingLabel="Private Key"
fieldInstructions="The Private Key value found in the PEM file generated from the OAuth2 Client Application Profile."
/>
</Form.Group>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyOAuthUserId"
type="text"
floatingLabel="OAuth User ID"
fieldInstructions="Username of the BizX administrator account that is configured for edX by the customer."
/>
</Form.Group>
</>
);

return (

<Container size="md">

<Form style={{ maxWidth: '60rem' }}>
<h2>Enter integration details</h2>
<h3>Set display name</h3>
<Form.Group className="mb-4">
<ValidatedFormControl
formId="DummyDisplayName"
type="text"
floatingLabel="Display Name (Optional)"
fieldInstructions="Create a custom display name for this SSO integration"
/>
</Form.Group>

{/* TODO: Render SAP fields selectively once logic is in place */}
{renderNonSAPFields()}
{renderSAPFields()}
</Form>
</Container>
);
};

export default SSOConfigConfigureStep;
Loading

0 comments on commit 77eeb1b

Please sign in to comment.