feat: support force refresh in feed edit and feed entries page

This commit is contained in:
njzy 2023-08-08 14:12:41 +00:00 committed by Frédéric Guillot
parent 3060946cc1
commit 79c91d71c8
30 changed files with 88 additions and 23 deletions

View file

@ -47,7 +47,7 @@ func (h *handler) refreshFeed(w http.ResponseWriter, r *http.Request) {
return
}
err := feedHandler.RefreshFeed(h.store, userID, feedID)
err := feedHandler.RefreshFeed(h.store, userID, feedID, false)
if err != nil {
json.ServerError(w, r, err)
return

View file

@ -34,7 +34,7 @@ func refreshFeeds(store *storage.Storage) {
defer wg.Done()
for job := range jobQueue {
logger.Info("[Cronjob] Refreshing feed #%d for user #%d in worker #%d", job.FeedID, job.UserID, workerID)
if err := feedHandler.RefreshFeed(store, job.UserID, job.FeedID); err != nil {
if err := feedHandler.RefreshFeed(store, job.UserID, job.FeedID, false); err != nil {
logger.Error("[Cronjob] Refreshing the feed #%d returned this error: %v", job.FeedID, err)
}
}

View file

@ -107,6 +107,22 @@ func QueryInt64Param(r *http.Request, param string, defaultValue int64) int64 {
return val
}
// QueryBoolParam returns a query string parameter as bool.
func QueryBoolParam(r *http.Request, param string, defaultValue bool) bool {
value := r.URL.Query().Get(param)
if value == "" {
return defaultValue
}
val, err := strconv.ParseBool(value)
if err != nil {
return defaultValue
}
return val
}
// HasQueryParam checks if the query string contains the given parameter.
func HasQueryParam(r *http.Request, param string) bool {
values := r.URL.Query()

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Sind Sie sicher?",
"confirm.question.refresh": "Möchten Sie eine erzwungene Aktualisierung durchführen?",
"confirm.yes": "ja",
"confirm.no": "nein",
"confirm.loading": "In Arbeit...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Είστε σίγουροι;",
"confirm.question.refresh": "Θέλετε να επιτελέσετε μια υποχρεωτική ανανέωση;",
"confirm.yes": "ναι",
"confirm.no": "όχι",
"confirm.loading": "Σε εξέλιξη...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Are you sure?",
"confirm.question.refresh": "Are you want to force refresh?",
"confirm.yes": "yes",
"confirm.no": "no",
"confirm.loading": "In progress…",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "¿Estás seguro?",
"confirm.question.refresh": "¿Quieres forzar la actualización?",
"confirm.yes": "sí",
"confirm.no": "no",
"confirm.loading": "En progreso...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Oletko varma?",
"confirm.question.refresh": "Haluatko pakottaa päivityksen?",
"confirm.yes": "kyllä",
"confirm.no": "ei",
"confirm.loading": "Käynnissä...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Êtes-vous sûr ?",
"confirm.question.refresh": "Voulez-vous forcer le rafraîchissement ?",
"confirm.yes": "oui",
"confirm.no": "non",
"confirm.loading": "En cours...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "मंजूर है?",
"confirm.question.refresh": "क्या आप बल द्वारा ताज़ा करना चाहते हैं?",
"confirm.yes": "हाँ",
"confirm.no": " नहीं",
"confirm.loading": " प्रगति में है ...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Apakah Anda yakin?",
"confirm.question.refresh": "Apakah Anda ingin memaksa penyegaran?",
"confirm.yes": "ya",
"confirm.no": "tidak",
"confirm.loading": "Sedang progres...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Sei sicuro?",
"confirm.question.refresh": "Vuoi forzare l'aggiornamento?",
"confirm.yes": "sì",
"confirm.no": "no",
"confirm.loading": "In corso...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "よろしいですか?",
"confirm.question.refresh": "強制的に更新しますか?",
"confirm.yes": "はい",
"confirm.no": "いいえ",
"confirm.loading": "実行中…",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Weet je het zeker?",
"confirm.question.refresh": "Wil je een gedwongen vernieuwing uitvoeren?",
"confirm.yes": "ja",
"confirm.no": "nee",
"confirm.loading": "Bezig...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Czy jesteś pewny?",
"confirm.question.refresh": "Czy chcesz wymusić odświeżenie?",
"confirm.yes": "tak",
"confirm.no": "nie",
"confirm.loading": "W toku...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Tem certeza?",
"confirm.question.refresh": "Você deseja forçar a atualização?",
"confirm.yes": "Sim",
"confirm.no": "Não",
"confirm.loading": "Carregando...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Вы уверены?",
"confirm.question.refresh": "Вы хотите выполнить принудительное обновление?",
"confirm.yes": "да",
"confirm.no": "нет",
"confirm.loading": "В процессе…",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Emin misiniz?",
"confirm.question.refresh": "Zorla yenilemek istiyor musunuz?",
"confirm.yes": "evet",
"confirm.no": "hayır",
"confirm.loading": "Devam ediyor...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "Ви впевнені?",
"confirm.question.refresh": "Ви хочете змусити оновити?",
"confirm.yes": "так",
"confirm.no": "ні",
"confirm.loading": "В процесі...",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "您确认吗?",
"confirm.question.refresh": "您是否要强制刷新?",
"confirm.yes": "是",
"confirm.no": "否",
"confirm.loading": "执行中…",

View file

@ -1,5 +1,6 @@
{
"confirm.question": "您確認嗎?",
"confirm.question.refresh": "您想要強制刷新嗎?",
"confirm.yes": "是",
"confirm.no": "否",
"confirm.loading": "執行中…",

View file

@ -83,7 +83,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
subscription.WithClientResponse(response)
subscription.CheckedNow()
processor.ProcessFeedEntries(store, subscription, user)
processor.ProcessFeedEntries(store, subscription, user, true)
if storeErr := store.CreateFeed(subscription); storeErr != nil {
return nil, storeErr
@ -104,7 +104,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
}
// RefreshFeed refreshes a feed.
func RefreshFeed(store *storage.Storage, userID, feedID int64) error {
func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool) error {
defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[RefreshFeed] feedID=%d", feedID))
user, storeErr := store.UserByID(userID)
if storeErr != nil {
@ -173,10 +173,11 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64) error {
}
originalFeed.Entries = updatedFeed.Entries
processor.ProcessFeedEntries(store, originalFeed, user)
processor.ProcessFeedEntries(store, originalFeed, user, forceRefresh)
// We don't update existing entries when the crawler is enabled (we crawl only inexisting entries).
if storeErr := store.RefreshFeedEntries(originalFeed.UserID, originalFeed.ID, originalFeed.Entries, !originalFeed.Crawler); storeErr != nil {
// We don't update existing entries when the crawler is enabled (we crawl only inexisting entries). Unless it is forced to refresh
updateExistingEntries := forceRefresh || !originalFeed.Crawler
if storeErr := store.RefreshFeedEntries(originalFeed.UserID, originalFeed.ID, originalFeed.Entries, updateExistingEntries); storeErr != nil {
originalFeed.WithError(storeErr.Error())
store.UpdateFeedError(originalFeed)
return storeErr

View file

@ -38,7 +38,7 @@ var (
)
// ProcessFeedEntries downloads original web page for entries and apply filters.
func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.User) {
func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.User, forceRefresh bool) {
var filteredEntries model.Entries
// array used for bulk push
@ -56,7 +56,7 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.Us
url := getUrlFromEntry(feed, entry)
entryIsNew := !store.EntryURLExists(feed.ID, entry.URL)
if feed.Crawler && entryIsNew {
if feed.Crawler && (entryIsNew || forceRefresh) {
logger.Debug("[Processor] Crawling entry %q from feed %q", url, feed.FeedURL)
startTime := time.Now()

View file

@ -11,7 +11,14 @@
<a href="{{ route "feedEntries" "feedID" .feed.ID }}">{{ icon "entries" }}{{ t "menu.feed_entries" }}</a>
</li>
<li>
<a href="{{ route "refreshFeed" "feedID" .feed.ID }}">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
<a href="#"
data-confirm="true"
data-label-question="{{ t "confirm.question.refresh" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=true"
data-no-action-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=false">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
</li>
</ul>
</section>

View file

@ -37,7 +37,14 @@
</li>
{{ end }}
<li>
<a href="{{ route "refreshFeed" "feedID" .feed.ID }}">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
<a href="#"
data-confirm="true"
data-label-question="{{ t "confirm.question.refresh" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=true"
data-no-action-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=false">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
</li>
<li>
<a href="{{ route "editFeed" "feedID" .feed.ID }}">{{ icon "edit" }}{{ t "menu.edit_feed" }}</a>

View file

@ -15,7 +15,8 @@ import (
func (h *handler) refreshFeed(w http.ResponseWriter, r *http.Request) {
feedID := request.RouteInt64Param(r, "feedID")
if err := feedHandler.RefreshFeed(h.store, request.UserID(r), feedID); err != nil {
forceRefresh := request.QueryBoolParam(r, "forceRefresh", false)
if err := feedHandler.RefreshFeed(h.store, request.UserID(r), feedID, forceRefresh); err != nil {
logger.Error("[UI:RefreshFeed] %v", err)
}

View file

@ -561,18 +561,22 @@ function handleConfirmationMessage(linkElement, callback) {
let containerElement = linkElement.parentNode;
let questionElement = document.createElement("span");
let yesElement = document.createElement("a");
yesElement.href = "#";
yesElement.appendChild(document.createTextNode(linkElement.dataset.labelYes));
yesElement.onclick = (event) => {
event.preventDefault();
function createLoadingElement() {
let loadingElement = document.createElement("span");
loadingElement.className = "loading";
loadingElement.appendChild(document.createTextNode(linkElement.dataset.labelLoading));
questionElement.remove();
containerElement.appendChild(loadingElement);
}
let yesElement = document.createElement("a");
yesElement.href = "#";
yesElement.appendChild(document.createTextNode(linkElement.dataset.labelYes));
yesElement.onclick = (event) => {
event.preventDefault();
createLoadingElement();
callback(linkElement.dataset.url, linkElement.dataset.redirectUrl);
};
@ -582,8 +586,16 @@ function handleConfirmationMessage(linkElement, callback) {
noElement.appendChild(document.createTextNode(linkElement.dataset.labelNo));
noElement.onclick = (event) => {
event.preventDefault();
const noActionUrl = linkElement.dataset.noActionUrl;
if (noActionUrl) {
createLoadingElement();
callback(noActionUrl, linkElement.dataset.redirectUrl);
} else {
linkElement.style.display = "inline";
questionElement.remove();
}
};
questionElement.className = "confirm";

View file

@ -59,9 +59,11 @@ document.addEventListener("DOMContentLoaded", function () {
onClick("a[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => {
let request = new RequestBuilder(url);
request.withCallback(() => {
request.withCallback((response) => {
if (redirectURL) {
window.location.href = redirectURL;
} else if (response && response.redirected && response.url) {
window.location.href = response.url;
} else {
window.location.reload();
}

View file

@ -66,7 +66,8 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
uiRouter.HandleFunc("/feeds/refresh", handler.refreshAllFeeds).Name("refreshAllFeeds").Methods(http.MethodGet)
// Individual feed pages.
uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Name("refreshFeed").Methods(http.MethodGet)
uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Name("refreshFeed").Methods(http.MethodGet, http.MethodPost)
uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Queries("forceRefresh", "{forceRefresh:true|false}").Name("refreshFeed").Methods(http.MethodGet, http.MethodPost)
uiRouter.HandleFunc("/feed/{feedID}/edit", handler.showEditFeedPage).Name("editFeed").Methods(http.MethodGet)
uiRouter.HandleFunc("/feed/{feedID}/remove", handler.removeFeed).Name("removeFeed").Methods(http.MethodPost)
uiRouter.HandleFunc("/feed/{feedID}/update", handler.updateFeed).Name("updateFeed").Methods(http.MethodPost)

View file

@ -29,7 +29,7 @@ func (w *Worker) Run(c chan model.Job) {
logger.Debug("[Worker #%d] Received feed #%d for user #%d", w.id, job.FeedID, job.UserID)
startTime := time.Now()
refreshErr := feedHandler.RefreshFeed(w.store, job.UserID, job.FeedID)
refreshErr := feedHandler.RefreshFeed(w.store, job.UserID, job.FeedID, false)
if config.Opts.HasMetricsCollector() {
status := "success"