-
Notifications
You must be signed in to change notification settings - Fork 378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Site status message #7659
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
d03fd2e
Status WIP
933f0be
feat: Status
04ab0ba
Merge branch 'main' into status-message
175aea5
fix: Status tests
6408b65
feat: status redirect
4f47554
chore: Status tests
cd5f9ae
chore: Status tests
1a338bb
feat: Status tests
d6b2eee
chore: Status playwright tests
7e98341
fix: PR feedback, mostly Vue and copyright dates
5c10968
fix: Status model migration tidy up
42ed840
chore: Status - one migration
5bc8145
feat: status on doc/html pages
174fdd8
chore: merging main. Resolving conflict in ietf.scss
7a0c02b
chore: Resetting Status migration
d207c1d
chore: removing unused FieldError
b64d267
fix: Update Status test to remove 'by'
3423355
chore: fixing API test to exclude 'status'
holloway ad8d265
chore: fixing status_page test
holloway 93c986e
feat: Site Status PR feedback. URL coverage debugging
holloway 7368ae1
Adding ietf.status to Tastypie omitted apps
holloway 09c87c0
feat: Site Status PR feedback
holloway 9db7147
Merge branch 'main' into feat/status-message
holloway 93eb0f0
chore: correct copyright year on newly created files
holloway 2c9c897
Merge branch 'feat/status-message' of gh-ietf:ietf-tools/datatracker …
holloway dfa3732
Merge branch 'main' into feat/status-message
rjsparks b21661c
Merge remote-tracking branch 'ietf-tools/main' into feat/status-message
rjsparks 04cef0f
chore: repair merge damage
rjsparks a7e627f
Merge branch 'main' into feat/status-message
rjsparks de8a991
chore: repair more merge damage
rjsparks e7ef7f2
fix: reconcile the api init refactor with ignoring apps
rjsparks File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Various uses of semi-colons. Remove all.