From 889e4f4fb0dfab9d1f0ea6824b76a72ff4fd3ba6 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Mon, 1 Mar 2021 17:01:18 +0100 Subject: [PATCH] SearchMenu uses query string instead of json form --- modules/webapp/src/main/elm/Api.elm | 12 +- .../webapp/src/main/elm/Comp/SearchMenu.elm | 102 +++++---- .../src/main/elm/Data/CustomFieldChange.elm | 1 - .../webapp/src/main/elm/Data/ItemQuery.elm | 202 ++++++++++++++++++ .../webapp/src/main/elm/Page/Home/Data.elm | 8 +- .../webapp/src/main/elm/Page/Home/Update.elm | 14 +- .../webapp/src/main/elm/Page/Home/View.elm | 61 ++---- 7 files changed, 305 insertions(+), 95 deletions(-) create mode 100644 modules/webapp/src/main/elm/Data/ItemQuery.elm diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index 1064da36..30202fd6 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -1696,22 +1696,22 @@ itemIndexSearch flags query receive = } -itemSearch : Flags -> ItemSearch -> (Result Http.Error ItemLightList -> msg) -> Cmd msg +itemSearch : Flags -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg itemSearch flags search receive = Http2.authPost - { url = flags.config.baseUrl ++ "/api/v1/sec/item/searchFormWithTags" + { url = flags.config.baseUrl ++ "/api/v1/sec/item/search" , account = getAccount flags - , body = Http.jsonBody (Api.Model.ItemSearch.encode search) + , body = Http.jsonBody (Api.Model.ItemQuery.encode search) , expect = Http.expectJson receive Api.Model.ItemLightList.decoder } -itemSearchStats : Flags -> ItemSearch -> (Result Http.Error SearchStats -> msg) -> Cmd msg +itemSearchStats : Flags -> ItemQuery -> (Result Http.Error SearchStats -> msg) -> Cmd msg itemSearchStats flags search receive = Http2.authPost - { url = flags.config.baseUrl ++ "/api/v1/sec/item/searchFormStats" + { url = flags.config.baseUrl ++ "/api/v1/sec/item/searchStats" , account = getAccount flags - , body = Http.jsonBody (Api.Model.ItemSearch.encode search) + , body = Http.jsonBody (Api.Model.ItemQuery.encode search) , expect = Http.expectJson receive Api.Model.SearchStats.decoder } diff --git a/modules/webapp/src/main/elm/Comp/SearchMenu.elm b/modules/webapp/src/main/elm/Comp/SearchMenu.elm index 5a88fee6..1cc34890 100644 --- a/modules/webapp/src/main/elm/Comp/SearchMenu.elm +++ b/modules/webapp/src/main/elm/Comp/SearchMenu.elm @@ -3,7 +3,7 @@ module Comp.SearchMenu exposing , Msg(..) , NextState , TextSearchModel - , getItemSearch + , getItemQuery , init , isFulltextSearch , isNamesSearch @@ -21,7 +21,7 @@ import Api.Model.EquipmentList exposing (EquipmentList) import Api.Model.FolderStats exposing (FolderStats) import Api.Model.IdName exposing (IdName) import Api.Model.ItemFieldValue exposing (ItemFieldValue) -import Api.Model.ItemSearch exposing (ItemSearch) +import Api.Model.ItemQuery exposing (ItemQuery) import Api.Model.PersonList exposing (PersonList) import Api.Model.ReferenceList exposing (ReferenceList) import Api.Model.SearchStats exposing (SearchStats) @@ -38,6 +38,7 @@ import Data.DropdownStyle as DS import Data.Fields import Data.Flags exposing (Flags) import Data.Icons as Icons +import Data.ItemQuery as Q exposing (ItemQuery) import Data.PersonUse import Data.UiSettings exposing (UiSettings) import DatePicker exposing (DatePicker) @@ -234,11 +235,21 @@ getDirection model = Nothing -getItemSearch : Model -> ItemSearch -getItemSearch model = +getItemQuery : Model -> Maybe ItemQuery +getItemQuery model = let - e = - Api.Model.ItemSearch.empty + when flag body = + if flag then + Just body + + else + Nothing + + whenNot flag body = + when (not flag) body + + whenNotEmpty list f = + whenNot (List.isEmpty list) (f list) amendWildcards s = if String.startsWith "\"" s && String.endsWith "\"" s then @@ -254,35 +265,52 @@ getItemSearch model = textSearch = textSearchValue model.textSearchModel in - { e - | tagsInclude = model.tagSelection.includeTags |> List.map .tag |> List.map .id - , tagsExclude = model.tagSelection.excludeTags |> List.map .tag |> List.map .id - , corrPerson = Comp.Dropdown.getSelected model.corrPersonModel |> List.map .id |> List.head - , corrOrg = Comp.Dropdown.getSelected model.orgModel |> List.map .id |> List.head - , concPerson = Comp.Dropdown.getSelected model.concPersonModel |> List.map .id |> List.head - , concEquip = Comp.Dropdown.getSelected model.concEquipmentModel |> List.map .id |> List.head - , folder = model.selectedFolder |> Maybe.map .id - , direction = - Comp.Dropdown.getSelected model.directionModel - |> List.head - |> Maybe.map Data.Direction.toString - , inbox = model.inboxCheckbox - , dateFrom = model.fromDate - , dateUntil = model.untilDate - , dueDateFrom = model.fromDueDate - , dueDateUntil = model.untilDueDate - , name = - model.nameModel - |> Maybe.map amendWildcards - , allNames = - textSearch.nameSearch - |> Maybe.map amendWildcards - , fullText = textSearch.fullText - , tagCategoriesInclude = model.tagSelection.includeCats |> List.map .name - , tagCategoriesExclude = model.tagSelection.excludeCats |> List.map .name - , customValues = Data.CustomFieldChange.toFieldValues model.customValues - , source = model.sourceModel - } + Q.and + [ when model.inboxCheckbox (Q.Inbox True) + , whenNotEmpty (model.tagSelection.includeTags |> List.map (.tag >> .id)) + (Q.TagIds Q.AllMatch) + , whenNotEmpty (model.tagSelection.excludeTags |> List.map (.tag >> .id)) + (\ids -> Q.Not (Q.TagIds Q.AnyMatch ids)) + , whenNotEmpty (model.tagSelection.includeCats |> List.map .name) + (Q.CatNames Q.AllMatch) + , whenNotEmpty (model.tagSelection.excludeCats |> List.map .name) + (\ids -> Q.Not <| Q.CatNames Q.AnyMatch ids) + , model.selectedFolder |> Maybe.map .id |> Maybe.map (Q.FolderId Q.Eq) + , Comp.Dropdown.getSelected model.orgModel + |> List.map .id + |> List.head + |> Maybe.map (Q.CorrOrgId Q.Eq) + , Comp.Dropdown.getSelected model.corrPersonModel + |> List.map .id + |> List.head + |> Maybe.map (Q.CorrPersId Q.Eq) + , Comp.Dropdown.getSelected model.concPersonModel + |> List.map .id + |> List.head + |> Maybe.map (Q.ConcPersId Q.Eq) + , Comp.Dropdown.getSelected model.concEquipmentModel + |> List.map .id + |> List.head + |> Maybe.map (Q.ConcEquipId Q.Eq) + , whenNotEmpty (Data.CustomFieldChange.toFieldValues model.customValues) + (List.map (Q.CustomField Q.Like) >> Q.And) + , Maybe.map (Q.DateMs Q.Gte) model.fromDate + , Maybe.map (Q.DateMs Q.Lte) model.untilDate + , Maybe.map (Q.DueDateMs Q.Gte) model.fromDueDate + , Maybe.map (Q.DueDateMs Q.Lte) model.untilDueDate + , Maybe.map (Q.Source Q.Like) model.sourceModel + , model.nameModel + |> Maybe.map amendWildcards + |> Maybe.map (Q.ItemName Q.Like) + , textSearch.nameSearch + |> Maybe.map amendWildcards + |> Maybe.map Q.AllNames + , Comp.Dropdown.getSelected model.directionModel + |> List.head + |> Maybe.map Q.Dir + , textSearch.fullText + |> Maybe.map Q.Contents + ] resetModel : Model -> Model @@ -437,7 +465,7 @@ updateDrop ddm flags settings msg model = { model = mdp , cmd = Cmd.batch - [ Api.itemSearchStats flags Api.Model.ItemSearch.empty GetAllTagsResp + [ Api.itemSearchStats flags Api.Model.ItemQuery.empty GetAllTagsResp , Api.getOrgLight flags GetOrgResp , Api.getEquipments flags "" GetEquipResp , Api.getPersons flags "" GetPersonResp @@ -450,7 +478,7 @@ updateDrop ddm flags settings msg model = ResetForm -> { model = resetModel model - , cmd = Api.itemSearchStats flags Api.Model.ItemSearch.empty GetAllTagsResp + , cmd = Api.itemSearchStats flags Api.Model.ItemQuery.empty GetAllTagsResp , stateChange = True , dragDrop = DD.DragDropData ddm Nothing } diff --git a/modules/webapp/src/main/elm/Data/CustomFieldChange.elm b/modules/webapp/src/main/elm/Data/CustomFieldChange.elm index 04f69b1c..2ee75c02 100644 --- a/modules/webapp/src/main/elm/Data/CustomFieldChange.elm +++ b/modules/webapp/src/main/elm/Data/CustomFieldChange.elm @@ -10,7 +10,6 @@ module Data.CustomFieldChange exposing import Api.Model.CustomField exposing (CustomField) import Api.Model.CustomFieldValue exposing (CustomFieldValue) -import Api.Model.ItemFieldValue exposing (ItemFieldValue) import Dict exposing (Dict) diff --git a/modules/webapp/src/main/elm/Data/ItemQuery.elm b/modules/webapp/src/main/elm/Data/ItemQuery.elm new file mode 100644 index 00000000..ab886b3f --- /dev/null +++ b/modules/webapp/src/main/elm/Data/ItemQuery.elm @@ -0,0 +1,202 @@ +module Data.ItemQuery exposing + ( AttrMatch(..) + , ItemQuery(..) + , TagMatch(..) + , and + , render + , renderMaybe + , request + ) + +{-| Models the query language for the purpose of generating a query string. +-} + +import Api.Model.CustomFieldValue exposing (CustomFieldValue) +import Api.Model.ItemQuery as RQ +import Data.Direction exposing (Direction) + + +type TagMatch + = AnyMatch + | AllMatch + + +type AttrMatch + = Eq + | Neq + | Lt + | Gt + | Lte + | Gte + | Like + + +type ItemQuery + = Inbox Bool + | And (List ItemQuery) + | Or (List ItemQuery) + | Not ItemQuery + | TagIds TagMatch (List String) + | CatNames TagMatch (List String) + | FolderId AttrMatch String + | CorrOrgId AttrMatch String + | CorrPersId AttrMatch String + | ConcPersId AttrMatch String + | ConcEquipId AttrMatch String + | CustomField AttrMatch CustomFieldValue + | DateMs AttrMatch Int + | DueDateMs AttrMatch Int + | Source AttrMatch String + | Dir Direction + | ItemIdIn (List String) + | ItemName AttrMatch String + | AllNames String + | Contents String + + +and : List (Maybe ItemQuery) -> Maybe ItemQuery +and list = + case List.filterMap identity list of + [] -> + Nothing + + es -> + Just (And es) + + +request : Maybe ItemQuery -> RQ.ItemQuery +request mq = + { offset = Nothing + , limit = Nothing + , withDetails = Just True + , query = renderMaybe mq + } + + +renderMaybe : Maybe ItemQuery -> String +renderMaybe mq = + Maybe.map render mq + |> Maybe.withDefault "" + + +render : ItemQuery -> String +render q = + let + boolStr flag = + if flag then + "yes" + + else + "no" + + between left right str = + left ++ str ++ right + + surround lr str = + between lr lr str + + tagMatchStr tm = + case tm of + AnyMatch -> + ":" + + AllMatch -> + "=" + + quoteStr = + --TODO escape quotes + surround "\"" + in + case q of + And inner -> + List.map render inner + |> String.join " " + |> between "(& " " )" + + Or inner -> + List.map render inner + |> String.join " " + |> between "(| " " )" + + Not inner -> + "!" ++ render inner + + Inbox flag -> + "inbox:" ++ boolStr flag + + TagIds m ids -> + List.map quoteStr ids + |> String.join "," + |> between ("tag.id" ++ tagMatchStr m) "" + + CatNames m ids -> + List.map quoteStr ids + |> String.join "," + |> between ("cat" ++ tagMatchStr m) "" + + FolderId m id -> + "folder.id" ++ attrMatch m ++ quoteStr id + + CorrOrgId m id -> + "correspondent.org.id" ++ attrMatch m ++ quoteStr id + + CorrPersId m id -> + "correspondent.person.id" ++ attrMatch m ++ quoteStr id + + ConcPersId m id -> + "concerning.person.id" ++ attrMatch m ++ quoteStr id + + ConcEquipId m id -> + "concerning.equip.id" ++ attrMatch m ++ quoteStr id + + CustomField m kv -> + "f:" ++ kv.field ++ attrMatch m ++ quoteStr kv.value + + DateMs m ms -> + "date" ++ attrMatch m ++ "ms" ++ String.fromInt ms + + DueDateMs m ms -> + "due" ++ attrMatch m ++ "ms" ++ String.fromInt ms + + Source m str -> + "source" ++ attrMatch m ++ quoteStr str + + Dir dir -> + "incoming:" ++ boolStr (dir == Data.Direction.Incoming) + + ItemIdIn ids -> + "id~=" ++ String.join "," ids + + ItemName m str -> + "name" ++ attrMatch m ++ quoteStr str + + AllNames str -> + "$names:" ++ quoteStr str + + Contents str -> + "content:" ++ quoteStr str + + +attrMatch : AttrMatch -> String +attrMatch am = + case am of + Eq -> + "=" + + Neq -> + "!=" + + Like -> + ":" + + Gt -> + ">" + + Gte -> + ">=" + + Lt -> + "<" + + Lte -> + "<=" diff --git a/modules/webapp/src/main/elm/Page/Home/Data.elm b/modules/webapp/src/main/elm/Page/Home/Data.elm index b0bf156c..1e13d309 100644 --- a/modules/webapp/src/main/elm/Page/Home/Data.elm +++ b/modules/webapp/src/main/elm/Page/Home/Data.elm @@ -31,6 +31,7 @@ import Comp.SearchMenu import Comp.YesNoDimmer import Data.Flags exposing (Flags) import Data.ItemNav exposing (ItemNav) +import Data.ItemQuery as Q import Data.Items import Data.UiSettings exposing (UiSettings) import Http @@ -239,12 +240,13 @@ doSearchDefaultCmd : SearchParam -> Model -> Cmd Msg doSearchDefaultCmd param model = let smask = - Comp.SearchMenu.getItemSearch model.searchMenuModel + Q.request + (Comp.SearchMenu.getItemQuery model.searchMenuModel) mask = { smask - | limit = param.pageSize - , offset = param.offset + | limit = Just param.pageSize + , offset = Just param.offset } in if param.offset == 0 then diff --git a/modules/webapp/src/main/elm/Page/Home/Update.elm b/modules/webapp/src/main/elm/Page/Home/Update.elm index a2cddb7f..5a7e7594 100644 --- a/modules/webapp/src/main/elm/Page/Home/Update.elm +++ b/modules/webapp/src/main/elm/Page/Home/Update.elm @@ -3,7 +3,7 @@ module Page.Home.Update exposing (update) import Api import Api.Model.IdList exposing (IdList) import Api.Model.ItemLightList exposing (ItemLightList) -import Api.Model.ItemSearch +import Api.Model.ItemQuery import Browser.Navigation as Nav import Comp.FixedDropdown import Comp.ItemCardList @@ -13,6 +13,7 @@ import Comp.LinkTarget exposing (LinkTarget) import Comp.SearchMenu import Comp.YesNoDimmer import Data.Flags exposing (Flags) +import Data.ItemQuery as Q import Data.ItemSelection import Data.Items import Data.UiSettings exposing (UiSettings) @@ -648,16 +649,15 @@ loadChangedItems flags ids = else let - searchInit = - Api.Model.ItemSearch.empty - idList = - IdList (Set.toList ids) + Set.toList ids + + searchInit = + Q.request (Just <| Q.ItemIdIn idList) search = { searchInit - | itemSubset = Just idList - , limit = Set.size ids + | limit = Just <| Set.size ids } in Api.itemSearch flags search ReplaceChangedItemsResp diff --git a/modules/webapp/src/main/elm/Page/Home/View.elm b/modules/webapp/src/main/elm/Page/Home/View.elm index f4d8124d..c12a31c9 100644 --- a/modules/webapp/src/main/elm/Page/Home/View.elm +++ b/modules/webapp/src/main/elm/Page/Home/View.elm @@ -320,8 +320,9 @@ viewSearchBar flags model = [ a [ classList [ ( "search-menu-toggle ui icon button", True ) - , ( "primary", not (searchMenuFilled model) ) - , ( "secondary", searchMenuFilled model ) + + -- , ( "primary", not (searchMenuFilled model) ) + -- , ( "secondary", searchMenuFilled model ) ] , onClick ToggleSearchMenu , href "#" @@ -332,24 +333,23 @@ viewSearchBar flags model = , div [ class "right menu" ] [ div [ class "fitted item" ] [ div [ class "ui left icon right action input" ] - [ i - [ classList - [ ( "search link icon", not model.searchInProgress ) - , ( "loading spinner icon", model.searchInProgress ) - ] - , href "#" - , onClick (DoSearch model.searchTypeDropdownValue) - ] - (if hasMoreSearch model then - [ i [ class "icons search-corner-icons" ] - [ i [ class "tiny blue circle icon" ] [] - ] - ] - - else - [] - ) - , input + [ -- i + -- [ classList + -- [ ( "search link icon", not model.searchInProgress ) + -- , ( "loading spinner icon", model.searchInProgress ) + -- ] + -- , href "#" + -- , onClick (DoSearch model.searchTypeDropdownValue) + -- ] + -- (if hasMoreSearch model then + -- [ i [ class "icons search-corner-icons" ] + -- [ i [ class "tiny blue circle icon" ] [] + -- ] + -- ] + -- else + -- [] + -- ) + input [ type_ "text" , placeholder (case model.searchTypeDropdownValue of @@ -384,27 +384,6 @@ viewSearchBar flags model = ] -searchMenuFilled : Model -> Bool -searchMenuFilled model = - let - is = - Comp.SearchMenu.getItemSearch model.searchMenuModel - in - is /= Api.Model.ItemSearch.empty - - -hasMoreSearch : Model -> Bool -hasMoreSearch model = - let - is = - Comp.SearchMenu.getItemSearch model.searchMenuModel - - is_ = - { is | allNames = Nothing, fullText = Nothing } - in - is_ /= Api.Model.ItemSearch.empty - - deleteAllDimmer : Comp.YesNoDimmer.Settings deleteAllDimmer = { message = "Really delete all selected items?"