Skip to content

Commit

Permalink
feat: New SSO workflow skeleton (#1032)
Browse files Browse the repository at this point in the history
* feat: New SSO workflow skeleton

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

test: Add test for SSO metadata input hiding

feat: Add back buttons to sso config workflow

chore: rename function

* test: New sso flow cancel button test
  • Loading branch information
marlonkeating authored Sep 21, 2023
1 parent f4328dc commit e5edf15
Show file tree
Hide file tree
Showing 17 changed files with 554 additions and 17 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
25 changes: 22 additions & 3 deletions src/components/forms/FormWorkflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export type FormWorkflowStep<FormData> = {
errHandler: FormWorkflowErrorHandler
) => Promise<boolean>;
nextButtonConfig: (FormData: FormData) => FormWorkflowButtonConfig<FormData>;
showBackButton?: boolean;
showCancelButton?: boolean;
};

export type FormWorkflowConfig<FormData> = {
Expand All @@ -61,14 +63,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 @@ -150,6 +154,15 @@ const FormWorkflow = <FormConfigData extends unknown>({
}
};

const onBack = () => {
if (step?.index !== undefined) {
const previousStep: number = step.index - 1;
if (previousStep >= 0) {
dispatch(setStepAction({ step: formWorkflowConfig.steps[previousStep] }));
}
}
};

const stepBody = (currentStep: FormWorkflowStep<FormConfigData>) => {
if (currentStep) {
const FormComponent: DynamicComponent = currentStep?.formComponent;
Expand All @@ -175,6 +188,10 @@ const FormWorkflow = <FormConfigData extends unknown>({
}
}, [formFields]);

// Show back button only if showBackButton === true
const showBackButton = (step?.index !== undefined) && (step.index > 0) && step.showBackButton;
// Show cancel button by default
const showCancelButton = step?.showCancelButton === undefined || step?.showCancelButton;
return (
<>
<ConfigErrorModal
Expand All @@ -191,12 +208,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 All @@ -206,7 +224,8 @@ const FormWorkflow = <FormConfigData extends unknown>({
Help Center: Integrations
</HelpCenterButton>
<ActionRow.Spacer />
<Button variant="tertiary" onClick={onCancel}>Cancel</Button>
{showCancelButton && <Button variant="tertiary" onClick={onCancel}>Cancel</Button>}
{showBackButton && <Button variant="tertiary" onClick={onBack}>Back</Button>}
{nextButtonConfig && (
<Button onClick={onNext} disabled={awaitingAsyncAction}>
{nextButtonConfig.buttonText}
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);
};

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

export default NewSSOStepper;
59 changes: 59 additions & 0 deletions src/components/settings/SettingsSSOTab/SSOFormWorkflowConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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'),
showBackButton: true,
showCancelButton: false,
}, {
index: 2,
formComponent: SSOConfigAuthorizeStep,
validations: [],
stepName: 'Authorize',
nextButtonConfig: placeHolderButton(),
showBackButton: true,
showCancelButton: false,
}, {
index: 3,
formComponent: SSOConfigConfirmStep,
validations: [],
stepName: 'Confirm and Test',
nextButtonConfig: placeHolderButton('Finish'),
showBackButton: true,
showCancelButton: false,
},
];

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

0 comments on commit e5edf15

Please sign in to comment.