2 Module : Gargantext.API.Ngrams
3 Description : Server API
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
13 get ngrams filtered by NgramsType
18 {-# LANGUAGE DataKinds #-}
19 {-# LANGUAGE DeriveGeneric #-}
20 {-# LANGUAGE NoImplicitPrelude #-}
21 {-# LANGUAGE OverloadedStrings #-}
22 {-# LANGUAGE ScopedTypeVariables #-}
23 {-# LANGUAGE TemplateHaskell #-}
24 {-# LANGUAGE TypeOperators #-}
25 {-# LANGUAGE FlexibleInstances #-}
26 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
27 {-# LANGUAGE RankNTypes #-}
28 {-# OPTIONS -fno-warn-orphans #-}
30 module Gargantext.API.Ngrams
33 import Prelude (round)
34 -- import Gargantext.Database.Schema.User (UserId)
35 import Data.Functor (($>))
36 import Data.Patch.Class (Replace, replace, new)
37 --import qualified Data.Map.Strict.Patch as PM
39 --import Data.Semigroup
41 import qualified Data.Set as Set
42 --import Data.Maybe (catMaybes)
43 -- import qualified Data.Map.Strict as DM
44 import Data.Map.Strict (Map)
45 --import qualified Data.Set as Set
46 import Control.Lens (makeLenses, Prism', prism', (^..), (.~), (#), to, withIndex, folded, ifolded)
47 import Control.Monad (guard)
48 import Control.Monad.Error.Class (MonadError, throwError)
50 import Data.Aeson.TH (deriveJSON)
51 import Data.Either(Either(Left))
52 import Data.Map (lookup)
53 import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
54 import Data.Swagger hiding (version, patch)
55 import Data.Text (Text)
56 import GHC.Generics (Generic)
57 import Gargantext.Core.Utils.Prefix (unPrefix)
58 import Gargantext.Database.Types.Node (NodeType(..))
59 import Gargantext.Database.Schema.Node (defaultList, HasNodeError)
60 import Gargantext.Database.Schema.Ngrams (NgramsType, NgramsTypeId, ngramsTypeId)
61 import qualified Gargantext.Database.Schema.Ngrams as Ngrams
62 import Gargantext.Database.Schema.NodeNgram
63 import Gargantext.Database.Schema.NodeNgramsNgrams
64 import Gargantext.Database.Utils (Cmd)
65 import Gargantext.Prelude
66 import Gargantext.Core.Types (ListType(..), ListTypeId, ListId, CorpusId, Limit, Offset, listTypeId)
67 import Prelude (Enum, Bounded, minBound, maxBound)
68 import Servant hiding (Patch)
69 import Test.QuickCheck (elements)
70 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
72 ------------------------------------------------------------------------
73 --data FacetFormat = Table | Chart
74 data TabType = Docs | Terms | Sources | Authors | Institutes | Trash
76 deriving (Generic, Enum, Bounded)
78 instance FromHttpApiData TabType
80 parseUrlPiece "Docs" = pure Docs
81 parseUrlPiece "Terms" = pure Terms
82 parseUrlPiece "Sources" = pure Sources
83 parseUrlPiece "Institutes" = pure Institutes
84 parseUrlPiece "Authors" = pure Authors
85 parseUrlPiece "Trash" = pure Trash
87 parseUrlPiece "Contacts" = pure Contacts
89 parseUrlPiece _ = Left "Unexpected value of TabType"
91 instance ToParamSchema TabType
92 instance ToJSON TabType
93 instance FromJSON TabType
94 instance ToSchema TabType
95 instance Arbitrary TabType
97 arbitrary = elements [minBound .. maxBound]
99 ------------------------------------------------------------------------
100 type NgramsTerm = Text
103 NgramsElement { _ne_ngrams :: NgramsTerm
104 , _ne_list :: ListType
105 , _ne_occurrences :: Int
106 , _ne_parent :: Maybe NgramsTerm
107 , _ne_children :: Set NgramsTerm
109 deriving (Ord, Eq, Show, Generic)
111 deriveJSON (unPrefix "_ne_") ''NgramsElement
112 makeLenses ''NgramsElement
114 instance ToSchema NgramsElement
115 instance Arbitrary NgramsElement where
116 arbitrary = elements [NgramsElement "sport" GraphList 1 Nothing mempty]
118 ------------------------------------------------------------------------
119 newtype NgramsTable = NgramsTable { _ngramsTable :: [NgramsElement] }
120 deriving (Ord, Eq, Generic, ToJSON, FromJSON, Show)
122 instance Arbitrary NgramsTable where
125 [ NgramsElement "animal" GraphList 1 Nothing (Set.fromList ["dog", "cat"])
126 , NgramsElement "cat" GraphList 1 (Just "animal") mempty
127 , NgramsElement "cats" StopList 4 Nothing mempty
128 , NgramsElement "dog" GraphList 3 (Just "animal")(Set.fromList ["dogs"])
129 , NgramsElement "dogs" StopList 4 (Just "dog") mempty
130 , NgramsElement "fox" GraphList 1 Nothing mempty
131 , NgramsElement "object" CandidateList 2 Nothing mempty
132 , NgramsElement "nothing" StopList 4 Nothing mempty
133 , NgramsElement "organic" GraphList 3 Nothing (Set.singleton "flower")
134 , NgramsElement "flower" GraphList 3 (Just "organic") mempty
135 , NgramsElement "moon" CandidateList 1 Nothing mempty
136 , NgramsElement "sky" StopList 1 Nothing mempty
139 instance ToSchema NgramsTable
141 ------------------------------------------------------------------------
142 -- On the Client side:
143 --data Action = InGroup NgramsId NgramsId
144 -- | OutGroup NgramsId NgramsId
145 -- | SetListType NgramsId ListType
147 data PatchSet a = PatchSet
151 deriving (Eq, Ord, Show, Generic)
153 instance (Ord a, Arbitrary a) => Arbitrary (PatchSet a) where
154 arbitrary = PatchSet <$> arbitrary <*> arbitrary
156 instance ToJSON a => ToJSON (PatchSet a) where
157 toJSON = genericToJSON $ unPrefix "_"
158 toEncoding = genericToEncoding $ unPrefix "_"
160 instance (Ord a, FromJSON a) => FromJSON (PatchSet a) where
161 parseJSON = genericParseJSON $ unPrefix "_"
163 instance ToSchema a => ToSchema (PatchSet a)
165 instance ToSchema a => ToSchema (Replace a) where
166 declareNamedSchema (_ :: proxy (Replace a)) = do
167 -- TODO Keep constructor is not supported here.
168 aSchema <- declareSchemaRef (Proxy :: Proxy a)
169 return $ NamedSchema (Just "Replace") $ mempty
170 & type_ .~ SwaggerObject
172 InsOrdHashMap.fromList
176 & required .~ [ "old", "new" ]
179 NgramsPatch { _patch_children :: PatchSet NgramsTerm
180 , _patch_list :: Replace ListType -- TODO Map UserId ListType
182 deriving (Ord, Eq, Show, Generic)
183 deriveJSON (unPrefix "_") ''NgramsPatch
184 makeLenses ''NgramsPatch
186 -- instance Semigroup NgramsPatch where
188 instance ToSchema NgramsPatch
190 instance Arbitrary NgramsPatch where
191 arbitrary = NgramsPatch <$> arbitrary <*> (replace <$> arbitrary <*> arbitrary)
193 newtype NgramsTablePatch =
194 NgramsTablePatch { _ntp_ngrams_patches :: Map NgramsTerm NgramsPatch }
195 deriving (Ord, Eq, Show, Generic, Arbitrary, ToJSON, FromJSON)
196 makeLenses ''NgramsTablePatch
197 instance ToSchema NgramsTablePatch
199 -- TODO: replace by mempty once we have the Monoid instance
200 emptyNgramsTablePatch :: NgramsTablePatch
201 emptyNgramsTablePatch = NgramsTablePatch mempty
203 ------------------------------------------------------------------------
204 ------------------------------------------------------------------------
207 data Versioned a = Versioned
208 { _v_version :: Version
212 deriveJSON (unPrefix "_v_") ''Versioned
213 makeLenses ''Versioned
214 instance ToSchema a => ToSchema (Versioned a)
215 instance Arbitrary a => Arbitrary (Versioned a) where
216 arbitrary = Versioned 1 <$> arbitrary -- TODO 1 is constant so far
219 -- TODO sequencs of modifications (Patchs)
220 type NgramsIdPatch = Patch NgramsId NgramsPatch
222 ngramsPatch :: Int -> NgramsPatch
223 ngramsPatch n = NgramsPatch (DM.fromList [(1, StopList)]) (Set.fromList [n]) Set.empty
225 toEdit :: NgramsId -> NgramsPatch -> Edit NgramsId NgramsPatch
226 toEdit n p = Edit n p
227 ngramsIdPatch :: Patch NgramsId NgramsPatch
228 ngramsIdPatch = fromList $ catMaybes $ reverse [ replace (1::NgramsId) (Just $ ngramsPatch 1) Nothing
229 , replace (1::NgramsId) Nothing (Just $ ngramsPatch 2)
230 , replace (2::NgramsId) Nothing (Just $ ngramsPatch 2)
233 -- applyPatchBack :: Patch -> IO Patch
234 -- isEmptyPatch = Map.all (\x -> Set.isEmpty (add_children x) && Set.isEmpty ... )
236 ------------------------------------------------------------------------
237 ------------------------------------------------------------------------
238 ------------------------------------------------------------------------
240 type TableNgramsApiGet = Summary " Table Ngrams API Get"
241 :> QueryParam "ngramsType" TabType
242 :> QueryParam "list" ListId
243 :> QueryParam "limit" Limit
244 :> QueryParam "offset" Offset
245 :> Get '[JSON] (Versioned NgramsTable)
247 type TableNgramsApi = Summary " Table Ngrams API Change"
248 :> QueryParam "ngramsType" TabType
249 :> QueryParam "list" ListId
250 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
251 :> Put '[JSON] (Versioned NgramsTablePatch)
253 data NgramError = UnsupportedVersion
256 class HasNgramError e where
257 _NgramError :: Prism' e NgramError
259 instance HasNgramError ServantErr where
260 _NgramError = prism' make match
262 err = err500 { errBody = "NgramError: Unsupported version" }
263 make UnsupportedVersion = err
264 match e = guard (e == err) $> UnsupportedVersion
266 ngramError :: (MonadError e m, HasNgramError e) => NgramError -> m a
267 ngramError nne = throwError $ _NgramError # nne
269 -- TODO: Replace.old is ignored which means that if the current list
270 -- `GraphList` and that the patch is `Replace CandidateList StopList` then
271 -- the list is going to be `StopList` while it should keep `GraphList`.
272 -- However this should not happen in non conflicting situations.
273 mkListsUpdate :: ListId -> NgramsType -> NgramsTablePatch -> [(ListId, NgramsTypeId, NgramsTerm, ListTypeId)]
274 mkListsUpdate lId nt patches =
275 [ (lId, ngramsTypeId nt, ng, listTypeId lt)
276 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
277 , lt <- patch ^.. patch_list . new
280 mkChildrenGroups :: ListId
281 -> (PatchSet NgramsTerm -> Set NgramsTerm)
283 -> [(ListId, NgramsParent, NgramsChild, Maybe Double)]
284 mkChildrenGroups lId addOrRem patches =
285 [ (lId, parent, child, Just 1)
286 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
287 , child <- patch ^.. patch_children . to addOrRem . folded
290 ngramsTypeFromTabType :: Maybe TabType -> NgramsType
291 ngramsTypeFromTabType maybeTabType =
292 let lieu = "Garg.API.Ngrams: " :: Text in
294 Nothing -> Ngrams.Sources -- panic (lieu <> "Indicate the Table")
295 Just tab -> case tab of
296 Sources -> Ngrams.Sources
297 Authors -> Ngrams.Authors
298 Institutes -> Ngrams.Institutes
299 Terms -> Ngrams.NgramsTerms
300 _ -> panic $ lieu <> "No Ngrams for this tab"
303 -- Apply the given patch to the DB and returns the patch to be applied on the
306 -- In this perliminary version the OT aspect is missing, therefore the version
307 -- number is always 1 and the returned patch is always empty.
308 tableNgramsPatch :: (HasNgramError err, HasNodeError err)
309 => CorpusId -> Maybe TabType -> Maybe ListId
310 -> Versioned NgramsTablePatch
311 -> Cmd err (Versioned NgramsTablePatch)
312 tableNgramsPatch corpusId maybeTabType maybeList (Versioned version patch) = do
313 when (version /= 1) $ ngramError UnsupportedVersion
314 let ngramsType = ngramsTypeFromTabType maybeTabType
315 listId <- maybe (defaultList corpusId) pure maybeList
316 updateNodeNgrams $ NodeNgramsUpdate
317 { _nnu_lists_update = mkListsUpdate listId ngramsType patch
318 , _nnu_rem_children = mkChildrenGroups listId _rem patch
319 , _nnu_add_children = mkChildrenGroups listId _add patch
321 pure $ Versioned 1 emptyNgramsTablePatch
323 -- | TODO Errors management
324 -- TODO: polymorphic for Annuaire or Corpus or ...
325 getTableNgrams :: HasNodeError err
326 => CorpusId -> Maybe TabType
327 -> Maybe ListId -> Maybe Limit -> Maybe Offset
328 -> Cmd err (Versioned NgramsTable)
329 getTableNgrams cId maybeTabType maybeListId mlimit moffset = do
330 let lieu = "Garg.API.Ngrams: " :: Text
331 let ngramsType = ngramsTypeFromTabType maybeTabType
332 listId <- maybe (defaultList cId) pure maybeListId
335 defaultLimit = 10 -- TODO
336 limit_ = maybe defaultLimit identity mlimit
337 offset_ = maybe 0 identity moffset
339 (ngramsTableDatas, mapToParent, mapToChildren) <-
340 Ngrams.getNgramsTableDb NodeDocument ngramsType (Ngrams.NgramsTableParam listId cId) limit_ offset_
342 -- printDebug "ngramsTableDatas" ngramsTableDatas
345 NgramsTable $ map (\(Ngrams.NgramsTableData ngs _ lt w) ->
347 (maybe (panic $ lieu <> "listType") identity lt)
349 (lookup ngs mapToParent)
350 (maybe mempty identity $ lookup ngs mapToChildren)