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, (^?))
23 import Debug.Trace (trace)
24 import GHC.Generics (Generic)
25 import Gargantext.API.Admin.Orchestrator.Types
26 import Gargantext.API.Ngrams.Tools
27 import Gargantext.API.Ngrams.Types (NgramsRepo, r_version)
28 import Gargantext.API.Prelude
29 import Gargantext.Core.Methods.Distances (Distance(..), GraphMetric(..))
30 import Gargantext.Core.Types.Main
31 import Gargantext.Core.Viz.Graph
32 import Gargantext.Core.Viz.Graph.GEXF ()
33 import Gargantext.Core.Viz.Graph.Tools -- (cooc2graph)
34 import Gargantext.Database.Action.Metrics.NgramsByNode (getNodesByNgramsOnlyUser)
35 import Gargantext.Database.Action.Node (mkNodeWithParent)
36 import Gargantext.Database.Admin.Config
37 import Gargantext.Database.Admin.Types.Node
38 import Gargantext.Database.Prelude (Cmd)
39 import Gargantext.Database.Query.Table.Node
40 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
41 import Gargantext.Database.Query.Table.Node.Select
42 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
43 import Gargantext.Database.Query.Table.Node.User (getNodeUser)
44 import Gargantext.Database.Schema.Ngrams
45 import Gargantext.Database.Schema.Node (node_parentId, node_hyperdata, node_name, node_userId)
46 import Gargantext.Prelude
48 import Servant.Job.Async
50 import qualified Data.HashMap.Strict as HashMap
51 ------------------------------------------------------------------------
52 -- | There is no Delete specific API for Graph since it can be deleted
54 type GraphAPI = Get '[JSON] HyperdataGraphAPI
55 :<|> "async" :> GraphAsyncAPI
57 :> ReqBody '[JSON] HyperdataGraphAPI
58 :> Post '[JSON] NodeId
59 :<|> "gexf" :> Get '[XML] (Headers '[Servant.Header "Content-Disposition" Text] Graph)
60 :<|> "versions" :> GraphVersionsAPI
63 GraphVersions { gv_graph :: Maybe Int
66 deriving (Show, Generic)
68 instance ToJSON GraphVersions
69 instance ToSchema GraphVersions
71 graphAPI :: UserId -> NodeId -> GargServer GraphAPI
72 graphAPI u n = getGraph u n
76 :<|> graphVersionsAPI u n
78 ------------------------------------------------------------------------
79 getGraph :: UserId -> NodeId -> GargNoServer HyperdataGraphAPI
80 getGraph _uId nId = do
81 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
82 let graph = nodeGraph ^. node_hyperdata . hyperdataGraph
83 let camera = nodeGraph ^. node_hyperdata . hyperdataCamera
87 let cId = maybe (panic "[G.V.G.API] Node has no parent")
89 $ nodeGraph ^. node_parentId
91 -- TODO Distance in Graph params
94 graph' <- computeGraph cId Conditional NgramsTerms repo
95 mt <- defaultGraphMetadata cId "Title" repo
96 let graph'' = set graph_metadata (Just mt) graph'
97 let hg = HyperdataGraphAPI graph'' camera
98 -- _ <- updateHyperdata nId hg
99 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
100 pure $ trace "[G.V.G.API] Graph empty, computing" hg
102 Just graph' -> pure $ trace "[G.V.G.API] Graph exists, returning" $
103 HyperdataGraphAPI graph' camera
106 recomputeGraph :: UserId -> NodeId -> Distance -> GargNoServer Graph
107 recomputeGraph _uId nId d = do
108 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
109 let graph = nodeGraph ^. node_hyperdata . hyperdataGraph
110 let camera = nodeGraph ^. node_hyperdata . hyperdataCamera
111 let graphMetadata = graph ^? _Just . graph_metadata . _Just
112 let listVersion = graph ^? _Just . graph_metadata . _Just . gm_list . lfg_version
115 let v = repo ^. r_version
116 let cId = maybe (panic "[G.V.G.API.recomputeGraph] Node has no parent")
118 $ nodeGraph ^. node_parentId
122 graph' <- computeGraph cId d NgramsTerms repo
123 mt <- defaultGraphMetadata cId "Title" repo
124 let graph'' = set graph_metadata (Just mt) graph'
125 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
126 pure $ trace "[G.V.G.API.recomputeGraph] Graph empty, computed" graph''
128 Just graph' -> if listVersion == Just v
131 graph'' <- computeGraph cId d NgramsTerms repo
132 let graph''' = set graph_metadata graphMetadata graph''
133 _ <- updateHyperdata nId (HyperdataGraph (Just graph''') camera)
134 pure $ trace "[G.V.G.API] Graph exists, recomputing" graph'''
137 -- TODO use Database Monad only here ?
138 computeGraph :: HasNodeError err
144 computeGraph cId d nt repo = do
145 lId <- defaultList cId
147 lIds <- selectNodesWithUsername NodeList userMaster
148 let ngs = filterListWithRoot MapTerm $ mapTermListRoot [lId] nt repo
150 -- TODO split diagonal
151 myCooc <- HashMap.filter (>1)
152 <$> getCoocByNgrams (Diagonal True)
153 <$> groupNodesByNgrams ngs
154 <$> getNodesByNgramsOnlyUser cId (lIds <> [lId]) nt (HashMap.keys ngs)
156 graph <- liftBase $ cooc2graph d 0 myCooc
161 defaultGraphMetadata :: HasNodeError err
165 -> Cmd err GraphMetadata
166 defaultGraphMetadata cId t repo = do
167 lId <- defaultList cId
169 pure $ GraphMetadata {
171 , _gm_metric = Order1
172 , _gm_corpusId = [cId]
174 LegendField 1 "#FFF" "Cluster1"
175 , LegendField 2 "#FFF" "Cluster2"
176 , LegendField 3 "#FFF" "Cluster3"
177 , LegendField 4 "#FFF" "Cluster4"
179 , _gm_list = (ListForGraph lId (repo ^. r_version))
180 , _gm_startForceAtlas = True
182 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
185 ------------------------------------------------------------
186 type GraphAsyncAPI = Summary "Recompute graph"
188 :> AsyncJobsAPI JobLog () JobLog
191 graphAsync :: UserId -> NodeId -> GargServer GraphAsyncAPI
194 JobFunction (\_ log' -> graphRecompute u n (liftBase . log'))
197 graphRecompute :: UserId
199 -> (JobLog -> GargNoServer ())
200 -> GargNoServer JobLog
201 graphRecompute u n logStatus = do
202 logStatus JobLog { _scst_succeeded = Just 0
203 , _scst_failed = Just 0
204 , _scst_remaining = Just 1
205 , _scst_events = Just []
207 _g <- trace (show u) $ recomputeGraph u n Conditional
208 pure JobLog { _scst_succeeded = Just 1
209 , _scst_failed = Just 0
210 , _scst_remaining = Just 0
211 , _scst_events = Just []
214 ------------------------------------------------------------
215 type GraphVersionsAPI = Summary "Graph versions"
216 :> Get '[JSON] GraphVersions
217 :<|> Summary "Recompute graph version"
218 :> Post '[JSON] Graph
220 graphVersionsAPI :: UserId -> NodeId -> GargServer GraphVersionsAPI
221 graphVersionsAPI u n =
223 :<|> recomputeVersions u n
225 graphVersions :: NodeId -> GargNoServer GraphVersions
226 graphVersions nId = do
227 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
228 let graph = nodeGraph ^. node_hyperdata . hyperdataGraph
229 let listVersion = graph ^? _Just
236 let v = repo ^. r_version
238 pure $ GraphVersions { gv_graph = listVersion
241 recomputeVersions :: UserId -> NodeId -> GargNoServer Graph
242 recomputeVersions uId nId = recomputeGraph uId nId Conditional
244 ------------------------------------------------------------
248 -> GargNoServer NodeId
249 graphClone uId pId (HyperdataGraphAPI { _hyperdataAPIGraph = graph
250 , _hyperdataAPICamera = camera }) = do
251 let nodeType = NodeGraph
252 nodeUser <- getNodeUser (NodeId uId)
253 nodeParent <- getNodeWith pId (Proxy :: Proxy HyperdataGraph)
254 let uId' = nodeUser ^. node_userId
255 nIds <- mkNodeWithParent nodeType (Just pId) uId' $ nodeParent ^. node_name
260 let graphP' = set (graph_metadata . _Just . gm_startForceAtlas) False graphP
262 _ <- updateHyperdata nId (HyperdataGraph (Just graphP') camera)
266 ------------------------------------------------------------
267 getGraphGexf :: UserId
269 -> GargNoServer (Headers '[Servant.Header "Content-Disposition" Text] Graph)
270 getGraphGexf uId nId = do
271 HyperdataGraphAPI { _hyperdataAPIGraph = graph } <- getGraph uId nId
272 pure $ addHeader "attachment; filename=graph.gexf" graph