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
12 {-# LANGUAGE OverloadedLists #-} -- allows to write Map and HashMap as lists
13 {-# LANGUAGE TypeOperators #-}
15 module Gargantext.Core.Viz.Graph.API
18 import Control.Lens (set, (^.), _Just, (^?), at)
20 import Data.Maybe (fromMaybe)
22 import Data.Text hiding (head)
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.Prelude
28 import Gargantext.Core.Methods.Distances (Distance(..), GraphMetric(..), withMetric)
29 import Gargantext.Core.NodeStory
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.Flow.Types (FlowCmdM)
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 -> GargServer HyperdataGraphAPI
82 getGraph :: FlowCmdM env err m
85 -> m HyperdataGraphAPI
86 getGraph _uId nId = do
87 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
90 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
91 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
93 mcId <- getClosestParentIdByType nId NodeCorpus
94 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
96 printDebug "[getGraph] getting list for cId" cId
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
133 similarity = case graphMetric of
134 Nothing -> withMetric Order1
135 Just m -> withMetric m
137 mcId <- getClosestParentIdByType nId NodeCorpus
138 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
140 listId <- defaultList cId
141 repo <- getRepo' [listId]
142 let v = repo ^. unNodeStory . at listId . _Just . a_version
146 graph' <- computeGraph cId similarity NgramsTerms repo
147 mt <- defaultGraphMetadata cId "Title" repo (fromMaybe Order1 maybeDistance)
148 let graph'' = set graph_metadata (Just mt) graph'
149 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
150 pure $ trace "[G.V.G.API.recomputeGraph] Graph empty, computed" graph''
152 Just graph' -> if listVersion == Just v
155 graph'' <- computeGraph cId similarity NgramsTerms repo
156 let graph''' = set graph_metadata graphMetadata graph''
157 _ <- updateHyperdata nId (HyperdataGraph (Just graph''') camera)
158 pure $ trace "[G.V.G.API] Graph exists, recomputing" graph'''
161 computeGraph :: FlowCmdM env err m
167 computeGraph cId d nt repo = do
168 lId <- defaultList cId
169 lIds <- selectNodesWithUsername NodeList userMaster
171 let ngs = filterListWithRoot MapTerm
172 $ mapTermListRoot [lId] nt repo
174 myCooc <- HashMap.filter (>1) -- Removing the hapax (ngrams with 1 cooc)
175 -- <$> HashMap.filterWithKey (\(x,y) _ -> x /= y)
176 -- <$> getCoocByNgrams (if d == Conditional then Diagonal True else Diagonal False)
177 <$> getCoocByNgrams (Diagonal True)
178 <$> groupNodesByNgrams ngs
179 <$> getNodesByNgramsOnlyUser cId (lIds <> [lId]) nt (HashMap.keys ngs)
181 -- printDebug "myCooc" myCooc
182 -- saveAsFileDebug "debug/my-cooc" myCooc
184 listNgrams <- getListNgrams [lId] nt
186 -- graph <- liftBase $ cooc2graphWith Bac d 0 myCooc
187 graph <- liftBase $ cooc2graphWith Spinglass d 0 myCooc
188 -- saveAsFileDebug "debug/graph" graph
190 pure $ mergeGraphNgrams graph (Just listNgrams)
193 defaultGraphMetadata :: HasNodeError err
198 -> Cmd err GraphMetadata
199 defaultGraphMetadata cId t repo gm = do
200 lId <- defaultList cId
202 pure $ GraphMetadata {
205 , _gm_corpusId = [cId]
207 LegendField 1 "#FFF" "Cluster1"
208 , LegendField 2 "#FFF" "Cluster2"
209 , LegendField 3 "#FFF" "Cluster3"
210 , LegendField 4 "#FFF" "Cluster4"
212 , _gm_list = (ListForGraph lId (repo ^. unNodeStory . at lId . _Just . a_version))
213 , _gm_startForceAtlas = True
215 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
217 ------------------------------------------------------------
218 type GraphAsyncAPI = Summary "Recompute graph"
220 :> AsyncJobsAPI JobLog () JobLog
223 graphAsync :: UserId -> NodeId -> GargServer GraphAsyncAPI
226 JobFunction (\_ log' -> graphRecompute u n (liftBase . log'))
229 --graphRecompute :: UserId
231 -- -> (JobLog -> GargNoServer ())
232 -- -> GargNoServer JobLog
233 graphRecompute :: FlowCmdM env err m
238 graphRecompute u n logStatus = do
239 logStatus JobLog { _scst_succeeded = Just 0
240 , _scst_failed = Just 0
241 , _scst_remaining = Just 1
242 , _scst_events = Just []
244 _g <- trace (show u) $ recomputeGraph u n Nothing
245 pure JobLog { _scst_succeeded = Just 1
246 , _scst_failed = Just 0
247 , _scst_remaining = Just 0
248 , _scst_events = Just []
251 ------------------------------------------------------------
252 type GraphVersionsAPI = Summary "Graph versions"
253 :> Get '[JSON] GraphVersions
254 :<|> Summary "Recompute graph version"
255 :> Post '[JSON] Graph
257 graphVersionsAPI :: UserId -> NodeId -> GargServer GraphVersionsAPI
258 graphVersionsAPI u n =
260 :<|> recomputeVersions u n
262 graphVersions :: Int -> NodeId -> GargNoServer GraphVersions
263 graphVersions n nId = do
264 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
277 mcId <- getClosestParentIdByType nId NodeCorpus
278 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
280 maybeListId <- defaultListMaybe cId
283 then graphVersions (n+1) cId
284 else panic "[G.V.G.API] list not found after iterations"
287 repo <- getRepo' [listId]
288 let v = repo ^. unNodeStory . at listId . _Just . a_version
289 printDebug "graphVersions" v
291 pure $ GraphVersions { gv_graph = listVersion
294 --recomputeVersions :: UserId -> NodeId -> GargNoServer Graph
295 recomputeVersions :: FlowCmdM env err m
299 recomputeVersions uId nId = recomputeGraph uId nId Nothing
301 ------------------------------------------------------------
305 -> GargNoServer NodeId
306 graphClone uId pId (HyperdataGraphAPI { _hyperdataAPIGraph = graph
307 , _hyperdataAPICamera = camera }) = do
308 let nodeType = NodeGraph
309 nodeUser <- getNodeUser (NodeId uId)
310 nodeParent <- getNodeWith pId (Proxy :: Proxy HyperdataGraph)
311 let uId' = nodeUser ^. node_user_id
312 nIds <- mkNodeWithParent nodeType (Just pId) uId' $ nodeParent ^. node_name
317 let graphP' = set (graph_metadata . _Just . gm_startForceAtlas) False graphP
319 _ <- updateHyperdata nId (HyperdataGraph (Just graphP') camera)
323 ------------------------------------------------------------
324 --getGraphGexf :: UserId
326 -- -> GargNoServer (Headers '[Servant.Header "Content-Disposition" Text] Graph)
327 getGraphGexf :: FlowCmdM env err m
330 -> m (Headers '[Servant.Header "Content-Disposition" Text] Graph)
331 getGraphGexf uId nId = do
332 HyperdataGraphAPI { _hyperdataAPIGraph = graph } <- getGraph uId nId
333 pure $ addHeader "attachment; filename=graph.gexf" graph