feat: support force refresh in feed edit and feed entries page
This commit is contained in:
parent
3060946cc1
commit
79c91d71c8
30 changed files with 88 additions and 23 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"confirm.question": "Είστε σίγουροι;",
|
||||
"confirm.question.refresh": "Θέλετε να επιτελέσετε μια υποχρεωτική ανανέωση;",
|
||||
"confirm.yes": "ναι",
|
||||
"confirm.no": "όχι",
|
||||
"confirm.loading": "Σε εξέλιξη...",
|
||||
|
|
|
@ -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…",
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -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ä...",
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"confirm.question": "मंजूर है?",
|
||||
"confirm.question.refresh": "क्या आप बल द्वारा ताज़ा करना चाहते हैं?",
|
||||
"confirm.yes": "हाँ",
|
||||
"confirm.no": " नहीं",
|
||||
"confirm.loading": " प्रगति में है ...",
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"confirm.question": "よろしいですか?",
|
||||
"confirm.question.refresh": "強制的に更新しますか?",
|
||||
"confirm.yes": "はい",
|
||||
"confirm.no": "いいえ",
|
||||
"confirm.loading": "実行中…",
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"confirm.question": "Вы уверены?",
|
||||
"confirm.question.refresh": "Вы хотите выполнить принудительное обновление?",
|
||||
"confirm.yes": "да",
|
||||
"confirm.no": "нет",
|
||||
"confirm.loading": "В процессе…",
|
||||
|
|
|
@ -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...",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"confirm.question": "Ви впевнені?",
|
||||
"confirm.question.refresh": "Ви хочете змусити оновити?",
|
||||
"confirm.yes": "так",
|
||||
"confirm.no": "ні",
|
||||
"confirm.loading": "В процесі...",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"confirm.question": "您确认吗?",
|
||||
"confirm.question.refresh": "您是否要强制刷新?",
|
||||
"confirm.yes": "是",
|
||||
"confirm.no": "否",
|
||||
"confirm.loading": "执行中…",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"confirm.question": "您確認嗎?",
|
||||
"confirm.question.refresh": "您想要強制刷新嗎?",
|
||||
"confirm.yes": "是",
|
||||
"confirm.no": "否",
|
||||
"confirm.loading": "執行中…",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
linkElement.style.display = "inline";
|
||||
questionElement.remove();
|
||||
|
||||
const noActionUrl = linkElement.dataset.noActionUrl;
|
||||
if (noActionUrl) {
|
||||
createLoadingElement();
|
||||
|
||||
callback(noActionUrl, linkElement.dataset.redirectUrl);
|
||||
} else {
|
||||
linkElement.style.display = "inline";
|
||||
questionElement.remove();
|
||||
}
|
||||
};
|
||||
|
||||
questionElement.className = "confirm";
|
||||
|
|
4
ui/static/js/bootstrap.js
vendored
4
ui/static/js/bootstrap.js
vendored
|
@ -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();
|
||||
}
|
||||
|
|
3
ui/ui.go
3
ui/ui.go
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue