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
93 cId = maybe (panic "[G.V.G.API] Node has no parent")
95 $ nodeGraph ^. node_parent_id
97 listId <- defaultList cId
98 repo <- getRepo' [listId]
100 -- TODO Distance in Graph params
103 let defaultMetric = Order1
104 graph' <- computeGraph cId (withMetric defaultMetric) NgramsTerms repo
105 mt <- defaultGraphMetadata cId "Title" repo defaultMetric
107 graph'' = set graph_metadata (Just mt) graph'
108 hg = HyperdataGraphAPI graph'' camera
109 -- _ <- updateHyperdata nId hg
110 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
111 pure $ trace "[G.V.G.API] Graph empty, computing" hg
113 Just graph' -> pure $ trace "[G.V.G.API] Graph exists, returning" $
114 HyperdataGraphAPI graph' camera
117 --recomputeGraph :: UserId -> NodeId -> Maybe GraphMetric -> GargNoServer Graph
118 recomputeGraph :: FlowCmdM env err m
123 recomputeGraph _uId nId maybeDistance = do
124 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
126 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
127 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
128 graphMetadata = graph ^? _Just . graph_metadata . _Just
129 listVersion = graph ^? _Just . graph_metadata . _Just . gm_list . lfg_version
130 graphMetric = case maybeDistance of
131 Nothing -> graph ^? _Just . graph_metadata . _Just . gm_metric
135 cId = maybe (panic "[G.C.V.G.API.recomputeGraph] Node has no parent")
137 $ nodeGraph ^. node_parent_id
138 similarity = case graphMetric of
139 Nothing -> withMetric Order1
140 Just m -> withMetric m
142 listId <- defaultList cId
143 repo <- getRepo' [listId]
144 let v = repo ^. unNodeStory . at listId . _Just . a_version
148 graph' <- computeGraph cId similarity NgramsTerms repo
149 mt <- defaultGraphMetadata cId "Title" repo (fromMaybe Order1 maybeDistance)
150 let graph'' = set graph_metadata (Just mt) graph'
151 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
152 pure $ trace "[G.V.G.API.recomputeGraph] Graph empty, computed" graph''
154 Just graph' -> if listVersion == Just v
157 graph'' <- computeGraph cId similarity NgramsTerms repo
158 let graph''' = set graph_metadata graphMetadata graph''
159 _ <- updateHyperdata nId (HyperdataGraph (Just graph''') camera)
160 pure $ trace "[G.V.G.API] Graph exists, recomputing" graph'''
163 -- TODO use Database Monad only here ?
164 --computeGraph :: HasNodeError err
170 computeGraph :: FlowCmdM env err m
176 computeGraph cId d nt repo = do
177 lId <- defaultList cId
178 lIds <- selectNodesWithUsername NodeList userMaster
180 let ngs = filterListWithRoot MapTerm
181 $ mapTermListRoot [lId] nt repo
183 myCooc <- HashMap.filter (>1) -- Removing the hapax (ngrams with 1 cooc)
184 <$> getCoocByNgrams (if d == Conditional then Diagonal True else Diagonal False)
185 <$> groupNodesByNgrams ngs
186 <$> getNodesByNgramsOnlyUser cId (lIds <> [lId]) nt (HashMap.keys ngs)
188 -- printDebug "myCooc" myCooc
189 -- saveAsFileDebug "debug/my-cooc" myCooc
191 listNgrams <- getListNgrams [lId] nt
193 graph <- liftBase $ cooc2graphWith Spinglass d 0 myCooc
194 -- saveAsFileDebug "debug/graph" graph
195 pure $ mergeGraphNgrams graph (Just listNgrams)
198 defaultGraphMetadata :: HasNodeError err
203 -> Cmd err GraphMetadata
204 defaultGraphMetadata cId t repo gm = do
205 lId <- defaultList cId
207 pure $ GraphMetadata {
210 , _gm_corpusId = [cId]
212 LegendField 1 "#FFF" "Cluster1"
213 , LegendField 2 "#FFF" "Cluster2"
214 , LegendField 3 "#FFF" "Cluster3"
215 , LegendField 4 "#FFF" "Cluster4"
217 , _gm_list = (ListForGraph lId (repo ^. unNodeStory . at lId . _Just . a_version))
218 , _gm_startForceAtlas = True
220 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
223 ------------------------------------------------------------
224 type GraphAsyncAPI = Summary "Recompute graph"
226 :> AsyncJobsAPI JobLog () JobLog
229 graphAsync :: UserId -> NodeId -> GargServer GraphAsyncAPI
232 JobFunction (\_ log' -> graphRecompute u n (liftBase . log'))
235 --graphRecompute :: UserId
237 -- -> (JobLog -> GargNoServer ())
238 -- -> GargNoServer JobLog
239 graphRecompute :: FlowCmdM env err m
244 graphRecompute u n logStatus = do
245 logStatus JobLog { _scst_succeeded = Just 0
246 , _scst_failed = Just 0
247 , _scst_remaining = Just 1
248 , _scst_events = Just []
250 _g <- trace (show u) $ recomputeGraph u n Nothing
251 pure JobLog { _scst_succeeded = Just 1
252 , _scst_failed = Just 0
253 , _scst_remaining = Just 0
254 , _scst_events = Just []
257 ------------------------------------------------------------
258 type GraphVersionsAPI = Summary "Graph versions"
259 :> Get '[JSON] GraphVersions
260 :<|> Summary "Recompute graph version"
261 :> Post '[JSON] Graph
263 graphVersionsAPI :: UserId -> NodeId -> GargServer GraphVersionsAPI
264 graphVersionsAPI u n =
266 :<|> recomputeVersions u n
268 graphVersions :: Int -> NodeId -> GargNoServer GraphVersions
269 graphVersions n nId = do
270 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
283 mcId <- getClosestParentIdByType nId NodeCorpus
284 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
286 maybeListId <- defaultListMaybe cId
289 then graphVersions (n+1) cId
290 else panic "[G.V.G.API] list not found after iterations"
293 repo <- getRepo' [listId]
294 let v = repo ^. unNodeStory . at listId . _Just . a_version
295 printDebug "graphVersions" v
297 pure $ GraphVersions { gv_graph = listVersion
300 --recomputeVersions :: UserId -> NodeId -> GargNoServer Graph
301 recomputeVersions :: FlowCmdM env err m
305 recomputeVersions uId nId = recomputeGraph uId nId Nothing
307 ------------------------------------------------------------
311 -> GargNoServer NodeId
312 graphClone uId pId (HyperdataGraphAPI { _hyperdataAPIGraph = graph
313 , _hyperdataAPICamera = camera }) = do
314 let nodeType = NodeGraph
315 nodeUser <- getNodeUser (NodeId uId)
316 nodeParent <- getNodeWith pId (Proxy :: Proxy HyperdataGraph)
317 let uId' = nodeUser ^. node_user_id
318 nIds <- mkNodeWithParent nodeType (Just pId) uId' $ nodeParent ^. node_name
323 let graphP' = set (graph_metadata . _Just . gm_startForceAtlas) False graphP
325 _ <- updateHyperdata nId (HyperdataGraph (Just graphP') camera)
329 ------------------------------------------------------------
330 --getGraphGexf :: UserId
332 -- -> GargNoServer (Headers '[Servant.Header "Content-Disposition" Text] Graph)
333 getGraphGexf :: FlowCmdM env err m
336 -> m (Headers '[Servant.Header "Content-Disposition" Text] Graph)
337 getGraphGexf uId nId = do
338 HyperdataGraphAPI { _hyperdataAPIGraph = graph } <- getGraph uId nId
339 pure $ addHeader "attachment; filename=graph.gexf" graph