-
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.
feat(core): Add Pager UI for finding and paging application owners (#…
- Loading branch information
Justin Reynolds
committed
Mar 30, 2018
1 parent
5edff2e
commit 103d709
Showing
11 changed files
with
1,216 additions
and
0 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
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
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,65 @@ | ||
import * as React from 'react'; | ||
import { BindAll } from 'lodash-decorators'; | ||
|
||
import { Application, IPagerDutyService } from '@spinnaker/core'; | ||
|
||
import { PageModal } from './PageModal'; | ||
|
||
export interface IPageButtonProps { | ||
applications?: Application[]; | ||
closeCallback: (succeeded: boolean) => void; | ||
disabled?: boolean; | ||
services: IPagerDutyService[]; | ||
forceOpen?: boolean; | ||
} | ||
|
||
export interface IPageButtonState { | ||
showModal: boolean; | ||
} | ||
|
||
@BindAll() | ||
export class PageButton extends React.Component<IPageButtonProps, IPageButtonState> { | ||
constructor(props: IPageButtonProps) { | ||
super(props); | ||
|
||
this.state = { | ||
showModal: props.forceOpen || false, | ||
}; | ||
} | ||
|
||
public componentWillReceiveProps(nextProps: IPageButtonProps): void { | ||
if (nextProps.forceOpen && !this.state.showModal) { | ||
this.setState({ showModal: true }); | ||
} | ||
} | ||
|
||
private closeCallback(succeeded: boolean) { | ||
this.setState({ showModal: false }); | ||
this.props.closeCallback(succeeded); | ||
} | ||
|
||
private sendPage() { | ||
this.setState({ showModal: true }); | ||
} | ||
|
||
public render() { | ||
return ( | ||
<button | ||
disabled={this.props.disabled} | ||
className="btn btn-sm btn-primary" | ||
style={{ marginRight: '5px' }} | ||
onClick={this.sendPage} | ||
> | ||
<span>Send Page</span> | ||
{this.state.showModal && ( | ||
<PageModal | ||
show={this.state.showModal} | ||
closeCallback={this.closeCallback} | ||
applications={this.props.applications} | ||
services={this.props.services} | ||
/> | ||
)} | ||
</button> | ||
) | ||
} | ||
} |
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,174 @@ | ||
import { IDeferred } from 'angular'; | ||
import { IModalServiceInstance } from 'angular-ui-bootstrap'; | ||
import * as React from 'react'; | ||
import { MouseEvent } from 'react'; | ||
import { Button, Modal } from 'react-bootstrap'; | ||
import { get } from 'lodash'; | ||
import { BindAll } from 'lodash-decorators'; | ||
import { $q } from 'ngimport'; | ||
|
||
import { | ||
Application, | ||
IPagerDutyService, | ||
NgReact, | ||
ReactInjector, | ||
SETTINGS, | ||
SubmitButton, | ||
TaskMonitor, | ||
TaskMonitorBuilder, | ||
} from '@spinnaker/core'; | ||
|
||
import { IPageButtonProps } from './PageButton'; | ||
|
||
export interface IPageModalProps { | ||
applications?: Application[]; | ||
services: IPagerDutyService[]; | ||
show?: boolean; | ||
closeCallback: (completed: boolean) => void; | ||
} | ||
|
||
export interface IPageModalState { | ||
accountName: string; | ||
details: string; | ||
pageCount: number; | ||
subject: string; | ||
submitting: boolean; | ||
taskMonitor: TaskMonitor; | ||
} | ||
|
||
@BindAll() | ||
export class PageModal extends React.Component<IPageModalProps, IPageModalState> { | ||
private taskMonitorBuilder: TaskMonitorBuilder = ReactInjector.taskMonitorBuilder; | ||
private $uibModalInstanceEmulation: IModalServiceInstance & { deferred?: IDeferred<any> }; | ||
|
||
constructor(props: IPageModalProps) { | ||
super(props); | ||
this.state = this.getDefaultState(props); | ||
|
||
const deferred = $q.defer(); | ||
const promise = deferred.promise; | ||
this.$uibModalInstanceEmulation = { | ||
result: promise, | ||
close: () => this.close(), | ||
dismiss: () => this.close(), | ||
} as IModalServiceInstance; | ||
Object.assign(this.$uibModalInstanceEmulation, { deferred }); | ||
} | ||
|
||
public componentWillReceiveProps(nextProps: IPageButtonProps): void { | ||
if (nextProps.services.length !== this.state.pageCount) { | ||
this.setState({ pageCount: nextProps.services.length }); | ||
} | ||
} | ||
|
||
private getDefaultState(props: IPageModalProps): IPageModalState { | ||
|
||
const defaultSubject = get(SETTINGS, 'pagerDuty.defaultSubject', 'Urgent Issue'); | ||
const defaultDetails = get(SETTINGS, 'pagerDuty.defaultDetails', ''); | ||
|
||
return { | ||
accountName: SETTINGS.pagerDuty && SETTINGS.pagerDuty.accountName || '', | ||
subject: defaultSubject, | ||
details: defaultDetails, | ||
pageCount: props.services.length, | ||
submitting: false, | ||
taskMonitor: null, | ||
}; | ||
} | ||
|
||
public close(evt?: MouseEvent<any>): void { | ||
evt && evt.stopPropagation(); | ||
this.setState(this.getDefaultState(this.props)); | ||
this.props.closeCallback(false); | ||
} | ||
|
||
private handleSubjectChanged(event: any): void { | ||
const value = event.target.value; | ||
this.setState({ subject: value }); | ||
} | ||
|
||
private handleDetailsChanged(event: any): void { | ||
const value = event.target.value; | ||
this.setState({ details: value }); | ||
} | ||
|
||
public sendPage(): void { | ||
const { pagerDutyWriter } = ReactInjector; | ||
const taskMonitor = this.taskMonitorBuilder.buildTaskMonitor({ | ||
title: `Sending page to ${this.state.pageCount} policies`, | ||
modalInstance: this.$uibModalInstanceEmulation, | ||
onTaskComplete: () => this.props.closeCallback(true), | ||
}); | ||
|
||
const submitMethod = () => { | ||
const { applications, services } = this.props; | ||
const { subject, details } = this.state; | ||
|
||
return pagerDutyWriter.sendPage(applications, services.map((s) => s.integration_key), subject, { details }); | ||
}; | ||
|
||
taskMonitor.submit(submitMethod); | ||
|
||
this.setState({ taskMonitor, submitting: true }); | ||
} | ||
|
||
public render() { | ||
const formValid = true; | ||
|
||
const { TaskMonitorWrapper } = NgReact; | ||
const { services } = this.props; | ||
const { accountName, details, pageCount, subject, submitting, taskMonitor } = this.state; | ||
|
||
return ( | ||
<Modal show={this.props.show} onHide={this.close} className="page-modal" backdrop="static"> | ||
<TaskMonitorWrapper monitor={taskMonitor} /> | ||
{!submitting && ( | ||
<Modal.Header closeButton={true}> | ||
<Modal.Title>Page {pageCount} {pageCount === 1 ? 'service' : 'services'}</Modal.Title> | ||
</Modal.Header> | ||
)} | ||
{!submitting && ( | ||
<Modal.Body> | ||
<div> | ||
<div> | ||
<label>Services to Page</label> | ||
<div> | ||
{services.map((service) => ( | ||
<span className="service-to-page" key={service.integration_key}> | ||
<a | ||
title={service.name} | ||
href={`https://${accountName}.pagerduty.com/services/${service.id}`} | ||
target="_blank" | ||
> | ||
{service.name} | ||
</a> | ||
</span> | ||
))} | ||
</div> | ||
</div> | ||
<div> | ||
<label>Subject</label> | ||
<input name="subject" type="text" style={{ width: '100%' }} value={subject} onChange={this.handleSubjectChanged}/> | ||
</div> | ||
<div> | ||
<label>Details (Not visible/heard in phone/SMS)</label> | ||
<textarea name="details" style={{ width: '100%', height: '260px' }} value={details} onChange={this.handleDetailsChanged}/> | ||
</div> | ||
</div> | ||
</Modal.Body> | ||
)} | ||
{!submitting && ( | ||
<Modal.Footer> | ||
<Button onClick={this.close}>Cancel</Button> | ||
<SubmitButton | ||
label="Send Page" | ||
submitting={submitting} | ||
isDisabled={!formValid || submitting} | ||
onClick={this.sendPage} | ||
/> | ||
</Modal.Footer> | ||
)} | ||
</Modal> | ||
); | ||
} | ||
} |
Oops, something went wrong.