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
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.Orchestrator.Types
27 import Gargantext.API.Ngrams.Tools
28 import Gargantext.API.Prelude
29 import Gargantext.Core.Methods.Distances (Distance(..), GraphMetric(..), withMetric)
30 import Gargantext.Core.NodeStory
31 import Gargantext.Core.Types.Main
32 import Gargantext.Core.Viz.Graph
33 import Gargantext.Core.Viz.Graph.GEXF ()
34 import Gargantext.Core.Viz.Graph.Tools -- (cooc2graph)
35 import Gargantext.Database.Action.Metrics.NgramsByNode (getNodesByNgramsOnlyUser)
36 import Gargantext.Database.Action.Flow.Types (FlowCmdM)
37 import Gargantext.Database.Action.Node (mkNodeWithParent)
38 import Gargantext.Database.Admin.Config
39 import Gargantext.Database.Admin.Types.Node
40 import Gargantext.Database.Prelude (Cmd)
41 import Gargantext.Database.Query.Table.Node
42 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
43 import Gargantext.Database.Query.Table.Node.Select
44 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
45 import Gargantext.Database.Query.Table.Node.User (getNodeUser)
46 import Gargantext.Database.Schema.Node
47 import Gargantext.Database.Schema.Ngrams
48 import Gargantext.Prelude
50 import Servant.Job.Async
52 import qualified Data.HashMap.Strict as HashMap
54 ------------------------------------------------------------------------
55 -- | There is no Delete specific API for Graph since it can be deleted
57 type GraphAPI = Get '[JSON] HyperdataGraphAPI
58 :<|> "async" :> GraphAsyncAPI
60 :> ReqBody '[JSON] HyperdataGraphAPI
61 :> Post '[JSON] NodeId
62 :<|> "gexf" :> Get '[XML] (Headers '[Servant.Header "Content-Disposition" Text] Graph)
63 :<|> "versions" :> GraphVersionsAPI
66 GraphVersions { gv_graph :: Maybe Int
69 deriving (Show, Generic)
71 instance ToJSON GraphVersions
72 instance ToSchema GraphVersions
74 graphAPI :: UserId -> NodeId -> GargServer GraphAPI
75 graphAPI u n = getGraph u n
79 :<|> graphVersionsAPI u n
81 ------------------------------------------------------------------------
82 --getGraph :: UserId -> NodeId -> GargServer HyperdataGraphAPI
83 getGraph :: FlowCmdM env err m
86 -> m HyperdataGraphAPI
87 getGraph _uId nId = do
88 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
91 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
92 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
94 mcId <- getClosestParentIdByType nId NodeCorpus
95 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
97 printDebug "[getGraph] getting list for cId" cId
98 listId <- defaultList cId
99 repo <- getRepo' [listId]
101 -- TODO Distance in Graph params
104 let defaultMetric = Order1
105 graph' <- computeGraph cId (withMetric defaultMetric) NgramsTerms repo
106 mt <- defaultGraphMetadata cId "Title" repo defaultMetric
108 graph'' = set graph_metadata (Just mt) graph'
109 hg = HyperdataGraphAPI graph'' camera
110 -- _ <- updateHyperdata nId hg
111 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
112 pure $ trace "[G.V.G.API] Graph empty, computing" hg
114 Just graph' -> pure $ trace "[G.V.G.API] Graph exists, returning" $
115 HyperdataGraphAPI graph' camera
118 --recomputeGraph :: UserId -> NodeId -> Maybe GraphMetric -> GargNoServer Graph
119 recomputeGraph :: FlowCmdM env err m
124 recomputeGraph _uId nId maybeDistance = do
125 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
127 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
128 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
129 graphMetadata = graph ^? _Just . graph_metadata . _Just
130 listVersion = graph ^? _Just . graph_metadata . _Just . gm_list . lfg_version
131 graphMetric = case maybeDistance of
132 Nothing -> graph ^? _Just . graph_metadata . _Just . gm_metric
134 similarity = case graphMetric of
135 Nothing -> withMetric Order1
136 Just m -> withMetric m
138 mcId <- getClosestParentIdByType nId NodeCorpus
139 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
141 listId <- defaultList cId
142 repo <- getRepo' [listId]
143 let v = repo ^. unNodeStory . at listId . _Just . a_version
147 graph' <- computeGraph cId similarity NgramsTerms repo
148 mt <- defaultGraphMetadata cId "Title" repo (fromMaybe Order1 maybeDistance)
149 let graph'' = set graph_metadata (Just mt) graph'
150 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
151 pure $ trace "[G.V.G.API.recomputeGraph] Graph empty, computed" graph''
153 Just graph' -> if listVersion == Just v
156 graph'' <- computeGraph cId similarity NgramsTerms repo
157 let graph''' = set graph_metadata graphMetadata graph''
158 _ <- updateHyperdata nId (HyperdataGraph (Just graph''') camera)
159 pure $ trace "[G.V.G.API] Graph exists, recomputing" graph'''
162 -- TODO use Database Monad only here ?
163 --computeGraph :: HasNodeError err
169 computeGraph :: FlowCmdM env err m
175 computeGraph cId d nt repo = do
176 lId <- defaultList cId
177 lIds <- selectNodesWithUsername NodeList userMaster
179 let ngs = filterListWithRoot MapTerm
180 $ mapTermListRoot [lId] nt repo
182 myCooc <- HashMap.filter (>1) -- Removing the hapax (ngrams with 1 cooc)
183 <$> getCoocByNgrams (if d == Conditional then Diagonal True else Diagonal False)
184 <$> groupNodesByNgrams ngs
185 <$> getNodesByNgramsOnlyUser cId (lIds <> [lId]) nt (HashMap.keys ngs)
187 -- printDebug "myCooc" myCooc
188 -- saveAsFileDebug "debug/my-cooc" myCooc
190 listNgrams <- getListNgrams [lId] nt
192 -- graph <- liftBase $ cooc2graphWith Bac d 0 myCooc
193 graph <- liftBase $ cooc2graphWith Spinglass d 0 myCooc
194 -- saveAsFileDebug "debug/graph" graph
196 pure $ mergeGraphNgrams graph (Just listNgrams)
199 defaultGraphMetadata :: HasNodeError err
204 -> Cmd err GraphMetadata
205 defaultGraphMetadata cId t repo gm = do
206 lId <- defaultList cId
208 pure $ GraphMetadata {
211 , _gm_corpusId = [cId]
213 LegendField 1 "#FFF" "Cluster1"
214 , LegendField 2 "#FFF" "Cluster2"
215 , LegendField 3 "#FFF" "Cluster3"
216 , LegendField 4 "#FFF" "Cluster4"
218 , _gm_list = (ListForGraph lId (repo ^. unNodeStory . at lId . _Just . a_version))
219 , _gm_startForceAtlas = True
221 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
224 ------------------------------------------------------------
225 type GraphAsyncAPI = Summary "Recompute graph"
227 :> AsyncJobsAPI JobLog () JobLog
230 graphAsync :: UserId -> NodeId -> GargServer GraphAsyncAPI
233 JobFunction (\_ log' -> graphRecompute u n (liftBase . log'))
236 --graphRecompute :: UserId
238 -- -> (JobLog -> GargNoServer ())
239 -- -> GargNoServer JobLog
240 graphRecompute :: FlowCmdM env err m
245 graphRecompute u n logStatus = do
246 logStatus JobLog { _scst_succeeded = Just 0
247 , _scst_failed = Just 0
248 , _scst_remaining = Just 1
249 , _scst_events = Just []
251 _g <- trace (show u) $ recomputeGraph u n Nothing
252 pure JobLog { _scst_succeeded = Just 1
253 , _scst_failed = Just 0
254 , _scst_remaining = Just 0
255 , _scst_events = Just []
258 ------------------------------------------------------------
259 type GraphVersionsAPI = Summary "Graph versions"
260 :> Get '[JSON] GraphVersions
261 :<|> Summary "Recompute graph version"
262 :> Post '[JSON] Graph
264 graphVersionsAPI :: UserId -> NodeId -> GargServer GraphVersionsAPI
265 graphVersionsAPI u n =
267 :<|> recomputeVersions u n
269 graphVersions :: Int -> NodeId -> GargNoServer GraphVersions
270 graphVersions n nId = do
271 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
284 mcId <- getClosestParentIdByType nId NodeCorpus
285 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
287 maybeListId <- defaultListMaybe cId
290 then graphVersions (n+1) cId
291 else panic "[G.V.G.API] list not found after iterations"
294 repo <- getRepo' [listId]
295 let v = repo ^. unNodeStory . at listId . _Just . a_version
296 printDebug "graphVersions" v
298 pure $ GraphVersions { gv_graph = listVersion
301 --recomputeVersions :: UserId -> NodeId -> GargNoServer Graph
302 recomputeVersions :: FlowCmdM env err m
306 recomputeVersions uId nId = recomputeGraph uId nId Nothing
308 ------------------------------------------------------------
312 -> GargNoServer NodeId
313 graphClone uId pId (HyperdataGraphAPI { _hyperdataAPIGraph = graph
314 , _hyperdataAPICamera = camera }) = do
315 let nodeType = NodeGraph
316 nodeUser <- getNodeUser (NodeId uId)
317 nodeParent <- getNodeWith pId (Proxy :: Proxy HyperdataGraph)
318 let uId' = nodeUser ^. node_user_id
319 nIds <- mkNodeWithParent nodeType (Just pId) uId' $ nodeParent ^. node_name
324 let graphP' = set (graph_metadata . _Just . gm_startForceAtlas) False graphP
326 _ <- updateHyperdata nId (HyperdataGraph (Just graphP') camera)
330 ------------------------------------------------------------
331 --getGraphGexf :: UserId
333 -- -> GargNoServer (Headers '[Servant.Header "Content-Disposition" Text] Graph)
334 getGraphGexf :: FlowCmdM env err m
337 -> m (Headers '[Servant.Header "Content-Disposition" Text] Graph)
338 getGraphGexf uId nId = do
339 HyperdataGraphAPI { _hyperdataAPIGraph = graph } <- getGraph uId nId
340 pure $ addHeader "attachment; filename=graph.gexf" graph