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 {-# OPTIONS_GHC -fno-warn-orphans #-}
15 {-# LANGUAGE OverloadedLists #-} -- allows to write Map and HashMap as lists
16 {-# LANGUAGE TypeOperators #-}
18 module Gargantext.Core.Viz.Graph.API
21 import Control.Lens (set, (^.), _Just, (^?))
23 import qualified Data.Map as Map
24 import Data.Maybe (Maybe(..))
27 import Debug.Trace (trace)
28 import GHC.Generics (Generic)
30 import Servant.Job.Async
33 import Gargantext.API.Admin.Orchestrator.Types
34 import Gargantext.API.Ngrams (NgramsRepo, r_version)
35 import Gargantext.API.Ngrams.Tools
36 import Gargantext.API.Prelude
37 import Gargantext.Core.Types.Main
38 import Gargantext.Database.Action.Metrics.NgramsByNode (getNodesByNgramsOnlyUser)
39 import Gargantext.Database.Action.Node (mkNodeWithParent)
40 import Gargantext.Database.Admin.Config
41 import Gargantext.Database.Admin.Types.Node
42 import Gargantext.Database.Prelude (Cmd)
43 import Gargantext.Database.Query.Table.Node
44 import Gargantext.Database.Query.Table.Node.User (getNodeUser)
45 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
46 import Gargantext.Database.Query.Table.Node.Select
47 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
48 import Gargantext.Database.Schema.Ngrams
49 import Gargantext.Database.Schema.Node (node_parentId, node_hyperdata, node_name, node_userId)
50 import Gargantext.Prelude
51 import Gargantext.Core.Viz.Graph
52 import Gargantext.Core.Viz.Graph.GEXF ()
53 import Gargantext.Core.Viz.Graph.Tools -- (cooc2graph)
54 import Gargantext.Core.Viz.Graph.Distances (Distance(..), GraphMetric(..))
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 ToJSON GraphVersions
74 instance ToSchema GraphVersions
76 graphAPI :: UserId -> NodeId -> GargServer GraphAPI
77 graphAPI u n = getGraph u n
81 :<|> graphVersionsAPI u n
83 ------------------------------------------------------------------------
84 getGraph :: UserId -> NodeId -> GargNoServer HyperdataGraphAPI
85 getGraph _uId nId = do
86 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
87 let graph = nodeGraph ^. node_hyperdata . hyperdataGraph
88 let camera = nodeGraph ^. node_hyperdata . hyperdataCamera
92 let cId = maybe (panic "[G.V.G.API] Node has no parent")
94 $ nodeGraph ^. node_parentId
96 -- TODO Distance in Graph params
99 graph' <- computeGraph cId Conditional NgramsTerms repo
100 mt <- defaultGraphMetadata cId "Title" repo
101 let graph'' = set graph_metadata (Just mt) graph'
102 let hg = HyperdataGraphAPI graph'' camera
103 _ <- updateHyperdata nId hg
104 pure $ trace "[G.V.G.API] Graph empty, computing" hg
106 Just graph' -> pure $ trace "[G.V.G.API] Graph exists, returning" $
107 HyperdataGraphAPI graph' camera
110 recomputeGraph :: UserId -> NodeId -> Distance -> GargNoServer Graph
111 recomputeGraph _uId nId d = do
112 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
113 let graph = nodeGraph ^. node_hyperdata . hyperdataGraph
114 let camera = nodeGraph ^. node_hyperdata . hyperdataCamera
115 let graphMetadata = graph ^? _Just . graph_metadata . _Just
116 let listVersion = graph ^? _Just . graph_metadata . _Just . gm_list . lfg_version
119 let v = repo ^. r_version
120 let cId = maybe (panic "[G.V.G.API.recomputeGraph] Node has no parent")
122 $ nodeGraph ^. node_parentId
126 graph' <- computeGraph cId d NgramsTerms repo
127 mt <- defaultGraphMetadata cId "Title" repo
128 let graph'' = set graph_metadata (Just mt) graph'
129 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
130 pure $ trace "[G.V.G.API.recomputeGraph] Graph empty, computed" graph''
132 Just graph' -> if listVersion == Just v
135 graph'' <- computeGraph cId d NgramsTerms repo
136 let graph''' = set graph_metadata graphMetadata graph''
137 _ <- updateHyperdata nId (HyperdataGraph (Just graph''') camera)
138 pure $ trace "[G.V.G.API] Graph exists, recomputing" graph'''
141 -- TODO use Database Monad only here ?
142 computeGraph :: HasNodeError err
148 computeGraph cId d nt repo = do
149 lId <- defaultList cId
151 lIds <- selectNodesWithUsername NodeList userMaster
152 let ngs = filterListWithRoot MapTerm $ mapTermListRoot [lId] nt repo
154 -- TODO split diagonal
155 myCooc <- Map.filter (>1)
156 <$> getCoocByNgrams (Diagonal True)
157 <$> groupNodesByNgrams ngs
158 <$> getNodesByNgramsOnlyUser cId (lIds <> [lId]) nt (Map.keys ngs)
160 graph <- liftBase $ cooc2graph d 0 myCooc
165 defaultGraphMetadata :: HasNodeError err
169 -> Cmd err GraphMetadata
170 defaultGraphMetadata cId t repo = do
171 lId <- defaultList cId
173 pure $ GraphMetadata {
175 , _gm_metric = Order1
176 , _gm_corpusId = [cId]
178 LegendField 1 "#FFF" "Cluster1"
179 , LegendField 2 "#FFF" "Cluster2"
180 , LegendField 3 "#FFF" "Cluster3"
181 , LegendField 4 "#FFF" "Cluster4"
183 , _gm_list = (ListForGraph lId (repo ^. r_version))
184 , _gm_startForceAtlas = True
186 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
189 ------------------------------------------------------------
190 type GraphAsyncAPI = Summary "Recompute graph"
192 :> AsyncJobsAPI JobLog () JobLog
195 graphAsync :: UserId -> NodeId -> GargServer GraphAsyncAPI
198 JobFunction (\_ log' -> graphRecompute u n (liftBase . log'))
201 graphRecompute :: UserId
203 -> (JobLog -> GargNoServer ())
204 -> GargNoServer JobLog
205 graphRecompute u n logStatus = do
206 logStatus JobLog { _scst_succeeded = Just 0
207 , _scst_failed = Just 0
208 , _scst_remaining = Just 1
209 , _scst_events = Just []
211 _g <- trace (show u) $ recomputeGraph u n Conditional
212 pure JobLog { _scst_succeeded = Just 1
213 , _scst_failed = Just 0
214 , _scst_remaining = Just 0
215 , _scst_events = Just []
218 ------------------------------------------------------------
219 type GraphVersionsAPI = Summary "Graph versions"
220 :> Get '[JSON] GraphVersions
221 :<|> Summary "Recompute graph version"
222 :> Post '[JSON] Graph
224 graphVersionsAPI :: UserId -> NodeId -> GargServer GraphVersionsAPI
225 graphVersionsAPI u n =
227 :<|> recomputeVersions u n
229 graphVersions :: UserId -> NodeId -> GargNoServer GraphVersions
230 graphVersions _uId nId = do
231 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
232 let graph = nodeGraph ^. node_hyperdata . hyperdataGraph
233 let listVersion = graph ^? _Just
240 let v = repo ^. r_version
242 pure $ GraphVersions { gv_graph = listVersion
245 recomputeVersions :: UserId -> NodeId -> GargNoServer Graph
246 recomputeVersions uId nId = recomputeGraph uId nId Conditional
248 ------------------------------------------------------------
252 -> GargNoServer NodeId
253 graphClone uId pId (HyperdataGraphAPI { _hyperdataAPIGraph = graph
254 , _hyperdataAPICamera = camera }) = do
255 let nodeType = NodeGraph
256 nodeUser <- getNodeUser (NodeId uId)
257 nodeParent <- getNodeWith pId (Proxy :: Proxy HyperdataGraph)
258 let uId' = nodeUser ^. node_userId
259 nIds <- mkNodeWithParent nodeType (Just pId) uId' $ nodeParent ^. node_name
264 let graphP' = set (graph_metadata . _Just . gm_startForceAtlas) False graphP
266 _ <- updateHyperdata nId (HyperdataGraph (Just graphP') camera)
270 ------------------------------------------------------------
271 getGraphGexf :: UserId
273 -> GargNoServer (Headers '[Servant.Header "Content-Disposition" Text] Graph)
274 getGraphGexf uId nId = do
275 HyperdataGraphAPI { _hyperdataAPIGraph = graph } <- getGraph uId nId
276 pure $ addHeader "attachment; filename=graph.gexf" graph