Skip to content

Commit

Permalink
feat(core): Add Pager UI for finding and paging application owners (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Reynolds committed Mar 30, 2018
1 parent 5edff2e commit 103d709
Show file tree
Hide file tree
Showing 11 changed files with 1,216 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/scripts/modules/core/src/config/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export interface ISpinnakerSettings {
notifications: INotificationSettings;
pagerDuty?: {
accountName?: string;
defaultSubject?: string;
defaultDetails?: string;
required?: boolean;
};
pollSchedule: number;
Expand Down
21 changes: 21 additions & 0 deletions app/scripts/modules/core/src/help/HelpMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import * as React from 'react';
import { Dropdown, MenuItem, Glyphicon } from 'react-bootstrap';
import { UISref } from '@uirouter/react';

import './HelpMenu.less';

import { SETTINGS } from '@spinnaker/core';

const DOCS_URL = 'https://spinnaker.io/docs';
const COMMUNITY_URL = 'https://spinnaker.io/community';

Expand All @@ -21,6 +24,15 @@ export const HelpMenu = () => {
<MenuItem href={COMMUNITY_URL} target="_blank">
Community Resources
</MenuItem>
{ SETTINGS.feature.pagerDuty && (
<li role="presentation">
<UISref to="home.page">
<a className="clickable">
<span className="feedback-item-label">Send a Page</span>
</a>
</UISref>
</li>
)}
</Dropdown.Menu>
</Dropdown>

Expand All @@ -35,6 +47,15 @@ export const HelpMenu = () => {
<MenuItem href={COMMUNITY_URL} target="_blank">
Community Resources
</MenuItem>
{ SETTINGS.feature.pagerDuty && (
<li role="presentation">
<UISref to="home.page">
<a className="clickable">
<span className="feedback-item-label">Send a Page</span>
</a>
</UISref>
</li>
)}
</Dropdown.Menu>
</Dropdown>
</li>
Expand Down
65 changes: 65 additions & 0 deletions app/scripts/modules/core/src/pagerDuty/PageButton.tsx
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>
)
}
}
174 changes: 174 additions & 0 deletions app/scripts/modules/core/src/pagerDuty/PageModal.tsx
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>
);
}
}
Loading

0 comments on commit 103d709

Please sign in to comment.