-
Notifications
You must be signed in to change notification settings - Fork 900
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(titus/serverGroup): Reactify Titus Resize Server Group Modal (…
…#7175) * refactor(titus/serverGroup): Reactify Titus Resize Server Group Modal Also reactify the server group capacity details section.
- Loading branch information
1 parent
4d9a192
commit 20df06e
Showing
11 changed files
with
415 additions
and
266 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
app/scripts/modules/titus/src/serverGroup/details/TitusCapacityDetailsSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import * as React from 'react'; | ||
|
||
import { ReactModal, Application, Overridable } from '@spinnaker/core'; | ||
import { ITitusServerGroup } from 'titus/domain'; | ||
import { ITitusResizeServerGroupModalProps, TitusResizeServerGroupModal } from './resize/TitusResizeServerGroupModal'; | ||
|
||
interface ICapacityDetailsSectionProps { | ||
app: Application; | ||
serverGroup: ITitusServerGroup; | ||
} | ||
|
||
export const TitusSimpleMinMaxDesired = ({ serverGroup }: ICapacityDetailsSectionProps) => ( | ||
<> | ||
<dt>Min/Max</dt> | ||
<dd>{serverGroup.capacity.desired}</dd> | ||
<dt>Current</dt> | ||
<dd>{serverGroup.instances.length}</dd> | ||
</> | ||
); | ||
|
||
export const TitusAdvancedMinMaxDesired = ({ serverGroup }: ICapacityDetailsSectionProps) => ( | ||
<> | ||
<dt>Min</dt> | ||
<dd>{serverGroup.capacity.min}</dd> | ||
<dt>Desired</dt> | ||
<dd>{serverGroup.capacity.desired}</dd> | ||
<dt>Max</dt> | ||
<dd>{serverGroup.capacity.max}</dd> | ||
<dt>Current</dt> | ||
<dd>{serverGroup.instances.length}</dd> | ||
</> | ||
); | ||
|
||
export const TitusCapacityGroup = ({ serverGroup }: ICapacityDetailsSectionProps) => ( | ||
<> | ||
<dt>Cap. Group</dt> | ||
<dd>{serverGroup.capacityGroup}</dd> | ||
</> | ||
); | ||
|
||
@Overridable('titus.serverGroup.CapacityDetailsSection') | ||
export class TitusCapacityDetailsSection extends React.Component<ICapacityDetailsSectionProps> { | ||
public render(): JSX.Element { | ||
const { serverGroup, app: application } = this.props; | ||
const isSimpleMode = serverGroup.capacity.min === serverGroup.capacity.max; | ||
const resizeServerGroup = () => | ||
ReactModal.show<ITitusResizeServerGroupModalProps>(TitusResizeServerGroupModal, { serverGroup, application }); | ||
|
||
return ( | ||
<> | ||
<dl className="dl-horizontal dl-flex"> | ||
{isSimpleMode ? <TitusSimpleMinMaxDesired {...this.props} /> : <TitusAdvancedMinMaxDesired {...this.props} />} | ||
{serverGroup.capacityGroup && <TitusCapacityGroup {...this.props} />} | ||
</dl> | ||
|
||
<div> | ||
<a className="clickable" onClick={resizeServerGroup}> | ||
Resize Server Group | ||
</a> | ||
</div> | ||
</> | ||
); | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
app/scripts/modules/titus/src/serverGroup/details/capacityDetailsSection.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { module } from 'angular'; | ||
import { react2angular } from 'react2angular'; | ||
|
||
import { TitusCapacityDetailsSection } from './TitusCapacityDetailsSection'; | ||
|
||
export const TITUS_SERVERGROUP_DETAILS_CAPACITYDETAILSSECTION = 'titus.servergroup.details.capacitydetailssection'; | ||
module(TITUS_SERVERGROUP_DETAILS_CAPACITYDETAILSSECTION, []).component( | ||
'titusCapacityDetailsSection', | ||
react2angular(TitusCapacityDetailsSection, ['serverGroup', 'app']), | ||
); |
305 changes: 305 additions & 0 deletions
305
app/scripts/modules/titus/src/serverGroup/details/resize/TitusResizeServerGroupModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,305 @@ | ||
import * as React from 'react'; | ||
import { Modal } from 'react-bootstrap'; | ||
import { Form, Formik, FormikContext } from 'formik'; | ||
import { ITitusServerGroup } from 'titus/domain'; | ||
import { | ||
Application, | ||
FormikFormField, | ||
ICapacity, | ||
IModalComponentProps, | ||
MinMaxDesiredChanges, | ||
ModalClose, | ||
NgReact, | ||
NumberInput, | ||
PlatformHealthOverride, | ||
ReactInjector, | ||
UserVerification, | ||
ValidationMessage, | ||
} from '@spinnaker/core'; | ||
import { useTaskMonitor } from 'titus/serverGroup/details/resize/useTaskMonitor'; | ||
|
||
const { useState, useEffect, useMemo } = React; | ||
|
||
export interface ITitusResizeServerGroupModalProps extends IModalComponentProps { | ||
application: Application; | ||
serverGroup: ITitusServerGroup; | ||
} | ||
|
||
interface ITitusResizeServerGroupCommand { | ||
capacity: ICapacity; | ||
serverGroupName: string; | ||
instances: number; | ||
interestingHealthProviderNames: string[]; | ||
region: string; | ||
} | ||
|
||
function surfacedErrorMessage(formik: FormikContext<ITitusResizeServerGroupCommand>) { | ||
const capacityErrors = formik.errors.capacity || ({} as any); | ||
const { min, max, desired } = capacityErrors; | ||
return [min, max, desired].find(x => !!x); | ||
} | ||
|
||
function SimpleMode({ formik, serverGroup, toggleMode }: IAdvancedModeProps) { | ||
useEffect(() => { | ||
formik.setFieldValue('capacity.min', formik.values.capacity.desired); | ||
formik.setFieldValue('capacity.max', formik.values.capacity.desired); | ||
}, [formik.values.capacity.desired]); | ||
|
||
const errorMessage = surfacedErrorMessage(formik); | ||
|
||
return ( | ||
<div> | ||
<p>Sets min, max, and desired instance counts to the same value.</p> | ||
|
||
<p> | ||
To allow autoscaling, use the{' '} | ||
<a className="clickable" onClick={toggleMode}> | ||
Advanced Mode | ||
</a> | ||
. | ||
</p> | ||
|
||
<div className="form-group row"> | ||
<div className="col-md-3 sm-label-right">Current size</div> | ||
<div className="col-md-4"> | ||
<div className="horizontal middle"> | ||
<input | ||
type="number" | ||
className="NumberInput form-control" | ||
value={serverGroup.capacity.desired} | ||
disabled={true} | ||
/> | ||
<div className="sp-padding-xs-xaxis">instances</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div className="form-group"> | ||
<div className="col-md-3 sm-label-right">Resize to</div> | ||
<div className="col-md-4"> | ||
<div className="horizontal middle"> | ||
<FormikFormField | ||
name="capacity.desired" | ||
input={props => <NumberInput {...props} min={0} />} | ||
layout={({ input }) => <>{input}</>} | ||
touched={true} | ||
onChange={() => {}} | ||
/> | ||
<div className="sp-padding-xs-xaxis">instances</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
{!!errorMessage && ( | ||
<div className="col-md-offset-3 col-md-9"> | ||
<ValidationMessage message={errorMessage} type="error" /> | ||
</div> | ||
)} | ||
|
||
<div className="form-group"> | ||
<div className="col-md-3 sm-label-right">Changes</div> | ||
<div className="col-md-9 sm-control-field"> | ||
<MinMaxDesiredChanges current={serverGroup.capacity} next={formik.values.capacity} /> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
interface IAdvancedModeProps { | ||
formik: FormikContext<ITitusResizeServerGroupCommand>; | ||
serverGroup: ITitusServerGroup; | ||
toggleMode: () => void; | ||
} | ||
|
||
function AdvancedMode({ formik, serverGroup, toggleMode }: IAdvancedModeProps) { | ||
const { min, max } = formik.values.capacity || ({} as any); | ||
|
||
const DisabledNumberField = ({ value }: { value: string | number }) => ( | ||
<div className="col-md-2"> | ||
<input className="NumberInput form-control" type="number" disabled={true} value={value} /> | ||
</div> | ||
); | ||
const errorMessage = surfacedErrorMessage(formik); | ||
|
||
return ( | ||
<div> | ||
<p>Sets up auto-scaling for this server group.</p> | ||
<p> | ||
To disable auto-scaling, use the{' '} | ||
<a className="clickable" onClick={toggleMode}> | ||
Simple Mode | ||
</a> | ||
. | ||
</p> | ||
|
||
<div className="form-group bold"> | ||
<div className="col-md-2 col-md-offset-3">Min</div> | ||
<div className="col-md-2">Max</div> | ||
<div className="col-md-2">Desired</div> | ||
</div> | ||
|
||
<div className="form-group"> | ||
<div className="col-md-3 sm-label-right">Current</div> | ||
<DisabledNumberField value={serverGroup.capacity.min} /> | ||
<DisabledNumberField value={serverGroup.capacity.max} /> | ||
<DisabledNumberField value={serverGroup.capacity.desired} /> | ||
</div> | ||
|
||
<div className="form-group"> | ||
<div className="col-md-3 sm-label-right">Resize to</div> | ||
<div className="col-md-2"> | ||
<FormikFormField | ||
fastField={false} | ||
name="capacity.min" | ||
input={props => <NumberInput {...props} min={0} max={max} />} | ||
layout={({ input }) => <>{input}</>} | ||
touched={true} | ||
/> | ||
</div> | ||
|
||
<div className="col-md-2"> | ||
<FormikFormField | ||
fastField={false} | ||
name="capacity.max" | ||
input={props => <NumberInput {...props} min={min} />} | ||
layout={({ input }) => <>{input}</>} | ||
touched={true} | ||
/> | ||
</div> | ||
|
||
<div className="col-md-2"> | ||
<FormikFormField | ||
fastField={false} | ||
name="capacity.desired" | ||
input={props => <NumberInput {...props} min={min} max={max} />} | ||
layout={({ input }) => <>{input}</>} | ||
touched={true} | ||
/> | ||
</div> | ||
</div> | ||
|
||
{!!errorMessage && ( | ||
<div className="col-md-offset-3 col-md-9"> | ||
<ValidationMessage message={errorMessage} type="error" /> | ||
</div> | ||
)} | ||
|
||
<div className="form-group"> | ||
<div className="col-md-3 sm-label-right">Changes</div> | ||
<div className="col-md-9 sm-control-field"> | ||
<MinMaxDesiredChanges current={serverGroup.capacity} next={formik.values.capacity} /> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
function validateResizeCommand(values: ITitusResizeServerGroupCommand) { | ||
const { min, max, desired } = values.capacity; | ||
const capacityErrors = {} as any; | ||
|
||
// try to only show one error message at a time | ||
if (min > max) { | ||
capacityErrors.min = capacityErrors.max = 'Min cannot be larger than Max'; | ||
} else if (desired < min) { | ||
capacityErrors.desired = capacityErrors.min = 'Desired cannot be smaller than Min'; | ||
} else if (desired > max) { | ||
capacityErrors.desired = capacityErrors.max = 'Desired cannot be larger than Max'; | ||
} | ||
|
||
if (Object.keys(capacityErrors).length) { | ||
return { capacity: capacityErrors }; | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
export function TitusResizeServerGroupModal(props: ITitusResizeServerGroupModalProps) { | ||
const { TaskMonitorWrapper } = NgReact; | ||
const { serverGroup, application, dismissModal } = props; | ||
|
||
const initialAdvancedMode = useMemo(() => { | ||
const { min, max, desired } = serverGroup.capacity; | ||
return desired !== max || desired !== min; | ||
}, []); | ||
const [advancedMode, setAdvancedMode] = useState(initialAdvancedMode); | ||
|
||
const platformHealthOnlyShowOverride = | ||
application.attributes && application.attributes.platformHealthOnlyShowOverride; | ||
const [verified, setVerified] = useState(); | ||
|
||
const taskMonitor = useTaskMonitor( | ||
{ | ||
application, | ||
title: `Resizing ${serverGroup.name}`, | ||
onTaskComplete: () => application.getDataSource('serverGroups').refresh(true), | ||
}, | ||
dismissModal, | ||
); | ||
const submit = (command: ITitusResizeServerGroupCommand) => | ||
taskMonitor.submit(() => ReactInjector.serverGroupWriter.resizeServerGroup(serverGroup, application, command)); | ||
|
||
const initialValues = { capacity: serverGroup.capacity } as ITitusResizeServerGroupCommand; | ||
|
||
return ( | ||
<> | ||
<TaskMonitorWrapper monitor={taskMonitor} /> | ||
|
||
<Formik<ITitusResizeServerGroupCommand> | ||
initialValues={initialValues} | ||
validate={validateResizeCommand} | ||
onSubmit={submit} | ||
render={formik => { | ||
return ( | ||
<> | ||
<Modal.Header> | ||
<h3>Resize {serverGroup.name}</h3> | ||
</Modal.Header> | ||
|
||
<ModalClose dismiss={dismissModal} /> | ||
|
||
<Modal.Body> | ||
<Form className="form-horizontal"> | ||
{advancedMode ? ( | ||
<AdvancedMode formik={formik} serverGroup={serverGroup} toggleMode={() => setAdvancedMode(false)} /> | ||
) : ( | ||
<SimpleMode formik={formik} serverGroup={serverGroup} toggleMode={() => setAdvancedMode(true)} /> | ||
)} | ||
|
||
{platformHealthOnlyShowOverride && ( | ||
<PlatformHealthOverride | ||
interestingHealthProviderNames={formik.values.interestingHealthProviderNames} | ||
platformHealthType="Titus" | ||
showHelpDetails={true} | ||
onChange={names => | ||
formik.setFieldValue('interestingHealthProviderNames', names ? names : undefined) | ||
} | ||
/> | ||
)} | ||
</Form> | ||
</Modal.Body> | ||
|
||
<Modal.Footer> | ||
<UserVerification account={serverGroup.account} onValidChange={setVerified} /> | ||
|
||
<button className="btn btn-default" onClick={dismissModal}> | ||
Cancel | ||
</button> | ||
|
||
<button | ||
type="submit" | ||
disabled={!verified || !formik.isValid} | ||
className="btn btn-primary" | ||
onClick={() => submit(formik.values)} | ||
> | ||
Submit | ||
</button> | ||
</Modal.Footer> | ||
</> | ||
); | ||
}} | ||
/> | ||
</> | ||
); | ||
} |
Oops, something went wrong.