-
Notifications
You must be signed in to change notification settings - Fork 378
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Status WIP * feat: Status * fix: Status tests * feat: status redirect * chore: Status tests * chore: Status tests * feat: Status tests * chore: Status playwright tests * fix: PR feedback, mostly Vue and copyright dates * fix: Status model migration tidy up * chore: Status - one migration * feat: status on doc/html pages * chore: Resetting Status migration * chore: removing unused FieldError * fix: Update Status test to remove 'by' * chore: fixing API test to exclude 'status' * chore: fixing status_page test * feat: Site Status PR feedback. URL coverage debugging * Adding ietf.status to Tastypie omitted apps * feat: Site Status PR feedback * chore: correct copyright year on newly created files * chore: repair merge damage * chore: repair more merge damage * fix: reconcile the api init refactor with ignoring apps --------- Co-authored-by: Matthew Holloway <Matthew Holloway> Co-authored-by: Robert Sparks <[email protected]>
- Loading branch information
Showing
26 changed files
with
574 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<script setup> | ||
import { h, onMounted } from 'vue' | ||
import { useNotification } from 'naive-ui' | ||
import { localStorageWrapper } from '../shared/local-storage-wrapper' | ||
import { JSONWrapper } from '../shared/json-wrapper' | ||
import { STATUS_STORAGE_KEY, generateStatusTestId } from '../shared/status-common' | ||
const getDismissedStatuses = () => { | ||
const jsonString = localStorageWrapper.getItem(STATUS_STORAGE_KEY) | ||
const jsonValue = JSONWrapper.parse(jsonString, []) | ||
if(Array.isArray(jsonValue)) { | ||
return jsonValue | ||
} | ||
return [] | ||
} | ||
const dismissStatus = (id) => { | ||
const dissmissed = [id, ...getDismissedStatuses()] | ||
localStorageWrapper.setItem(STATUS_STORAGE_KEY, JSONWrapper.stringify(dissmissed)) | ||
return true | ||
} | ||
let notificationInstances = {} // keyed by status.id | ||
let notification | ||
const pollStatusAPI = () => { | ||
fetch('/status/latest.json') | ||
.then(resp => resp.json()) | ||
.then(status => { | ||
if(status === null || status.hasMessage === false) { | ||
console.debug("No status message") | ||
return | ||
} | ||
const dismissedStatuses = getDismissedStatuses() | ||
if(dismissedStatuses.includes(status.id)) { | ||
console.debug(`Not showing site status ${status.id} because it was already dismissed. Dismissed Ids:`, dismissedStatuses) | ||
return | ||
} | ||
const isSameStatusPage = Boolean(document.querySelector(`[data-status-id="${status.id}"]`)) | ||
if(isSameStatusPage) { | ||
console.debug(`Not showing site status ${status.id} because we're on the target page`) | ||
return | ||
} | ||
if(notificationInstances[status.id]) { | ||
console.debug(`Not showing site status ${status.id} because it's already been displayed`) | ||
return | ||
} | ||
notificationInstances[status.id] = notification.create({ | ||
title: status.title, | ||
content: status.body, | ||
meta: `${status.date}`, | ||
action: () => | ||
h( | ||
'a', | ||
{ | ||
'data-testid': generateStatusTestId(status.id), | ||
href: status.url, | ||
'aria-label': `Read more about ${status.title}` | ||
}, | ||
"Read more" | ||
), | ||
onClose: () => { | ||
return dismissStatus(status.id) | ||
} | ||
}) | ||
}) | ||
.catch(e => { | ||
console.error(e) | ||
}) | ||
} | ||
onMounted(() => { | ||
notification = useNotification() | ||
pollStatusAPI(notification) | ||
}) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export const JSONWrapper = { | ||
parse(jsonString, defaultValue) { | ||
if(typeof jsonString !== "string") { | ||
return defaultValue | ||
} | ||
try { | ||
return JSON.parse(jsonString); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
return defaultValue | ||
}, | ||
stringify(data) { | ||
try { | ||
return JSON.stringify(data); | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
|
||
/* | ||
* DEVELOPER NOTE | ||
* | ||
* Some browsers can block storage (localStorage, sessionStorage) | ||
* access for privacy reasons, and all browsers can have storage | ||
* that's full, and then they throw exceptions. | ||
* | ||
* See https://michalzalecki.com/why-using-localStorage-directly-is-a-bad-idea/ | ||
* | ||
* Exceptions can even be thrown when testing if localStorage | ||
* even exists. This can throw: | ||
* | ||
* if (window.localStorage) | ||
* | ||
* Also localStorage/sessionStorage can be enabled after DOMContentLoaded | ||
* so we handle it gracefully. | ||
* | ||
* 1) we need to wrap all usage in try/catch | ||
* 2) we need to defer actual usage of these until | ||
* necessary, | ||
* | ||
*/ | ||
|
||
export const localStorageWrapper = { | ||
getItem: (key) => { | ||
try { | ||
return localStorage.getItem(key) | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
return null; | ||
}, | ||
setItem: (key, value) => { | ||
try { | ||
return localStorage.setItem(key, value) | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
return; | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// Used in Playwright Status and components | ||
|
||
export const STATUS_STORAGE_KEY = "status-dismissed" | ||
|
||
export const generateStatusTestId = (id) => `status-${id}` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Copyright The IETF Trust 2024, All Rights Reserved | ||
# -*- coding: utf-8 -*- | ||
|
||
from datetime import datetime | ||
from django.contrib import admin | ||
from django.template.defaultfilters import slugify | ||
from .models import Status | ||
|
||
class StatusAdmin(admin.ModelAdmin): | ||
list_display = ['title', 'body', 'active', 'date', 'by', 'page'] | ||
raw_id_fields = ['by'] | ||
|
||
def get_changeform_initial_data(self, request): | ||
date = datetime.now() | ||
return { | ||
"slug": slugify(f"{date.year}-{date.month}-{date.day}-"), | ||
} | ||
|
||
admin.site.register(Status, StatusAdmin) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Copyright The IETF Trust 2024, All Rights Reserved | ||
# -*- coding: utf-8 -*- | ||
|
||
from django.apps import AppConfig | ||
|
||
|
||
class StatusConfig(AppConfig): | ||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "ietf.status" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# Generated by Django 4.2.13 on 2024-07-21 22:47 | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import django.utils.timezone | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
("person", "0002_alter_historicalperson_ascii_and_more"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="Status", | ||
fields=[ | ||
( | ||
"id", | ||
models.BigAutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
("date", models.DateTimeField(default=django.utils.timezone.now)), | ||
("slug", models.SlugField(unique=True)), | ||
( | ||
"title", | ||
models.CharField( | ||
help_text="Your site status notification title.", | ||
max_length=255, | ||
verbose_name="Status title", | ||
), | ||
), | ||
( | ||
"body", | ||
models.CharField( | ||
help_text="Your site status notification body.", | ||
max_length=255, | ||
verbose_name="Status body", | ||
), | ||
), | ||
( | ||
"active", | ||
models.BooleanField( | ||
default=True, | ||
help_text="Only active messages will be shown.", | ||
verbose_name="Active?", | ||
), | ||
), | ||
( | ||
"page", | ||
models.TextField( | ||
blank=True, | ||
help_text="More detail shown after people click 'Read more'. If empty no 'read more' will be shown", | ||
null=True, | ||
verbose_name="More detail (markdown)", | ||
), | ||
), | ||
( | ||
"by", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, to="person.person" | ||
), | ||
), | ||
], | ||
options={ | ||
"verbose_name_plural": "statuses", | ||
}, | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Copyright The IETF Trust 2024, All Rights Reserved | ||
# -*- coding: utf-8 -*- | ||
|
||
from django.utils import timezone | ||
from django.db import models | ||
from django.db.models import ForeignKey | ||
|
||
import debug # pyflakes:ignore | ||
|
||
class Status(models.Model): | ||
name = 'Status' | ||
|
||
date = models.DateTimeField(default=timezone.now) | ||
slug = models.SlugField(blank=False, null=False, unique=True) | ||
title = models.CharField(max_length=255, verbose_name="Status title", help_text="Your site status notification title.") | ||
body = models.CharField(max_length=255, verbose_name="Status body", help_text="Your site status notification body.", unique=False) | ||
active = models.BooleanField(default=True, verbose_name="Active?", help_text="Only active messages will be shown.") | ||
by = ForeignKey('person.Person', on_delete=models.CASCADE) | ||
page = models.TextField(blank=True, null=True, verbose_name="More detail (markdown)", help_text="More detail shown after people click 'Read more'. If empty no 'read more' will be shown") | ||
|
||
def __str__(self): | ||
return "{} {} {} {}".format(self.date, self.active, self.by, self.title) | ||
class Meta: | ||
verbose_name_plural = "statuses" |
Oops, something went wrong.