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 (isJust)
43 import Data.Tuple.Extra (first)
44 -- import qualified Data.Map.Strict as DM
45 import Data.Map.Strict (Map, mapKeys, fromListWith)
46 --import qualified Data.Set as Set
47 import Control.Lens (makeLenses, Prism', prism', (^..), (.~), (#), to, withIndex, folded, ifolded)
48 import Control.Monad (guard)
49 import Control.Monad.Error.Class (MonadError, throwError)
51 import Data.Aeson.TH (deriveJSON)
52 import Data.Either(Either(Left))
53 import Data.Map (lookup)
54 import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
55 import Data.Swagger hiding (version, patch)
56 import Data.Text (Text)
57 import GHC.Generics (Generic)
58 import Gargantext.Core.Utils.Prefix (unPrefix)
59 import Gargantext.Database.Types.Node (NodeType(..))
60 import Gargantext.Database.Schema.Node (defaultList, HasNodeError)
61 import Gargantext.Database.Schema.Ngrams (NgramsType, NgramsTypeId, ngramsTypeId, NgramsTableData(..))
62 import qualified Gargantext.Database.Schema.Ngrams as Ngrams
63 import Gargantext.Database.Schema.NodeNgram
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 -- | TODO Check N and Weight
123 toNgramsElement :: [NgramsTableData] -> [NgramsElement]
124 toNgramsElement ns = map toNgramsElement' ns
126 toNgramsElement' (NgramsTableData _ p t _ lt w) = NgramsElement t lt' (round w) p' c'
130 Just x -> lookup x mapParent
131 c' = maybe mempty identity $ lookup t mapChildren
132 lt' = maybe (panic "API.Ngrams: listypeId") identity lt
134 mapParent :: Map Int Text
135 mapParent = fromListWith (<>) $ map (\(NgramsTableData i _ t _ _ _) -> (i,t)) ns
137 mapChildren :: Map Text (Set Text)
138 mapChildren = mapKeys (\i -> (maybe (panic "API.Ngrams.mapChildren: ParentId with no Terms: Impossible") identity $ lookup i mapParent))
140 $ map (first fromJust)
141 $ filter (isJust . fst)
142 $ map (\(NgramsTableData _ p t _ _ _) -> (p, Set.singleton t)) ns
145 instance Arbitrary NgramsTable where
148 [ NgramsElement "animal" GraphList 1 Nothing (Set.fromList ["dog", "cat"])
149 , NgramsElement "cat" GraphList 1 (Just "animal") mempty
150 , NgramsElement "cats" StopList 4 Nothing mempty
151 , NgramsElement "dog" GraphList 3 (Just "animal")(Set.fromList ["dogs"])
152 , NgramsElement "dogs" StopList 4 (Just "dog") mempty
153 , NgramsElement "fox" GraphList 1 Nothing mempty
154 , NgramsElement "object" CandidateList 2 Nothing mempty
155 , NgramsElement "nothing" StopList 4 Nothing mempty
156 , NgramsElement "organic" GraphList 3 Nothing (Set.singleton "flower")
157 , NgramsElement "flower" GraphList 3 (Just "organic") mempty
158 , NgramsElement "moon" CandidateList 1 Nothing mempty
159 , NgramsElement "sky" StopList 1 Nothing mempty
162 instance ToSchema NgramsTable
164 ------------------------------------------------------------------------
165 -- On the Client side:
166 --data Action = InGroup NgramsId NgramsId
167 -- | OutGroup NgramsId NgramsId
168 -- | SetListType NgramsId ListType
170 data PatchSet a = PatchSet
174 deriving (Eq, Ord, Show, Generic)
176 instance (Ord a, Arbitrary a) => Arbitrary (PatchSet a) where
177 arbitrary = PatchSet <$> arbitrary <*> arbitrary
179 instance ToJSON a => ToJSON (PatchSet a) where
180 toJSON = genericToJSON $ unPrefix "_"
181 toEncoding = genericToEncoding $ unPrefix "_"
183 instance (Ord a, FromJSON a) => FromJSON (PatchSet a) where
184 parseJSON = genericParseJSON $ unPrefix "_"
186 instance ToSchema a => ToSchema (PatchSet a)
188 instance ToSchema a => ToSchema (Replace a) where
189 declareNamedSchema (_ :: proxy (Replace a)) = do
190 -- TODO Keep constructor is not supported here.
191 aSchema <- declareSchemaRef (Proxy :: Proxy a)
192 return $ NamedSchema (Just "Replace") $ mempty
193 & type_ .~ SwaggerObject
195 InsOrdHashMap.fromList
199 & required .~ [ "old", "new" ]
202 NgramsPatch { _patch_children :: PatchSet NgramsTerm
203 , _patch_list :: Replace ListType -- TODO Map UserId ListType
205 deriving (Ord, Eq, Show, Generic)
206 deriveJSON (unPrefix "_") ''NgramsPatch
207 makeLenses ''NgramsPatch
209 -- instance Semigroup NgramsPatch where
211 instance ToSchema NgramsPatch
213 instance Arbitrary NgramsPatch where
214 arbitrary = NgramsPatch <$> arbitrary <*> (replace <$> arbitrary <*> arbitrary)
216 newtype NgramsTablePatch =
217 NgramsTablePatch { _ntp_ngrams_patches :: Map NgramsTerm NgramsPatch }
218 deriving (Ord, Eq, Show, Generic, Arbitrary, ToJSON, FromJSON)
219 makeLenses ''NgramsTablePatch
220 instance ToSchema NgramsTablePatch
222 -- TODO: replace by mempty once we have the Monoid instance
223 emptyNgramsTablePatch :: NgramsTablePatch
224 emptyNgramsTablePatch = NgramsTablePatch mempty
226 ------------------------------------------------------------------------
227 ------------------------------------------------------------------------
230 data Versioned a = Versioned
231 { _v_version :: Version
235 deriveJSON (unPrefix "_v_") ''Versioned
236 makeLenses ''Versioned
237 instance ToSchema a => ToSchema (Versioned a)
238 instance Arbitrary a => Arbitrary (Versioned a) where
239 arbitrary = Versioned 1 <$> arbitrary -- TODO 1 is constant so far
242 -- TODO sequencs of modifications (Patchs)
243 type NgramsIdPatch = Patch NgramsId NgramsPatch
245 ngramsPatch :: Int -> NgramsPatch
246 ngramsPatch n = NgramsPatch (DM.fromList [(1, StopList)]) (Set.fromList [n]) Set.empty
248 toEdit :: NgramsId -> NgramsPatch -> Edit NgramsId NgramsPatch
249 toEdit n p = Edit n p
250 ngramsIdPatch :: Patch NgramsId NgramsPatch
251 ngramsIdPatch = fromList $ catMaybes $ reverse [ replace (1::NgramsId) (Just $ ngramsPatch 1) Nothing
252 , replace (1::NgramsId) Nothing (Just $ ngramsPatch 2)
253 , replace (2::NgramsId) Nothing (Just $ ngramsPatch 2)
256 -- applyPatchBack :: Patch -> IO Patch
257 -- isEmptyPatch = Map.all (\x -> Set.isEmpty (add_children x) && Set.isEmpty ... )
259 ------------------------------------------------------------------------
260 ------------------------------------------------------------------------
261 ------------------------------------------------------------------------
263 type TableNgramsApiGet = Summary " Table Ngrams API Get"
264 :> QueryParam "ngramsType" TabType
265 :> QueryParam "list" ListId
266 :> QueryParam "limit" Limit
267 :> QueryParam "offset" Offset
268 :> Get '[JSON] (Versioned NgramsTable)
270 type TableNgramsApi = Summary " Table Ngrams API Change"
271 :> QueryParam "ngramsType" TabType
272 :> QueryParam "list" ListId
273 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
274 :> Put '[JSON] (Versioned NgramsTablePatch)
276 data NgramError = UnsupportedVersion
279 class HasNgramError e where
280 _NgramError :: Prism' e NgramError
282 instance HasNgramError ServantErr where
283 _NgramError = prism' make match
285 err = err500 { errBody = "NgramError: Unsupported version" }
286 make UnsupportedVersion = err
287 match e = guard (e == err) $> UnsupportedVersion
289 ngramError :: (MonadError e m, HasNgramError e) => NgramError -> m a
290 ngramError nne = throwError $ _NgramError # nne
292 -- TODO: Replace.old is ignored which means that if the current list
293 -- `GraphList` and that the patch is `Replace CandidateList StopList` then
294 -- the list is going to be `StopList` while it should keep `GraphList`.
295 -- However this should not happen in non conflicting situations.
296 mkListsUpdate :: NgramsType -> NgramsTablePatch -> [(NgramsTypeId, NgramsTerm, ListTypeId)]
297 mkListsUpdate nt patches =
298 [ (ngramsTypeId nt, ng, listTypeId lt)
299 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
300 , lt <- patch ^.. patch_list . new
303 mkChildrenGroups :: (PatchSet NgramsTerm -> Set NgramsTerm)
306 -> [(NgramsTypeId, NgramsParent, NgramsChild)]
307 mkChildrenGroups addOrRem nt patches =
308 [ (ngramsTypeId nt, parent, child)
309 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
310 , child <- patch ^.. patch_children . to addOrRem . folded
313 ngramsTypeFromTabType :: Maybe TabType -> NgramsType
314 ngramsTypeFromTabType maybeTabType =
315 let lieu = "Garg.API.Ngrams: " :: Text in
317 Nothing -> panic (lieu <> "Indicate the Table")
318 Just tab -> case tab of
319 Sources -> Ngrams.Sources
320 Authors -> Ngrams.Authors
321 Institutes -> Ngrams.Institutes
322 Terms -> Ngrams.NgramsTerms
323 _ -> panic $ lieu <> "No Ngrams for this tab"
326 -- Apply the given patch to the DB and returns the patch to be applied on the
329 -- In this perliminary version the OT aspect is missing, therefore the version
330 -- number is always 1 and the returned patch is always empty.
331 tableNgramsPatch :: (HasNgramError err, HasNodeError err)
332 => CorpusId -> Maybe TabType -> Maybe ListId
333 -> Versioned NgramsTablePatch
334 -> Cmd err (Versioned NgramsTablePatch)
335 tableNgramsPatch corpusId maybeTabType maybeList (Versioned version patch) = do
336 when (version /= 1) $ ngramError UnsupportedVersion
337 let ngramsType = ngramsTypeFromTabType maybeTabType
338 listId <- maybe (defaultList corpusId) pure maybeList
339 updateNodeNgrams $ NodeNgramsUpdate
340 { _nnu_user_list_id = listId
341 , _nnu_lists_update = mkListsUpdate ngramsType patch
342 , _nnu_rem_children = mkChildrenGroups _rem ngramsType patch
343 , _nnu_add_children = mkChildrenGroups _add ngramsType patch
345 pure $ Versioned 1 emptyNgramsTablePatch
347 -- | TODO Errors management
348 -- TODO: polymorphic for Annuaire or Corpus or ...
349 getTableNgrams :: HasNodeError err
350 => CorpusId -> Maybe TabType
351 -> Maybe ListId -> Maybe Limit -> Maybe Offset
352 -> Cmd err (Versioned NgramsTable)
353 getTableNgrams cId maybeTabType maybeListId mlimit moffset = do
354 let ngramsType = ngramsTypeFromTabType maybeTabType
355 listId <- maybe (defaultList cId) pure maybeListId
358 defaultLimit = 10 -- TODO
359 limit_ = maybe defaultLimit identity mlimit
360 offset_ = maybe 0 identity moffset
363 Ngrams.getNgramsTableDb NodeDocument ngramsType (Ngrams.NgramsTableParam listId cId) limit_ offset_
365 -- printDebug "ngramsTableDatas" ngramsTableDatas
367 pure $ Versioned 1 $ NgramsTable (toNgramsElement ngramsTableDatas)