Bug 1123814 - Get desktop notifications when pushes or jobs complete (#3373)

This commit is contained in:
Philipp Kewisch 2018-03-29 18:55:32 +02:00 коммит произвёл Cameron Dawson
Родитель 9c017283c7
Коммит 7cf1013f09
3 изменённых файлов: 117 добавлений и 9 удалений

Просмотреть файл

@ -4,7 +4,7 @@
.push-bar {
border-top: 1px solid black;
padding: 2px 0 0 34px;
padding: 2px 0 1px 34px;
white-space: nowrap;
display: flex;
flex-flow: row nowrap;
@ -59,6 +59,11 @@
margin-right: 25px;
}
.push-buttons > .btn,
.push-buttons > .btn-group{
margin: 0 5px;
}
.btn-push {
color: #666;
background-color: transparent;
@ -93,6 +98,12 @@ th-action-button .btn-push {
font-size: 14px;
}
.watch-commit-btn:not([data-watch-state="none"]) {
border: 1px solid;
border-radius: 3px;
overflow: visible;
}
/* Encompasses unknown push,repo */
.unknown-message-body {
padding-top: 10px;

Просмотреть файл

@ -5,26 +5,90 @@ import PushHeader from './PushHeader';
import { RevisionList } from './RevisionList';
import * as aggregateIds from './aggregateIds';
const watchCycleStates = [
"none",
"push",
"job",
"none"
];
export default class Push extends React.Component {
constructor(props) {
super(props);
const { $injector, repoName, push } = props;
const { id: pushId, revision, job_counts } = push;
this.$rootScope = $injector.get('$rootScope');
this.thEvents = $injector.get('thEvents');
this.ThResultSetStore = $injector.get('ThResultSetStore');
this.aggregateId = aggregateIds.getPushTableId(
repoName, push.id, push.revision
);
this.aggregateId = aggregateIds.getPushTableId(repoName, pushId, revision);
this.showRunnableJobs = this.showRunnableJobs.bind(this);
this.hideRunnableJobs = this.hideRunnableJobs.bind(this);
this.state = {
runnableVisible: false
runnableVisible: false,
watched: "none",
// props.push isn't actually immutable due to the way it hooks up to angular, therefore we
// need to keep the previous value in the state.
last_job_counts: job_counts ? { ...job_counts } : null,
};
}
componentWillReceiveProps(nextProps) {
this.showUpdateNotifications(nextProps);
}
showUpdateNotifications(nextProps) {
const { watched, last_job_counts } = this.state;
const { repoName, push: { revision, id: pushId } } = this.props;
if (Notification.permission !== "granted" || watched === "none") {
return;
}
const nextCounts = nextProps.push.job_counts;
if (last_job_counts) {
const nextUncompleted = nextCounts.pending + nextCounts.running;
const lastUncompleted = last_job_counts.pending + last_job_counts.running;
const nextCompleted = nextCounts.completed;
const lastCompleted = last_job_counts.completed;
let message;
if (lastUncompleted > 0 && nextUncompleted === 0) {
message = "Push completed";
this.setState({ watched: "none" });
} else if (watched === "job" && lastCompleted < nextCompleted) {
const completeCount = nextCompleted - lastCompleted;
message = completeCount + " jobs completed";
}
if (message) {
const notification = new Notification(message, {
body: `${repoName} rev ${revision.substring(0, 12)}`,
tag: pushId
});
notification.onerror = (event) => {
this.thNotify.send(`${event.target.title}: ${event.target.body}`, "danger");
};
notification.onclick = (event) => {
if (this.container) {
this.container.scrollIntoView();
event.target.close();
}
};
}
}
if (nextCounts) {
this.setState({ last_job_counts: Object.assign({}, nextCounts) });
}
}
showRunnableJobs() {
this.$rootScope.$emit(this.thEvents.showRunnableJobs, this.props.push.id);
this.setState({ runnableVisible: true });
@ -36,27 +100,45 @@ export default class Push extends React.Component {
this.setState({ runnableVisible: false });
}
async cycleWatchState() {
let next = watchCycleStates[watchCycleStates.indexOf(this.state.watched) + 1];
if (next !== "none" && Notification.permission !== "granted") {
const result = await Notification.requestPermission();
if (result === "denied") {
this.thNotify.send("Notification permission denied", "danger");
next = "none";
}
}
this.setState({ watched: next });
}
render() {
const { push, loggedIn, isStaff, $injector, repoName } = this.props;
const { watched, runnableVisible } = this.state;
const { currentRepo, urlBasePath } = this.$rootScope;
const { id, push_timestamp, revision, job_counts, author } = push;
return (
<div className="push">
<div className="push" ref={(ref) => { this.container = ref; }}>
<PushHeader
pushId={id}
pushTimestamp={push_timestamp}
author={author}
revision={revision}
jobCounts={job_counts}
watchState={watched}
loggedIn={loggedIn}
isStaff={isStaff}
repoName={repoName}
urlBasePath={urlBasePath}
$injector={$injector}
runnableVisible={this.state.runnableVisible}
runnableVisible={runnableVisible}
showRunnableJobsCb={this.showRunnableJobs}
hideRunnableJobsCb={this.hideRunnableJobs}
cycleWatchState={() => this.cycleWatchState()}
/>
<div className="push-body-divider" />
<div className="row push clearfix">

Просмотреть файл

@ -162,14 +162,20 @@ export default class PushHeader extends React.PureComponent {
render() {
const { repoName, loggedIn, pushId, isStaff, jobCounts, author,
revision, runnableVisible, $injector,
showRunnableJobsCb, hideRunnableJobsCb } = this.props;
revision, runnableVisible, $injector, watchState,
showRunnableJobsCb, hideRunnableJobsCb, cycleWatchState } = this.props;
const { filterParams } = this.state;
const cancelJobsTitle = loggedIn ?
"Cancel all jobs" :
"Must be logged in to cancel jobs";
const counts = jobCounts || { pending: 0, running: 0, completed: 0 };
const watchStateLabel = {
none: "Watch",
push: "Notifying (per-push)",
job: "Notifying (per-job)"
}[watchState];
return (
<div className="push-header">
<div className="push-bar" key="push-header">
@ -192,6 +198,13 @@ export default class PushHeader extends React.PureComponent {
completed={counts.completed}
/>
<span className="push-buttons">
{counts.pending + counts.running > 0 &&
<button
className="btn btn-sm btn-push watch-commit-btn"
title="Get Desktop Notifications for this Push"
data-watch-state={watchState}
onClick={() => cycleWatchState()}
>{watchStateLabel}</button>}
<a
className="btn btn-sm btn-push test-view-btn"
href={`/testview.html?repo=${repoName}&revision=${revision}`}
@ -272,6 +285,7 @@ PushHeader.propTypes = {
author: PropTypes.string.isRequired,
revision: PropTypes.string.isRequired,
jobCounts: PropTypes.object,
watchState: PropTypes.string,
loggedIn: PropTypes.bool,
isStaff: PropTypes.bool,
repoName: PropTypes.string.isRequired,
@ -280,4 +294,5 @@ PushHeader.propTypes = {
runnableVisible: PropTypes.bool.isRequired,
showRunnableJobsCb: PropTypes.func.isRequired,
hideRunnableJobsCb: PropTypes.func.isRequired,
cycleWatchState: PropTypes.func.isRequired,
};