2 Module : Gargantext.Core.Viz.Graph
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
12 {-# LANGUAGE BangPatterns #-}
13 {-# LANGUAGE OverloadedLists #-} -- allows to write Map and HashMap as lists
14 {-# LANGUAGE TypeOperators #-}
16 module Gargantext.Core.Viz.Graph.API
19 import Control.Lens (set, (^.), _Just, (^?), at)
21 import Data.Maybe (fromMaybe)
23 import Data.Text hiding (head)
24 import Debug.Trace (trace)
25 import GHC.Generics (Generic)
26 import Gargantext.API.Admin.EnvTypes (GargJob(..), Env)
27 import Gargantext.API.Admin.Orchestrator.Types
28 import Gargantext.API.Ngrams.Tools
29 import Gargantext.API.Prelude
30 import Gargantext.Core.Methods.Similarities (Similarity(..), GraphMetric(..), withMetric)
31 import Gargantext.Core.NodeStory
32 import Gargantext.Core.Types.Main
33 import Gargantext.Core.Viz.Graph
34 import Gargantext.Core.Viz.Graph.GEXF ()
35 import Gargantext.Core.Viz.Graph.Tools -- (cooc2graph)
36 import Gargantext.Database.Action.Metrics.NgramsByContext (getContextsByNgramsOnlyUser)
37 import Gargantext.Database.Action.Flow.Types (FlowCmdM)
38 import Gargantext.Database.Action.Node (mkNodeWithParent)
39 import Gargantext.Database.Admin.Config
40 import Gargantext.Database.Admin.Types.Node
41 import Gargantext.Database.Prelude (Cmd)
42 import Gargantext.Database.Query.Table.Node
43 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
44 import Gargantext.Database.Query.Table.Node.Select
45 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
46 import Gargantext.Database.Query.Table.Node.User (getNodeUser)
47 import Gargantext.Database.Schema.Node
48 import Gargantext.Database.Schema.Ngrams
49 import Gargantext.Prelude
50 import Gargantext.Utils.Jobs (serveJobsAPI)
52 import Servant.Job.Async (AsyncJobsAPI)
54 import qualified Data.HashMap.Strict as HashMap
56 ------------------------------------------------------------------------
57 -- | There is no Delete specific API for Graph since it can be deleted
59 type GraphAPI = Get '[JSON] HyperdataGraphAPI
60 :<|> "async" :> GraphAsyncAPI
62 :> ReqBody '[JSON] HyperdataGraphAPI
63 :> Post '[JSON] NodeId
64 :<|> "gexf" :> Get '[XML] (Headers '[Servant.Header "Content-Disposition" Text] Graph)
65 :<|> "versions" :> GraphVersionsAPI
68 GraphVersions { gv_graph :: Maybe Int
71 deriving (Show, Generic)
73 instance FromJSON GraphVersions
74 instance ToJSON GraphVersions
75 instance ToSchema GraphVersions
77 graphAPI :: UserId -> NodeId -> ServerT GraphAPI (GargM Env GargError)
78 graphAPI u n = getGraph u n
82 :<|> graphVersionsAPI u n
84 ------------------------------------------------------------------------
85 --getGraph :: UserId -> NodeId -> GargServer HyperdataGraphAPI
86 getGraph :: FlowCmdM env err m
89 -> m HyperdataGraphAPI
90 getGraph _uId nId = do
91 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
94 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
95 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
97 mcId <- getClosestParentIdByType nId NodeCorpus
98 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
100 -- printDebug "[getGraph] getting list for cId" cId
101 listId <- defaultList cId
102 repo <- getRepo [listId]
104 -- TODO Similarity in Graph params
107 let defaultMetric = Order1
108 let defaultPartitionMethod = Spinglass
109 let defaultEdgesStrength = Strong
110 graph' <- computeGraph cId defaultPartitionMethod (withMetric defaultMetric) defaultEdgesStrength (NgramsTerms, NgramsTerms) repo
111 mt <- defaultGraphMetadata cId "Title" repo defaultMetric defaultEdgesStrength
113 graph'' = set graph_metadata (Just mt) graph'
114 hg = HyperdataGraphAPI graph'' camera
115 -- _ <- updateHyperdata nId hg
116 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
117 pure $ trace "[G.V.G.API] Graph empty, computing" hg
119 Just graph' -> pure $ trace "[G.V.G.API] Graph exists, returning" $
120 HyperdataGraphAPI graph' camera
123 --recomputeGraph :: UserId -> NodeId -> Maybe GraphMetric -> GargNoServer Graph
124 recomputeGraph :: FlowCmdM env err m
134 recomputeGraph _uId nId method maybeSimilarity maybeStrength nt1 nt2 force = do
135 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
137 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
138 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
139 graphMetadata = graph ^? _Just . graph_metadata . _Just
140 listVersion = graph ^? _Just . graph_metadata . _Just . gm_list . lfg_version
141 graphMetric = case maybeSimilarity of
142 Nothing -> graph ^? _Just . graph_metadata . _Just . gm_metric
144 similarity = case graphMetric of
145 Nothing -> withMetric Order1
146 Just m -> withMetric m
148 strength = case maybeStrength of
149 Nothing -> case graph ^? _Just . graph_metadata . _Just . gm_edgesStrength of
151 Just mr -> fromMaybe Strong mr
154 mcId <- getClosestParentIdByType nId NodeCorpus
155 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
157 listId <- defaultList cId
158 repo <- getRepo [listId]
159 let v = repo ^. unNodeStory . at listId . _Just . a_version
162 !g <- computeGraph cId method similarity strength (nt1,nt2) repo
163 let g' = set graph_metadata mt g
164 _nentries <- updateHyperdata nId (HyperdataGraph (Just g') camera)
169 mt <- defaultGraphMetadata cId "Title" repo (fromMaybe Order1 maybeSimilarity) strength
170 g <- computeG $ Just mt
171 pure $ trace "[G.V.G.API.recomputeGraph] Graph empty, computed" g
172 Just graph' -> if (listVersion == Just v) && (not force)
175 g <- computeG graphMetadata
176 pure $ trace "[G.V.G.API] Graph exists, recomputing" g
180 computeGraph :: FlowCmdM env err m
185 -> (NgramsType, NgramsType)
188 computeGraph corpusId method similarity strength (nt1,nt2) repo = do
189 -- Getting the Node parameters
190 lId <- defaultList corpusId
191 lIds <- selectNodesWithUsername NodeList userMaster
193 -- Getting the Ngrams to compute with and grouping it according to the lists
195 groupedContextsByNgrams nt corpusId' (lists_master, lists_user) = do
197 ngs = filterListWithRoot [MapTerm] $ mapTermListRoot lists_user nt repo
198 groupNodesByNgrams ngs <$> getContextsByNgramsOnlyUser corpusId'
199 (lists_user <> lists_master) nt (HashMap.keys ngs)
201 -- Optim if nt1 == nt2 : do not compute twice
203 m1 <- groupedContextsByNgrams nt1 corpusId (lIds, [lId])
208 m2 <- groupedContextsByNgrams nt2 corpusId (lIds, [lId])
211 -- Removing the hapax (ngrams with 1 cooc)
212 let !myCooc = HashMap.filter (>1)
213 $ getCoocByNgrams'' (Diagonal True) (identity, identity) (m1,m2)
215 -- TODO MultiPartite Here
217 $ cooc2graphWith method (MultiPartite (Partite (HashMap.keysSet m1) nt1)
218 (Partite (HashMap.keysSet m2) nt2)
220 similarity 0 strength myCooc
226 defaultGraphMetadata :: HasNodeError err
232 -> Cmd err GraphMetadata
233 defaultGraphMetadata cId t repo gm str = do
234 lId <- defaultList cId
236 pure $ GraphMetadata { _gm_title = t
238 , _gm_edgesStrength = Just str
239 , _gm_corpusId = [cId]
241 LegendField 1 "#FFF" "Cluster1"
242 , LegendField 2 "#FFF" "Cluster2"
243 , LegendField 3 "#FFF" "Cluster3"
244 , LegendField 4 "#FFF" "Cluster4"
246 , _gm_list = (ListForGraph lId (repo ^. unNodeStory . at lId . _Just . a_version))
247 , _gm_startForceAtlas = True
249 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
251 ------------------------------------------------------------
252 type GraphAsyncAPI = Summary "Recompute graph"
254 :> AsyncJobsAPI JobLog () JobLog
257 graphAsync :: UserId -> NodeId -> ServerT GraphAsyncAPI (GargM Env GargError)
259 serveJobsAPI RecomputeGraphJob $ \_ log' ->
260 graphRecompute u n (liftBase . log')
263 --graphRecompute :: UserId
265 -- -> (JobLog -> GargNoServer ())
266 -- -> GargNoServer JobLog
267 -- TODO get Graph Metadata to recompute
268 graphRecompute :: FlowCmdM env err m
273 graphRecompute u n logStatus = do
274 logStatus JobLog { _scst_succeeded = Just 0
275 , _scst_failed = Just 0
276 , _scst_remaining = Just 1
277 , _scst_events = Just []
279 _g <- recomputeGraph u n Spinglass Nothing Nothing NgramsTerms NgramsTerms False
280 pure JobLog { _scst_succeeded = Just 1
281 , _scst_failed = Just 0
282 , _scst_remaining = Just 0
283 , _scst_events = Just []
286 ------------------------------------------------------------
287 type GraphVersionsAPI = Summary "Graph versions"
288 :> Get '[JSON] GraphVersions
289 :<|> Summary "Recompute graph version"
290 :> Post '[JSON] Graph
292 graphVersionsAPI :: UserId -> NodeId -> GargServer GraphVersionsAPI
293 graphVersionsAPI u n =
295 :<|> recomputeVersions u n
297 graphVersions :: Int -> NodeId -> GargNoServer GraphVersions
298 graphVersions n nId = do
299 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
312 mcId <- getClosestParentIdByType nId NodeCorpus
313 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
315 maybeListId <- defaultListMaybe cId
318 then graphVersions (n+1) cId
319 else panic "[G.V.G.API] list not found after iterations"
322 repo <- getRepo [listId]
323 let v = repo ^. unNodeStory . at listId . _Just . a_version
324 -- printDebug "graphVersions" v
326 pure $ GraphVersions { gv_graph = listVersion
329 --recomputeVersions :: UserId -> NodeId -> GargNoServer Graph
330 recomputeVersions :: FlowCmdM env err m
334 recomputeVersions uId nId = recomputeGraph uId nId Spinglass Nothing Nothing NgramsTerms NgramsTerms False
336 ------------------------------------------------------------
340 -> GargNoServer NodeId
341 graphClone uId pId (HyperdataGraphAPI { _hyperdataAPIGraph = graph
342 , _hyperdataAPICamera = camera }) = do
343 let nodeType = NodeGraph
344 nodeUser <- getNodeUser (NodeId uId)
345 nodeParent <- getNodeWith pId (Proxy :: Proxy HyperdataGraph)
346 let uId' = nodeUser ^. node_user_id
347 nIds <- mkNodeWithParent nodeType (Just pId) uId' $ nodeParent ^. node_name
352 let graphP' = set (graph_metadata . _Just . gm_startForceAtlas) False graphP
354 _ <- updateHyperdata nId (HyperdataGraph (Just graphP') camera)
358 ------------------------------------------------------------
359 --getGraphGexf :: UserId
361 -- -> GargNoServer (Headers '[Servant.Header "Content-Disposition" Text] Graph)
362 getGraphGexf :: FlowCmdM env err m
365 -> m (Headers '[Servant.Header "Content-Disposition" Text] Graph)
366 getGraphGexf uId nId = do
367 HyperdataGraphAPI { _hyperdataAPIGraph = graph } <- getGraph uId nId
368 pure $ addHeader "attachment; filename=graph.gexf" graph