Refresh item after addon is done

This commit is contained in:
eikek
2022-05-18 00:23:46 +02:00
parent 5abbe92f2b
commit 29a5894884
15 changed files with 123 additions and 19 deletions

View File

@ -83,7 +83,7 @@ object OJob {
else else
pubsub.publish1IgnoreErrors( pubsub.publish1IgnoreErrors(
JobDone.topic, JobDone.topic,
JobDone(job.id, job.group, job.task, job.args, JobState.Cancelled) JobDone(job.id, job.group, job.task, job.args, JobState.Cancelled, None)
) )
} yield JobCancelResult.removed } yield JobCancelResult.removed

View File

@ -14,6 +14,8 @@ import docspell.pubsub.api.PubSubT
import docspell.restserver.ws.OutputEvent import docspell.restserver.ws.OutputEvent
import docspell.scheduler.msg.{JobDone, JobSubmitted} import docspell.scheduler.msg.{JobDone, JobSubmitted}
import io.circe.parser
/** Subscribes to those events from docspell that are forwarded to the websocket endpoints /** Subscribes to those events from docspell that are forwarded to the websocket endpoints
*/ */
object Subscriptions { object Subscriptions {
@ -27,7 +29,14 @@ object Subscriptions {
def jobDone[F[_]](pubSub: PubSubT[F]): Stream[F, OutputEvent] = def jobDone[F[_]](pubSub: PubSubT[F]): Stream[F, OutputEvent] =
pubSub pubSub
.subscribe(JobDone.topic) .subscribe(JobDone.topic)
.map(m => OutputEvent.JobDone(m.body.group, m.body.task)) .map(m =>
OutputEvent.JobDone(
m.body.group,
m.body.task,
parser.parse(m.body.args).toOption,
m.body.result
)
)
def jobSubmitted[F[_]](pubSub: PubSubT[F]): Stream[F, OutputEvent] = def jobSubmitted[F[_]](pubSub: PubSubT[F]): Stream[F, OutputEvent] =
pubSub pubSub

View File

@ -40,12 +40,20 @@ object OutputEvent {
Msg("job-submitted", task).asJson Msg("job-submitted", task).asJson
} }
final case class JobDone(group: Ident, task: Ident) extends OutputEvent { final case class JobDone(
group: Ident,
task: Ident,
args: Option[Json],
result: Option[Json]
) extends OutputEvent {
def forCollective(token: AuthToken): Boolean = def forCollective(token: AuthToken): Boolean =
token.account.collective == group token.account.collective == group
def asJson: Json = def asJson: Json =
Msg("job-done", task).asJson Msg(
"job-done",
Map("task" -> task.asJson, "args" -> args.asJson, "result" -> result.asJson)
).asJson
} }
final case class JobsWaiting(collective: Ident, count: Int) extends OutputEvent { final case class JobsWaiting(collective: Ident, count: Int) extends OutputEvent {

View File

@ -10,7 +10,7 @@ import docspell.common._
import docspell.pubsub.api.{Topic, TypedTopic} import docspell.pubsub.api.{Topic, TypedTopic}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder} import io.circe.{Decoder, Encoder, Json}
/** Message to notify about finished jobs. They have a final state. */ /** Message to notify about finished jobs. They have a final state. */
final case class JobDone( final case class JobDone(
@ -18,7 +18,8 @@ final case class JobDone(
group: Ident, group: Ident,
task: Ident, task: Ident,
args: String, args: String,
state: JobState state: JobState,
result: Option[Json]
) )
object JobDone { object JobDone {
implicit val jsonDecoder: Decoder[JobDone] = implicit val jsonDecoder: Decoder[JobDone] =

View File

@ -231,7 +231,7 @@ final class SchedulerImpl[F[_]: Async](
_ <- Sync[F].whenA(JobState.isDone(finishState))( _ <- Sync[F].whenA(JobState.isDone(finishState))(
pubSub.publish1IgnoreErrors( pubSub.publish1IgnoreErrors(
JobDone.topic, JobDone.topic,
JobDone(job.id, job.group, job.task, job.args, finishState) JobDone(job.id, job.group, job.task, job.args, finishState, result.json)
) )
) )
_ <- Sync[F].whenA(JobState.isDone(finishState))( _ <- Sync[F].whenA(JobState.isDone(finishState))(

View File

@ -314,13 +314,16 @@ updateWithSub msg model =
ReceiveWsMessage data -> ReceiveWsMessage data ->
case data of case data of
Ok (JobDone task) -> Ok (JobDone details) ->
let let
isProcessItem = isProcessItem =
task == "process-item" details.task == "process-item"
isDownloadZip = isDownloadZip =
task == "download-query-zip" details.task == "download-query-zip"
isAddonExistingItem =
Data.ServerEvent.isAddonExistingItem model.itemDetailModel.detail.item.id details
newModel = newModel =
{ model { model
@ -337,6 +340,9 @@ updateWithSub msg model =
else if Page.isDashboardPage model.page && isProcessItem then else if Page.isDashboardPage model.page && isProcessItem then
updateDashboard texts Page.Dashboard.Data.reloadDashboardData newModel updateDashboard texts Page.Dashboard.Data.reloadDashboardData newModel
else if Page.isDetailPage model.page && isAddonExistingItem then
updateItemDetail texts (Page.ItemDetail.Data.ReloadItem True) newModel
else else
( newModel, Cmd.none, Sub.none ) ( newModel, Cmd.none, Sub.none )

View File

@ -76,7 +76,7 @@ view texts model =
, a , a
[ class S.successMessageLink [ class S.successMessageLink
, href "#" , href "#"
, onClick ReloadItem , onClick (ReloadItem False)
] ]
[ text texts.refreshNow [ text texts.refreshNow
] ]

View File

@ -281,7 +281,7 @@ initSelectViewModel =
type Msg type Msg
= ToggleMenu = ToggleMenu
| ReloadItem | ReloadItem Bool
| Init | Init
| SetItem ItemDetail | SetItem ItemDetail
| SetActiveAttachment Int | SetActiveAttachment Int

View File

@ -387,12 +387,22 @@ update inav env msg model =
resultModel resultModel
{ model | menuOpen = not model.menuOpen } { model | menuOpen = not model.menuOpen }
ReloadItem -> ReloadItem withFile ->
if model.item.id == "" then if model.item.id == "" then
resultModel model resultModel model
else else
resultModelCmd ( model, Api.itemDetail env.flags model.item.id GetItemResp ) resultModelCmd
( model
, Cmd.batch
[ Api.itemDetail env.flags model.item.id GetItemResp
, if withFile then
Ports.refreshFileView "ds-pdf-view-iframe"
else
Cmd.none
]
)
FolderDropdownMsg m -> FolderDropdownMsg m ->
let let
@ -1002,7 +1012,7 @@ update inav env msg model =
DeleteAttachResp (Ok res) -> DeleteAttachResp (Ok res) ->
if res.success then if res.success then
update inav env ReloadItem model update inav env (ReloadItem False) model
else else
resultModel model resultModel model

View File

@ -5,7 +5,7 @@
-} -}
module Data.ServerEvent exposing (AddonInfo, ServerEvent(..), decode) module Data.ServerEvent exposing (AddonInfo, JobDoneDetails, ServerEvent(..), decode, isAddonExistingItem)
import Json.Decode as D import Json.Decode as D
import Json.Decode.Pipeline as P import Json.Decode.Pipeline as P
@ -13,7 +13,7 @@ import Json.Decode.Pipeline as P
type ServerEvent type ServerEvent
= JobSubmitted String = JobSubmitted String
| JobDone String | JobDone JobDoneDetails
| JobsWaiting Int | JobsWaiting Int
| AddonInstalled AddonInfo | AddonInstalled AddonInfo
@ -26,6 +26,32 @@ type alias AddonInfo =
} }
type alias JobDoneDetails =
{ task : String
, args : Maybe D.Value
, result : Maybe D.Value
}
{-| Return wether the job done details belong to running an addon of
that item with the given id.
-}
isAddonExistingItem : String -> JobDoneDetails -> Bool
isAddonExistingItem itemId details =
let
itemIdDecoder =
D.field "itemId" D.string
-- This decodes the structure from scalas ItemAddonTaskArgs (only itemId)
decodedId =
Maybe.map (D.decodeValue itemIdDecoder) details.args
|> Maybe.andThen Result.toMaybe
in
details.task
== "addon-existing-item"
&& (itemId /= "" && decodedId == Just itemId)
addonInfoDecoder : D.Decoder AddonInfo addonInfoDecoder : D.Decoder AddonInfo
addonInfoDecoder = addonInfoDecoder =
D.succeed AddonInfo D.succeed AddonInfo
@ -51,8 +77,7 @@ decodeTag : String -> D.Decoder ServerEvent
decodeTag tag = decodeTag tag =
case tag of case tag of
"job-done" -> "job-done" ->
D.field "content" D.string D.field "content" (D.map JobDone decodeJobDoneDetails)
|> D.map JobDone
"job-submitted" -> "job-submitted" ->
D.field "content" D.string D.field "content" D.string
@ -68,3 +93,11 @@ decodeTag tag =
_ -> _ ->
D.fail ("Unknown tag: " ++ tag) D.fail ("Unknown tag: " ++ tag)
decodeJobDoneDetails : D.Decoder JobDoneDetails
decodeJobDoneDetails =
D.map3 JobDoneDetails
(D.field "task" D.string)
(D.field "args" (D.maybe D.value))
(D.field "result" (D.maybe D.value))

View File

@ -14,6 +14,7 @@ module Page exposing
, hasSidebar , hasSidebar
, href , href
, isDashboardPage , isDashboardPage
, isDetailPage
, isOpen , isOpen
, isSearchPage , isSearchPage
, isSecured , isSecured
@ -175,6 +176,16 @@ isDashboardPage page =
False False
isDetailPage : Page -> Bool
isDetailPage page =
case page of
ItemDetailPage _ ->
True
_ ->
False
pageName : Page -> String pageName : Page -> String
pageName page = pageName page =
case page of case page of

View File

@ -38,6 +38,7 @@ type Msg
| ItemResp (Result Http.Error ItemDetail) | ItemResp (Result Http.Error ItemDetail)
| ScrollResult (Result Dom.Error ()) | ScrollResult (Result Dom.Error ())
| UiSettingsUpdated | UiSettingsUpdated
| ReloadItem Bool
type alias UpdateResult = type alias UpdateResult =

View File

@ -47,6 +47,13 @@ update inav env msg model =
, selectedItems = env.selectedItems , selectedItems = env.selectedItems
} }
ReloadItem withFiles ->
let
m =
ItemDetailMsg (Comp.ItemDetail.Model.ReloadItem withFiles)
in
update inav env m model
ItemDetailMsg lmsg -> ItemDetailMsg lmsg ->
let let
result = result =

View File

@ -11,6 +11,7 @@ port module Ports exposing
, printElement , printElement
, receiveCheckQueryResult , receiveCheckQueryResult
, receiveServerEvent , receiveServerEvent
, refreshFileView
, removeAccount , removeAccount
, setAccount , setAccount
, setUiTheme , setUiTheme
@ -54,6 +55,11 @@ port printElement : String -> Cmd msg
port receiveWsMessage : (D.Value -> msg) -> Sub msg port receiveWsMessage : (D.Value -> msg) -> Sub msg
{-| Given an ID of an element that is either EMBED or IFRAME the js will reload its src
-}
port refreshFileView : String -> Cmd msg
--- Higher level functions based on ports --- Higher level functions based on ports

View File

@ -121,6 +121,16 @@ elmApp.ports.printElement.subscribe(function(id) {
} }
}); });
elmApp.ports.refreshFileView.subscribe(function(id) {
var el = document.getElementById(id);
if (el) {
var tag = el.tagName;
if (tag === "EMBED" || tag === "IFRAME") {
var url = el.src;
el.src = url;
}
}
});
var dsWebSocket = null; var dsWebSocket = null;
function closeWS() { function closeWS() {
@ -146,6 +156,8 @@ function initWS() {
} }
}); });
} }
// Websockets are not used yet for communicating to the server
// elmApp.ports.sendWsMessage.subscribe(function(msg) { // elmApp.ports.sendWsMessage.subscribe(function(msg) {
// socket.send(msg); // socket.send(msg);
// }); // });