Skip to content

Commit 7f46a9f

Browse files
authored
Merge pull request #9 from bitrise-io/changelog-handle-proxy-outage
Changelog handle proxy outage
2 parents 96c4e01 + ce5ca02 commit 7f46a9f

15 files changed

Lines changed: 187 additions & 180 deletions

File tree

index.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,16 @@ async function discoverWorkers(dir) {
7979
const config = {};
8080
const lines = tomlContent.split('\n');
8181
lines.forEach((line) => {
82-
const match = line.match(/^(\w+)\s*=\s*["']?([^"'\n]+)["']?/);
83-
if (match) {
84-
const [, key, value] = match;
85-
config[key] = value;
82+
const matchArray = line.match(/^(\w+)\s*=\s*\[(.*)\]$/);
83+
if (matchArray) {
84+
const [, key, value] = matchArray;
85+
config[key] = JSON.parse(`[${value}]`);
86+
} else {
87+
const match = line.match(/^(\w+)\s*=\s*["']?([^"'\n]+)["']?/);
88+
if (match) {
89+
const [, key, value] = match;
90+
config[key] = value;
91+
}
8692
}
8793
});
8894

@@ -108,7 +114,11 @@ async function getWorker(urlObject) {
108114
}
109115

110116
const matchedWorker = workers.filter((worker) => {
111-
let routePattern = worker.config.route.replace('bitrise.io', '^');
117+
let route = worker.config.route ? worker.config.route : null;
118+
if (!route && worker.config.routes) [route] = worker.config.routes;
119+
if (!route) return false;
120+
121+
let routePattern = route.replace('bitrise.io', '^');
112122
if (routePattern.endsWith('*')) {
113123
routePattern = routePattern.slice(0, -1);
114124
} else {

src/css/404.css

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,58 +4,3 @@ body {
44
#intercom-container {
55
display: none !important;
66
}
7-
8-
9-
10-
11-
12-
div#bitrise-status {
13-
--radii-4: 0.25rem;
14-
15-
--space-8: 0.5rem;
16-
--space-12: 0.75rem;
17-
18-
--neutral-10: #201b22;
19-
--purple-80: #ead4f2;
20-
--red-70: #ff8091;
21-
--green-70: #77cf8f;
22-
--orange-70: #fd9730;
23-
--yellow-70: #ebba00;
24-
}
25-
26-
div#bitrise-status a {
27-
font-size: 0.6875rem;
28-
line-height: 1rem;
29-
font-weight: 700;
30-
text-transform: uppercase;
31-
text-decoration: none;
32-
border-radius: var(--radii-4);
33-
display: inline-block;
34-
padding: calc(var(--space-8) - 1px) calc(var(--space-12) - 1px);
35-
border: 1px solid var(--purple-80);
36-
color: var(--purple-80);
37-
}
38-
39-
div#bitrise-status a.severity-critical {
40-
background-color: var(--red-70);
41-
border-color: var(--red-70);
42-
color: var(--neutral-10);
43-
}
44-
45-
div#bitrise-status a.severity-major {
46-
background-color: var(--orange-70);
47-
border-color: var(--orange-70);
48-
color: var(--neutral-10);
49-
}
50-
51-
div#bitrise-status a.severity-minor {
52-
background-color: var(--yellow-70);
53-
border-color: var(--yellow-70);
54-
color: var(--neutral-10);
55-
}
56-
57-
div#bitrise-status a.severity-none {
58-
background-color: var(--green-70);
59-
border-color: var(--green-70);
60-
color: var(--neutral-10);
61-
}

src/html/maintenance.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88

99
<base href="/">
1010
<link rel="stylesheet" href="maintenance.css">
11-
<link rel="stylesheet" href="404.css">
12-
<script src="404.js"></script>
1311

1412
<!-- Google Tag Manager -->
1513
<script>window.dataLayer = window.dataLayer || [];</script>
@@ -24,7 +22,7 @@
2422
<h1>
2523
<a href="/" title="Go to dashboard">
2624
<figure>
27-
<img src="maintenance/bitrise_logo.svg" alt="bitrise" />
25+
<img src="bitrise_logo.svg" alt="bitrise" />
2826
</figure>
2927
</a>
3028
</h1>
@@ -35,12 +33,12 @@ <h2>A quick tune-up is in progress. Faster builds on the way.</h2>
3533
</main>
3634
</section>
3735
<figure>
38-
<img class="error" src="errors/bitrise_maintenance.svg" alt="Bitrise - Under Maintenance">
36+
<img class="error" src="bitrise_maintenance.svg" alt="Bitrise - Under Maintenance">
3937
</figure>
4038
</div>
4139
<footer>
42-
<div class="status" id="bitrise-status">
43-
<a href="https://status.bitrise.io/" rel="noreferrer" target="_blank">Status</a>
40+
<div class="status">
41+
<a id="statuspage" href="https://status.bitrise.io/" rel="noreferrer" target="_blank">Status</a>
4442
</div>
4543
<div class="copyright">
4644
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
@@ -66,5 +64,7 @@ <h2>A quick tune-up is in progress. Faster builds on the way.</h2>
6664
<!-- Google Tag Manager (noscript) -->
6765
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-TZK32GR" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
6866
<!-- End Google Tag Manager (noscript) -->
67+
68+
<script src="status.js"></script>
6969
</body>
7070
</html>

src/js/404.js

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,22 @@
11
import '../css/404.css';
2+
import '../css/status.css';
3+
import ChangelogService from './changelog/ChangelogService';
4+
import { detectTopicFromUrl } from './shared/common';
5+
import { renderStatus } from './shared/status';
26

3-
/**
4-
* @typedef {{
5-
* page: {
6-
* id: string;
7-
* name: string;
8-
* url: string;
9-
* time_zone: string;
10-
* updated_at: string;
11-
* };
12-
* status: {
13-
* indicator: "minor" | "major" | "critical" | "none" | "maintenance";
14-
* description: string;
15-
* };
16-
* }} StatusResult
17-
*/
18-
19-
/**
20-
* @param {URL} statusURL
21-
* @returns {Promise<{
22-
* severity: "minor" | "major" | "critical" | "none";
23-
* message: string;
24-
* }>}
25-
*/
26-
const getStatus = async (statusURL) => {
27-
const statusAPIURL = new URL(statusURL);
28-
statusAPIURL.pathname = '/api/v2/status.json';
29-
const result = await fetch(statusAPIURL);
30-
/** @type {StatusResult} */
31-
const data = await result.json();
32-
return {
33-
severity: data.status.indicator === 'maintenance' ? 'minor' : data.status.indicator,
34-
message: data.status.description,
35-
};
36-
};
7+
(async () => {
8+
const url = new URL(document.location.href);
9+
const topicSlugId = detectTopicFromUrl(url, 'changelog', 'topic');
10+
if (topicSlugId) {
11+
const apiBase = document.location.hostname.match(/(localhost|127\.0\.0\.1)/) ? '' : 'https://bitrise.io';
12+
const changelogService = new ChangelogService(apiBase, 'https://app.bitrise.io');
13+
const currentApiBase = await changelogService.getApiBase();
14+
if (currentApiBase === changelogService.fallbackApiBase) {
15+
window.location.href = `/changelog/topic?topic=${topicSlugId}`;
16+
}
17+
}
18+
})();
3719

3820
window.addEventListener('load', async () => {
39-
try {
40-
const statusLink = document.querySelector('div#bitrise-status a');
41-
const { severity, message } = await getStatus(statusLink.href);
42-
statusLink.className = `severity-${severity}`;
43-
statusLink.innerHTML = `Status: ${message}`;
44-
} catch (e) {
45-
/* don't update status badge */
46-
}
21+
renderStatus(document.querySelector('div#bitrise-status a'));
4722
});

src/js/changelog-topic.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ const topicMetaSeparator = document.querySelector('#changelog-topic-meta-separat
1616
topicMetaSeparator.style.display = 'none';
1717

1818
const url = new URL(document.location.href);
19-
const topicSlugId = detectTopicFromUrl(url, 'changelog');
19+
const topicSlugId = detectTopicFromUrl(url, 'changelog', 'topic');
2020

2121
/** @type {string} */
2222
const apiBase = document.location.hostname.match(/(localhost|127\.0\.0\.1)/) ? '' : 'https://bitrise.io';
23-
const changelogService = new ChangelogService(apiBase);
23+
const changelogService = new ChangelogService(apiBase, 'https://app.bitrise.io');
2424

2525
document.getElementById('changelog-topic-title').innerHTML = "<span class='changelog-loading'>Loading</span>";
2626
document.getElementById('changelog-topic-meta').innerHTML = "<span class='changelog-loading'>Loading</span>";

src/js/changelog.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ document.cookie = `${cookieName}=${cookieValue};expires=${expires};${domain ? `d
3434

3535
/** @type {string} */
3636
const apiBase = document.location.hostname.match(/(localhost|127\.0\.0\.1)/) ? '' : 'https://bitrise.io';
37-
const changelogService = new ChangelogService(apiBase);
37+
const changelogService = new ChangelogService(apiBase, 'https://app.bitrise.io');
3838

3939
/** @type {HTMLElement} */
4040
const changelogList = new ChangelogList(document.getElementById('changelog-list'));
@@ -82,7 +82,8 @@ const getChangelogSettings = () => {
8282
*/
8383
const renderFullChangelog = async (fullUrl) => {
8484
const topics = await changelogService.loadTopics(fullUrl);
85-
changelogList.render(topics, lastVisitDate, getChangelogSettings());
85+
const useFallback = changelogService.currentApiBase === changelogService.fallbackApiBase;
86+
changelogList.render(topics, lastVisitDate, getChangelogSettings(), useFallback);
8687
};
8788

8889
const loadMoreClickHandler = async (event) => {
@@ -113,7 +114,8 @@ const loadMoreClickHandler = async (event) => {
113114
*/
114115
const renderLatestChangelog = async (latestUrl) => {
115116
const topics = await changelogService.loadTopics(latestUrl);
116-
changelogList.render(topics, lastVisitDate, getChangelogSettings());
117+
const useFallback = changelogService.currentApiBase === changelogService.fallbackApiBase;
118+
changelogList.render(topics, lastVisitDate, getChangelogSettings(), useFallback);
117119
};
118120

119121
const changelogSettingsTriggerClickHandler = () => {

src/js/changelog/ChangelogList.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ class ChangelogList {
5151
/**
5252
* @param {ChangelogTopic} topic
5353
* @param {boolean} isUnread
54+
* @param {boolean} useFallback
5455
* @returns {HTMLLIElement}
5556
*/
56-
renderListItem(topic, isUnread = false) {
57+
renderListItem(topic, isUnread = false, useFallback = false) {
5758
/** @type {HTMLLIElement} */
5859
const listItem = (isUnread ? this.unreadListItemTemplate : this.readListItemTemplate).cloneNode(true);
5960
listItem.querySelector('.changelog-timestamp').innerHTML = formatDate(topic.createdAt);
@@ -62,16 +63,17 @@ class ChangelogList {
6263
const listItemTag = this.tagFactory.getTopicTag(topic.tags);
6364
if (listItemTag) listItem.querySelector('.changelog-tag-placeholder').replaceWith(listItemTag);
6465

65-
listItem.querySelector('a').href = topic.webflowUrl;
66+
listItem.querySelector('a').href = topic.webflowUrl(useFallback);
6667
return listItem;
6768
}
6869

6970
/**
7071
* @param {ChangelogTopic[]} topics
7172
* @param {Date} lastVisitedDate
7273
* @param {{search?: string}} filters
74+
* @param {boolean} useFallback
7375
*/
74-
render(topics, lastVisitedDate, filters = {}) {
76+
render(topics, lastVisitedDate, filters = {}, useFallback = false) {
7577
this.list.innerHTML = '';
7678
let timestamp = null;
7779
topics.forEach((topic) => {
@@ -90,7 +92,7 @@ class ChangelogList {
9092
}
9193

9294
const isUnread = topic.createdAt.getTime() > lastVisitedDate.getTime();
93-
const listItem = this.renderListItem(topic, isUnread);
95+
const listItem = this.renderListItem(topic, isUnread, useFallback);
9496
this.list.append(listItem);
9597
});
9698
if (this.list.innerHTML === '') {

src/js/changelog/ChangelogService.js

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,52 @@ import ChangelogTopic from './ChangelogTopic';
33
class ChangelogService {
44
/**
55
* @param {string} apiBase
6+
* @param {string} fallbackApiBase
67
*/
7-
constructor(apiBase) {
8+
constructor(apiBase, fallbackApiBase) {
89
/** @type {string} */
910
this.apiBase = apiBase;
11+
/** @type {string} */
12+
this.fallbackApiBase = fallbackApiBase;
13+
/** @type {string|null} */
14+
this.currentApiBase = null;
15+
}
16+
17+
/**
18+
* Checks the availability of the primary and fallback API bases, and returns the one that is reachable.
19+
* @throws {Error} if neither the primary nor the fallback API base is reachable
20+
* @returns {Promise<string>} the reachable API base URL
21+
*/
22+
async getApiBase() {
23+
if (this.currentApiBase !== null) {
24+
return this.currentApiBase;
25+
}
26+
try {
27+
const response = await fetch(`${this.apiBase}/changelog-proxy`);
28+
if (response.ok) {
29+
this.currentApiBase = this.apiBase;
30+
}
31+
} catch (error) {
32+
this.currentApiBase = this.fallbackApiBase;
33+
}
34+
if (this.currentApiBase === null) {
35+
const response = await fetch(`${this.fallbackApiBase}/changelog-proxy`);
36+
if (response.ok) {
37+
this.currentApiBase = this.fallbackApiBase;
38+
}
39+
}
40+
return this.currentApiBase;
1041
}
1142

1243
/**
1344
* @param {string} changelogUrl
1445
* @returns {Promise<ChangelogTopic[]>}
1546
*/
1647
async loadTopics(changelogUrl) {
48+
const apiBase = await this.getApiBase();
1749
const d = new Date().toISOString().split(':');
1850
const cacheBuster = `${d[0].replace(/[^\d]/g, '')}${Math.floor(d[1] / 15)}`;
19-
const url = `${this.apiBase + changelogUrl}?_=${cacheBuster}`;
51+
const url = `${apiBase + changelogUrl}?_=${cacheBuster}`;
2052
const response = await fetch(url);
2153
const json = await response.json();
2254
return json.topic_list.topics.map((data) => new ChangelogTopic(data));
@@ -27,8 +59,9 @@ class ChangelogService {
2759
* @returns {Promise<ChangelogTopic>}
2860
*/
2961
async loadTopic(topicSlugId) {
62+
const apiBase = await this.getApiBase();
3063
if (topicSlugId.match(/rm-\d+/)) {
31-
const url = `${this.apiBase}/changelog.json`;
64+
const url = `${apiBase}/changelog.json`;
3265
const response = await fetch(url);
3366
const json = await response.json();
3467
const topicJson = json.topic_list.topics.filter(
@@ -39,7 +72,7 @@ class ChangelogService {
3972
}
4073
}
4174

42-
const url = `${this.apiBase}/changelog/api/t/${topicSlugId}.json`;
75+
const url = `${apiBase}/changelog/api/t/${topicSlugId}.json`;
4376
const response = await fetch(url);
4477
const json = await response.json();
4578
if (!json.errors || json.errors.length === 0) {

src/js/changelog/ChangelogTopic.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,20 @@ class ChangelogTopic {
7171
return this.data.slug;
7272
}
7373

74-
/** @returns {string} */
75-
get webflowUrl() {
74+
/**
75+
* @param {boolean} useFallback
76+
* @returns {string}
77+
*/
78+
webflowUrl(useFallback = false) {
79+
let basePath = '/changelog/';
80+
if (useFallback) {
81+
basePath = '/changelog/topic?topic=';
82+
}
83+
7684
if (this.id === 0) {
77-
return `/changelog/${this.slug}`;
85+
return `${basePath}${this.slug}`;
7886
}
79-
return `/changelog/${this.slug}/${this.id}`;
87+
return `${basePath}${this.slug}/${this.id}`;
8088
}
8189

8290
/** @returns {boolean} */

0 commit comments

Comments
 (0)