summaryrefslogtreecommitdiff
path: root/source/libs/pr-ci-status.ts
blob: 52d67b95ed0818abe7ff2f9523a3f2599075318b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import select from 'select-dom';
import observeEl from './simplified-element-observer';

type CommitStatus = false | typeof SUCCESS | typeof FAILURE | typeof PENDING | typeof COMMIT_CHANGED;
type StatusListener = (status: CommitStatus) => void;

// `.TimelineItem--condensed` excludes unrelated references. See `deemphasize-unrelated-commit-references` feature
const commitSelector = '.js-commit.TimelineItem--condensed';

function getLastCommitReference(): string | null {
	return select.last(`${commitSelector} code`)!.textContent;
}

export const SUCCESS = Symbol('Success');
export const FAILURE = Symbol('Failure');
export const PENDING = Symbol('Pending');
export const COMMIT_CHANGED = Symbol('Commit changed');

export function get(): CommitStatus {
	// Excludes commit references. Sometimes commits don't have a status icon at all, yet
	const lastCommit = select.last(commitSelector);
	if (lastCommit) {
		if (lastCommit.querySelector('.octicon-check')) {
			return SUCCESS;
		}

		if (lastCommit.querySelector('.octicon-x')) {
			return FAILURE;
		}

		if (lastCommit.querySelector('.octicon-primitive-dot')) {
			return PENDING;
		}
	}

	return false;
}

export async function wait(): Promise<CommitStatus> {
	return new Promise(resolve => {
		addEventListener(function handler(newStatus: CommitStatus) {
			removeEventListener(handler);
			resolve(newStatus);
		});
	});
}

const observers = new WeakMap<StatusListener, MutationObserver>();

export function addEventListener(listener: StatusListener): void {
	if (observers.has(listener)) {
		return;
	}

	let previousCommit = getLastCommitReference();
	let previousStatus = get();
	const filteredListener = (): void => {
		// Cancel submission if a new commit was pushed
		const newCommit = getLastCommitReference();
		if (newCommit !== previousCommit) {
			previousCommit = newCommit;
			listener(COMMIT_CHANGED);
			return;
		}

		// Ignore update if the status hasn't changed
		const newStatus = get();
		if (newStatus !== previousStatus) {
			previousStatus = newStatus;
			listener(newStatus);
		}
	};

	const observer = observeEl('.js-discussion', filteredListener, {
		childList: true,
		subtree: true
	})!;
	observers.set(listener, observer);
}

export function removeEventListener(listener: StatusListener): void {
	observers.get(listener)!.disconnect();
}