Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
239 changes: 239 additions & 0 deletions IMPLEMENTATION_DETAILS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# Sticky Incidents Fix - Code Changes Summary

## Overview
This document provides a detailed breakdown of the code changes made to fix the sticky incidents display issue.

## Problem Statement
From the GitHub issue:
- User created an incident and marked it as "sticky"
- Expected: Incident should display prominently at the top of the status page
- Actual: Incident only appeared in the "Past Incidents" section under its date (1/2/2026)

## Solution Architecture

### 1. Data Layer Changes (`IncidentTimeline.php`)

#### New Method: `stickiedIncidents()`
Fetches all sticky incidents that should be displayed at the top:

```php
private function stickiedIncidents(): Collection
{
return Incident::query()
->with([
'components',
'updates' => fn ($query) => $query->orderByDesc('created_at'),
])
->visible(auth()->check())
->stickied() // Uses existing scope from Incident model
->get()
->sortByDesc(fn (Incident $incident) => $incident->timestamp);
}
```

**Key Points:**
- Uses the existing `stickied()` scope defined in the Incident model
- Maintains visibility checks (respects authentication status)
- Eager loads components and updates for performance
- Sorts by timestamp (newest first)

#### Modified Method: `incidents()`
Excludes sticky incidents from the regular timeline:

```php
private function incidents(Carbon $startDate, Carbon $endDate, bool $onlyDisruptedDays = false): Collection
{
return Incident::query()
->with([...])
->visible(auth()->check())
->where('stickied', false) // ← NEW: Exclude sticky incidents
->when($this->appSettings->recent_incidents_only, function ($query) {
// ... existing logic
})
// ... rest of the method
}
```

**Key Points:**
- Added `->where('stickied', false)` to explicitly exclude sticky incidents
- This ensures sticky incidents don't appear in both sections
- All other logic remains unchanged

#### Updated Method: `render()`
Passes sticky incidents to the view:

```php
public function render(): View
{
// ... existing logic

return view('cachet::components.incident-timeline', [
'stickiedIncidents' => $this->stickiedIncidents(), // ← NEW
'incidents' => $this->incidents(...),
// ... existing data
]);
}
```

### 2. Presentation Layer Changes (`incident-timeline.blade.php`)

#### New Section: Stickied Incidents
Added at the top of the timeline, before "Past Incidents":

```blade
@if($stickiedIncidents->isNotEmpty())
<div class="flex flex-col gap-5">
<div class="md:border-b py-2 dark:border-zinc-700">
<h2 class="text-2xl font-semibold">
{{ __('cachet::incident.timeline.stickied_incidents_header') }}
</h2>
</div>

@foreach($stickiedIncidents as $incident)
<div class="ring-2 ring-amber-500 dark:ring-amber-600 ...">
<div class="bg-amber-50 dark:bg-amber-950/30 ...">
<!-- Incident details -->
</div>
<!-- Updates section -->
</div>
@endforeach
</div>
@endif
```

**Visual Styling:**
- `ring-2 ring-amber-500 dark:ring-amber-600` - Prominent amber border
- `bg-amber-50 dark:bg-amber-950/30` - Subtle amber background tint
- Maintains all existing incident display features (updates, status, components)

**Conditional Display:**
- Section only appears if `$stickiedIncidents->isNotEmpty()`
- No visual clutter when no sticky incidents exist

### 3. Localization Changes (`incident.php`)

Added translation key for the new section header:

```php
'timeline' => [
'past_incidents_header' => 'Past Incidents',
'recent_incidents_header' => 'Recent Incidents',
'stickied_incidents_header' => 'Stickied Incidents', // ← NEW
// ...
],
```

### 4. Test Coverage (`IncidentTimelineTest.php`)

#### Test 1: Display Separation
```php
it('displays stickied incidents separately at the top', function () {
$stickyIncident = Incident::factory()->create(['stickied' => true]);
$regularIncident = Incident::factory()->create(['stickied' => false]);

$response = get(route('cachet.status-page'));

$response->assertSeeInOrder([
'Stickied Incidents',
'Important Sticky Incident',
'Past Incidents',
'Regular Incident',
]);
});
```

#### Test 2: No Duplication
```php
it('does not show stickied incidents in the regular timeline', function () {
// Creates both sticky and regular incidents
// Verifies sticky incidents only in stickiedIncidents collection
// Verifies regular incidents only in incidents collection
});
```

#### Test 3: Sorting
```php
it('shows multiple stickied incidents sorted by timestamp', function () {
// Verifies newer sticky incidents appear first
});
```

#### Test 4: Conditional Display
```php
it('does not show stickied incidents section when there are none', function () {
// Verifies section doesn't appear unnecessarily
});
```

## Data Flow Diagram

```
User requests status page
IncidentTimeline component loads
┌────┴────┐
↓ ↓
stickiedIncidents() incidents()
↓ ↓
Query: stickied=true Query: stickied=false
↓ ↓
Sorted by timestamp Grouped by date
↓ ↓
└────┬────┘
Passed to view template
┌────┴────┐
↓ ↓
Sticky Section Timeline Section
(if not empty) (date-grouped)
↓ ↓
[Amber Border] [Regular Display]
```

## Performance Considerations

### Optimizations Implemented:
1. **Single Query per Type**: One query for sticky incidents, one for regular
2. **Eager Loading**: Components and updates loaded upfront
3. **Conditional Rendering**: Sticky section only renders when needed
4. **Existing Scopes**: Leverages existing `stickied()` scope on Incident model

### Database Impact:
- **Before**: 1 query for all incidents
- **After**: 2 queries (1 for sticky, 1 for regular)
- **Trade-off**: Minimal - Better UX worth the extra query

## Backward Compatibility

✅ **Fully backward compatible:**
- Existing incidents without `stickied=true` display unchanged
- No database migrations required (column already exists)
- No API changes
- Existing tests remain valid

## Edge Cases Handled

1. ✅ No sticky incidents → Section doesn't display
2. ✅ Multiple sticky incidents → Sorted by timestamp
3. ✅ Sticky incident with updates → All updates display correctly
4. ✅ Visibility rules → Respects auth()->check() for both sections
5. ✅ Date filtering → Sticky incidents always show (not date-filtered)

## Migration Path

For deployment to production:

1. Merge changes to `cachethq/core` repository
2. Tag new version of core package
3. Update `cachethq/cachet` to use new core version
4. No database migrations needed
5. No configuration changes required

## Rollback Plan

If issues arise:
1. Revert core package to previous version
2. No data loss (database unchanged)
3. System returns to previous behavior
61 changes: 61 additions & 0 deletions QUICK_START.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Sticky Incidents Fix - Quick Start

## What This Fix Does

✅ Makes sticky incidents display prominently at the top of your Cachet status page
✅ Adds distinctive amber visual styling to sticky incidents
✅ Prevents sticky incidents from appearing in the date-based timeline
✅ No database changes or migrations required

## Quick Apply (for testing)

If you want to test this fix immediately in your local development environment:

```bash
# Navigate to your Cachet installation
cd /path/to/cachet

# Apply the patch to the core package
cd vendor/cachethq/core
patch -p1 < ../../../sticky-incidents-fix.patch

# Clear caches
php artisan config:clear
php artisan view:clear
```

## Production Deployment

For production, this fix should be merged into the `cachethq/core` repository:

1. The changes are in 4 files (see patch file)
2. Once merged and tagged in core, update your `composer.json`
3. Run `composer update cachethq/core`

## Files Changed

1. `src/View/Components/IncidentTimeline.php` - Logic changes
2. `resources/views/components/incident-timeline.blade.php` - UI changes
3. `resources/lang/en/incident.php` - Translation
4. `tests/Feature/View/Components/IncidentTimelineTest.php` - Tests (new)

## Documentation

- **SUMMARY.md** - Quick overview with visuals
- **STICKY_INCIDENTS_FIX.md** - Detailed user documentation
- **IMPLEMENTATION_DETAILS.md** - Technical deep dive
- **sticky-incidents-fix.patch** - The actual code changes

## Testing

After applying, test by:

1. Create an incident and mark it as "Sticky"
2. Visit your status page
3. Verify the incident appears in "Stickied Incidents" section at top
4. Verify it has an amber border
5. Verify it's NOT in the "Past Incidents" timeline

## Questions?

Refer to the documentation files in this directory for complete details.
Loading