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.Node (mkNodeWithParent)
37 import Gargantext.Database.Admin.Config
38 import Gargantext.Database.Admin.Types.Node
39 import Gargantext.Database.Prelude (Cmd)
40 import Gargantext.Database.Query.Table.Node
41 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
42 import Gargantext.Database.Query.Table.Node.Select
43 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
44 import Gargantext.Database.Query.Table.Node.User (getNodeUser)
45 import Gargantext.Database.Schema.Node
46 import Gargantext.Database.Schema.Ngrams
47 import Gargantext.Prelude
49 import Servant.Job.Async
51 import qualified Data.HashMap.Strict as HashMap
53 ------------------------------------------------------------------------
54 -- | There is no Delete specific API for Graph since it can be deleted
56 type GraphAPI = Get '[JSON] HyperdataGraphAPI
57 :<|> "async" :> GraphAsyncAPI
59 :> ReqBody '[JSON] HyperdataGraphAPI
60 :> Post '[JSON] NodeId
61 :<|> "gexf" :> Get '[XML] (Headers '[Servant.Header "Content-Disposition" Text] Graph)
62 :<|> "versions" :> GraphVersionsAPI
65 GraphVersions { gv_graph :: Maybe Int
68 deriving (Show, Generic)
70 instance ToJSON GraphVersions
71 instance ToSchema GraphVersions
73 graphAPI :: UserId -> NodeId -> GargServer GraphAPI
74 graphAPI u n = getGraph u n
78 :<|> graphVersionsAPI u n
80 ------------------------------------------------------------------------
81 getGraph :: UserId -> NodeId -> GargNoServer HyperdataGraphAPI
82 getGraph _uId nId = do
83 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
86 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
87 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
88 cId = maybe (panic "[G.V.G.API] Node has no parent")
90 $ nodeGraph ^. node_parent_id
92 listId <- defaultList cId
93 repo <- getRepo' [listId]
95 -- TODO Distance in Graph params
98 let defaultMetric = Order1
99 graph' <- computeGraph cId (withMetric defaultMetric) NgramsTerms repo
100 mt <- defaultGraphMetadata cId "Title" repo defaultMetric
102 graph'' = set graph_metadata (Just mt) graph'
103 hg = HyperdataGraphAPI graph'' camera
104 -- _ <- updateHyperdata nId hg
105 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
106 pure $ trace "[G.V.G.API] Graph empty, computing" hg
108 Just graph' -> pure $ trace "[G.V.G.API] Graph exists, returning" $
109 HyperdataGraphAPI graph' camera
112 recomputeGraph :: UserId -> NodeId -> Maybe GraphMetric -> GargNoServer Graph
113 recomputeGraph _uId nId maybeDistance = do
114 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
116 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
117 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
118 graphMetadata = graph ^? _Just . graph_metadata . _Just
119 listVersion = graph ^? _Just . graph_metadata . _Just . gm_list . lfg_version
120 graphMetric = case maybeDistance of
121 Nothing -> graph ^? _Just . graph_metadata . _Just . gm_metric
125 cId = maybe (panic "[G.V.G.API.recomputeGraph] Node has no parent")
127 $ nodeGraph ^. node_parent_id
128 similarity = case graphMetric of
129 Nothing -> withMetric Order1
130 Just m -> withMetric m
132 listId <- defaultList cId
133 repo <- getRepo' [listId]
134 let v = repo ^. unNodeStory . at listId . _Just . a_version
138 graph' <- computeGraph cId similarity NgramsTerms repo
139 mt <- defaultGraphMetadata cId "Title" repo (fromMaybe Order1 maybeDistance)
140 let graph'' = set graph_metadata (Just mt) graph'
141 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
142 pure $ trace "[G.V.G.API.recomputeGraph] Graph empty, computed" graph''
144 Just graph' -> if listVersion == Just v
147 graph'' <- computeGraph cId similarity NgramsTerms repo
148 let graph''' = set graph_metadata graphMetadata graph''
149 _ <- updateHyperdata nId (HyperdataGraph (Just graph''') camera)
150 pure $ trace "[G.V.G.API] Graph exists, recomputing" graph'''
153 -- TODO use Database Monad only here ?
154 computeGraph :: HasNodeError err
160 computeGraph cId d nt repo = do
161 lId <- defaultList cId
162 lIds <- selectNodesWithUsername NodeList userMaster
164 let ngs = filterListWithRoot MapTerm
165 $ mapTermListRoot [lId] nt repo
167 myCooc <- HashMap.filter (>2) -- Removing the hapax (ngrams with 1 cooc)
168 <$> getCoocByNgrams (Diagonal True)
169 <$> groupNodesByNgrams ngs
170 <$> getNodesByNgramsOnlyUser cId (lIds <> [lId]) nt (HashMap.keys ngs)
172 -- printDebug "myCooc" myCooc
173 -- saveAsFileDebug "debug/my-cooc" myCooc
175 graph <- liftBase $ cooc2graphWith Spinglass d 0 myCooc
176 -- saveAsFileDebug "debug/graph" graph
180 defaultGraphMetadata :: HasNodeError err
185 -> Cmd err GraphMetadata
186 defaultGraphMetadata cId t repo gm = do
187 lId <- defaultList cId
189 pure $ GraphMetadata {
192 , _gm_corpusId = [cId]
194 LegendField 1 "#FFF" "Cluster1"
195 , LegendField 2 "#FFF" "Cluster2"
196 , LegendField 3 "#FFF" "Cluster3"
197 , LegendField 4 "#FFF" "Cluster4"
199 , _gm_list = (ListForGraph lId (repo ^. unNodeStory . at lId . _Just . a_version))
200 , _gm_startForceAtlas = True
202 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
205 ------------------------------------------------------------
206 type GraphAsyncAPI = Summary "Recompute graph"
208 :> AsyncJobsAPI JobLog () JobLog
211 graphAsync :: UserId -> NodeId -> GargServer GraphAsyncAPI
214 JobFunction (\_ log' -> graphRecompute u n (liftBase . log'))
217 graphRecompute :: UserId
219 -> (JobLog -> GargNoServer ())
220 -> GargNoServer JobLog
221 graphRecompute u n logStatus = do
222 logStatus JobLog { _scst_succeeded = Just 0
223 , _scst_failed = Just 0
224 , _scst_remaining = Just 1
225 , _scst_events = Just []
227 _g <- trace (show u) $ recomputeGraph u n Nothing
228 pure JobLog { _scst_succeeded = Just 1
229 , _scst_failed = Just 0
230 , _scst_remaining = Just 0
231 , _scst_events = Just []
234 ------------------------------------------------------------
235 type GraphVersionsAPI = Summary "Graph versions"
236 :> Get '[JSON] GraphVersions
237 :<|> Summary "Recompute graph version"
238 :> Post '[JSON] Graph
240 graphVersionsAPI :: UserId -> NodeId -> GargServer GraphVersionsAPI
241 graphVersionsAPI u n =
243 :<|> recomputeVersions u n
245 graphVersions :: Int -> NodeId -> GargNoServer GraphVersions
246 graphVersions n nId = do
247 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
260 mcId <- getClosestParentIdByType nId NodeCorpus
261 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
263 maybeListId <- defaultListMaybe cId
266 then graphVersions (n+1) cId
267 else panic "[G.V.G.API] list not found after iterations"
270 repo <- getRepo' [listId]
271 let v = repo ^. unNodeStory . at listId . _Just . a_version
272 printDebug "graphVersions" v
274 pure $ GraphVersions { gv_graph = listVersion
277 recomputeVersions :: UserId -> NodeId -> GargNoServer Graph
278 recomputeVersions uId nId = recomputeGraph uId nId Nothing
280 ------------------------------------------------------------
284 -> GargNoServer NodeId
285 graphClone uId pId (HyperdataGraphAPI { _hyperdataAPIGraph = graph
286 , _hyperdataAPICamera = camera }) = do
287 let nodeType = NodeGraph
288 nodeUser <- getNodeUser (NodeId uId)
289 nodeParent <- getNodeWith pId (Proxy :: Proxy HyperdataGraph)
290 let uId' = nodeUser ^. node_user_id
291 nIds <- mkNodeWithParent nodeType (Just pId) uId' $ nodeParent ^. node_name
296 let graphP' = set (graph_metadata . _Just . gm_startForceAtlas) False graphP
298 _ <- updateHyperdata nId (HyperdataGraph (Just graphP') camera)
302 ------------------------------------------------------------
303 getGraphGexf :: UserId
305 -> GargNoServer (Headers '[Servant.Header "Content-Disposition" Text] Graph)
306 getGraphGexf uId nId = do
307 HyperdataGraphAPI { _hyperdataAPIGraph = graph } <- getGraph uId nId
308 pure $ addHeader "attachment; filename=graph.gexf" graph