Skip to content

Commit

Permalink
feat: White glove support (#2271)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleixhub authored Nov 20, 2024
1 parent 1cfdd4c commit c13137b
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 235 deletions.
139 changes: 60 additions & 79 deletions catalog/ui/src/app/Catalog/CatalogItemForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {
createWorkshop,
createWorkshopProvision,
fetcher,
openWorkshopSupportTicket,
} from '@app/api';
import { CatalogItem, TPurposeOpts } from '@app/types';
import { displayName, isLabDeveloper, randomString } from '@app/util';
Expand All @@ -52,7 +51,6 @@ import AutoStopDestroy from '@app/components/AutoStopDestroy';
import CatalogItemFormAutoStopDestroyModal, { TDates, TDatesTypes } from './CatalogItemFormAutoStopDestroyModal';
import { formatCurrency, getEstimatedCost, isAutoStopDisabled } from './catalog-utils';
import ErrorBoundaryPage from '@app/components/ErrorBoundaryPage';
import useImpersonateUser from '@app/utils/useImpersonateUser';
import { SearchIcon } from '@patternfly/react-icons';
import SearchSalesforceIdModal from '@app/components/SearchSalesforceIdModal';
import useInterfaceConfig from '@app/utils/useInterfaceConfig';
Expand All @@ -70,12 +68,7 @@ const CatalogItemFormData: React.FC<{ catalogItemName: string; catalogNamespaceN
const [searchSalesforceIdModal, openSearchSalesforceIdModal] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { isAdmin, groups, roles, serviceNamespaces, userNamespace, email } = useSession().getSession();
const { userImpersonated } = useImpersonateUser();
const { sfdc_enabled } = useInterfaceConfig();
let userEmail = email;
if (userImpersonated) {
userEmail = userImpersonated;
}
const { data: catalogItem } = useSWRImmutable<CatalogItem>(
apiPaths.CATALOG_ITEM({ namespace: catalogNamespaceName, name: catalogItemName }),
fetcher
Expand All @@ -92,7 +85,6 @@ const CatalogItemFormData: React.FC<{ catalogItemName: string; catalogNamespaceN
provisionCount: 1,
provisionConcurrency: catalogItem.spec.multiuser ? 1 : 10,
provisionStartDelay: 30,
createTicket: false,
}),
[catalogItem]
);
Expand Down Expand Up @@ -180,6 +172,7 @@ const CatalogItemFormData: React.FC<{ catalogItemName: string; catalogNamespaceN
email,
parameterValues,
skippedSfdc: formState.salesforceId.skip,
whiteGloved: formState.whiteGloved,
});
const redirectUrl = `/workshops/${workshop.metadata.namespace}/${workshop.metadata.name}`;
await createWorkshopProvision({
Expand All @@ -192,20 +185,6 @@ const CatalogItemFormData: React.FC<{ catalogItemName: string; catalogNamespaceN
useAutoDetach: formState.useAutoDetach,
usePoolIfAvailable: formState.usePoolIfAvailable,
});
try {
if (formState.workshop.createTicket) {
await openWorkshopSupportTicket(workshop, {
number_of_attendees: provisionCount,
sfdc: formState.salesforceId.value,
name: catalogItemName,
event_name: displayName,
url: `${window.location.origin}${redirectUrl}`,
start_date: formState.startDate,
end_date: formState.endDate,
email: userEmail,
});
}
} catch {}
navigate(redirectUrl);
} else {
const resourceClaim = await createServiceRequest({
Expand All @@ -221,6 +200,7 @@ const CatalogItemFormData: React.FC<{ catalogItemName: string; catalogNamespaceN
endDate: formState.endDate,
email,
skippedSfdc: formState.salesforceId.skip,
whiteGloved: formState.whiteGloved,
});

navigate(`/services/${resourceClaim.metadata.namespace}/${resourceClaim.metadata.name}`);
Expand Down Expand Up @@ -933,69 +913,70 @@ const CatalogItemFormData: React.FC<{ catalogItemName: string; catalogNamespaceN
</FormGroup>
</>
) : null}
<FormGroup fieldId="workshopCreateTicket" isRequired>
<div className="catalog-item-form__group-control--single">
<Switch
id="support-ticket-switch"
aria-label="Enable White-Glove Support"
label="Enable White-Glove Support"
isChecked={formState.workshop.createTicket}
hasCheckIcon
onChange={(_event, isChecked) => {
dispatchFormState({
type: 'workshop',
workshop: { ...formState.workshop, createTicket: isChecked },
});
}}
/>
</div>
</FormGroup>
</>
)}
</div>
) : null}

{isAdmin ? (
<>
<FormGroup key="pooling-switch" fieldId="pooling-switch">
<div className="catalog-item-form__group-control--single">
<Switch
id="pooling-switch"
aria-label="Use pool if available"
label="Use pool if available (only visible to admins)"
isChecked={formState.usePoolIfAvailable}
hasCheckIcon
onChange={(_event, isChecked) =>
dispatchFormState({
type: 'usePoolIfAvailable',
usePoolIfAvailable: isChecked,
})
}
/>
</div>
</FormGroup>
</>
<FormGroup fieldId="white-glove" isRequired>
<div className="catalog-item-form__group-control--single">
<Switch
id="white-glove-switch"
aria-label="White-Glove Support"
label="White-Glove Support (for admins to tick when giving a white gloved experience)"
isChecked={formState.whiteGloved}
hasCheckIcon
onChange={(_event, isChecked) => {
dispatchFormState({
type: 'whiteGloved',
whiteGloved: isChecked,
});
}}
/>
</div>
</FormGroup>
) : null}

{isAdmin ? (
<FormGroup key="pooling-switch" fieldId="pooling-switch">
<div className="catalog-item-form__group-control--single">
<Switch
id="pooling-switch"
aria-label="Use pool if available"
label="Use pool if available (only visible to admins)"
isChecked={formState.usePoolIfAvailable}
hasCheckIcon
onChange={(_event, isChecked) =>
dispatchFormState({
type: 'usePoolIfAvailable',
usePoolIfAvailable: isChecked,
})
}
/>
</div>
</FormGroup>
) : null}

{isAdmin || isLabDeveloper(groups) ? (
<FormGroup key="auto-detach-switch" fieldId="auto-detach-switch">
<div className="catalog-item-form__group-control--single">
<Switch
id="auto-detach-switch"
aria-label="Keep instance if provision fails"
label="Keep instance if provision fails (only visible to admins)"
isChecked={!formState.useAutoDetach}
hasCheckIcon
onChange={(_event, isChecked) => {
dispatchFormState({
type: 'useAutoDetach',
useAutoDetach: !isChecked,
});
}}
/>
</div>
</FormGroup>
) : null}
<>
{isAdmin || isLabDeveloper(groups) ? (
<FormGroup key="auto-detach-switch" fieldId="auto-detach-switch">
<div className="catalog-item-form__group-control--single">
<Switch
id="auto-detach-switch"
aria-label="Keep instance if provision fails"
label="Keep instance if provision fails (only visible to admins)"
isChecked={!formState.useAutoDetach}
hasCheckIcon
onChange={(_event, isChecked) => {
dispatchFormState({
type: 'useAutoDetach',
useAutoDetach: !isChecked,
});
}}
/>
</div>
</FormGroup>
) : null}
</>

{catalogItem.spec.termsOfService ? (
<TermsOfService
Expand Down
14 changes: 13 additions & 1 deletion catalog/ui/src/app/Catalog/CatalogItemFormReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ type WorkshopProps = {
provisionCount: number;
provisionConcurrency: number;
provisionStartDelay: number;
createTicket: boolean;
};
type FormState = {
user: UserProps;
Expand All @@ -33,6 +32,7 @@ type FormState = {
serviceNamespace: ServiceNamespace;
termsOfServiceAgreed: boolean;
termsOfServiceRequired: boolean;
whiteGloved: boolean;
workshop?: WorkshopProps;
error: string;
usePoolIfAvailable: boolean;
Expand Down Expand Up @@ -73,6 +73,7 @@ export type FormStateAction = {
| 'salesforceId'
| 'salesforceIdMessage'
| 'serviceNamespace'
| 'whiteGloved'
| 'complete';
allowServiceNamespaces?: ServiceNamespace[];
catalogItem?: CatalogItem;
Expand Down Expand Up @@ -102,6 +103,7 @@ export type FormStateAction = {
startDate?: Date;
stopDate?: Date;
endDate?: Date;
whiteGloved?: boolean;
};

export type FormStateParameter = {
Expand Down Expand Up @@ -347,6 +349,7 @@ function reduceFormStateInit(
purpose: null,
purposeOpts,
explanation: null,
whiteGloved: false,
salesforceId: {
required: false,
value: null,
Expand Down Expand Up @@ -405,6 +408,13 @@ function reduceFormStateTermsOfServiceAgreed(initialState: FormState, termsOfSer
};
}

function reduceFormWhiteGloved(initialState: FormState, whiteGloved: boolean): FormState {
return {
...initialState,
whiteGloved,
};
}

function reduceFormStateWorkshop(initialState: FormState, workshop: WorkshopProps = null): FormState {
const isSalesforceIdRequired = salesforceIdRequired({ ...initialState, workshop });
const salesforceId = { ...initialState.salesforceId, required: isSalesforceIdRequired };
Expand Down Expand Up @@ -544,6 +554,8 @@ export function reduceFormState(state: FormState, action: FormStateAction): Form
return reduceFormStateDates(state, action.startDate, action.stopDate, action.endDate);
case 'termsOfServiceAgreed':
return reduceFormStateTermsOfServiceAgreed(state, action.termsOfServiceAgreed);
case 'whiteGloved':
return reduceFormWhiteGloved(state, action.whiteGloved);
case 'workshop':
return reduceFormStateWorkshop(state, action.workshop);
case 'usePoolIfAvailable':
Expand Down
Loading

0 comments on commit c13137b

Please sign in to comment.