Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 74 additions & 22 deletions site/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1185,34 +1185,64 @@ const render = async () => {
const timeline = document.getElementById('incidentTimeline');
timeline.innerHTML = '';

const grouped = new Map();
incidents.forEach((incident) => {
const date = formatDate(incidentStartDate(incident));
if (!grouped.has(date)) grouped.set(date, []);
grouped.get(date).push(incident);
});
const activeFilters = new Set();

const getFilteredIncidents = () =>
activeFilters.size === 0
? incidents
: incidents.filter((i) => activeFilters.has(i.impact || 'none'));

const entries = Array.from(grouped.entries());
let showAll = false;
const sortSelect = document.getElementById('timelineSort');
let sortOrder = sortSelect ? sortSelect.value : 'date-desc';
const toggleButtons = Array.from(document.querySelectorAll('[data-toggle-timeline]'));

const renderTimeline = () => {
timeline.innerHTML = '';
const slice = showAll ? entries : entries.slice(0, 8);
slice.forEach(([date, list]) => {
const group = document.createElement('div');
group.className = 'incident-group';

const heading = document.createElement('h4');
heading.textContent = date;
group.appendChild(heading);

list.forEach((incident) => {
group.appendChild(renderIncidentCard(incident));
const filtered = getFilteredIncidents();
if (sortOrder === 'date-desc') {
const grouped = new Map();
filtered.forEach((incident) => {
const date = formatDate(incidentStartDate(incident));
if (!grouped.has(date)) grouped.set(date, []);
grouped.get(date).push(incident);
});

timeline.appendChild(group);
});
const entries = Array.from(grouped.entries());
const slice = showAll ? entries : entries.slice(0, 8);
slice.forEach(([date, list]) => {
const group = document.createElement('div');
group.className = 'incident-group';
const heading = document.createElement('h4');
heading.textContent = date;
group.appendChild(heading);
list.forEach((incident) => {
group.appendChild(renderIncidentCard(incident));
});
timeline.appendChild(group);
});
} else {
const sorted = filtered.slice().sort((a, b) => {
const aDur = a.duration_minutes ?? 0;
const bDur = b.duration_minutes ?? 0;
return sortOrder === 'duration-desc' ? bDur - aDur : aDur - bDur;
});
const slice = showAll ? sorted : sorted.slice(0, 20);
let lastDate = null;
let currentGroup = null;
slice.forEach((incident) => {
const date = formatDate(incidentStartDate(incident));
if (date !== lastDate) {
currentGroup = document.createElement('div');
currentGroup.className = 'incident-group';
const heading = document.createElement('h4');
heading.textContent = date;
currentGroup.appendChild(heading);
timeline.appendChild(currentGroup);
lastDate = date;
}
currentGroup.appendChild(renderIncidentCard(incident));
});
}
};

renderTimeline();
Expand All @@ -1224,9 +1254,31 @@ const render = async () => {

updateToggleButtons();

if (sortSelect) {
sortSelect.addEventListener('change', () => {
sortOrder = sortSelect.value;
renderTimeline();
});
}

const filterPills = Array.from(document.querySelectorAll('[data-filter]'));
filterPills.forEach((pill) => {
pill.addEventListener('click', () => {
const filter = pill.dataset.filter;
if (activeFilters.has(filter)) {
activeFilters.delete(filter);
pill.setAttribute('aria-pressed', 'false');
} else {
activeFilters.add(filter);
pill.setAttribute('aria-pressed', 'true');
}
renderTimeline();
});
});

toggleButtons.forEach((button) => {
button.addEventListener('click', () => {
showAll = !showAll;
showAll = !showAll;
updateToggleButtons();
renderTimeline();
});
Expand Down
11 changes: 11 additions & 0 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,20 @@ <h3>Uptime history</h3>
<div class="panel-header">
<h3>Incident timeline</h3>
<div class="panel-actions">
<select class="sort-select" id="timelineSort" aria-label="Sort incidents by" autocomplete="off">
<option value="date-desc">Date (newest first)</option>
<option value="duration-desc">Duration (longest first)</option>
<option value="duration-asc">Duration (shortest first)</option>
</select>
<button class="ghost-button" type="button" id="togglePast" data-toggle-timeline>Show more</button>
</div>
</div>
<div class="timeline-filters">
<button class="filter-pill filter-pill-major" type="button" data-filter="major" aria-pressed="false">Major</button>
<button class="filter-pill filter-pill-minor" type="button" data-filter="minor" aria-pressed="false">Minor</button>
<button class="filter-pill filter-pill-maintenance" type="button" data-filter="maintenance" aria-pressed="false">Maintenance</button>
<button class="filter-pill filter-pill-operational" type="button" data-filter="none" aria-pressed="false">Operational</button>
</div>
Comment thread
abhishekmagdum marked this conversation as resolved.
<div id="incidentTimeline"></div>
<div class="timeline-footer">
<button class="ghost-button" type="button" id="togglePastBottom" data-toggle-timeline>
Expand Down
82 changes: 82 additions & 0 deletions site/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,88 @@ a:hover {
box-shadow: 0 10px 22px -15px rgba(0, 0, 0, 0.3);
}

.timeline-filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
padding: 14px 0 2px;
position: relative;
margin-top: 4px;
}

.timeline-filters::before {
content: '';
position: absolute;
top: 0;
left: 10%;
right: 10%;
height: 1px;
background: linear-gradient(to right, transparent, var(--border), transparent);
}

.filter-pill {
border-radius: 999px;
padding: 5px 13px;
font-size: 0.78rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
cursor: pointer;
transition: opacity 0.15s ease, box-shadow 0.15s ease;
font-family: inherit;
}

.filter-pill-major {
background: rgba(207, 34, 46, 0.12);
color: var(--badge-major-ink);
border: 1px solid rgba(207, 34, 46, 0.3);
}

.filter-pill-minor {
background: rgba(217, 119, 6, 0.12);
color: var(--badge-minor-ink);
border: 1px solid rgba(217, 119, 6, 0.3);
}

.filter-pill-maintenance {
background: rgba(31, 111, 235, 0.12);
color: var(--badge-maintenance-ink);
border: 1px solid rgba(31, 111, 235, 0.3);
}

.filter-pill-operational {
background: rgba(45, 164, 78, 0.12);
color: var(--badge-operational-ink);
border: 1px solid rgba(45, 164, 78, 0.3);
}

.filter-pill[aria-pressed="false"] {
opacity: 0.38;
}

.filter-pill[aria-pressed="true"] {
opacity: 1;
box-shadow: 0 0 0 2px currentColor;
}

.sort-select {
border: 1px solid var(--border);
background: var(--card);
border-radius: 999px;
padding: 10px 36px 10px 18px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
color: inherit;
font-family: inherit;
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23888' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 14px center;
}

main {
position: relative;
z-index: 1;
Expand Down
Loading