Remove daml visualize / damlc visual (#16901)

* Remove daml visualize / damlc visual

* fix redundant imports

* remove visual-web subtest
This commit is contained in:
dylant-da 2023-05-30 10:50:36 +01:00 committed by GitHub
parent 3dea2d6bfc
commit 6058070637
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 3 additions and 1226 deletions

View File

@ -10,7 +10,6 @@ features:
- Type-information on hover
- Renaming of symbols
- Daml snippet support
- Command to generate visualization for Daml project via command palette ctrl + p.
Please note that this will only install the VSCode extension. Full use of the
above features will also require that you have a working Daml SDK installed,
@ -20,8 +19,6 @@ which you can get with:
curl -s https://get.daml.com | sh
```
To see graphs from `daml.visualize` command please install [Graphivz plugin](https://marketplace.visualstudio.com/items?itemName=EFanZh.graphviz-preview).
For more information on Daml please see [docs.daml.com](https://docs.daml.com).
## Troubleshooting

View File

@ -62,11 +62,6 @@
"command": "daml.openDamlDocs",
"title": "[Daml Documentation]"
},
{
"command": "daml.visualize",
"title": "Visualize daml project",
"when": "editorLangId == daml"
},
{
"command": "daml.resetTelemetryConsent",
"title": "Ask me about Daml telemetry again on startup"

View File

@ -96,7 +96,6 @@ export async function activate(context: vscode.ExtensionContext) {
);
let d2 = vscode.commands.registerCommand("daml.openDamlDocs", openDamlDocs);
let d5 = vscode.commands.registerCommand("daml.visualize", visualize);
let highlight = vscode.window.createTextEditorDecorationType({
backgroundColor: "rgba(200,200,200,.35)",
@ -135,7 +134,7 @@ export async function activate(context: vscode.ExtensionContext) {
resetTelemetryConsent(context),
);
context.subscriptions.push(d1, d2, d3, d4, d5);
context.subscriptions.push(d1, d2, d3, d4);
}
// Compare the extension version with the one stored in the global state.
@ -207,34 +206,6 @@ function getViewColumnForShowResource(): ViewColumn {
}
}
function visualize() {
if (vscode.window.activeTextEditor) {
let currentFile = vscode.window.activeTextEditor.document.fileName;
if (vscode.window.activeTextEditor.document.languageId != "daml") {
vscode.window.showInformationMessage("Open the daml file to visualize");
} else {
damlLanguageClient
.sendRequest(ExecuteCommandRequest.type, {
command: "daml/damlVisualize",
arguments: [currentFile],
})
.then(dotFileContents => {
vscode.workspace
.openTextDocument({ content: dotFileContents, language: "dot" })
.then(doc =>
vscode.window
.showTextDocument(doc, vscode.ViewColumn.One, true)
.then(_ => loadPreviewIfAvailable()),
);
});
}
} else {
vscode.window.showInformationMessage(
"Please open a Daml module to be visualized and then run the command",
);
}
}
function loadPreviewIfAvailable() {
if (vscode.extensions.getExtension("EFanZh.graphviz-preview")) {
vscode.commands.executeCommand("graphviz.showPreviewToSide");

View File

@ -222,7 +222,6 @@ da_haskell_library(
"//compiler/damlc/daml-opts:daml-opts-types",
"//compiler/damlc/daml-package-config",
"//compiler/damlc/daml-rule-types",
"//compiler/damlc/daml-visual",
"//compiler/repl-service/client",
"//compiler/scenario-service/client",
"//compiler/scenario-service/protos:scenario_service_haskell_proto",

View File

@ -64,7 +64,6 @@ da_haskell_library(
"//compiler/damlc/daml-opts",
"//compiler/damlc/daml-opts:daml-opts-types",
"//compiler/damlc/daml-rule-types",
"//compiler/damlc/daml-visual",
"//compiler/scenario-service/client",
"//libs-haskell/bazel-runfiles",
"//libs-haskell/da-hs-base",
@ -105,7 +104,6 @@ da_haskell_library(
"//compiler/damlc/daml-compiler",
"//compiler/damlc/daml-opts",
"//compiler/damlc/daml-opts:daml-opts-types",
"//compiler/damlc/daml-visual",
"//compiler/scenario-service/client",
"//libs-haskell/bazel-runfiles",
"//libs-haskell/da-hs-base",

View File

@ -15,9 +15,6 @@ module Development.IDE.Core.API.Testing
, GoToDefinitionPattern (..)
, HoverExpectation (..)
, D.DiagnosticSeverity(..)
, ExpectedGraph(..)
, ExpectedSubGraph(..)
, ExpectedChoiceDetails(..)
, runShakeTest
, runShakeTestOpts
, makeFile
@ -42,7 +39,6 @@ module Development.IDE.Core.API.Testing
, expectNoVirtualResource
, expectVirtualResourceNote
, expectNoVirtualResourceNote
, expectedGraph
, timedSection
, example
) where
@ -51,11 +47,9 @@ module Development.IDE.Core.API.Testing
import qualified Development.IDE.Core.API as API
import Development.IDE.Core.Debouncer
import Development.IDE.Core.Shake (ShakeLspEnv(..), NotificationHandler(..))
import qualified Development.IDE.Core.Rules.Daml as API
import qualified Development.IDE.Types.Diagnostics as D
import qualified Development.IDE.Types.Location as D
import DA.Daml.LF.ScenarioServiceClient as SS
import Development.IDE.Core.API.Testing.Visualize
import Development.IDE.Core.Rules.Daml
import Development.IDE.Types.Logger
import DA.Daml.Options
@ -69,7 +63,6 @@ import Language.LSP.Types hiding (SemanticTokenAbsolute (..), SemanticTokenRelat
import Control.Concurrent.STM
import Control.Exception.Extra
import qualified Control.Monad.Reader as Reader
import Data.Either.Combinators
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import qualified Data.Aeson as Aeson
@ -106,7 +99,6 @@ data ShakeTestError
| ExpectedVirtualResourceNote VirtualResource T.Text (Map VirtualResource T.Text)
| ExpectedNoVirtualResourceNote VirtualResource (Map VirtualResource T.Text)
| ExpectedNoErrors [D.FileDiagnostic]
| ExpectedGraphProps FailedGraphExpectation
| ExpectedDefinition Cursor GoToDefinitionPattern (Maybe D.Location)
| ExpectedHoverText Cursor HoverExpectation [T.Text]
| TimedSectionTookTooLong Clock.NominalDiffTime Clock.NominalDiffTime
@ -534,14 +526,6 @@ timedSection targetDiffTime block = do
throwError $ TimedSectionTookTooLong targetDiffTime actualDiffTime
return value
-- Not using the ide call as we do not have a rule defined for visualization because of memory overhead
expectedGraph :: D.NormalizedFilePath -> ExpectedGraph -> ShakeTest ()
expectedGraph damlFilePath expectedGraph = do
ideState <- ShakeTest $ Reader.asks steService
wrld <- Reader.liftIO $ API.runActionSync ideState (API.worldForFile damlFilePath)
expectNoErrors
whenLeft (graphTest wrld expectedGraph) $ throwError . ExpectedGraphProps
-- | Example testing scenario.
example :: ShakeTest ()
example = do

View File

@ -1,64 +0,0 @@
-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
module Development.IDE.Core.API.Testing.Visualize
( ExpectedGraph(..)
, ExpectedSubGraph(..)
, ExpectedChoiceDetails(..)
, FailedGraphExpectation(..)
, graphTest
)
where
import Control.Monad
import Data.Bifunctor
import qualified Data.Text as T
import qualified DA.Daml.LF.Ast as LF
import qualified DA.Daml.Visual as V
type TemplateName = String
type ChoiceName = String
data ExpectedGraph = ExpectedGraph
{ expectedSubgraphs :: [ExpectedSubGraph]
, expectedEdges :: [(ExpectedChoiceDetails, ExpectedChoiceDetails)]
} deriving (Eq, Ord, Show )
data ExpectedSubGraph = ExpectedSubGraph
{ expectedNodes :: [ChoiceName]
, expectedTplFields :: [String]
, expectedTemplate :: TemplateName
} deriving (Eq, Ord, Show )
data ExpectedChoiceDetails = ExpectedChoiceDetails
{ expectedConsuming :: Bool
, expectedName :: String
} deriving (Eq, Ord, Show )
subgraphToExpectedSubgraph :: V.SubGraph -> ExpectedSubGraph
subgraphToExpectedSubgraph vSubgraph = ExpectedSubGraph vNodes vFields vTplName
where vNodes = map (T.unpack . LF.unChoiceName . V.displayChoiceName) (V.nodes vSubgraph)
vFields = map T.unpack (V.templateFields vSubgraph)
vTplName = T.unpack $ V.tplNameUnqual (V.clusterTemplate vSubgraph)
graphToExpectedGraph :: V.Graph -> ExpectedGraph
graphToExpectedGraph vGraph = ExpectedGraph vSubgrpaghs vEdges
where vSubgrpaghs = map subgraphToExpectedSubgraph (V.subgraphs vGraph)
vEdges = map (bimap expectedChcDetails expectedChcDetails) (V.edges vGraph)
expectedChcDetails chc = ExpectedChoiceDetails (V.consuming chc)
((T.unpack . LF.unChoiceName . V.displayChoiceName) chc)
data FailedGraphExpectation = FailedGraphExpectation
{ expected :: ExpectedGraph
, actual :: ExpectedGraph
}
deriving (Eq, Show)
graphTest :: LF.World -> ExpectedGraph -> Either FailedGraphExpectation ()
graphTest wrld expectedGraph = do
let actualGraph = V.graphFromWorld wrld
let actual = graphToExpectedGraph actualGraph
unless (expectedGraph == actual) $
Left $ FailedGraphExpectation expectedGraph actual

View File

@ -38,7 +38,6 @@ da_haskell_library(
"//compiler/damlc/daml-ide-core",
"//compiler/damlc/daml-lf-util",
"//compiler/damlc/daml-rule-types",
"//compiler/damlc/daml-visual",
"//libs-haskell/da-hs-base",
],
)

View File

@ -27,7 +27,6 @@ import Development.IDE.Core.Service.Daml
import Development.IDE.Plugin
import qualified DA.Daml.SessionTelemetry as SessionTelemetry
import qualified DA.Daml.LanguageServer.Visualize as Visualize
import qualified DA.Service.Logger as Lgr
import qualified Network.URI as URI
@ -88,7 +87,7 @@ runLanguageServer
-> (LSP.LanguageContextEnv c -> VFSHandle -> Maybe FilePath -> IO IdeState)
-> IO ()
runLanguageServer lgr plugins conf getIdeState = SessionTelemetry.withPlugin lgr $ \sessionHandlerPlugin -> do
let allPlugins = plugins <> setHandlersKeepAlive <> setHandlersVirtualResource <> VirtualResource.plugin <> Visualize.plugin <> sessionHandlerPlugin
let allPlugins = plugins <> setHandlersKeepAlive <> setHandlersVirtualResource <> VirtualResource.plugin <> sessionHandlerPlugin
let onConfigurationChange c _ = Right c
let options = def { LSP.executeCommandCommands = Just (commandIds allPlugins) }
LS.runLanguageServer options conf onConfigurationChange allPlugins getIdeState

View File

@ -1,37 +0,0 @@
-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
module DA.Daml.LanguageServer.Visualize
( plugin
) where
import Control.Monad.IO.Class
import qualified Data.Aeson as Aeson
import Language.LSP.Types
import Development.IDE.Types.Logger
import qualified Data.Text as T
import Development.IDE.Plugin
import Development.IDE.Core.Rules
import Development.IDE.Core.Rules.Daml
import Development.IDE.Core.Service.Daml
import qualified Language.LSP.Server as LSP
import Development.IDE.Types.Location
import qualified DA.Daml.Visual as Visual
onCommand
:: IdeState
-> FilePath
-> LSP.LspM c (Either ResponseError Aeson.Value)
onCommand ide (toNormalizedFilePath' -> mod) = do
liftIO $ logInfo (ideLogger ide) "Generating visualization for current daml project"
world <- liftIO $ runAction ide (worldForFile mod)
let dots = T.pack $ Visual.dotFileGen world
return $ Right $ Aeson.String dots
plugin :: Plugin c
plugin = Plugin
{ pluginRules = mempty
, pluginCommands = [PluginCommand "daml/damlVisualize" onCommand]
, pluginNotificationHandlers = mempty
, pluginHandlers = mempty
}

View File

@ -56,7 +56,6 @@ da_haskell_library(
"//compiler/damlc/daml-doctest",
"//compiler/damlc/daml-lf-conversion",
"//compiler/damlc/daml-opts:daml-opts-types",
"//compiler/damlc/daml-visual",
"//compiler/scenario-service/client",
"//libs-haskell/bazel-runfiles",
"//libs-haskell/da-hs-base",

View File

@ -1,40 +0,0 @@
# Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
load("//bazel_tools:haskell.bzl", "da_haskell_library")
da_haskell_library(
name = "daml-visual",
srcs = glob(["src/**/*.hs"]),
data = [
"@static_asset_d3plus//:js/d3.min.js",
"@static_asset_d3plus//:js/d3plus.min.js",
],
hackage_deps = [
"aeson",
"base",
"bytestring",
"containers",
"extra",
"filepath",
"mtl",
"open-browser",
"text",
"safe",
"stache",
"unordered-containers",
"utf8-string",
"zip-archive",
"uniplate",
],
src_strip_prefix = "src",
visibility = ["//visibility:public"],
deps = [
"//compiler/daml-lf-ast",
"//compiler/daml-lf-proto",
"//compiler/daml-lf-reader",
"//compiler/daml-lf-tools",
"//libs-haskell/bazel-runfiles",
"//libs-haskell/da-hs-base",
],
)

View File

@ -1,438 +0,0 @@
-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE PatternSynonyms #-}
-- | Main entry-point of the Daml compiler
module DA.Daml.Visual
( execVisual
, tplNameUnqual
, TemplateChoices(..)
, ChoiceAndAction(..)
, Action(..)
, Graph(..)
, SubGraph(..)
, ChoiceDetails(..)
, dotFileGen
, graphFromWorld
, execVisualHtml
) where
import qualified DA.Daml.LF.Ast as LF
import DA.Daml.LF.Ast.World as AST
import DA.Daml.LF.Reader
import qualified Data.NameMap as NM
import qualified Data.Set as Set
import qualified DA.Pretty as DAP
import qualified DA.Daml.LF.Proto3.Archive as Archive
import qualified "zip-archive" Codec.Archive.Zip as ZIPArchive
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString as B
import Data.Generics.Uniplate.Data
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import Data.Tuple.Extra (both)
import GHC.Generics
import Data.Aeson
import Text.Mustache
import qualified Data.Text.Lazy.IO as TIO
import qualified Data.Text.Encoding as DT
import Web.Browser
import DA.Bazel.Runfiles
import System.FilePath
import Safe
import Control.Monad
import Control.Monad.State
type IsConsuming = Bool
data Action = ACreate (LF.Qualified LF.TypeConName)
| AExercise (LF.Qualified LF.TypeConName) LF.ChoiceName deriving (Eq, Ord, Show )
data ChoiceAndAction = ChoiceAndAction
{ choiceName :: LF.ChoiceName
, choiceConsuming :: IsConsuming
, actions :: Set.Set Action
} deriving (Show)
data TemplateChoices = TemplateChoices
{ template :: LF.Qualified LF.Template
, choiceAndActions :: [ChoiceAndAction]
} deriving (Show)
templateId :: TemplateChoices -> LF.Qualified LF.TypeConName
templateId TemplateChoices{..} =
fmap LF.tplTypeCon template
data ChoiceDetails = ChoiceDetails
{ nodeId :: Int
, consuming :: Bool
, displayChoiceName :: LF.ChoiceName
} deriving (Show, Eq)
data SubGraph = SubGraph
{ nodes :: [ChoiceDetails]
, templateFields :: [T.Text]
, clusterTemplate :: LF.Template
} deriving (Show, Eq)
data Graph = Graph
{ subgraphs :: [SubGraph]
, edges :: [(ChoiceDetails, ChoiceDetails)]
} deriving (Show, Eq)
data D3Link = D3Link
{ source :: Int
, target :: Int
, weight :: Int
} deriving (Generic, Show)
-- can add more information like signatories, observers
data D3Node = D3Node
{ fields :: T.Text
, tplName :: T.Text
, id :: Int
, chcName :: T.Text
} deriving (Generic, Show)
data D3Graph = D3Graph
{ d3links :: [D3Link]
, d3nodes :: [D3Node]
} deriving (Generic, Show)
data WebPage = WebPage
{ links :: T.Text
, dnodes :: T.Text
, d3Js :: String
, d3PlusJs :: String
} deriving (Generic, Show)
d3LinksFromGraphEdges :: Graph -> [D3Link]
d3LinksFromGraphEdges g = map edgeToD3Link (edges g)
where edgeToD3Link edge = D3Link (nodeId (fst edge)) (nodeId (snd edge)) 10
d3NodesFromGraph :: Graph -> [D3Node]
d3NodesFromGraph g = concatMap subGraphToD3Nodes (subgraphs g)
where subGraphToD3Nodes sg = map (\chcD ->
D3Node (T.unlines $ templateFields sg)
(tplNameUnqual $ clusterTemplate sg)
(nodeId chcD)
(DAP.renderPretty $ displayChoiceName chcD)
)
(nodes sg)
graphToD3Graph :: Graph -> D3Graph
graphToD3Graph g = D3Graph (d3LinksFromGraphEdges g) (d3NodesFromGraph g)
instance ToJSON D3Link
instance ToJSON D3Node
instance ToJSON D3Graph
instance ToJSON WebPage
startFromUpdate :: Set.Set (LF.Qualified LF.ExprValName) -> LF.World -> LF.Update -> Set.Set Action
startFromUpdate seen world update = case update of
LF.UPure _ e -> startFromExpr seen world e
LF.UBind (LF.Binding _ e1) e2 -> startFromExpr seen world e1 `Set.union` startFromExpr seen world e2
LF.UGetTime -> Set.empty
LF.UEmbedExpr _ upEx -> startFromExpr seen world upEx
LF.UCreate tpl _ -> Set.singleton (ACreate tpl)
LF.UCreateInterface{} ->
error "Interfaces are not supported"
LF.UExercise tpl choice _ _ -> Set.singleton (AExercise tpl choice)
LF.UDynamicExercise {} ->
-- TODO https://github.com/digital-asset/daml/issues/16154 (dynamic-exercise)
error "Dynamic exercise is not supported"
LF.UExerciseInterface{} ->
-- TODO https://github.com/digital-asset/daml/issues/12051
error "Interfaces are not supported"
LF.UExerciseByKey tpl choice _ _ -> Set.singleton (AExercise tpl choice)
LF.UFetch{} -> Set.empty
LF.USoftFetch {} -> Set.empty
LF.UFetchInterface{} ->
-- TODO https://github.com/digital-asset/daml/issues/12051
error "Interfaces are not supported"
LF.ULookupByKey{} -> Set.empty
LF.UFetchByKey{} -> Set.empty
LF.UTryCatch _ e1 _ e2 -> startFromExpr seen world e1 `Set.union` startFromExpr seen world e2
startFromExpr :: Set.Set (LF.Qualified LF.ExprValName) -> LF.World -> LF.Expr -> Set.Set Action
startFromExpr seen world e = case e of
LF.EVar _ -> Set.empty
-- NOTE(MH/RJR): Do not explore the `$fChoice`/`$fTemplate` dictionaries because
-- they contain all the ledger actions and therefore creates too many edges
-- in the graph. We instead detect calls to the `create`, `archive` and
-- `exercise` methods from `Template` and `Choice` instances.
LF.EVal (LF.Qualified _ _ (LF.ExprValName ref))
| any (`T.isPrefixOf` ref)
[ "$fHasCreate"
, "$fHasExercise"
, "$fHasArchive"
, "$fHasFetch" -- also filters out $fHasFetchByKey
, "$fHasLookupByKey"
] -> Set.empty
LF.EVal ref -> case LF.lookupValue ref world of
Right LF.DefValue{..}
| ref `Set.member` seen -> Set.empty
| otherwise -> startFromExpr (Set.insert ref seen) world dvalBody
Left _ -> error "This should not happen"
LF.EUpdate upd -> startFromUpdate seen world upd
-- NOTE(RJR): Look for calls to `create` and `archive` methods from a
-- `Template` instance and produce the corresponding edges in the graph.
EInternalTemplateVal "create" `LF.ETyApp` LF.TCon tpl `LF.ETmApp` _dict
-> Set.singleton (ACreate tpl)
EInternalTemplateVal "archive" `LF.ETyApp` LF.TCon tpl `LF.ETmApp` _dict ->
Set.singleton (AExercise tpl (LF.ChoiceName "Archive"))
-- NOTE(RJR): Look for calls to the `exercise` method from a `Choice`
-- instance and produce the corresponding edge in the graph.
EInternalTemplateVal "exercise" `LF.ETyApp` LF.TCon tpl `LF.ETyApp` LF.TCon (LF.Qualified _ _ (LF.TypeConName [chc])) `LF.ETyApp` _ret `LF.ETmApp` _dict ->
Set.singleton (AExercise tpl (LF.ChoiceName chc))
EInternalTemplateVal "exerciseByKey" `LF.ETyApp` LF.TCon tpl `LF.ETyApp` _ `LF.ETyApp` LF.TCon (LF.Qualified _ _ (LF.TypeConName [chc])) `LF.ETyApp` _ret `LF.ETmApp` _dict ->
Set.singleton (AExercise tpl (LF.ChoiceName chc))
expr -> Set.unions $ map (startFromExpr seen world) $ children expr
pattern EInternalTemplateVal :: T.Text -> LF.Expr
pattern EInternalTemplateVal val <-
LF.EVal (LF.Qualified _pkg (LF.ModuleName ["DA", "Internal", "Template", "Functions"]) (LF.ExprValName val))
startFromChoice :: LF.World -> LF.TemplateChoice -> Set.Set Action
startFromChoice world chc = startFromExpr Set.empty world (LF.chcUpdate chc)
templatePossibleUpdates :: LF.World -> LF.Template -> [ChoiceAndAction]
templatePossibleUpdates world tpl = map toActions $ NM.toList $ LF.tplChoices tpl
where toActions c = ChoiceAndAction {
choiceName = LF.chcName c
, choiceConsuming = LF.chcConsuming c
, actions = startFromChoice world c
}
moduleAndTemplates :: LF.World -> LF.PackageRef -> LF.Module -> [TemplateChoices]
moduleAndTemplates world pkgRef mod =
map (\t -> TemplateChoices (LF.Qualified pkgRef (LF.moduleName mod) t) (templatePossibleUpdates world t))
(NM.toList $ LF.moduleTemplates mod)
dalfBytesToPakage :: BSL.ByteString -> ExternalPackage
dalfBytesToPakage bytes = case Archive.decodeArchive Archive.DecodeAsDependency $ BSL.toStrict bytes of
Right (pkgId, pkg) -> ExternalPackage pkgId pkg
Left err -> error (show err)
darToWorld :: Dalfs -> LF.World
darToWorld Dalfs{..} = case Archive.decodeArchive Archive.DecodeAsMain $ BSL.toStrict mainDalf of
Right (_, mainPkg) -> AST.initWorldSelf pkgs mainPkg
Left err -> error (show err)
where
pkgs = map dalfBytesToPakage dalfs
tplNameUnqual :: LF.Template -> T.Text
tplNameUnqual LF.Template {..} = headNote "tplNameUnqual" (LF.unTypeConName tplTypeCon)
data ChoiceIdentifier = ChoiceIdentifier
{ choiceIdTemplate :: !(LF.Qualified LF.TypeConName)
, choiceIdName :: !LF.ChoiceName
} deriving (Eq, Show, Ord)
choiceNameWithId :: [TemplateChoices] -> Map.Map ChoiceIdentifier ChoiceDetails
choiceNameWithId tplChcActions = Map.unions (evalState (mapM f tplChcActions) 0)
where
f :: TemplateChoices -> State Int (Map.Map ChoiceIdentifier ChoiceDetails)
f tpl@TemplateChoices{..} = do
choices <- forM (createChoice : choiceAndActions) $ \ChoiceAndAction{..} -> do
id <- get
put (id + 1)
let choiceId = ChoiceIdentifier (templateId tpl) choiceName
pure (choiceId, ChoiceDetails id choiceConsuming choiceName)
pure (Map.fromList choices)
createChoice = ChoiceAndAction
{ choiceName = LF.ChoiceName "Create"
, choiceConsuming = False
, actions = Set.empty
}
nodeIdForChoice :: Map.Map ChoiceIdentifier ChoiceDetails -> ChoiceIdentifier -> ChoiceDetails
nodeIdForChoice nodeLookUp chc = case Map.lookup chc nodeLookUp of
Just node -> node
Nothing -> error "Template node lookup failed"
addCreateChoice :: TemplateChoices -> Map.Map ChoiceIdentifier ChoiceDetails -> ChoiceDetails
addCreateChoice tpl lookupData = nodeIdForChoice lookupData tplNameCreateChoice
where
tplNameCreateChoice =
ChoiceIdentifier
(templateId tpl)
createChoiceName
labledField :: T.Text -> T.Text -> T.Text
labledField fname "" = fname
labledField fname label = fname <> "." <> label
typeConFieldsNames :: LF.World -> (LF.FieldName, LF.Type) -> [T.Text]
typeConFieldsNames world (LF.FieldName fName, LF.TConApp tcn _) = map (labledField fName) (typeConFields tcn world)
typeConFieldsNames _ (LF.FieldName fName, _) = [fName]
-- TODO: Anup This will fail if we were to recursively continue exploring the AST.
typeConFields :: LF.Qualified LF.TypeConName -> LF.World -> [T.Text]
typeConFields qName world = case LF.lookupDataType qName world of
Right dataType -> case LF.dataCons dataType of
LF.DataRecord re -> concatMap (typeConFieldsNames world) re
LF.DataVariant _ -> [""]
LF.DataEnum _ -> [""]
-- TODO https://github.com/digital-asset/daml/issues/12051
LF.DataInterface -> error "interfaces are not implemented"
Left _ -> error "malformed template constructor"
constructSubgraphsWithLables :: LF.World -> Map.Map ChoiceIdentifier ChoiceDetails -> TemplateChoices -> SubGraph
constructSubgraphsWithLables wrld lookupData tpla@TemplateChoices {..} =
SubGraph (addCreateChoice tpla lookupData : choices) fieldsInTemplate (LF.qualObject template)
where
fieldsInTemplate = typeConFields (templateId tpla) wrld
choicesInTemplate =
map (\c -> ChoiceIdentifier (templateId tpla) (choiceName c))
choiceAndActions
choices = map (nodeIdForChoice lookupData) choicesInTemplate
createChoiceName :: LF.ChoiceName
createChoiceName = LF.ChoiceName "Create"
actionToChoice :: Action -> ChoiceIdentifier
actionToChoice (ACreate tpl) =
ChoiceIdentifier tpl createChoiceName
actionToChoice (AExercise tpl chcT) =
ChoiceIdentifier tpl chcT
choiceActionToChoicePairs :: LF.Qualified LF.TypeConName -> ChoiceAndAction -> [(ChoiceIdentifier, ChoiceIdentifier)]
choiceActionToChoicePairs tpl ChoiceAndAction{..} =
map (\a -> (choiceId, actionToChoice a)) (Set.elems actions)
where
choiceId = ChoiceIdentifier tpl choiceName
graphEdges :: Map.Map ChoiceIdentifier ChoiceDetails -> [TemplateChoices] -> [(ChoiceDetails, ChoiceDetails)]
graphEdges lookupData tplChcActions =
map (both (nodeIdForChoice lookupData)) $
concat $
concatMap
(\tpl -> map (choiceActionToChoicePairs (templateId tpl)) (choiceAndActions tpl))
tplChcActions
subGraphHeader :: SubGraph -> String
subGraphHeader sg = "subgraph cluster_" ++ (DAP.renderPretty $ head (LF.unTypeConName $ LF.tplTypeCon $ clusterTemplate sg)) ++ "{\n"
choiceDetailsColorCode :: IsConsuming -> String
choiceDetailsColorCode True = "red"
choiceDetailsColorCode False = "green"
subGraphBodyLine :: ChoiceDetails -> String
subGraphBodyLine chc = "n" ++ show (nodeId chc)++ "[label=" ++ DAP.renderPretty (displayChoiceName chc) ++"][color=" ++ choiceDetailsColorCode (consuming chc) ++"]; "
subGraphEnd :: SubGraph -> String
subGraphEnd sg = "label=<" ++ tHeader ++ tTitle ++ tBody ++ tclose ++ ">" ++ ";color=" ++ "blue" ++ "\n}"
where tHeader = "<table align = \"left\" border=\"0\" cellborder=\"0\" cellspacing=\"1\">\n"
tTitle = "<tr><td align=\"center\"><b>" ++ DAP.renderPretty (LF.tplTypeCon $ clusterTemplate sg) ++ "</b></td></tr>"
tBody = concatMap fieldTableLine (templateFields sg)
fieldTableLine field = "<tr><td align=\"left\">" ++ T.unpack field ++ "</td></tr> \n"
tclose = "</table>"
subGraphCluster :: SubGraph -> String
subGraphCluster sg@SubGraph {..} = subGraphHeader sg ++ unlines (map subGraphBodyLine nodes) ++ subGraphEnd sg
drawEdge :: ChoiceDetails -> ChoiceDetails -> String
drawEdge n1 n2 = "n" ++ show (nodeId n1) ++ "->" ++ "n" ++ show (nodeId n2)
constructDotGraph :: Graph -> String
constructDotGraph graph = "digraph G {\ncompound=true;\n" ++ "rankdir=LR;\n"++ graphLines ++ "\n}\n"
where subgraphsLines = concatMap subGraphCluster (subgraphs graph)
edgesLines = unlines $ map (uncurry drawEdge) (edges graph)
graphLines = subgraphsLines ++ edgesLines
graphFromWorld :: LF.World -> Graph
graphFromWorld world = Graph subGraphs edges
where
templatesAndModules = concat
[ moduleAndTemplates world pkgRef mod
| (pkgRef, pkg) <- pkgs
, mod <- NM.toList $ LF.packageModules pkg
]
nodes = choiceNameWithId templatesAndModules
subGraphs = map (constructSubgraphsWithLables world nodes) templatesAndModules
edges = graphEdges nodes templatesAndModules
pkgs =
(LF.PRSelf, getWorldSelf world)
: map (\ExternalPackage{..} -> (LF.PRImport extPackageId, extPackagePkg))
(getWorldImported world)
dotFileGen :: LF.World -> String
dotFileGen world = constructDotGraph $ graphFromWorld world
webPageTemplate :: T.Text
webPageTemplate =
T.unlines [ "<html>"
, "<head><title>Daml Visualization</title><meta charset=\"utf-8\"></head>"
, "<body>"
, "<div id='viz'></div>"
, "<script>"
, "{{{d3Js}}}"
, "</script>"
, "<script>"
, "{{{d3PlusJs}}}"
, "</script>"
, "<script>"
, "var nodes = {{{dnodes}}}"
, "var links = {{{links}}}"
, "d3plus.viz()"
, " .container('#viz')"
, " .type('network')"
, " .data(nodes)"
, " .text('chcName')"
, " .edges({ value: links, arrows: true })"
, " .tooltip({"
, " Template: function (d) { return d['tplName'] },"
, " Fields: function (d) { return d['fields']; }"
, " })"
, " .draw();"
, "</script>"
, "</body>"
, "</html>"
]
type OpenBrowserFlag = Bool
execVisualHtml :: FilePath -> FilePath -> OpenBrowserFlag -> IO ()
execVisualHtml darFilePath webFilePath oBrowser = do
darBytes <- B.readFile darFilePath
dalfs <- either fail pure $
readDalfs $ ZIPArchive.toArchive (BSL.fromStrict darBytes)
d3js <- readFile =<< locateResource Resource
-- @static_asset_d3plus//:js/d3.min.js
{ resourcesPath = "d3.min.js"
-- In a packaged application, this is stored directly underneath the
-- resources directory because it's a single file.
-- See @bazel_tools/packaging/packaging.bzl@.
, runfilesPathPrefix = "static_asset_d3plus" </> "js"
}
d3plusjs <- readFile =<< locateResource Resource
-- @static_asset_d3plus//:js/d3plus.min.js
{ resourcesPath = "d3plus.min.js"
-- as above
, runfilesPathPrefix = "static_asset_d3plus" </> "js"
}
let world = darToWorld dalfs
graph = graphFromWorld world
d3G = graphToD3Graph graph
linksJson = DT.decodeUtf8 $ BSL.toStrict $ encode $ d3links d3G
nodesJson = DT.decodeUtf8 $ BSL.toStrict $ encode $ d3nodes d3G
webPage = WebPage linksJson nodesJson d3js d3plusjs
case compileMustacheText "Webpage" webPageTemplate of
Left err -> error $ show err
Right mTpl -> do
TIO.writeFile webFilePath $ renderMustache mTpl $ toJSON webPage
when oBrowser
(do _ <- openBrowser webFilePath
return ())
execVisual :: FilePath -> Maybe FilePath -> IO ()
execVisual darFilePath dotFilePath = do
darBytes <- B.readFile darFilePath
dalfs <- either fail pure $ readDalfs $ ZIPArchive.toArchive (BSL.fromStrict darBytes)
let world = darToWorld dalfs
result = dotFileGen world
case dotFilePath of
Just outDotFile -> writeFile outDotFile result
Nothing -> putStrLn result

View File

@ -1,29 +0,0 @@
### Dev Setup
The dev setup is not automated, to get a local dev env follow steps.
cd compiler/damlc/daml-visual/src/DA/d3-dev
wget https://github.com/alexandersimoes/d3plus/releases/download/v1.9.8/d3plus.zip
unzip d3plus.zip
python -m http.server --directory .
open http://0.0.0.0:8000/d3-dev.html
If there are any changes to be made to the JS, they will have to be made to the webpage template within `Visual.hs` module.
Note: We can automate this with using npm/yarn but is not done as of yet as we are not dealing with a lot of JS/HTML/CSS code. If we add more customization it might be worth the effort to setup a automated dev setup.
### About D3 and D3Plus and versions used in this project
[D3JS](https://d3js.org/) is a popular Data visualization library and [D3Plus](https://github.com/alexandersimoes/d3plus) gives simple api with reasonable defaults to draw SVG.
The version currently we are using is `1.9.8` and the next version of `D3Plus` is `2.0.0` which takes compositional style i.e. lets us pick the libs (tooltips, legends etc.) that we need. The reason we are not using latest(`2.0.0`) is the composition of libs is not very well documented and lacks advanced examples and also it is currently labeled as beta.
### Why are JS files packed into one HTML and inlined?
- This makes sharing the file a lot easy
- We don't have to worry about webserver on client machine as we can get away with just opening a file in browser vs referencing local files on disk which is restricted by CQRS policy.
- There might be performance issues with larger projects.
### Helpful Links
* [Getting Started](https://d3plus.org/blog/getting-started/2014/06/12/getting-started-1/)
* [Live Examples](https://d3plus.org/examples/)
* [Documentation](https://github.com/alexandersimoes/d3plus/wiki)
* [Google Group Discussions](https://groups.google.com/forum/#!forum/d3plus)

View File

@ -1,63 +0,0 @@
<!-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
<html>
<head>
<title>Daml Visualization</title>
<meta charset="utf-8">
<script src="/d3plus.v1.9.8/js/d3.js"></script>
<script src="/d3plus.v1.9.8/js/d3plus.js"></script>
</head>
<body>
<div id='viz'></div>
<script>
var nodes = [
{ "fields": "buyer\nseller\nbaseIouCid\nbaseIssuer\nbaseCurrency\nbaseAmount\nquoteIssuer\nquoteCurrency\nquoteAmount\n", "tplName": "IouTrade", "id": 0, "chcName": "Create" },
{ "fields": "buyer\nseller\nbaseIouCid\nbaseIssuer\nbaseCurrency\nbaseAmount\nquoteIssuer\nquoteCurrency\nquoteAmount\n", "tplName": "IouTrade", "id": 1, "chcName": "Archive" },
{ "fields": "buyer\nseller\nbaseIouCid\nbaseIssuer\nbaseCurrency\nbaseAmount\nquoteIssuer\nquoteCurrency\nquoteAmount\n", "tplName": "IouTrade", "id": 2, "chcName": "IouTrade_Accept" },
{ "fields": "buyer\nseller\nbaseIouCid\nbaseIssuer\nbaseCurrency\nbaseAmount\nquoteIssuer\nquoteCurrency\nquoteAmount\n", "tplName": "IouTrade", "id": 3, "chcName": "TradeProposal_Reject" }, { "fields": "issuer\nowner\ncurrency\namount\nobservers\n", "tplName": "Iou", "id": 4, "chcName": "Create" },
{ "fields": "issuer\nowner\ncurrency\namount\nobservers\n", "tplName": "Iou", "id": 5, "chcName": "Archive" },
{ "fields": "issuer\nowner\ncurrency\namount\nobservers\n", "tplName": "Iou", "id": 6, "chcName": "Iou_Split" },
{ "fields": "issuer\nowner\ncurrency\namount\nobservers\n", "tplName": "Iou", "id": 7, "chcName": "Iou_Merge" },
{ "fields": "issuer\nowner\ncurrency\namount\nobservers\n", "tplName": "Iou", "id": 8, "chcName": "Iou_Transfer" },
{ "fields": "issuer\nowner\ncurrency\namount\nobservers\n", "tplName": "Iou", "id": 9, "chcName": "Iou_AddObserver" },
{ "fields": "issuer\nowner\ncurrency\namount\nobservers\n", "tplName": "Iou", "id": 10, "chcName": "Iou_RemoveObserver" },
{ "fields": "iou.issuer\niou.owner\niou.currency\niou.amount\niou.observers\nnewOwner\n", "tplName": "IouTransfer", "id": 11, "chcName": "Create" },
{ "fields": "iou.issuer\niou.owner\niou.currency\niou.amount\niou.observers\nnewOwner\n", "tplName": "IouTransfer", "id": 12, "chcName": "Archive" },
{ "fields": "iou.issuer\niou.owner\niou.currency\niou.amount\niou.observers\nnewOwner\n", "tplName": "IouTransfer", "id": 13, "chcName": "IouTransfer_Cancel" },
{ "fields": "iou.issuer\niou.owner\niou.currency\niou.amount\niou.observers\nnewOwner\n", "tplName": "IouTransfer", "id": 14, "chcName": "IouTransfer_Reject" },
{ "fields": "iou.issuer\niou.owner\niou.currency\niou.amount\niou.observers\nnewOwner\n", "tplName": "IouTransfer", "id": 15, "chcName": "IouTransfer_Accept" }
]
var links = [
{ "source": 2, "target": 8, "weight": 10 },
{ "source": 2, "target": 15, "weight": 10 },
{ "source": 6, "target": 4, "weight": 10 },
{ "source": 7, "target": 4, "weight": 10 },
{ "source": 7, "target": 5, "weight": 10 },
{ "source": 8, "target": 11, "weight": 10 },
{ "source": 9, "target": 4, "weight": 10 },
{ "source": 10, "target": 4, "weight": 10 },
{ "source": 13, "target": 4, "weight": 10 },
{ "source": 14, "target": 4, "weight": 10 },
{ "source": 15, "target": 4, "weight": 10 }
]
d3plus.viz()
.container('#viz')
.type('network')
.data(nodes)
.text('chcName')
.edges({ value: links, arrows: true })
.tooltip({
Template: function (d) { return d['tplName'] },
Fields: function (d) { return d['fields']; }
})
.draw();
</script>
</body>
</html>

View File

@ -41,7 +41,6 @@ import DA.Daml.Package.Config
import DA.Daml.Project.Config
import DA.Daml.Project.Consts
import DA.Daml.Project.Types (ConfigError(..), ProjectPath(..))
import DA.Daml.Visual
import qualified DA.Pretty
import qualified DA.Service.Logger as Logger
import qualified DA.Service.Logger.Impl.GCP as Logger.GCP
@ -121,7 +120,6 @@ data CommandName =
| MergeDars
| Package
| Test
| Visual
| Repl
deriving (Ord, Show, Eq)
data Command = Command CommandName (Maybe ProjectOpts) (IO ())
@ -294,20 +292,6 @@ cmdInspect =
optional $ optionOnce auto $ long "detail" <> metavar "LEVEL" <> help "Detail level of the pretty printed output (default: 0)"
cmd = execInspect <$> inputFileOptWithExt ".dalf or .dar" <*> outputFileOpt <*> jsonOpt <*> detailOpt
cmdVisual :: Mod CommandFields Command
cmdVisual =
command "visual" $ info (helper <*> cmd) $ progDesc "Early Access (Labs). Generate visual from dar" <> fullDesc
where
cmd = vis <$> inputDarOpt <*> dotFileOpt
vis a b = Command Visual Nothing $ execVisual a b
cmdVisualWeb :: Mod CommandFields Command
cmdVisualWeb =
command "visual-web" $ info (helper <*> cmd) $ progDesc "Early Access (Labs). Generate D3-Web Visual from dar" <> fullDesc
where
cmd = vis <$> inputDarOpt <*> htmlOutFile <*> openBrowser
vis a b browser = Command Visual Nothing $ execVisualHtml a b browser
cmdBuild :: Int -> Mod CommandFields Command
cmdBuild numProcessors =
command "build" $
@ -1033,8 +1017,6 @@ options numProcessors =
<> cmdBuild numProcessors
<> cmdTest numProcessors
<> Damldoc.cmd numProcessors (\cli -> Command DamlDoc Nothing $ Damldoc.exec cli)
<> cmdVisual
<> cmdVisualWeb
<> cmdInspectDar
<> cmdValidateDar
<> cmdDocTest numProcessors
@ -1044,8 +1026,6 @@ options numProcessors =
<|> subparser
(internal -- internal commands
<> cmdInspect
<> cmdVisual
<> cmdVisualWeb
<> cmdMergeDars
<> cmdInit numProcessors
<> cmdCompile numProcessors
@ -1122,7 +1102,6 @@ cmdUseDamlYamlArgs = \case
MergeDars -> False -- just reads the dars
Package -> False -- deprecated
Test -> True
Visual -> False -- just reads the dar
Repl -> True
withProjectRoot' :: ProjectOpts -> ((FilePath -> IO FilePath) -> IO a) -> IO a

View File

@ -45,38 +45,6 @@ da_haskell_test(
],
)
# Tests for visualization
da_haskell_test(
name = "visualization",
srcs = ["src/DamlcVisualize.hs"],
data = [
"//compiler/damlc",
],
hackage_deps = [
"base",
"bytestring",
"directory",
"either",
"extra",
"filepath",
"tasty",
"tasty-expected-failure",
"tasty-hunit",
"zip-archive",
],
main_function = "DamlcVisualize.main",
visibility = ["//visibility:private"],
deps = [
"//:sdk-version-hs-lib",
"//compiler/daml-lf-ast",
"//compiler/daml-lf-proto",
"//compiler/daml-lf-reader",
"//compiler/damlc/daml-ide-core:ide-testing",
"//libs-haskell/bazel-runfiles",
"//libs-haskell/test-utils",
],
)
# Tests for damlc test
da_haskell_test(
name = "damlc-test",
@ -366,7 +334,6 @@ da_haskell_test(
"//compiler/damlc/daml-ide-core",
"//compiler/damlc/daml-ide-core:ide-testing",
"//compiler/damlc/daml-opts:daml-opts-types",
"//compiler/damlc/daml-visual",
"//compiler/scenario-service/client",
"//libs-haskell/da-hs-base",
],

View File

@ -52,7 +52,6 @@ ideTests mbScenarioService scriptPackageData =
, onHoverTests mbScenarioService scriptPackageData
, dlintSmokeTests mbScenarioService
, scriptTests mbScenarioService scriptPackageData
, visualDamlTests
]
addScriptOpts :: Maybe ScriptPackageData -> Daml.Options -> Daml.Options
@ -1332,168 +1331,3 @@ scriptTests mbScenarioService scriptPackageData = Tasty.testGroup "Script tests"
where
testCase' = testCase mbScenarioService (Just scriptPackageData)
testCaseFails' = testCaseFails mbScenarioService (Just scriptPackageData)
visualDamlTests :: Tasty.TestTree
visualDamlTests = Tasty.testGroup "Visual Tests"
[ testCase' "Template with no actions (edges) from choices" $ do
foo <- makeModule "F"
[ "template Coin"
, " with"
, " owner : Party"
, " where"
, " signatory owner"
, " choice Delete : ()"
, " controller owner"
, " do return ()"
]
setFilesOfInterest [foo]
expectedGraph foo (
ExpectedGraph {expectedSubgraphs =
[ExpectedSubGraph {expectedNodes = ["Create","Archive","Delete"]
, expectedTplFields = ["owner"]
, expectedTemplate = "Coin"}
]
, expectedEdges = []})
, testCase' "Fetch shoud not be an create action" $ do
fetchTest <- makeModule "F"
[ "template Coin"
, " with"
, " owner : Party"
, " amount : Int"
, " where"
, " signatory owner"
, " nonconsuming choice ReducedCoin : ()"
, " with otherCoin : ContractId Coin"
, " controller owner"
, " do cn <- fetch otherCoin"
, " return ()"
]
setFilesOfInterest [fetchTest]
expectNoErrors
expectedGraph fetchTest ( ExpectedGraph {expectedSubgraphs =
[ExpectedSubGraph {expectedNodes = ["Create","Archive","ReducedCoin"]
, expectedTplFields = ["owner","amount"]
, expectedTemplate = "Coin"}]
, expectedEdges = []})
, testCase' "Exercise should add an edge" $ do
exerciseTest <- makeModule "F"
[ "template TT"
, " with"
, " owner : Party"
, " where"
, " signatory owner"
, " choice Consume : ()"
, " with coinId : ContractId Coin"
, " controller owner"
, " do exercise coinId Delete"
, "template Coin"
, " with"
, " owner : Party"
, " where"
, " signatory owner"
, " choice Delete : ()"
, " controller owner"
, " do return ()"
]
setFilesOfInterest [exerciseTest]
expectNoErrors
expectedGraph exerciseTest (ExpectedGraph
[ ExpectedSubGraph { expectedNodes = ["Create", "Archive", "Delete"]
, expectedTplFields = ["owner"]
, expectedTemplate = "Coin"
}
, ExpectedSubGraph { expectedNodes = ["Create", "Consume", "Archive"]
, expectedTplFields = ["owner"]
, expectedTemplate = "TT"}]
[(ExpectedChoiceDetails {expectedConsuming = True
, expectedName = "Consume"},
ExpectedChoiceDetails {expectedConsuming = True
, expectedName = "Delete"})
])
-- test case taken from #5726
, testCase' "ExerciseByKey should add an edge" $ do
exerciseByKeyTest <- makeModule "F"
[ "template Ping"
, " with"
, " party : Party"
, " where"
, " signatory party"
, " key party: Party"
, " maintainer key"
, ""
, " nonconsuming choice ArchivePong : ()"
, " with"
, " pong : ContractId Pong"
, " controller party"
, " do exercise pong Archive"
, ""
, "template Pong"
, " with"
, " party : Party"
, " where"
, " signatory party"
, ""
, " nonconsuming choice ArchivePing : ()"
, " with"
, " pingParty : Party"
, " controller party"
, " do exerciseByKey @Ping pingParty Archive"
]
setFilesOfInterest [exerciseByKeyTest]
expectNoErrors
expectedGraph exerciseByKeyTest (ExpectedGraph
[ ExpectedSubGraph { expectedNodes = ["Create", "ArchivePong", "Archive"]
, expectedTplFields = ["party"]
, expectedTemplate = "Ping"
}
, ExpectedSubGraph { expectedNodes = ["Create", "Archive", "ArchivePing"]
, expectedTplFields = ["party"]
, expectedTemplate = "Pong"}
]
[ (ExpectedChoiceDetails {expectedConsuming = False
, expectedName = "ArchivePong"},
ExpectedChoiceDetails {expectedConsuming = True
, expectedName = "Archive"})
, (ExpectedChoiceDetails {expectedConsuming = False
, expectedName = "ArchivePing"},
ExpectedChoiceDetails {expectedConsuming = True
, expectedName = "Archive"})
])
, testCase' "Create on other template should be edge" $ do
createTest <- makeModule "F"
[ "template TT"
, " with"
, " owner : Party"
, " where"
, " signatory owner"
, " choice CreateCoin : ContractId Coin"
, " controller owner"
, " do create Coin with owner"
, "template Coin"
, " with"
, " owner : Party"
, " where"
, " signatory owner"
]
setFilesOfInterest [createTest]
expectNoErrors
expectedGraph createTest (ExpectedGraph
{expectedSubgraphs = [ExpectedSubGraph { expectedNodes = ["Create","Archive"]
, expectedTplFields = ["owner"]
, expectedTemplate = "Coin"}
,ExpectedSubGraph { expectedNodes = ["Create","Archive","CreateCoin"]
, expectedTplFields = ["owner"]
, expectedTemplate = "TT"
}
]
, expectedEdges = [(ExpectedChoiceDetails {expectedConsuming = True, expectedName = "CreateCoin"}
,ExpectedChoiceDetails {expectedConsuming = False, expectedName = "Create"})]})
]
where
testCase' = testCase Nothing Nothing
-- | Suppress unused binding warning in case we run out of tests for open issues.
_suppressUnusedWarning :: ()
_suppressUnusedWarning = testCaseFails `seq` ()

View File

@ -1,248 +0,0 @@
-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
-- cache-reset: 1
module DamlcVisualize (main) where
{- HLINT ignore "locateRunfiles/package_app" -}
import qualified "zip-archive" Codec.Archive.Zip as Zip
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Either.Combinators
import DA.Bazel.Runfiles
import qualified DA.Daml.LF.Proto3.Archive as Archive
import Development.IDE.Core.API.Testing.Visualize
import DA.Daml.LF.Ast
( ExternalPackage(..)
, World
, initWorldSelf
)
import DA.Daml.LF.Reader (Dalfs(..), readDalfs)
import DA.Test.Process
import SdkVersion
import System.Directory
import System.Environment.Blank
import System.FilePath
import System.IO.Extra
import Test.Tasty
import Test.Tasty.HUnit
main :: IO ()
main = do
setEnv "TASTY_NUM_THREADS" "1" True
damlc <- locateRunfiles (mainWorkspace </> "compiler" </> "damlc" </> exe "damlc")
defaultMain $ testGroup "visualize"
[ testCase "single package" $ withTempDir $ \dir -> do
writeFileUTF8 (dir </> "daml.yaml") $ unlines
[ "sdk-version: " <> sdkVersion
, "name: foobar"
, "source: ."
, "version: 0.0.1"
, "dependencies: [daml-stdlib, daml-prim]"
]
writeFileUTF8 (dir </> "A.daml") $ unlines
[ "module A where"
, "import B"
, "template A with"
, " p : Party"
, " where"
, " signatory p"
, " choice CreateB : ContractId B"
, " controller p"
, " do create B with p"
]
writeFileUTF8 (dir </> "B.daml") $ unlines
[ "module B where"
, "template B with"
, " p : Party"
, " where"
, " signatory p"
]
withCurrentDirectory dir $ callProcessSilent damlc ["build", "-o", "foobar.dar"]
testFile (dir </> "foobar.dar") ExpectedGraph
{ expectedSubgraphs =
[ ExpectedSubGraph
{ expectedNodes = ["Create", "Archive"]
, expectedTplFields = ["p"]
, expectedTemplate = "B"
}
, ExpectedSubGraph
{ expectedNodes = ["Create", "Archive", "CreateB"]
, expectedTplFields = ["p"]
, expectedTemplate = "A"
}
]
, expectedEdges =
[ ( ExpectedChoiceDetails
{ expectedConsuming = True
, expectedName = "CreateB"
}
, ExpectedChoiceDetails
{ expectedConsuming = False
, expectedName = "Create"
}
)
]
}
, multiPackageTests damlc
]
multiPackageTests :: FilePath -> TestTree
multiPackageTests damlc = testGroup "multiple packages"
[ testCase "different module names" $ withTempDir $ \dir -> do
createDirectory (dir </> "foobar-a")
createDirectory (dir </> "foobar-b")
writeFileUTF8 (dir </> "foobar-b" </> "daml.yaml") $ unlines
[ "sdk-version: " <> sdkVersion
, "name: foobar-b"
, "source: ."
, "version: 0.0.1"
, "dependencies: [daml-stdlib, daml-prim]"
]
writeFileUTF8 (dir </> "foobar-b" </> "B.daml") $ unlines
[ "module B where"
, "template B with"
, " p : Party"
, " where"
, " signatory p"
]
withCurrentDirectory (dir </> "foobar-b") $ callProcessSilent damlc ["build", "-o", "foobar-b.dar"]
writeFileUTF8 (dir </> "foobar-a" </> "daml.yaml") $ unlines
[ "sdk-version: " <> sdkVersion
, "name: foobar-a"
, "source: ."
, "version: 0.0.1"
, "dependencies: [daml-stdlib, daml-prim, " <>
show (dir </> "foobar-b" </> "foobar-b.dar") <> "]"
]
writeFileUTF8 (dir </> "foobar-a" </> "A.daml") $ unlines
[ "module A where"
, "import B"
, "template A with"
, " p : Party"
, " where"
, " signatory p"
, " choice CreateB : ContractId B"
, " controller p"
, " do create B with p"
]
withCurrentDirectory (dir </> "foobar-a") $ callProcessSilent damlc ["build", "-o", "foobar-a.dar"]
testFile (dir </> "foobar-a" </> "foobar-a.dar") ExpectedGraph
{ expectedSubgraphs =
[ ExpectedSubGraph
{ expectedNodes = ["Create", "Archive", "CreateB"]
, expectedTplFields = ["p"]
, expectedTemplate = "A"
}
, ExpectedSubGraph
{ expectedNodes = ["Create", "Archive"]
, expectedTplFields = ["p"]
, expectedTemplate = "B"
}
]
, expectedEdges =
[ ( ExpectedChoiceDetails
{ expectedConsuming = True
, expectedName = "CreateB"
}
, ExpectedChoiceDetails
{ expectedConsuming = False
, expectedName = "Create"
}
)
]
}
, testCase "same module names" $ withTempDir $ \dir -> do
createDirectory (dir </> "foobar-a")
createDirectory (dir </> "foobar-b")
writeFileUTF8 (dir </> "foobar-b" </> "daml.yaml") $ unlines
[ "sdk-version: " <> sdkVersion
, "name: foobar-b"
, "source: ."
, "version: 0.0.1"
, "dependencies: [daml-stdlib, daml-prim]"
]
writeFileUTF8 (dir </> "foobar-b" </> "A.daml") $ unlines
[ "module A where"
, "template T with"
, " p : Party"
, " where"
, " signatory p"
]
withCurrentDirectory (dir </> "foobar-b") $ callProcessSilent damlc ["build", "-o", "foobar-b.dar"]
writeFileUTF8 (dir </> "foobar-a" </> "daml.yaml") $ unlines
[ "sdk-version: " <> sdkVersion
, "name: foobar-a"
, "source: ."
, "version: 0.0.1"
, "dependencies: [daml-stdlib, daml-prim, " <>
show (dir </> "foobar-b" </> "foobar-b.dar") <> "]"
]
writeFileUTF8 (dir </> "foobar-a" </> "A.daml") $ unlines
[ "module A where"
, "import qualified \"foobar-b\" A as AA"
, "template T with"
, " p : Party"
, " where"
, " signatory p"
, " choice CreateB : ContractId AA.T"
, " controller p"
, " do create AA.T with p"
]
withCurrentDirectory (dir </> "foobar-a") $ callProcessSilent damlc ["build", "-o", "foobar-a.dar"]
testFile (dir </> "foobar-a" </> "foobar-a.dar") ExpectedGraph
{ expectedSubgraphs =
[ ExpectedSubGraph
{ expectedNodes = ["Create", "Archive", "CreateB"]
, expectedTplFields = ["p"]
, expectedTemplate = "T"
}
, ExpectedSubGraph
{ expectedNodes = ["Create", "Archive"]
, expectedTplFields = ["p"]
, expectedTemplate = "T"
}
]
, expectedEdges =
[ ( ExpectedChoiceDetails
{ expectedConsuming = True
, expectedName = "CreateB"
}
, ExpectedChoiceDetails
{ expectedConsuming = False
, expectedName = "Create"
}
)
]
}
]
testFile :: FilePath -> ExpectedGraph -> Assertion
testFile dar expected = do
darBytes <- BS.readFile dar
dalfs <- either fail pure $ readDalfs $ Zip.toArchive (BSL.fromStrict darBytes)
!world <- pure $ darToWorld dalfs
whenLeft (graphTest world expected) $
\(FailedGraphExpectation expected actual) ->
assertFailure $ unlines
[ "Failed graph expectation:"
, "Expected:"
, show expected
, "Actual:"
, show actual
]
dalfBytesToPakage :: BSL.ByteString -> ExternalPackage
dalfBytesToPakage bytes = case Archive.decodeArchive Archive.DecodeAsDependency $ BSL.toStrict bytes of
Right (pkgId, pkg) -> ExternalPackage pkgId pkg
Left err -> error (show err)
darToWorld :: Dalfs -> World
darToWorld Dalfs{..} = case Archive.decodeArchive Archive.DecodeAsMain $ BSL.toStrict mainDalf of
Right (_, mainPkg) -> initWorldSelf pkgs mainPkg
Left err -> error (show err)
where
pkgs = map dalfBytesToPakage dalfs

View File

@ -742,25 +742,7 @@ executeCommandTests
:: (Session () -> IO ())
-> TestTree
executeCommandTests run = testGroup "execute command"
[ testCase "execute commands" $ run $ do
main' <- openDoc' "Coin.daml" damlId $ T.unlines
[ "module Coin where"
, "template Coin"
, " with"
, " owner : Party"
, " where"
, " signatory owner"
, " choice Delete : ()"
, " controller owner"
, " do return ()"
]
Just escapedFp <- pure $ uriToFilePath (main' ^. uri)
actualDotString <- LSP.request SWorkspaceExecuteCommand $ ExecuteCommandParams
Nothing "daml/damlVisualize" (Just (List [Aeson.String $ T.pack escapedFp]))
let expectedDotString = "digraph G {\ncompound=true;\nrankdir=LR;\nsubgraph cluster_Coin{\nn0[label=Create][color=green]; \nn1[label=Archive][color=red]; \nn2[label=Delete][color=red]; \nlabel=<<table align = \"left\" border=\"0\" cellborder=\"0\" cellspacing=\"1\">\n<tr><td align=\"center\"><b>Coin</b></td></tr><tr><td align=\"left\">owner</td></tr> \n</table>>;color=blue\n}\n}\n"
liftIO $ assertEqual "Visulization command" (Right expectedDotString) (_result actualDotString)
closeDoc main'
, testCase "Invalid commands result in error" $ run $ do
[ testCase "Invalid commands result in error" $ run $ do
main' <- openDoc' "Empty.daml" damlId $ T.unlines
[ "module Empty where"
]
@ -769,10 +751,6 @@ executeCommandTests run = testGroup "execute command"
Nothing "daml/NoCommand" (Just (List [Aeson.String $ T.pack escapedFp]))
liftIO $ assertBool "Expected response error but got success" (isLeft $ _result actualDotString)
closeDoc main'
, testCase "Visualization command with no arguments" $ run $ do
actualDotString <- LSP.request SWorkspaceExecuteCommand $ ExecuteCommandParams
Nothing "daml/damlVisualize" Nothing
liftIO $ assertBool "Expected response error but got Nothing" (isLeft $ _result actualDotString)
]
-- | Do extreme things to the compiler service.

View File

@ -116,9 +116,6 @@ quickstartTests quickstartDir mvnDir getSandbox =
-- Testing `daml new` and `daml build` is done when the QuickSandboxResource is build.
subtest "daml damlc test --files" $
callCommandSilentIn quickstartDir "daml damlc test --files daml/Main.daml"
subtest "daml damlc visual-web" $
callCommandSilentIn quickstartDir
"daml damlc visual-web .daml/dist/quickstart-0.0.1.dar -o visual.html -b"
subtest "mvn compile" $ do
mvnDbTarball <-
locateRunfiles