├── .AL-Go ├── cloudDevEnv.ps1 ├── localDevEnv.ps1 └── settings.json ├── .github ├── AL-Go-Settings.json ├── ISSUE_TEMPLATE │ ├── BugReport.yml │ ├── FeatureSuggestion.yml │ └── config.yml ├── RELEASENOTES.copy.md ├── Test Current.settings.json ├── Test Next Major.settings.json ├── Test Next Minor.settings.json ├── copilot-instructions.md ├── pull_request_template.md └── workflows │ ├── AddExistingAppOrTestApp.yaml │ ├── CICD.yaml │ ├── CreateApp.yaml │ ├── CreateOnlineDevelopmentEnvironment.yaml │ ├── CreatePerformanceTestApp.yaml │ ├── CreateRelease.yaml │ ├── CreateTestApp.yaml │ ├── Current.yaml │ ├── DeployReferenceDocumentation.yaml │ ├── IncrementVersionNumber.yaml │ ├── NextMajor.yaml │ ├── NextMinor.yaml │ ├── PublishToEnvironment.yaml │ ├── PullRequestHandler.yaml │ ├── Troubleshooting.yaml │ ├── UpdateGitHubGoSystemFiles.yaml │ └── _BuildALGoProject.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dependency-Graph.Test ├── .vscode │ ├── launch.json │ ├── ruleset.json │ └── settings.json ├── AppSourceCop.json ├── DepGraphTestANJ.permissionset.al ├── Translations │ ├── Dependency-Graph.Test.en-US.g.xlf │ └── Dependency-Graph.Test.g.xlf ├── app.json ├── doc │ └── Dependency-Graph.Test.pdf ├── res │ └── logo.png └── src │ └── Test │ ├── ExpectedValuesANJ.Codeunit.al │ ├── FillingProTablesMockANJ.Codeunit.al │ ├── FillingProcessTablesMockANJ.EnumExt.al │ ├── GenerateFiguresTestANJ.Codeunit.al │ ├── NumberSequenceTestANJ.Codeunit.al │ └── TemporaryTablesTestANJ.Codeunit.al ├── Dependency-Graph.code-workspace ├── Dependency-Graph ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── ruleset.json │ └── settings.json ├── AppSourceCop.json ├── DependencyGraphANJ.permissionset.al ├── Test Odata │ └── Test API.http ├── Translations │ ├── Dependency-Graph.bg-BG.g.xlf │ ├── Dependency-Graph.cs-CZ.g.xlf │ ├── Dependency-Graph.da-DK.g.xlf │ ├── Dependency-Graph.de-AT.g.xlf │ ├── Dependency-Graph.de-CH.g.xlf │ ├── Dependency-Graph.de-DE.g.xlf │ ├── Dependency-Graph.en-AU.g.xlf │ ├── Dependency-Graph.en-CA.g.xlf │ ├── Dependency-Graph.en-GB.g.xlf │ ├── Dependency-Graph.en-NZ.g.xlf │ ├── Dependency-Graph.en-US.g.xlf │ ├── Dependency-Graph.en-ZA.g.xlf │ ├── Dependency-Graph.es-ES.g.xlf │ ├── Dependency-Graph.es-ES_tradnl.g.xlf │ ├── Dependency-Graph.es-MX.g.xlf │ ├── Dependency-Graph.et-EE.g.xlf │ ├── Dependency-Graph.fi-FI.g.xlf │ ├── Dependency-Graph.fr-BE.g.xlf │ ├── Dependency-Graph.fr-CA.g.xlf │ ├── Dependency-Graph.fr-CH.g.xlf │ ├── Dependency-Graph.fr-FR.g.xlf │ ├── Dependency-Graph.g.xlf │ ├── Dependency-Graph.hr-HR.g.xlf │ ├── Dependency-Graph.hu-HU.g.xlf │ ├── Dependency-Graph.is-IS.g.xlf │ ├── Dependency-Graph.it-CH.g.xlf │ ├── Dependency-Graph.it-IT.g.xlf │ ├── Dependency-Graph.ja-JP.g.xlf │ ├── Dependency-Graph.ko-KR.g.xlf │ ├── Dependency-Graph.lt-LT.g.xlf │ ├── Dependency-Graph.lv-LV.g.xlf │ ├── Dependency-Graph.nl-BE.g.xlf │ ├── Dependency-Graph.nl-NL.g.xlf │ ├── Dependency-Graph.pl-PL.g.xlf │ ├── Dependency-Graph.pt-BR.g.xlf │ ├── Dependency-Graph.pt-PT.g.xlf │ ├── Dependency-Graph.ro-RO.g.xlf │ ├── Dependency-Graph.ru-RU.g.xlf │ ├── Dependency-Graph.sk-SK.g.xlf │ ├── Dependency-Graph.sv-SE.g.xlf │ ├── Dependency-Graph.tr-TR.g.xlf │ ├── Dependency-Graph.uk-UA.g.xlf │ ├── Dependency-Graph.vi-VN.g.xlf │ ├── Dependency-Graph.zh-HK.g.xlf │ └── Dependency-Graph.zh-TW.g.xlf ├── app.json ├── doc │ └── Dependency-Graph.pdf ├── res │ └── logo.png └── src │ ├── Base │ ├── CleanTemporaryTablesANJ.Codeunit.al │ ├── DefaultFillProcessTabImpANJ.Codeunit.al │ ├── DependencyGraphFacadeANJ.Codeunit.al │ ├── FillingProcessingTablesANJ.Enum.al │ ├── GenerateTablesANJ.Codeunit.al │ ├── IFillingProcessingTablesANJ.Interface.al │ ├── JSONMethodsANJ.Codeunit.al │ ├── MarkdownFactboxANJ.Page.al │ ├── ShowInGraphANJ.Page.al │ └── WSAndMDInfoImpANJ.Codeunit.al │ ├── ControlAddinViewer │ ├── MarkdownViewerANJ.ControlAddin.al │ ├── MarkdownViewerFSANJ.ControlAddin.al │ ├── Scripts.js │ ├── Start.js │ └── Style.css │ ├── ExtensionTable │ ├── ExtensionsANJ.Page.al │ ├── ExtensionsANJ.Table.al │ ├── GenerateExtensionTableANJ.Codeunit.al │ └── NumberSequenceMgmtANJ.Codeunit.al │ ├── GenerateFigure │ ├── CircleIMPANJ.Codeunit.al │ ├── DefaultFigureIMPANJ.Codeunit.al │ ├── ExtensionScopeANJ.Enum.al │ ├── GenerateFigureANJ.Codeunit.al │ ├── GeometricFigureANJ.Enum.al │ ├── IFigureInGraphANJ.Interface.al │ ├── RhombusIMPANJ.Codeunit.al │ ├── RoundRectangleIMPANJ.Codeunit.al │ └── SquareRectangleIMPANJ.Codeunit.al │ ├── Markdown │ ├── MarkdownMgmtANJ.Codeunit.al │ ├── MarkdownViewerANJ.Page.al │ └── MarkdownViewerFullANJ.Page.al │ ├── Relations │ ├── GenerateRelationsTableANJ.Codeunit.al │ ├── RelationsANJ.Page.al │ └── RelationsANJ.Table.al │ ├── Setup │ ├── DependencyGraphSetupANJ.Page.al │ └── DependencyGraphSetupANJ.Table.al │ └── UpgradeTagsExample │ ├── DataUpgradesANJ.Codeunit.al │ └── UpgradeCodeunitANJ.Codeunit.al ├── Environment.ps1 ├── LICENSE.md ├── README.md ├── SECURITY.md └── res ├── 1AADBC.png ├── 1AADBCes-ES.png ├── 2DGSetup.png ├── 2DGSetupes-ES.png ├── PortalAzure001.png ├── PortalAzure002.png ├── PortalAzure003.png └── Usage.gif /.AL-Go/cloudDevEnv.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # Script for creating cloud development environment 3 | # Please do not modify this script as it will be auto-updated from the AL-Go Template 4 | # Recommended approach is to use as is or add a script (freddyk-devenv.ps1), which calls this script with the user specific parameters 5 | # 6 | Param( 7 | [string] $environmentName = "", 8 | [bool] $reuseExistingEnvironment, 9 | [switch] $fromVSCode, 10 | [switch] $clean 11 | ) 12 | 13 | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 14 | 15 | function DownloadHelperFile { 16 | param( 17 | [string] $url, 18 | [string] $folder 19 | ) 20 | 21 | $prevProgressPreference = $ProgressPreference; $ProgressPreference = 'SilentlyContinue' 22 | $name = [System.IO.Path]::GetFileName($url) 23 | Write-Host "Downloading $name from $url" 24 | $path = Join-Path $folder $name 25 | Invoke-WebRequest -UseBasicParsing -uri $url -OutFile $path 26 | $ProgressPreference = $prevProgressPreference 27 | return $path 28 | } 29 | 30 | try { 31 | Clear-Host 32 | Write-Host 33 | Write-Host -ForegroundColor Yellow @' 34 | _____ _ _ _____ ______ 35 | / ____| | | | | __ \ | ____| 36 | | | | | ___ _ _ __| | | | | | _____ __ |__ _ ____ __ 37 | | | | |/ _ \| | | |/ _` | | | | |/ _ \ \ / / __| | '_ \ \ / / 38 | | |____| | (_) | |_| | (_| | | |__| | __/\ V /| |____| | | \ V / 39 | \_____|_|\___/ \__,_|\__,_| |_____/ \___| \_/ |______|_| |_|\_/ 40 | 41 | '@ 42 | 43 | $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" 44 | New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null 45 | $GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v7.0/Github-Helper.psm1' -folder $tmpFolder 46 | $ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v7.0/AL-Go-Helper.ps1' -folder $tmpFolder 47 | DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v7.0/Packages.json' -folder $tmpFolder | Out-Null 48 | 49 | Import-Module $GitHubHelperPath 50 | . $ALGoHelperPath -local 51 | 52 | $baseFolder = GetBaseFolder -folder $PSScriptRoot 53 | $project = GetProject -baseFolder $baseFolder -projectALGoFolder $PSScriptRoot 54 | 55 | Write-Host @' 56 | 57 | This script will create a cloud based development environment (Business Central SaaS Sandbox) for your project. 58 | All apps and test apps will be compiled and published to the environment in the development scope. 59 | The script will also modify launch.json to have a "Cloud Sandbox ()" configuration point to your environment. 60 | 61 | '@ 62 | 63 | if (Test-Path (Join-Path $PSScriptRoot "NewBcContainer.ps1")) { 64 | Write-Host -ForegroundColor Red "WARNING: The project has a NewBcContainer override defined. Typically, this means that you cannot run a cloud development environment" 65 | } 66 | 67 | Write-Host 68 | 69 | if (-not $environmentName) { 70 | $environmentName = Enter-Value ` 71 | -title "Environment name" ` 72 | -question "Please enter the name of the environment to create" ` 73 | -default "$($env:USERNAME)-sandbox" ` 74 | -trimCharacters @('"',"'",' ') 75 | } 76 | 77 | if ($PSBoundParameters.Keys -notcontains 'reuseExistingEnvironment') { 78 | $reuseExistingEnvironment = (Select-Value ` 79 | -title "What if the environment already exists?" ` 80 | -options @{ "Yes" = "Reuse existing environment"; "No" = "Recreate environment" } ` 81 | -question "Select behavior" ` 82 | -default "No") -eq "Yes" 83 | } 84 | 85 | CreateDevEnv ` 86 | -kind cloud ` 87 | -caller local ` 88 | -environmentName $environmentName ` 89 | -reuseExistingEnvironment:$reuseExistingEnvironment ` 90 | -baseFolder $baseFolder ` 91 | -project $project ` 92 | -clean:$clean 93 | } 94 | catch { 95 | Write-Host -ForegroundColor Red "Error: $($_.Exception.Message)`nStacktrace: $($_.scriptStackTrace)" 96 | } 97 | finally { 98 | if ($fromVSCode) { 99 | Read-Host "Press ENTER to close this window" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /.AL-Go/localDevEnv.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # Script for creating local development environment 3 | # Please do not modify this script as it will be auto-updated from the AL-Go Template 4 | # Recommended approach is to use as is or add a script (freddyk-devenv.ps1), which calls this script with the user specific parameters 5 | # 6 | Param( 7 | [string] $containerName = "", 8 | [ValidateSet("UserPassword", "Windows")] 9 | [string] $auth = "", 10 | [pscredential] $credential = $null, 11 | [string] $licenseFileUrl = "", 12 | [switch] $fromVSCode, 13 | [switch] $accept_insiderEula, 14 | [switch] $clean 15 | ) 16 | 17 | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 18 | 19 | function DownloadHelperFile { 20 | param( 21 | [string] $url, 22 | [string] $folder 23 | ) 24 | 25 | $prevProgressPreference = $ProgressPreference; $ProgressPreference = 'SilentlyContinue' 26 | $name = [System.IO.Path]::GetFileName($url) 27 | Write-Host "Downloading $name from $url" 28 | $path = Join-Path $folder $name 29 | Invoke-WebRequest -UseBasicParsing -uri $url -OutFile $path 30 | $ProgressPreference = $prevProgressPreference 31 | return $path 32 | } 33 | 34 | try { 35 | Clear-Host 36 | Write-Host 37 | Write-Host -ForegroundColor Yellow @' 38 | _ _ _____ ______ 39 | | | | | | __ \ | ____| 40 | | | ___ ___ __ _| | | | | | _____ __ |__ _ ____ __ 41 | | | / _ \ / __/ _` | | | | | |/ _ \ \ / / __| | '_ \ \ / / 42 | | |____ (_) | (__ (_| | | | |__| | __/\ V /| |____| | | \ V / 43 | |______\___/ \___\__,_|_| |_____/ \___| \_/ |______|_| |_|\_/ 44 | 45 | '@ 46 | 47 | $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" 48 | New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null 49 | $GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v7.0/Github-Helper.psm1' -folder $tmpFolder 50 | $ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v7.0/AL-Go-Helper.ps1' -folder $tmpFolder 51 | DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v7.0/Packages.json' -folder $tmpFolder | Out-Null 52 | 53 | Import-Module $GitHubHelperPath 54 | . $ALGoHelperPath -local 55 | 56 | $baseFolder = GetBaseFolder -folder $PSScriptRoot 57 | $project = GetProject -baseFolder $baseFolder -projectALGoFolder $PSScriptRoot 58 | 59 | Write-Host @' 60 | 61 | This script will create a docker based local development environment for your project. 62 | 63 | NOTE: You need to have Docker installed, configured and be able to create Business Central containers for this to work. 64 | If this fails, you can setup a cloud based development environment by running cloudDevEnv.ps1 65 | 66 | All apps and test apps will be compiled and published to the environment in the development scope. 67 | The script will also modify launch.json to have a Local Sandbox configuration point to your environment. 68 | 69 | '@ 70 | 71 | $settings = ReadSettings -baseFolder $baseFolder -project $project -userName $env:USERNAME -workflowName 'localDevEnv' 72 | 73 | Write-Host "Checking System Requirements" 74 | $dockerProcess = (Get-Process "dockerd" -ErrorAction Ignore) 75 | if (!($dockerProcess)) { 76 | Write-Host -ForegroundColor Red "Dockerd process not found. Docker might not be started, not installed or not running Windows Containers." 77 | } 78 | if ($settings.keyVaultName) { 79 | if (-not (Get-Module -ListAvailable -Name 'Az.KeyVault')) { 80 | Write-Host -ForegroundColor Red "A keyvault name is defined in Settings, you need to have the Az.KeyVault PowerShell module installed (use Install-Module az) or you can set the keyVaultName to an empty string in the user settings file ($($ENV:UserName).settings.json)." 81 | } 82 | } 83 | 84 | Write-Host 85 | 86 | if (-not $containerName) { 87 | $containerName = Enter-Value ` 88 | -title "Container name" ` 89 | -question "Please enter the name of the container to create" ` 90 | -default "bcserver" ` 91 | -trimCharacters @('"',"'",' ') 92 | } 93 | 94 | if (-not $auth) { 95 | $auth = Select-Value ` 96 | -title "Authentication mechanism for container" ` 97 | -options @{ "Windows" = "Windows Authentication"; "UserPassword" = "Username/Password authentication" } ` 98 | -question "Select authentication mechanism for container" ` 99 | -default "UserPassword" 100 | } 101 | 102 | if (-not $credential) { 103 | if ($auth -eq "Windows") { 104 | $credential = Get-Credential -Message "Please enter your Windows Credentials" -UserName $env:USERNAME 105 | $CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName 106 | $domain = New-Object System.DirectoryServices.DirectoryEntry($CurrentDomain,$credential.UserName,$credential.GetNetworkCredential().password) 107 | if ($null -eq $domain.name) { 108 | Write-Host -ForegroundColor Red "Unable to verify your Windows Credentials, you might not be able to authenticate to your container" 109 | } 110 | } 111 | else { 112 | $credential = Get-Credential -Message "Please enter username and password for your container" -UserName "admin" 113 | } 114 | } 115 | 116 | if (-not $licenseFileUrl) { 117 | if ($settings.type -eq "AppSource App") { 118 | $description = "When developing AppSource Apps for Business Central versions prior to 22, your local development environment needs the developer licensefile with permissions to your AppSource app object IDs" 119 | $default = "none" 120 | } 121 | else { 122 | $description = "When developing PTEs, you can optionally specify a developer licensefile with permissions to object IDs of your dependant apps" 123 | $default = "none" 124 | } 125 | 126 | $licenseFileUrl = Enter-Value ` 127 | -title "LicenseFileUrl" ` 128 | -description $description ` 129 | -question "Local path or a secure download URL to license file " ` 130 | -default $default ` 131 | -doNotConvertToLower ` 132 | -trimCharacters @('"',"'",' ') 133 | } 134 | 135 | if ($licenseFileUrl -eq "none") { 136 | $licenseFileUrl = "" 137 | } 138 | 139 | CreateDevEnv ` 140 | -kind local ` 141 | -caller local ` 142 | -containerName $containerName ` 143 | -baseFolder $baseFolder ` 144 | -project $project ` 145 | -auth $auth ` 146 | -credential $credential ` 147 | -licenseFileUrl $licenseFileUrl ` 148 | -accept_insiderEula:$accept_insiderEula ` 149 | -clean:$clean 150 | } 151 | catch { 152 | Write-Host -ForegroundColor Red "Error: $($_.Exception.Message)`nStacktrace: $($_.scriptStackTrace)" 153 | } 154 | finally { 155 | if ($fromVSCode) { 156 | Read-Host "Press ENTER to close this window" 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /.AL-Go/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "country": "es", 3 | "environments": [ 4 | "TESTING", 5 | "PRODUCTION (Production)" 6 | ], 7 | "DeployToTESTING": { 8 | "EnvironmentName": "TESTING", 9 | "Projects": "*", 10 | "SyncMode": "ForceSync" 11 | }, 12 | "DeployToPRODUCTION": { 13 | "EnvironmentName": "PRODUCTION", 14 | "Projects": "*", 15 | "ContinuousDeployment": false 16 | }, 17 | "appFolders": [], 18 | "testFolders": [], 19 | "bcptTestFolders": [], 20 | "doNotRunBCPTTests": true, 21 | "doNotSignApps": true, 22 | "enableCodeCop": true, 23 | "enableUICop": true, 24 | "enablePerTenantExtensionCop": true, 25 | "enableAppSourceCop": true, 26 | "AppSourceCopMandatoryAffixes": [ 27 | "_ANJ" 28 | ], 29 | "customCodeCops": [ 30 | "https://github.com/StefanMaron/BusinessCentral.LinterCop/releases/latest/download/BusinessCentral.LinterCop.current.dll" 31 | ], 32 | "rulesetFile": "Dependency-Graph/.vscode/ruleset.json", 33 | "ALDoc": { 34 | "continuousDeployment": false 35 | }, 36 | "skipUpgrade": true, 37 | "repoVersion": "1.1" 38 | } 39 | -------------------------------------------------------------------------------- /.github/AL-Go-Settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "PTE", 3 | "nextMajorSchedule": "0 0 1,15 * *", 4 | "nextMinorSchedule": "0 0 15 * *", 5 | "UpdateGitHubGoSystemFilesSchedule": "0 0 1,15 * *", 6 | "templateUrl": "https://github.com/microsoft/AL-Go-PTE@main", 7 | "templateSha": "a91a50b7fa2a88d8fa0b8eab99a66b9032ac476d" 8 | } 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BugReport.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | projects: ["Dependency-Graph"] 6 | assignees: 7 | - novoadev 8 | body: 9 | - type: markdown 10 | attributes: 11 | value: | 12 | Thanks for taking the time to fill out this bug report! 13 | 14 | With your help we will try to annihilate all those :bug: bastards. 15 | - type: textarea 16 | id: What-bug-did-you-find 17 | attributes: 18 | label: What bug did you find? 19 | description: A clear and concise description of what the problem encountered is. 20 | placeholder: Tell us what you see! 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: Steps-To-Reproduce 25 | attributes: 26 | label: What are the steps to reproduce it? 27 | description: Please indicate which are the steps so that we can reproduce this bug. 28 | placeholder: What do we have to do? 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: Positive-Result 33 | attributes: 34 | label: Satisfactory solution result? 35 | description: Do you know what the expected behavior would be if the bug is fixed?. 36 | placeholder: Tell us what you expect! 37 | validations: 38 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FeatureSuggestion.yml: -------------------------------------------------------------------------------- 1 | name: Suggestion Feature 2 | description: Request a suggestion feature 3 | title: "[Suggestion]: " 4 | labels: ["enhancement"] 5 | projects: ["Dependency-Graph"] 6 | assignees: 7 | - novoadev 8 | body: 9 | - type: markdown 10 | attributes: 11 | value: | 12 | Thanks for taking the time to fill out this Suggestion report! 13 | 14 | I promise to read it and take it into considerations :two_hearts: 15 | - type: textarea 16 | id: New-Feature 17 | attributes: 18 | label: Describe the new feature and what need it meets 19 | description: Describe a small use case. 20 | placeholder: ... 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: alternatives 25 | attributes: 26 | label: Describe alternatives you've considered 27 | description: A clear and concise description of any alternative solutions or features you've considered. 28 | placeholder: ... 29 | validations: 30 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: GitHub Community Support 4 | url: https://github.com/NovoaDev/Dependency-Graph-BCExt/discussions 5 | about: Please ask and answer questions here. -------------------------------------------------------------------------------- /.github/Test Current.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "artifact": "////latest", 3 | "cacheImageName": "", 4 | "versioningStrategy": 15 5 | } 6 | -------------------------------------------------------------------------------- /.github/Test Next Major.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "artifact": "////nextmajor/{INSIDERSASTOKEN}", 3 | "cacheImageName": "", 4 | "versioningStrategy": 15 5 | } 6 | -------------------------------------------------------------------------------- /.github/Test Next Minor.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "artifact": "////nextminor/{INSIDERSASTOKEN}", 3 | "cacheImageName": "", 4 | "versioningStrategy": 15 5 | } 6 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # Instructions for Copilot 2 | ## Code Generation 3 | - All code must be written in **English**. 4 | - Explicitly declare **access modifiers** for procedures (`local`, `internal`, `public`), prioritizing `local` or `internal` as appropriate. 5 | - When using `Insert`, `Modify`, `Delete` or `DeleteAll`, always pass `true` as a parameter to ensure full execution of business logic. Example: `Insert(true)`, `Modify(true)`. 6 | - When using `FindSet()`, specify the parameter according to the block’s intent. If the table will be modified, use `FindSet(true)`; otherwise, use `FindSet(false)`. 7 | - When assigning a value to a field, **do not assign it directly**; use `Validate` to ensure the correct execution of related logic. 8 | - Do not hardcode **text** in messages or user interaction elements. Use `Labels` to facilitate translation file generation. 9 | - Do not declare multiple variables of the same type in a single line. Incorrect example: `MyInt1, MyInt2, MyInt3: Integer;`. 10 | - Control all graphical interface elements (`Message`, `Confirm`, etc.) using `GuiAllowed()` to prevent errors in background processes. 11 | - Use the correct suffix in `Label`-type variables according to their purpose: 12 | - `Msg` – Messages. 13 | - `Tok` – Constants such as tokens, URLs, etc. 14 | - `Err` – Errors. 15 | - `Qst` – For `StrMenu` or `Confirm`. 16 | - `Lbl` – Texts that require translation (`Label`, `Caption`). 17 | - Apply **SOLID** principles in code design: 18 | - **SRP** – Single Responsibility Principle: Each module should have only one reason to change. 19 | - **OCP** – Open/Closed Principle: Code should be open for extension (events, interfaces) but closed for direct modification. 20 | - **LSP** – Liskov Substitution Principle: Use interfaces to ensure implementations can be interchanged without altering expected behavior. 21 | - **ISP** – Interface Segregation Principle: Split large interfaces into several smaller, more specific ones. 22 | - **DIP** – Dependency Inversion Principle: Modules should depend on abstractions (interfaces, events), not on concrete implementations. 23 | - Do not use `Find(-)`, `Find(+)`, `Find(=)`, etc. Use `FindFirst()`, `FindLast()` or `FindSet()` instead. 24 | - Always use the most appropriate data type: `primitive types`, `List`, `Dictionary`, `JsonObject`, `TextBuilder`, among others. 25 | - All objects and procedures must be properly documented using **XML comments**. 26 | - Do not use `//` or `/* */` comments; all documentation must be provided through XML blocks above procedures and objects. 27 | - Avoid adding redundant or unhelpful comments. 28 | - When adding elements to pages, prioritize `AddFirst` or `AddLast` over `AddAfter` or `AddBefore`. 29 | - Ensure **referential integrity** when linking fields. 30 | - In event subscriptions, explicitly configure: `SkipOnMissingLicense = false` and `SkipOnMissingPermission = false`. 31 | - Remove unnecessary lines such as redundant calls to `Reset` or default initializations for newly created variables. 32 | - When a variable refers to an object, use the exact **same name** as the object, with no spaces, quotes, or special characters. 33 | - Consider the **cyclomatic complexity** of the code. 34 | - Simplify control structures and avoid unnecessary `begin/end` blocks. 35 | - If a function has more than six parameters, consider using structures like `Dictionary`, `Record`, or others to simplify the call. 36 | - Always declare the **exact length** for `Text` or `Code` type fields and variables. 37 | - If adding `FlowField` type fields in a table, make sure to set `Editable = false`. 38 | - Use **PascalCase** for naming objects, functions, and variables. 39 | - Do not use `Option`; create an `Enum` type instead. 40 | - When subscribing to an event that receives a table as a parameter, first check if it is **temporary**. If it is, use `exit;`. 41 | - Do not include code directly within **triggers**; create separate functions that can be called from the trigger. 42 | - In new tables, add the `DataClassification` property **only at the table level**, **never on individual fields**. 43 | - In table extensions, **do not add `DataClassification` at the table level**; this will cause an error. Add it only to specific fields. 44 | - Avoid **hardcoding** object IDs. Use the **object name** instead. 45 | - Structure the code according to its purpose. Prioritize in the following order: 46 | - Use **codeunits** for general and reusable logic. 47 | - Use **tables** for logic related to entities. 48 | - Use **pages** exclusively for user interaction. 49 | - All variable names must be **clear and descriptive**. Avoid meaningless abbreviations or single letters. 50 | - When creating a table, define the `DrillDownPageId` and `LookupPageId` properties, referencing the corresponding list-type page. 51 | - Use the condition directly in validations. Avoid constructs like `if Condition = true` or `= false`. 52 | 53 | ## Code Review 54 | - Verify that all added procedures are documented with XML blocks. 55 | - Ensure the code complies with all established generation instructions. 56 | - Make code review comments in English and respectfully insulting the developer in Spanish as a loving reminder that they can do better. 57 | 58 | ## Commit Message Generation 59 | - The message must be written in English. 60 | - Use the following format: 61 | `[Type] - [Branch Name] - [Brief Description]` 62 | Example: `Bug - EV001 - Optimización del cálculo de impuestos` 63 | - Include a clear, precise, and concise description of the changes made. 64 | 65 | ## Pull Request Title and Description Generation Instructions 66 | - Titles: Use a clear format that includes the main purpose of the changes. 67 | Example: `Fix: Error in discount calculation logic` 68 | - Descriptions: Include an overview of the changes made, explaining their impact and purpose. 69 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | - [ ] The PR was created from a branch associated to an Issue. 2 | - [ ] The pull request is complete and the project can be compiled. 3 | - [ ] The ticket acceptance criteria have been met. 4 | - [ ] There has been some kind of fedback from another contributor. 5 | - [ ] Documentation is up to date. 6 | - [ ] The automated tests are written (Optional). -------------------------------------------------------------------------------- /.github/workflows/AddExistingAppOrTestApp.yaml: -------------------------------------------------------------------------------- 1 | name: 'Add existing app or test app' 2 | 3 | run-name: "Add existing app or test app in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects 10 | required: false 11 | default: '.' 12 | url: 13 | description: Direct Download Url of .app or .zip file 14 | required: true 15 | directCommit: 16 | description: Direct Commit? 17 | type: boolean 18 | default: false 19 | useGhTokenWorkflow: 20 | description: Use GhTokenWorkflow for PR/Commit? 21 | type: boolean 22 | default: false 23 | 24 | permissions: 25 | actions: read 26 | contents: write 27 | id-token: write 28 | pull-requests: write 29 | 30 | defaults: 31 | run: 32 | shell: powershell 33 | 34 | env: 35 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 36 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 37 | 38 | jobs: 39 | AddExistingAppOrTestApp: 40 | needs: [ ] 41 | runs-on: [ windows-latest ] 42 | steps: 43 | - name: Dump Workflow Information 44 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 45 | with: 46 | shell: powershell 47 | 48 | - name: Checkout 49 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 50 | 51 | - name: Initialize the workflow 52 | id: init 53 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 54 | with: 55 | shell: powershell 56 | 57 | - name: Read settings 58 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 59 | with: 60 | shell: powershell 61 | 62 | - name: Read secrets 63 | id: ReadSecrets 64 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 65 | with: 66 | shell: powershell 67 | gitHubSecrets: ${{ toJson(secrets) }} 68 | getSecrets: 'TokenForPush' 69 | useGhTokenWorkflowForPush: '${{ github.event.inputs.useGhTokenWorkflow }}' 70 | 71 | - name: Add existing app 72 | uses: microsoft/AL-Go-Actions/AddExistingApp@v7.0 73 | with: 74 | shell: powershell 75 | token: ${{ steps.ReadSecrets.outputs.TokenForPush }} 76 | project: ${{ github.event.inputs.project }} 77 | url: ${{ github.event.inputs.url }} 78 | directCommit: ${{ github.event.inputs.directCommit }} 79 | 80 | - name: Finalize the workflow 81 | if: always() 82 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 83 | env: 84 | GITHUB_TOKEN: ${{ github.token }} 85 | with: 86 | shell: powershell 87 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 88 | currentJobContext: ${{ toJson(job) }} 89 | -------------------------------------------------------------------------------- /.github/workflows/CreateApp.yaml: -------------------------------------------------------------------------------- 1 | name: 'Create a new app' 2 | 3 | run-name: "Create a new app in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects 10 | required: false 11 | default: '.' 12 | name: 13 | description: Name 14 | required: true 15 | publisher: 16 | description: Publisher 17 | required: true 18 | idrange: 19 | description: ID range (from..to) 20 | required: true 21 | sampleCode: 22 | description: Include Sample code? 23 | type: boolean 24 | default: true 25 | directCommit: 26 | description: Direct Commit? 27 | type: boolean 28 | default: false 29 | useGhTokenWorkflow: 30 | description: Use GhTokenWorkflow for PR/Commit? 31 | type: boolean 32 | default: false 33 | 34 | permissions: 35 | actions: read 36 | contents: write 37 | id-token: write 38 | pull-requests: write 39 | 40 | defaults: 41 | run: 42 | shell: powershell 43 | 44 | env: 45 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 46 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 47 | 48 | jobs: 49 | CreateApp: 50 | needs: [ ] 51 | runs-on: [ windows-latest ] 52 | steps: 53 | - name: Dump Workflow Information 54 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 55 | with: 56 | shell: powershell 57 | 58 | - name: Checkout 59 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 60 | 61 | - name: Initialize the workflow 62 | id: init 63 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 64 | with: 65 | shell: powershell 66 | 67 | - name: Read settings 68 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 69 | with: 70 | shell: powershell 71 | get: type 72 | 73 | - name: Read secrets 74 | id: ReadSecrets 75 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 76 | with: 77 | shell: powershell 78 | gitHubSecrets: ${{ toJson(secrets) }} 79 | getSecrets: 'TokenForPush' 80 | useGhTokenWorkflowForPush: '${{ github.event.inputs.useGhTokenWorkflow }}' 81 | 82 | - name: Creating a new app 83 | uses: microsoft/AL-Go-Actions/CreateApp@v7.0 84 | with: 85 | shell: powershell 86 | token: ${{ steps.ReadSecrets.outputs.TokenForPush }} 87 | project: ${{ github.event.inputs.project }} 88 | type: ${{ env.type }} 89 | name: ${{ github.event.inputs.name }} 90 | publisher: ${{ github.event.inputs.publisher }} 91 | idrange: ${{ github.event.inputs.idrange }} 92 | sampleCode: ${{ github.event.inputs.sampleCode }} 93 | directCommit: ${{ github.event.inputs.directCommit }} 94 | 95 | - name: Finalize the workflow 96 | if: always() 97 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 98 | env: 99 | GITHUB_TOKEN: ${{ github.token }} 100 | with: 101 | shell: powershell 102 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 103 | currentJobContext: ${{ toJson(job) }} 104 | -------------------------------------------------------------------------------- /.github/workflows/CreateOnlineDevelopmentEnvironment.yaml: -------------------------------------------------------------------------------- 1 | name: ' Create Online Dev. Environment' 2 | 3 | run-name: "Create Online Dev. Environment for [${{ github.ref_name }} / ${{ github.event.inputs.project }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects 10 | required: false 11 | default: '.' 12 | environmentName: 13 | description: Name of the online environment 14 | required: true 15 | reUseExistingEnvironment: 16 | description: Reuse environment if it exists? 17 | type: boolean 18 | default: false 19 | directCommit: 20 | description: Direct Commit? 21 | type: boolean 22 | default: false 23 | useGhTokenWorkflow: 24 | description: Use GhTokenWorkflow for PR/Commit? 25 | type: boolean 26 | default: false 27 | 28 | permissions: 29 | actions: read 30 | contents: write 31 | id-token: write 32 | pull-requests: write 33 | 34 | defaults: 35 | run: 36 | shell: powershell 37 | 38 | env: 39 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 40 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 41 | 42 | jobs: 43 | Initialization: 44 | needs: [ ] 45 | runs-on: [ windows-latest ] 46 | outputs: 47 | deviceCode: ${{ steps.authenticate.outputs.deviceCode }} 48 | githubRunner: ${{ steps.ReadSettings.outputs.GitHubRunnerJson }} 49 | githubRunnerShell: ${{ steps.ReadSettings.outputs.GitHubRunnerShell }} 50 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 51 | steps: 52 | - name: Dump Workflow Information 53 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 54 | with: 55 | shell: powershell 56 | 57 | - name: Checkout 58 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 59 | 60 | - name: Initialize the workflow 61 | id: init 62 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 63 | with: 64 | shell: powershell 65 | 66 | - name: Read settings 67 | id: ReadSettings 68 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 69 | with: 70 | shell: powershell 71 | 72 | - name: Read secrets 73 | id: ReadSecrets 74 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 75 | with: 76 | shell: powershell 77 | gitHubSecrets: ${{ toJson(secrets) }} 78 | getSecrets: 'adminCenterApiCredentials' 79 | 80 | - name: Check AdminCenterApiCredentials / Initiate Device Login (open to see code) 81 | id: authenticate 82 | run: | 83 | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 84 | $settings = $env:Settings | ConvertFrom-Json 85 | if ('${{ fromJson(steps.ReadSecrets.outputs.Secrets).adminCenterApiCredentials }}') { 86 | Write-Host "AdminCenterApiCredentials provided in secret $($settings.adminCenterApiCredentialsSecretName)!" 87 | Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "Admin Center Api Credentials was provided in a secret called $($settings.adminCenterApiCredentialsSecretName). Using this information for authentication." 88 | } 89 | else { 90 | Write-Host "AdminCenterApiCredentials not provided, initiating Device Code flow" 91 | $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" 92 | $webClient = New-Object System.Net.WebClient 93 | $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v7.0/AL-Go-Helper.ps1', $ALGoHelperPath) 94 | . $ALGoHelperPath 95 | DownloadAndImportBcContainerHelper 96 | $authContext = New-BcAuthContext -includeDeviceLogin -deviceLoginTimeout ([TimeSpan]::FromSeconds(0)) 97 | Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "AL-Go needs access to the Business Central Admin Center Api and could not locate a secret called $($settings.adminCenterApiCredentialsSecretName) (https://aka.ms/ALGoSettings#AdminCenterApiCredentialsSecretName)`n`n$($authContext.message)" 98 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "deviceCode=$($authContext.deviceCode)" 99 | } 100 | 101 | CreateDevelopmentEnvironment: 102 | needs: [ Initialization ] 103 | runs-on: ${{ fromJson(needs.Initialization.outputs.githubRunner) }} 104 | defaults: 105 | run: 106 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 107 | name: Create Development Environment 108 | env: 109 | deviceCode: ${{ needs.Initialization.outputs.deviceCode }} 110 | steps: 111 | - name: Checkout 112 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 113 | 114 | - name: Read settings 115 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 116 | with: 117 | shell: powershell 118 | 119 | - name: Read secrets 120 | id: ReadSecrets 121 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 122 | with: 123 | shell: powershell 124 | gitHubSecrets: ${{ toJson(secrets) }} 125 | getSecrets: 'adminCenterApiCredentials,TokenForPush' 126 | useGhTokenWorkflowForPush: '${{ github.event.inputs.useGhTokenWorkflow }}' 127 | 128 | - name: Set AdminCenterApiCredentials 129 | id: SetAdminCenterApiCredentials 130 | run: | 131 | if ($env:deviceCode) { 132 | $adminCenterApiCredentials = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("{""deviceCode"":""$($env:deviceCode)""}")) 133 | } 134 | else { 135 | $adminCenterApiCredentials = '${{ fromJson(steps.ReadSecrets.outputs.Secrets).adminCenterApiCredentials }}' 136 | } 137 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -value "adminCenterApiCredentials=$adminCenterApiCredentials" 138 | 139 | - name: Create Development Environment 140 | uses: microsoft/AL-Go-Actions/CreateDevelopmentEnvironment@v7.0 141 | with: 142 | shell: powershell 143 | token: ${{ steps.ReadSecrets.outputs.TokenForPush }} 144 | environmentName: ${{ github.event.inputs.environmentName }} 145 | project: ${{ github.event.inputs.project }} 146 | reUseExistingEnvironment: ${{ github.event.inputs.reUseExistingEnvironment }} 147 | directCommit: ${{ github.event.inputs.directCommit }} 148 | adminCenterApiCredentials: ${{ steps.SetAdminCenterApiCredentials.outputs.adminCenterApiCredentials }} 149 | 150 | - name: Finalize the workflow 151 | if: always() 152 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 153 | env: 154 | GITHUB_TOKEN: ${{ github.token }} 155 | with: 156 | shell: powershell 157 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 158 | currentJobContext: ${{ toJson(job) }} 159 | -------------------------------------------------------------------------------- /.github/workflows/CreatePerformanceTestApp.yaml: -------------------------------------------------------------------------------- 1 | name: 'Create a new performance test app' 2 | 3 | run-name: "Create a new performance test app in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects 10 | required: false 11 | default: '.' 12 | name: 13 | description: Name 14 | required: true 15 | default: '.PerformanceTest' 16 | publisher: 17 | description: Publisher 18 | required: true 19 | idrange: 20 | description: ID range 21 | required: true 22 | default: '50000..99999' 23 | sampleCode: 24 | description: Include Sample code? 25 | type: boolean 26 | default: true 27 | sampleSuite: 28 | description: Include Sample BCPT Suite? 29 | type: boolean 30 | default: true 31 | directCommit: 32 | description: Direct Commit? 33 | type: boolean 34 | default: false 35 | useGhTokenWorkflow: 36 | description: Use GhTokenWorkflow for PR/Commit? 37 | type: boolean 38 | default: false 39 | 40 | permissions: 41 | actions: read 42 | contents: write 43 | id-token: write 44 | pull-requests: write 45 | 46 | defaults: 47 | run: 48 | shell: powershell 49 | 50 | env: 51 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 52 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 53 | 54 | jobs: 55 | CreatePerformanceTestApp: 56 | needs: [ ] 57 | runs-on: [ windows-latest ] 58 | steps: 59 | - name: Dump Workflow Information 60 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 61 | with: 62 | shell: powershell 63 | 64 | - name: Checkout 65 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 66 | 67 | - name: Initialize the workflow 68 | id: init 69 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 70 | with: 71 | shell: powershell 72 | 73 | - name: Read settings 74 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 75 | with: 76 | shell: powershell 77 | 78 | - name: Read secrets 79 | id: ReadSecrets 80 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 81 | with: 82 | shell: powershell 83 | gitHubSecrets: ${{ toJson(secrets) }} 84 | getSecrets: 'TokenForPush' 85 | useGhTokenWorkflowForPush: '${{ github.event.inputs.useGhTokenWorkflow }}' 86 | 87 | - name: Creating a new test app 88 | uses: microsoft/AL-Go-Actions/CreateApp@v7.0 89 | with: 90 | shell: powershell 91 | token: ${{ steps.ReadSecrets.outputs.TokenForPush }} 92 | project: ${{ github.event.inputs.project }} 93 | type: 'Performance Test App' 94 | name: ${{ github.event.inputs.name }} 95 | publisher: ${{ github.event.inputs.publisher }} 96 | idrange: ${{ github.event.inputs.idrange }} 97 | sampleCode: ${{ github.event.inputs.sampleCode }} 98 | sampleSuite: ${{ github.event.inputs.sampleSuite }} 99 | directCommit: ${{ github.event.inputs.directCommit }} 100 | 101 | - name: Finalize the workflow 102 | if: always() 103 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 104 | env: 105 | GITHUB_TOKEN: ${{ github.token }} 106 | with: 107 | shell: powershell 108 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 109 | currentJobContext: ${{ toJson(job) }} 110 | -------------------------------------------------------------------------------- /.github/workflows/CreateTestApp.yaml: -------------------------------------------------------------------------------- 1 | name: 'Create a new test app' 2 | 3 | run-name: "Create a new test app in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects 10 | required: false 11 | default: '.' 12 | name: 13 | description: Name 14 | required: true 15 | default: '.Test' 16 | publisher: 17 | description: Publisher 18 | required: true 19 | idrange: 20 | description: ID range 21 | required: true 22 | default: '50000..99999' 23 | sampleCode: 24 | description: Include Sample code? 25 | type: boolean 26 | default: true 27 | directCommit: 28 | description: Direct Commit? 29 | type: boolean 30 | default: false 31 | useGhTokenWorkflow: 32 | description: Use GhTokenWorkflow for PR/Commit? 33 | type: boolean 34 | default: false 35 | 36 | permissions: 37 | actions: read 38 | contents: write 39 | id-token: write 40 | pull-requests: write 41 | 42 | defaults: 43 | run: 44 | shell: powershell 45 | 46 | env: 47 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 48 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 49 | 50 | jobs: 51 | CreateTestApp: 52 | needs: [ ] 53 | runs-on: [ windows-latest ] 54 | steps: 55 | - name: Dump Workflow Information 56 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 57 | with: 58 | shell: powershell 59 | 60 | - name: Checkout 61 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 62 | 63 | - name: Initialize the workflow 64 | id: init 65 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 66 | with: 67 | shell: powershell 68 | 69 | - name: Read settings 70 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 71 | with: 72 | shell: powershell 73 | 74 | - name: Read secrets 75 | id: ReadSecrets 76 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 77 | with: 78 | shell: powershell 79 | gitHubSecrets: ${{ toJson(secrets) }} 80 | getSecrets: 'TokenForPush' 81 | useGhTokenWorkflowForPush: '${{ github.event.inputs.useGhTokenWorkflow }}' 82 | 83 | - name: Creating a new test app 84 | uses: microsoft/AL-Go-Actions/CreateApp@v7.0 85 | with: 86 | shell: powershell 87 | token: ${{ steps.ReadSecrets.outputs.TokenForPush }} 88 | project: ${{ github.event.inputs.project }} 89 | type: 'Test App' 90 | name: ${{ github.event.inputs.name }} 91 | publisher: ${{ github.event.inputs.publisher }} 92 | idrange: ${{ github.event.inputs.idrange }} 93 | sampleCode: ${{ github.event.inputs.sampleCode }} 94 | directCommit: ${{ github.event.inputs.directCommit }} 95 | 96 | - name: Finalize the workflow 97 | if: always() 98 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 99 | env: 100 | GITHUB_TOKEN: ${{ github.token }} 101 | with: 102 | shell: powershell 103 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 104 | currentJobContext: ${{ toJson(job) }} 105 | -------------------------------------------------------------------------------- /.github/workflows/Current.yaml: -------------------------------------------------------------------------------- 1 | name: ' Test Current' 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | actions: read 8 | contents: read 9 | id-token: write 10 | 11 | defaults: 12 | run: 13 | shell: powershell 14 | 15 | env: 16 | workflowDepth: 1 17 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 18 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 19 | 20 | jobs: 21 | Initialization: 22 | needs: [ ] 23 | runs-on: [ windows-latest ] 24 | outputs: 25 | projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} 26 | projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} 27 | buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} 28 | workflowDepth: ${{ steps.DetermineWorkflowDepth.outputs.WorkflowDepth }} 29 | artifactsRetentionDays: ${{ steps.DetermineWorkflowDepth.outputs.ArtifactsRetentionDays }} 30 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 31 | steps: 32 | - name: Dump Workflow Information 33 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 34 | with: 35 | shell: powershell 36 | 37 | - name: Checkout 38 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 39 | with: 40 | lfs: true 41 | 42 | - name: Initialize the workflow 43 | id: init 44 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 45 | with: 46 | shell: powershell 47 | 48 | - name: Read settings 49 | id: ReadSettings 50 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 51 | with: 52 | shell: powershell 53 | get: useGitSubmodules,shortLivedArtifactsRetentionDays 54 | 55 | - name: Read submodules token 56 | id: ReadSubmodulesToken 57 | if: env.useGitSubmodules != 'false' && env.useGitSubmodules != '' 58 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 59 | with: 60 | shell: powershell 61 | gitHubSecrets: ${{ toJson(secrets) }} 62 | getSecrets: '-gitSubmodulesToken' 63 | 64 | - name: Checkout Submodules 65 | if: env.useGitSubmodules != 'false' && env.useGitSubmodules != '' 66 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 67 | with: 68 | lfs: true 69 | submodules: ${{ env.useGitSubmodules }} 70 | token: '${{ fromJson(steps.ReadSubmodulesToken.outputs.Secrets).gitSubmodulesToken }}' 71 | 72 | - name: Determine Workflow Depth 73 | id: DetermineWorkflowDepth 74 | run: | 75 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "WorkflowDepth=$($env:workflowDepth)" 76 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ArtifactsRetentionDays=$($env:shortLivedArtifactsRetentionDays)" 77 | 78 | - name: Determine Projects To Build 79 | id: determineProjectsToBuild 80 | uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v7.0 81 | with: 82 | shell: powershell 83 | maxBuildDepth: ${{ env.workflowDepth }} 84 | 85 | Build: 86 | needs: [ Initialization ] 87 | if: (!failure()) && (!cancelled()) && fromJson(needs.Initialization.outputs.buildOrderJson)[0].projectsCount > 0 88 | strategy: 89 | matrix: 90 | include: ${{ fromJson(needs.Initialization.outputs.buildOrderJson)[0].buildDimensions }} 91 | fail-fast: false 92 | name: Build ${{ matrix.projectName }} (${{ matrix.buildMode }}) 93 | uses: ./.github/workflows/_BuildALGoProject.yaml 94 | secrets: inherit 95 | with: 96 | shell: ${{ matrix.githubRunnerShell }} 97 | runsOn: ${{ matrix.githubRunner }} 98 | project: ${{ matrix.project }} 99 | projectName: ${{ matrix.projectName }} 100 | buildMode: ${{ matrix.buildMode }} 101 | projectDependenciesJson: ${{ needs.Initialization.outputs.projectDependenciesJson }} 102 | secrets: 'licenseFileUrl,codeSignCertificateUrl,*codeSignCertificatePassword,keyVaultCertificateUrl,*keyVaultCertificatePassword,keyVaultClientId,gitHubPackagesContext,applicationInsightsConnectionString' 103 | artifactsRetentionDays: ${{ fromJson(needs.Initialization.outputs.artifactsRetentionDays) }} 104 | artifactsNameSuffix: 'Current' 105 | 106 | PostProcess: 107 | needs: [ Initialization, Build ] 108 | if: always() 109 | runs-on: [ windows-latest ] 110 | steps: 111 | - name: Checkout 112 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 113 | 114 | - name: Finalize the workflow 115 | id: PostProcess 116 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 117 | env: 118 | GITHUB_TOKEN: ${{ github.token }} 119 | with: 120 | shell: powershell 121 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 122 | currentJobContext: ${{ toJson(job) }} 123 | -------------------------------------------------------------------------------- /.github/workflows/DeployReferenceDocumentation.yaml: -------------------------------------------------------------------------------- 1 | name: ' Deploy Reference Documentation' 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | actions: read 8 | contents: read 9 | id-token: write 10 | pages: write 11 | 12 | defaults: 13 | run: 14 | shell: powershell 15 | 16 | env: 17 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 18 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 19 | 20 | jobs: 21 | DeployALDoc: 22 | runs-on: [ windows-latest ] 23 | name: Deploy Reference Documentation 24 | environment: 25 | name: github-pages 26 | url: ${{ steps.deployment.outputs.page_url }} 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 30 | 31 | - name: Initialize the workflow 32 | id: init 33 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 34 | with: 35 | shell: powershell 36 | 37 | - name: Read settings 38 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 39 | with: 40 | shell: powershell 41 | 42 | - name: Determine Deployment Environments 43 | id: DetermineDeploymentEnvironments 44 | uses: microsoft/AL-Go-Actions/DetermineDeploymentEnvironments@v7.0 45 | env: 46 | GITHUB_TOKEN: ${{ github.token }} 47 | with: 48 | shell: powershell 49 | getEnvironments: 'github-pages' 50 | type: 'Publish' 51 | 52 | - name: Setup Pages 53 | if: steps.DetermineDeploymentEnvironments.outputs.deployALDocArtifact == 1 54 | uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 55 | 56 | - name: Build Reference Documentation 57 | uses: microsoft/AL-Go-Actions/BuildReferenceDocumentation@v7.0 58 | with: 59 | shell: powershell 60 | artifacts: 'latest' 61 | 62 | - name: Upload pages artifact 63 | uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 64 | with: 65 | path: ".aldoc/_site/" 66 | 67 | - name: Deploy to GitHub Pages 68 | if: steps.DetermineDeploymentEnvironments.outputs.deployALDocArtifact == 1 69 | id: deployment 70 | uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 71 | 72 | - name: Finalize the workflow 73 | if: always() 74 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 75 | env: 76 | GITHUB_TOKEN: ${{ github.token }} 77 | with: 78 | shell: powershell 79 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 80 | currentJobContext: ${{ toJson(job) }} 81 | -------------------------------------------------------------------------------- /.github/workflows/IncrementVersionNumber.yaml: -------------------------------------------------------------------------------- 1 | name: ' Increment Version Number' 2 | 3 | run-name: "Increment Version Number in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | projects: 9 | description: Comma-separated list of project name patterns if the repository is setup for multiple projects (default is * for all projects) 10 | required: false 11 | default: '*' 12 | versionNumber: 13 | description: New Version Number in main branch. Use Major.Minor (optionally add .Build for versioningstrategy 3) for absolute change, or +1, +0.1 (or +0.0.1 for versioningstrategy 3) incremental change. 14 | required: false 15 | default: '' 16 | skipUpdatingDependencies: 17 | description: Skip updating dependency version numbers in all apps. 18 | type: boolean 19 | default: false 20 | directCommit: 21 | description: Direct Commit? 22 | type: boolean 23 | default: false 24 | useGhTokenWorkflow: 25 | description: Use GhTokenWorkflow for PR/Commit? 26 | type: boolean 27 | default: false 28 | 29 | defaults: 30 | run: 31 | shell: powershell 32 | 33 | env: 34 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 35 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 36 | 37 | jobs: 38 | IncrementVersionNumber: 39 | needs: [ ] 40 | runs-on: [ windows-latest ] 41 | permissions: 42 | actions: read 43 | contents: write 44 | id-token: write 45 | pull-requests: write 46 | steps: 47 | - name: Dump Workflow Information 48 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 49 | with: 50 | shell: powershell 51 | 52 | - name: Checkout 53 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 54 | 55 | - name: Initialize the workflow 56 | id: init 57 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 58 | with: 59 | shell: powershell 60 | 61 | - name: Read settings 62 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 63 | with: 64 | shell: powershell 65 | 66 | - name: Read secrets 67 | id: ReadSecrets 68 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 69 | with: 70 | shell: powershell 71 | gitHubSecrets: ${{ toJson(secrets) }} 72 | getSecrets: 'TokenForPush' 73 | useGhTokenWorkflowForPush: '${{ github.event.inputs.useGhTokenWorkflow }}' 74 | 75 | - name: Increment Version Number 76 | uses: microsoft/AL-Go-Actions/IncrementVersionNumber@v7.0 77 | with: 78 | shell: powershell 79 | token: ${{ steps.ReadSecrets.outputs.TokenForPush }} 80 | projects: ${{ github.event.inputs.projects }} 81 | versionNumber: ${{ github.event.inputs.versionNumber }} 82 | skipUpdatingDependencies: ${{ github.event.inputs.skipUpdatingDependencies }} 83 | directCommit: ${{ github.event.inputs.directCommit }} 84 | 85 | - name: Finalize the workflow 86 | if: always() 87 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 88 | env: 89 | GITHUB_TOKEN: ${{ github.token }} 90 | with: 91 | shell: powershell 92 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 93 | currentJobContext: ${{ toJson(job) }} 94 | -------------------------------------------------------------------------------- /.github/workflows/NextMajor.yaml: -------------------------------------------------------------------------------- 1 | name: ' Test Next Major' 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 1,15 * *' 7 | 8 | permissions: 9 | actions: read 10 | contents: read 11 | id-token: write 12 | 13 | defaults: 14 | run: 15 | shell: powershell 16 | 17 | env: 18 | workflowDepth: 1 19 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 20 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 21 | 22 | jobs: 23 | Initialization: 24 | needs: [ ] 25 | runs-on: [ windows-latest ] 26 | outputs: 27 | projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} 28 | projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} 29 | buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} 30 | workflowDepth: ${{ steps.DetermineWorkflowDepth.outputs.WorkflowDepth }} 31 | artifactsRetentionDays: ${{ steps.DetermineWorkflowDepth.outputs.ArtifactsRetentionDays }} 32 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 33 | steps: 34 | - name: Dump Workflow Information 35 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 36 | with: 37 | shell: powershell 38 | 39 | - name: Checkout 40 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 41 | with: 42 | lfs: true 43 | 44 | - name: Initialize the workflow 45 | id: init 46 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 47 | with: 48 | shell: powershell 49 | 50 | - name: Read settings 51 | id: ReadSettings 52 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 53 | with: 54 | shell: powershell 55 | get: useGitSubmodules,shortLivedArtifactsRetentionDays 56 | 57 | - name: Read submodules token 58 | id: ReadSubmodulesToken 59 | if: env.useGitSubmodules != 'false' && env.useGitSubmodules != '' 60 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 61 | with: 62 | shell: powershell 63 | gitHubSecrets: ${{ toJson(secrets) }} 64 | getSecrets: '-gitSubmodulesToken' 65 | 66 | - name: Checkout Submodules 67 | if: env.useGitSubmodules != 'false' && env.useGitSubmodules != '' 68 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 69 | with: 70 | lfs: true 71 | submodules: ${{ env.useGitSubmodules }} 72 | token: '${{ fromJson(steps.ReadSubmodulesToken.outputs.Secrets).gitSubmodulesToken }}' 73 | 74 | - name: Determine Workflow Depth 75 | id: DetermineWorkflowDepth 76 | run: | 77 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "WorkflowDepth=$($env:workflowDepth)" 78 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ArtifactsRetentionDays=$($env:shortLivedArtifactsRetentionDays)" 79 | 80 | - name: Determine Projects To Build 81 | id: determineProjectsToBuild 82 | uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v7.0 83 | with: 84 | shell: powershell 85 | maxBuildDepth: ${{ env.workflowDepth }} 86 | 87 | Build: 88 | needs: [ Initialization ] 89 | if: (!failure()) && (!cancelled()) && fromJson(needs.Initialization.outputs.buildOrderJson)[0].projectsCount > 0 90 | strategy: 91 | matrix: 92 | include: ${{ fromJson(needs.Initialization.outputs.buildOrderJson)[0].buildDimensions }} 93 | fail-fast: false 94 | name: Build ${{ matrix.projectName }} (${{ matrix.buildMode }}) 95 | uses: ./.github/workflows/_BuildALGoProject.yaml 96 | secrets: inherit 97 | with: 98 | shell: ${{ matrix.githubRunnerShell }} 99 | runsOn: ${{ matrix.githubRunner }} 100 | project: ${{ matrix.project }} 101 | projectName: ${{ matrix.projectName }} 102 | buildMode: ${{ matrix.buildMode }} 103 | projectDependenciesJson: ${{ needs.Initialization.outputs.projectDependenciesJson }} 104 | secrets: 'licenseFileUrl,codeSignCertificateUrl,*codeSignCertificatePassword,keyVaultCertificateUrl,*keyVaultCertificatePassword,keyVaultClientId,gitHubPackagesContext,applicationInsightsConnectionString' 105 | artifactsRetentionDays: ${{ fromJson(needs.Initialization.outputs.artifactsRetentionDays) }} 106 | artifactsNameSuffix: 'NextMajor' 107 | 108 | PostProcess: 109 | needs: [ Initialization, Build ] 110 | if: always() 111 | runs-on: [ windows-latest ] 112 | steps: 113 | - name: Checkout 114 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 115 | 116 | - name: Finalize the workflow 117 | id: PostProcess 118 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 119 | env: 120 | GITHUB_TOKEN: ${{ github.token }} 121 | with: 122 | shell: powershell 123 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 124 | currentJobContext: ${{ toJson(job) }} 125 | -------------------------------------------------------------------------------- /.github/workflows/NextMinor.yaml: -------------------------------------------------------------------------------- 1 | name: ' Test Next Minor' 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 15 * *' 7 | 8 | permissions: 9 | actions: read 10 | contents: read 11 | id-token: write 12 | 13 | defaults: 14 | run: 15 | shell: powershell 16 | 17 | env: 18 | workflowDepth: 1 19 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 20 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 21 | 22 | jobs: 23 | Initialization: 24 | needs: [ ] 25 | runs-on: [ windows-latest ] 26 | outputs: 27 | projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} 28 | projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} 29 | buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} 30 | workflowDepth: ${{ steps.DetermineWorkflowDepth.outputs.WorkflowDepth }} 31 | artifactsRetentionDays: ${{ steps.DetermineWorkflowDepth.outputs.ArtifactsRetentionDays }} 32 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 33 | steps: 34 | - name: Dump Workflow Information 35 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 36 | with: 37 | shell: powershell 38 | 39 | - name: Checkout 40 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 41 | with: 42 | lfs: true 43 | 44 | - name: Initialize the workflow 45 | id: init 46 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 47 | with: 48 | shell: powershell 49 | 50 | - name: Read settings 51 | id: ReadSettings 52 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 53 | with: 54 | shell: powershell 55 | get: useGitSubmodules,shortLivedArtifactsRetentionDays 56 | 57 | - name: Read submodules token 58 | id: ReadSubmodulesToken 59 | if: env.useGitSubmodules != 'false' && env.useGitSubmodules != '' 60 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 61 | with: 62 | shell: powershell 63 | gitHubSecrets: ${{ toJson(secrets) }} 64 | getSecrets: '-gitSubmodulesToken' 65 | 66 | - name: Checkout Submodules 67 | if: env.useGitSubmodules != 'false' && env.useGitSubmodules != '' 68 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 69 | with: 70 | lfs: true 71 | submodules: ${{ env.useGitSubmodules }} 72 | token: '${{ fromJson(steps.ReadSubmodulesToken.outputs.Secrets).gitSubmodulesToken }}' 73 | 74 | - name: Determine Workflow Depth 75 | id: DetermineWorkflowDepth 76 | run: | 77 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "WorkflowDepth=$($env:workflowDepth)" 78 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ArtifactsRetentionDays=$($env:shortLivedArtifactsRetentionDays)" 79 | 80 | - name: Determine Projects To Build 81 | id: determineProjectsToBuild 82 | uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v7.0 83 | with: 84 | shell: powershell 85 | maxBuildDepth: ${{ env.workflowDepth }} 86 | 87 | Build: 88 | needs: [ Initialization ] 89 | if: (!failure()) && (!cancelled()) && fromJson(needs.Initialization.outputs.buildOrderJson)[0].projectsCount > 0 90 | strategy: 91 | matrix: 92 | include: ${{ fromJson(needs.Initialization.outputs.buildOrderJson)[0].buildDimensions }} 93 | fail-fast: false 94 | name: Build ${{ matrix.projectName }} (${{ matrix.buildMode }}) 95 | uses: ./.github/workflows/_BuildALGoProject.yaml 96 | secrets: inherit 97 | with: 98 | shell: ${{ matrix.githubRunnerShell }} 99 | runsOn: ${{ matrix.githubRunner }} 100 | project: ${{ matrix.project }} 101 | projectName: ${{ matrix.projectName }} 102 | buildMode: ${{ matrix.buildMode }} 103 | projectDependenciesJson: ${{ needs.Initialization.outputs.projectDependenciesJson }} 104 | secrets: 'licenseFileUrl,codeSignCertificateUrl,*codeSignCertificatePassword,keyVaultCertificateUrl,*keyVaultCertificatePassword,keyVaultClientId,gitHubPackagesContext,applicationInsightsConnectionString' 105 | artifactsRetentionDays: ${{ fromJson(needs.Initialization.outputs.artifactsRetentionDays) }} 106 | artifactsNameSuffix: 'NextMinor' 107 | 108 | PostProcess: 109 | needs: [ Initialization, Build ] 110 | if: always() 111 | runs-on: [ windows-latest ] 112 | steps: 113 | - name: Checkout 114 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 115 | 116 | - name: Finalize the workflow 117 | id: PostProcess 118 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 119 | env: 120 | GITHUB_TOKEN: ${{ github.token }} 121 | with: 122 | shell: powershell 123 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 124 | currentJobContext: ${{ toJson(job) }} 125 | -------------------------------------------------------------------------------- /.github/workflows/PublishToEnvironment.yaml: -------------------------------------------------------------------------------- 1 | name: ' Publish To Environment' 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | appVersion: 7 | description: App version to deploy to environment(s) (current, prerelease, draft, latest, version number or PR_) 8 | required: false 9 | default: 'current' 10 | environmentName: 11 | description: Environment mask to receive the new version (* for all, PROD* for all environments starting with PROD) 12 | required: true 13 | 14 | permissions: 15 | actions: read 16 | contents: read 17 | id-token: write 18 | 19 | defaults: 20 | run: 21 | shell: powershell 22 | 23 | env: 24 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 25 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 26 | 27 | jobs: 28 | Initialization: 29 | needs: [ ] 30 | runs-on: [ windows-latest ] 31 | outputs: 32 | environmentsMatrixJson: ${{ steps.DetermineDeploymentEnvironments.outputs.EnvironmentsMatrixJson }} 33 | environmentCount: ${{ steps.DetermineDeploymentEnvironments.outputs.EnvironmentCount }} 34 | deploymentEnvironmentsJson: ${{ steps.DetermineDeploymentEnvironments.outputs.DeploymentEnvironmentsJson }} 35 | deviceCode: ${{ steps.Authenticate.outputs.deviceCode }} 36 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 37 | steps: 38 | - name: Dump Workflow Information 39 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 40 | with: 41 | shell: powershell 42 | 43 | - name: Checkout 44 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 45 | 46 | - name: Initialize the workflow 47 | id: init 48 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 49 | with: 50 | shell: powershell 51 | 52 | - name: Read settings 53 | id: ReadSettings 54 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 55 | with: 56 | shell: powershell 57 | 58 | - name: Determine Deployment Environments 59 | id: DetermineDeploymentEnvironments 60 | uses: microsoft/AL-Go-Actions/DetermineDeploymentEnvironments@v7.0 61 | env: 62 | GITHUB_TOKEN: ${{ github.token }} 63 | with: 64 | shell: powershell 65 | getEnvironments: ${{ github.event.inputs.environmentName }} 66 | type: 'Publish' 67 | 68 | - name: EnvName 69 | id: envName 70 | if: steps.DetermineDeploymentEnvironments.outputs.UnknownEnvironment == 1 71 | run: | 72 | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 73 | $envName = '${{ fromJson(steps.DetermineDeploymentEnvironments.outputs.environmentsMatrixJson).matrix.include[0].environment }}'.split(' ')[0] 74 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "envName=$envName" 75 | 76 | - name: Read secrets 77 | id: ReadSecrets 78 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 79 | if: steps.DetermineDeploymentEnvironments.outputs.UnknownEnvironment == 1 80 | with: 81 | shell: powershell 82 | gitHubSecrets: ${{ toJson(secrets) }} 83 | getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext' 84 | 85 | - name: Authenticate 86 | id: Authenticate 87 | if: steps.DetermineDeploymentEnvironments.outputs.UnknownEnvironment == 1 88 | run: | 89 | $envName = '${{ steps.envName.outputs.envName }}' 90 | $secretName = '' 91 | $secrets = '${{ steps.ReadSecrets.outputs.Secrets }}' | ConvertFrom-Json 92 | $authContext = $null 93 | "$($envName)-AuthContext", "$($envName)_AuthContext", "AuthContext" | ForEach-Object { 94 | if (!($authContext)) { 95 | if ($secrets."$_") { 96 | Write-Host "Using $_ secret as AuthContext" 97 | $authContext = $secrets."$_" 98 | $secretName = $_ 99 | } 100 | } 101 | } 102 | if ($authContext) { 103 | Write-Host "AuthContext provided in secret $secretName!" 104 | Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "AuthContext was provided in a secret called $secretName. Using this information for authentication." 105 | } 106 | else { 107 | Write-Host "No AuthContext provided for $envName, initiating Device Code flow" 108 | $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" 109 | $webClient = New-Object System.Net.WebClient 110 | $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v7.0/AL-Go-Helper.ps1', $ALGoHelperPath) 111 | . $ALGoHelperPath 112 | DownloadAndImportBcContainerHelper 113 | $authContext = New-BcAuthContext -includeDeviceLogin -deviceLoginTimeout ([TimeSpan]::FromSeconds(0)) 114 | Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "AL-Go needs access to the Business Central Environment $('${{ steps.envName.outputs.envName }}'.Split(' ')[0]) and could not locate a secret called ${{ steps.envName.outputs.envName }}_AuthContext`n`n$($authContext.message)" 115 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "deviceCode=$($authContext.deviceCode)" 116 | } 117 | 118 | Deploy: 119 | needs: [ Initialization ] 120 | if: needs.Initialization.outputs.environmentCount > 0 121 | strategy: ${{ fromJson(needs.Initialization.outputs.environmentsMatrixJson) }} 122 | runs-on: ${{ fromJson(matrix.os) }} 123 | name: Deploy to ${{ matrix.environment }} 124 | defaults: 125 | run: 126 | shell: ${{ matrix.shell }} 127 | environment: 128 | name: ${{ matrix.environment }} 129 | url: ${{ steps.Deploy.outputs.environmentUrl }} 130 | env: 131 | deviceCode: ${{ needs.Initialization.outputs.deviceCode }} 132 | steps: 133 | - name: Checkout 134 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 135 | 136 | - name: EnvName 137 | id: envName 138 | run: | 139 | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 140 | $envName = '${{ matrix.environment }}'.split(' ')[0] 141 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "envName=$envName" 142 | 143 | - name: Read settings 144 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 145 | with: 146 | shell: ${{ matrix.shell }} 147 | get: type,powerPlatformSolutionFolder 148 | 149 | - name: Read secrets 150 | id: ReadSecrets 151 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 152 | with: 153 | shell: ${{ matrix.shell }} 154 | gitHubSecrets: ${{ toJson(secrets) }} 155 | getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext' 156 | 157 | - name: Get Artifacts for deployment 158 | uses: microsoft/AL-Go-Actions/GetArtifactsForDeployment@v7.0 159 | with: 160 | shell: ${{ matrix.shell }} 161 | artifactsVersion: ${{ github.event.inputs.appVersion }} 162 | artifactsFolder: '.artifacts' 163 | 164 | - name: Deploy to Business Central 165 | id: Deploy 166 | uses: microsoft/AL-Go-Actions/Deploy@v7.0 167 | env: 168 | Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' 169 | with: 170 | shell: ${{ matrix.shell }} 171 | environmentName: ${{ matrix.environment }} 172 | artifactsFolder: '.artifacts' 173 | type: 'Publish' 174 | deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} 175 | artifactsVersion: ${{ github.event.inputs.appVersion }} 176 | 177 | - name: Deploy to Power Platform 178 | if: env.type == 'PTE' && env.powerPlatformSolutionFolder != '' 179 | uses: microsoft/AL-Go-Actions/DeployPowerPlatform@v7.0 180 | env: 181 | Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' 182 | with: 183 | shell: ${{ matrix.shell }} 184 | environmentName: ${{ matrix.environment }} 185 | artifactsFolder: '.artifacts' 186 | deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} 187 | 188 | PostProcess: 189 | needs: [ Initialization, Deploy ] 190 | if: always() 191 | runs-on: [ windows-latest ] 192 | steps: 193 | - name: Checkout 194 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 195 | 196 | - name: Finalize the workflow 197 | id: PostProcess 198 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 199 | env: 200 | GITHUB_TOKEN: ${{ github.token }} 201 | with: 202 | shell: powershell 203 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 204 | currentJobContext: ${{ toJson(job) }} 205 | -------------------------------------------------------------------------------- /.github/workflows/PullRequestHandler.yaml: -------------------------------------------------------------------------------- 1 | name: 'Pull Request Build' 2 | 3 | on: 4 | pull_request_target: 5 | branches: [ 'main' ] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number }} 9 | cancel-in-progress: true 10 | 11 | defaults: 12 | run: 13 | shell: powershell 14 | 15 | permissions: 16 | actions: read 17 | contents: read 18 | id-token: write 19 | pull-requests: read 20 | 21 | env: 22 | workflowDepth: 1 23 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 24 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 25 | 26 | jobs: 27 | PregateCheck: 28 | if: (github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name) && (github.event_name != 'pull_request') 29 | runs-on: windows-latest 30 | steps: 31 | - uses: microsoft/AL-Go-Actions/VerifyPRChanges@v7.0 32 | 33 | Initialization: 34 | needs: [ PregateCheck ] 35 | if: (!failure() && !cancelled()) 36 | runs-on: [ windows-latest ] 37 | outputs: 38 | projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} 39 | projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} 40 | buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} 41 | baselineWorkflowRunId: ${{ steps.determineProjectsToBuild.outputs.BaselineWorkflowRunId }} 42 | baselineWorkflowSHA: ${{ steps.determineProjectsToBuild.outputs.BaselineWorkflowSHA }} 43 | workflowDepth: ${{ steps.DetermineWorkflowDepth.outputs.WorkflowDepth }} 44 | artifactsRetentionDays: ${{ steps.DetermineWorkflowDepth.outputs.ArtifactsRetentionDays }} 45 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 46 | steps: 47 | - name: Dump Workflow Information 48 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 49 | with: 50 | shell: powershell 51 | 52 | - name: Checkout 53 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 54 | with: 55 | lfs: true 56 | ref: ${{ github.event_name == 'pull_request' && github.sha || format('refs/pull/{0}/merge', github.event.pull_request.number) }} 57 | 58 | - name: Initialize the workflow 59 | id: init 60 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 61 | with: 62 | shell: powershell 63 | 64 | - name: Read settings 65 | id: ReadSettings 66 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 67 | with: 68 | shell: powershell 69 | get: shortLivedArtifactsRetentionDays 70 | 71 | - name: Determine Workflow Depth 72 | id: DetermineWorkflowDepth 73 | run: | 74 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "WorkflowDepth=$($env:workflowDepth)" 75 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ArtifactsRetentionDays=$($env:shortLivedArtifactsRetentionDays)" 76 | 77 | - name: Determine Projects To Build 78 | id: determineProjectsToBuild 79 | uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v7.0 80 | with: 81 | shell: powershell 82 | maxBuildDepth: ${{ env.workflowDepth }} 83 | 84 | Build: 85 | needs: [ Initialization ] 86 | if: (!failure()) && (!cancelled()) && fromJson(needs.Initialization.outputs.buildOrderJson)[0].projectsCount > 0 87 | strategy: 88 | matrix: 89 | include: ${{ fromJson(needs.Initialization.outputs.buildOrderJson)[0].buildDimensions }} 90 | fail-fast: false 91 | name: Build ${{ matrix.projectName }} (${{ matrix.buildMode }}) 92 | uses: ./.github/workflows/_BuildALGoProject.yaml 93 | secrets: inherit 94 | with: 95 | shell: ${{ matrix.githubRunnerShell }} 96 | runsOn: ${{ matrix.githubRunner }} 97 | checkoutRef: ${{ github.event_name == 'pull_request' && github.sha || format('refs/pull/{0}/merge', github.event.pull_request.number) }} 98 | project: ${{ matrix.project }} 99 | projectName: ${{ matrix.projectName }} 100 | buildMode: ${{ matrix.buildMode }} 101 | projectDependenciesJson: ${{ needs.Initialization.outputs.projectDependenciesJson }} 102 | baselineWorkflowRunId: ${{ needs.Initialization.outputs.baselineWorkflowRunId }} 103 | baselineWorkflowSHA: ${{ needs.Initialization.outputs.baselineWorkflowSHA }} 104 | secrets: 'licenseFileUrl,keyVaultCertificateUrl,*keyVaultCertificatePassword,keyVaultClientId,gitHubPackagesContext,applicationInsightsConnectionString' 105 | artifactsRetentionDays: ${{ fromJson(needs.Initialization.outputs.artifactsRetentionDays) }} 106 | artifactsNameSuffix: 'PR${{ github.event.number }}' 107 | useArtifactCache: true 108 | 109 | StatusCheck: 110 | needs: [ Initialization, Build ] 111 | if: (!cancelled()) 112 | runs-on: [ windows-latest ] 113 | name: Pull Request Status Check 114 | steps: 115 | - name: Pull Request Status Check 116 | id: PullRequestStatusCheck 117 | uses: microsoft/AL-Go-Actions/PullRequestStatusCheck@v7.0 118 | env: 119 | GITHUB_TOKEN: ${{ github.token }} 120 | with: 121 | shell: powershell 122 | 123 | - name: Finalize the workflow 124 | id: PostProcess 125 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 126 | if: success() || failure() 127 | env: 128 | GITHUB_TOKEN: ${{ github.token }} 129 | with: 130 | shell: powershell 131 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 132 | currentJobContext: ${{ toJson(job) }} 133 | -------------------------------------------------------------------------------- /.github/workflows/Troubleshooting.yaml: -------------------------------------------------------------------------------- 1 | name: 'Troubleshooting' 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | displayNameOfSecrets: 7 | description: Display the name (not the value) of secrets available to the repository 8 | type: boolean 9 | default: false 10 | 11 | permissions: 12 | actions: read 13 | contents: read 14 | 15 | defaults: 16 | run: 17 | shell: powershell 18 | 19 | env: 20 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 21 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 22 | 23 | jobs: 24 | Troubleshooting: 25 | runs-on: [ windows-latest ] 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 29 | with: 30 | lfs: true 31 | 32 | - name: Troubleshooting 33 | uses: microsoft/AL-Go-Actions/Troubleshooting@v7.0 34 | with: 35 | shell: powershell 36 | gitHubSecrets: ${{ toJson(secrets) }} 37 | displayNameOfSecrets: ${{ github.event.inputs.displayNameOfSecrets }} 38 | -------------------------------------------------------------------------------- /.github/workflows/UpdateGitHubGoSystemFiles.yaml: -------------------------------------------------------------------------------- 1 | name: ' Update AL-Go System Files' 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | templateUrl: 7 | description: Template Repository URL (current is https://github.com/microsoft/AL-Go-PTE@main) 8 | required: false 9 | default: '' 10 | downloadLatest: 11 | description: Download latest from template repository 12 | type: boolean 13 | default: true 14 | directCommit: 15 | description: Direct Commit? 16 | type: boolean 17 | default: false 18 | includeBranches: 19 | description: Specify a comma-separated list of branches to update. Wildcards are supported. The AL-Go settings will be read for every branch. Leave empty to update the current branch only. 20 | required: false 21 | default: '' 22 | schedule: 23 | - cron: '0 0 1,15 * *' 24 | 25 | permissions: 26 | actions: read 27 | contents: read 28 | id-token: write 29 | 30 | defaults: 31 | run: 32 | shell: powershell 33 | 34 | env: 35 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 36 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 37 | 38 | jobs: 39 | Initialize: 40 | runs-on: windows-latest 41 | name: Initialize 42 | outputs: 43 | UpdateBranches: ${{ steps.GetBranches.outputs.Result }} 44 | TemplateUrl: ${{ steps.DetermineTemplateUrl.outputs.TemplateUrl }} 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 48 | 49 | - name: Read settings 50 | id: ReadSettings 51 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 52 | with: 53 | shell: powershell 54 | get: templateUrl 55 | 56 | - name: Get Workflow Multi-Run Branches 57 | id: GetBranches 58 | uses: microsoft/AL-Go-Actions/GetWorkflowMultiRunBranches@v7.0 59 | with: 60 | shell: powershell 61 | includeBranches: ${{ github.event.inputs.includeBranches }} 62 | 63 | - name: Determine Template URL 64 | id: DetermineTemplateUrl 65 | env: 66 | TemplateUrlAsInput: '${{ github.event.inputs.templateUrl }}' 67 | run: | 68 | $templateUrl = $env:templateUrl # Available from ReadSettings step 69 | if ($ENV:TemplateUrlAsInput) { 70 | # Use the input value if it is provided 71 | $templateUrl = $ENV:TemplateUrlAsInput 72 | } 73 | Write-Host "Using template URL: $templateUrl" 74 | Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "TemplateUrl=$templateUrl" 75 | 76 | UpdateALGoSystemFiles: 77 | name: "[${{ matrix.branch }}] Update AL-Go System Files" 78 | needs: [ Initialize ] 79 | runs-on: [ windows-latest ] 80 | strategy: 81 | matrix: 82 | branch: ${{ fromJson(needs.Initialize.outputs.UpdateBranches).branches }} 83 | fail-fast: false 84 | 85 | steps: 86 | - name: Dump Workflow Information 87 | uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v7.0 88 | with: 89 | shell: powershell 90 | 91 | - name: Checkout 92 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 93 | with: 94 | ref: ${{ matrix.branch }} 95 | 96 | - name: Initialize the workflow 97 | id: init 98 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v7.0 99 | with: 100 | shell: powershell 101 | 102 | - name: Read settings 103 | uses: microsoft/AL-Go-Actions/ReadSettings@v7.0 104 | with: 105 | shell: powershell 106 | get: commitOptions 107 | 108 | - name: Read secrets 109 | id: ReadSecrets 110 | uses: microsoft/AL-Go-Actions/ReadSecrets@v7.0 111 | with: 112 | shell: powershell 113 | gitHubSecrets: ${{ toJson(secrets) }} 114 | getSecrets: 'ghTokenWorkflow' 115 | 116 | - name: Calculate Commit Options 117 | env: 118 | directCommit: '${{ github.event.inputs.directCommit }}' 119 | downloadLatest: '${{ github.event.inputs.downloadLatest }}' 120 | run: | 121 | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 122 | if('${{ github.event_name }}' -eq 'workflow_dispatch') { 123 | Write-Host "Using inputs from workflow_dispatch event" 124 | $directCommit = $env:directCommit 125 | $downloadLatest = $env:downloadLatest 126 | } 127 | else { 128 | Write-Host "Using inputs from commitOptions setting" 129 | $commitOptions = $env:commitOptions | ConvertFrom-Json # Available from ReadSettings step 130 | $directCommit=$(-not $commitOptions.createPullRequest) 131 | $downloadLatest=$true 132 | } 133 | Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "directCommit=$directCommit" 134 | Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "downloadLatest=$downloadLatest" 135 | 136 | - name: Update AL-Go system files 137 | uses: microsoft/AL-Go-Actions/CheckForUpdates@v7.0 138 | with: 139 | shell: powershell 140 | token: ${{ fromJson(steps.ReadSecrets.outputs.Secrets).ghTokenWorkflow }} 141 | downloadLatest: ${{ env.downloadLatest }} 142 | update: 'Y' 143 | templateUrl: ${{ needs.Initialize.outputs.TemplateUrl }} 144 | directCommit: ${{ env.directCommit }} 145 | updateBranch: ${{ matrix.branch }} 146 | 147 | - name: Finalize the workflow 148 | if: always() 149 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v7.0 150 | env: 151 | GITHUB_TOKEN: ${{ github.token }} 152 | with: 153 | shell: powershell 154 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 155 | currentJobContext: ${{ toJson(job) }} 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.app 2 | *.flf 3 | *.bclicense 4 | .DS_Store 5 | Thumbs.db 6 | TestResults*.xml 7 | bcptTestResults*.json 8 | BuildOutput.txt 9 | rad.json 10 | .output/ 11 | .dependencies/ 12 | .buildartifacts/ 13 | .alpackages/ 14 | .packages/ 15 | .alcache/ 16 | .altemplates/ 17 | .snapshots/ 18 | cache_* 19 | ~$* 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [email](mailto:anovoa@novoadev.com). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribute 2 | ## What to contribute 3 | Any bug fixes or new features are welcome in this project. PRs will be accepted after review and testing without promises or dates. They should be associated with an Issues. There may or may not be a previous conversation, either in discussions or in the issue itself. 4 | 5 | ## How to contribute 6 | In case you want to contribute code to this project, you must first establish what type of contribution it is and create an issue according to its type: 7 | 8 | ### A small bug fix. 🐛 9 | In this case the review will be lighter, only the functionality will be tested and tests will be added or modified if necessary. 10 | 11 | ### New capabilities or major change implementations. 🥇 12 | The process will be slower. 13 | - There should be a prior conversation about the functionality you want to implement, either through [discussions](https://github.com/NovoaDev/Dependency-Graph-BCExt/discussions). or within an issue. The point of this is that you don't waste time writing code, which then may not be accepted. 14 | - Fork the repository. 15 | - Create a Pull Request with the changes. 16 | - The next step is to wait for the approval of the PR, it will be evaluated and approved or rejected. 17 | 18 | Feel free to contact any of the maintainers if you have any questions about the contribution process. 19 | -------------------------------------------------------------------------------- /Dependency-Graph.Test/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "CDX - Development Sandbox", 6 | "request": "launch", 7 | "type": "al", 8 | "environmentType": "Sandbox", 9 | "environmentName": "DEVELOPMENT", 10 | "startupObjectId": 80800, 11 | "startupObjectType": "Page", 12 | "breakOnError": "All", 13 | "launchBrowser": true, 14 | "enableLongRunningSqlStatements": true, 15 | "enableSqlInformationDebugger": true, 16 | "tenant": "6de27021-dcdb-4539-b0e1-c5ecb27c4e48" 17 | }, 18 | { 19 | "name": "Docker - DependencyGraph", 20 | "request": "launch", 21 | "type": "al", 22 | "environmentType": "OnPrem", 23 | "server": "http://DependencyGraph", 24 | "serverInstance": "BC", 25 | "authentication": "UserPassword", 26 | "startupObjectId": 22, 27 | "startupObjectType": "Page", 28 | "breakOnError": "All", 29 | "launchBrowser": true, 30 | "enableLongRunningSqlStatements": true, 31 | "enableSqlInformationDebugger": true, 32 | "tenant": "default", 33 | "usePublicURLFromServer": true 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /Dependency-Graph.Test/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "al.enableCodeAnalysis": true, 3 | "al.backgroundCodeAnalysis": "Project", 4 | "al.codeAnalyzers": [ 5 | "${CodeCop}", 6 | "${UICop}", 7 | "${PerTenantExtensionCop}", 8 | "${AppSourceCop}", 9 | "${analyzerFolder}BusinessCentral.LinterCop.dll" 10 | ], 11 | "al.enableCodeActions": true, 12 | "al.ruleSetPath": "./.vscode/ruleset.json", 13 | "CRS.FileNamePattern": "..al", 14 | "CRS.FileNamePatternExtensions": "..al", 15 | "CRS.FileNamePatternPageCustomizations": "..al", 16 | "CRS.OnSaveAlFileAction": "DoNothing", 17 | "CRS.RenameWithGit": true, 18 | "editor.formatOnSave": true, 19 | "CRS.ObjectNameSuffix": "_ANJ", 20 | "rest-client.rememberCookiesForSubsequentRequests": false, 21 | "editor.codeActionsOnSave": { 22 | "source.fixAll.al": "explicit" 23 | }, 24 | "alOutline.codeActionsOnSave": [ 25 | "SortProperties", 26 | "SortVariables", 27 | "FormatDocument" 28 | ], 29 | "al-xml-doc.CheckObjectDocumentationInformationLevel": "Error", 30 | "al-xml-doc.CheckProcedureDocumentationInformationLevel": "Error", 31 | "al-xml-doc.CheckProcedureDocumentationForType": [ 32 | "Global Procedures", 33 | "Local Procedures", 34 | "Internal Procedures", 35 | "Protected Procedures", 36 | "Event Publisher" 37 | ], 38 | "al-xml-doc.CheckProcedureDocumentationForAccessLevel": [ 39 | "Public", 40 | "Internal", 41 | "Local" 42 | ] 43 | } -------------------------------------------------------------------------------- /Dependency-Graph.Test/AppSourceCop.json: -------------------------------------------------------------------------------- 1 | { 2 | "mandatorySuffix": "_ANJ", 3 | "supportedCountries": ["US"] 4 | } -------------------------------------------------------------------------------- /Dependency-Graph.Test/DepGraphTestANJ.permissionset.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// permissionset DepGraphTest_ANJ (ID 99990). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | permissionset 99990 DepGraphTest_ANJ 6 | { 7 | Access = Public; 8 | Assignable = true; 9 | Caption = 'Dep. Graph Test', Locked = true; 10 | Permissions = codeunit ExpectedValues_ANJ = X, 11 | codeunit FillingProTablesMock_ANJ = X, 12 | codeunit GenerateFiguresTest_ANJ = X, 13 | codeunit NumberSequenceTest_ANJ = X, 14 | codeunit TemporaryTablesTest_ANJ = X; 15 | } -------------------------------------------------------------------------------- /Dependency-Graph.Test/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "d7bcd883-c9cb-4e98-a771-a3b2ba51444e", 3 | "name": "Dependency-Graph.Test", 4 | "publisher": "ANJ", 5 | "version": "2.9.0.0", 6 | "brief": "App to test Dependency-Graph", 7 | "description": "App to test Dependency-Graph", 8 | "privacyStatement": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 9 | "EULA": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 10 | "help": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 11 | "url": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 12 | "logo": "res/logo.png", 13 | "contextSensitiveHelpUrl": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 14 | "dependencies": [ 15 | { 16 | "publisher": "Microsoft", 17 | "name": "Library Assert", 18 | "id": "dd0be2ea-f733-4d65-bb34-a28f4624fb14", 19 | "version": "23.0.0.0" 20 | }, 21 | { 22 | "id": "5d86850b-0d76-4eca-bd7b-951ad998e997", 23 | "publisher": "Microsoft", 24 | "name": "Tests-TestLibraries", 25 | "version": "23.0.0.0" 26 | }, 27 | { 28 | "publisher": "Microsoft", 29 | "name": "Any", 30 | "id": "e7320ebb-08b3-4406-b1ec-b4927d3e280b", 31 | "version": "23.0.0.0" 32 | }, 33 | { 34 | "id": "3c9a4c22-42f6-4e8f-8b96-744c7fef331f", 35 | "name": "Dependency-Graph", 36 | "publisher": "ANJ", 37 | "version": "3.6.0.0" 38 | } 39 | ], 40 | "screenshots": [], 41 | "platform": "1.0.0.0", 42 | "application": "23.0.0.0", 43 | "idRanges": [ 44 | { 45 | "from": 99990, 46 | "to": 99999 47 | } 48 | ], 49 | "features": [ 50 | "NoImplicitWith", 51 | "TranslationFile" 52 | ], 53 | "resourceExposurePolicy": { 54 | "allowDebugging": true, 55 | "allowDownloadingSource": false, 56 | "includeSourceInSymbolFile": false 57 | }, 58 | "runtime": "13.0" 59 | } 60 | -------------------------------------------------------------------------------- /Dependency-Graph.Test/doc/Dependency-Graph.Test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/Dependency-Graph.Test/doc/Dependency-Graph.Test.pdf -------------------------------------------------------------------------------- /Dependency-Graph.Test/res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/Dependency-Graph.Test/res/logo.png -------------------------------------------------------------------------------- /Dependency-Graph.Test/src/Test/ExpectedValuesANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit ExpectedValues_ANJ (ID 99994). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 99994 ExpectedValues_ANJ 6 | { 7 | Access = Internal; 8 | 9 | /// 10 | /// GetExpectedMarkdownText. 11 | /// 12 | /// Return value of type Text. 13 | procedure GetExpectedMarkdownText(): Text 14 | var 15 | ExpectedValueTextBuilder: TextBuilder; 16 | begin 17 | ExpectedValueTextBuilder.AppendLine(Header1Lbl); 18 | ExpectedValueTextBuilder.AppendLine(Header2Lbl); 19 | ExpectedValueTextBuilder.AppendLine(ExpectedRelationLbl); 20 | ExpectedValueTextBuilder.AppendLine(); 21 | ExpectedValueTextBuilder.AppendLine(FooterLbl); 22 | 23 | exit(ExpectedValueTextBuilder.ToText()); 24 | end; 25 | 26 | /// 27 | /// GetExpectedMarkdownMermaidText. 28 | /// 29 | /// Return value of type Text. 30 | procedure GetExpectedMarkdownMermaidText(): Text 31 | var 32 | ExpectedValueTextBuilder: TextBuilder; 33 | begin 34 | ExpectedValueTextBuilder.AppendLine(Header2Lbl); 35 | ExpectedValueTextBuilder.AppendLine(ExpectedRelationLbl); 36 | 37 | exit(ExpectedValueTextBuilder.ToText()); 38 | end; 39 | 40 | var 41 | ExpectedRelationLbl: Label 'E1[Take Order Sample] --> E2[DependencyGraph]'; 42 | FooterLbl: Label '```'; 43 | Header1Lbl: Label '```mermaid'; 44 | Header2Lbl: Label 'graph BT'; 45 | } -------------------------------------------------------------------------------- /Dependency-Graph.Test/src/Test/FillingProTablesMockANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "FillingProTablesMock_ANJ" (ID 99993). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 99993 FillingProTablesMock_ANJ implements IFillingProcessingTables_ANJ 6 | { 7 | Access = Internal; 8 | 9 | /// 10 | /// GetExtensions. 11 | /// 12 | /// Return value of type Text. 13 | procedure GetExtensions(): Text 14 | var 15 | ExtensionArry: JsonArray; 16 | AuxJsonObject: JsonObject; 17 | Extensions: Text; 18 | begin 19 | AddNewExtensionToJsonArry( 20 | ExtensionArry, 21 | Ext01IDLbl, 22 | Ext01NameLbl, 23 | Ext01PublisherLbl, 24 | ScopeLbl); 25 | 26 | AddNewExtensionToJsonArry( 27 | ExtensionArry, 28 | Ext02IDLbl, 29 | Ext02NameLbl, 30 | Ext02PublisherLbl, 31 | ScopeLbl); 32 | 33 | AuxJsonObject.Add(ValueLbl, ExtensionArry); 34 | AuxJsonObject.WriteTo(Extensions); 35 | 36 | exit(Extensions); 37 | end; 38 | 39 | /// 40 | /// GetRelations. 41 | /// 42 | /// Return value of type Text. 43 | procedure GetRelations(): Text 44 | var 45 | RelationsArry: JsonArray; 46 | Relations: Text; 47 | begin 48 | AddNewRelationToJsonArry(RelationsArry, 49 | Ext01IDLbl, 50 | Ext02IDLbl); 51 | RelationsArry.WriteTo(Relations); 52 | 53 | exit(Relations); 54 | end; 55 | 56 | /// 57 | /// AddNewExtensionToJsonArry. 58 | /// 59 | /// VAR JsonArray. 60 | /// Text. 61 | /// Text. 62 | /// Text. 63 | /// Text. 64 | local procedure AddNewExtensionToJsonArry( 65 | var ExtensionArry: JsonArray; 66 | PackageId: Text; 67 | DisplayName: Text; 68 | Publisher: Text; 69 | PublishedAs: Text) 70 | var 71 | ExtensionJsonObject: JsonObject; 72 | begin 73 | ExtensionJsonObject.Add(IdLbl, PackageId); 74 | ExtensionJsonObject.Add(PackageIdLbl, PackageId); 75 | ExtensionJsonObject.Add(DisplayNameLbl, DisplayName); 76 | ExtensionJsonObject.Add(PublisherLbl, Publisher); 77 | ExtensionJsonObject.Add(PublishedAsLbl, PublishedAs); 78 | ExtensionJsonObject.Add(IsInstalledLbl, true); 79 | 80 | ExtensionArry.Add(ExtensionJsonObject); 81 | end; 82 | 83 | /// 84 | /// AddNewRelationToJsonArry. 85 | /// 86 | /// VAR JsonArray. 87 | /// Guid. 88 | /// Guid. 89 | local procedure AddNewRelationToJsonArry( 90 | var RelationsArry: JsonArray; 91 | SourceAppID: Guid; 92 | DestinationAppID: Guid) 93 | var 94 | RelationJsonObject: JsonObject; 95 | begin 96 | RelationJsonObject.Add(SourceAppIDLbl, SourceAppID); 97 | RelationJsonObject.Add(DestinationAppIDLbl, DestinationAppID); 98 | 99 | RelationsArry.Add(RelationJsonObject); 100 | end; 101 | 102 | var 103 | DestinationAppIDLbl: Label 'DestinationAppID'; 104 | DisplayNameLbl: Label 'displayName'; 105 | Ext01IDLbl: Label 'a1f81352-6244-48a4-96a0-a81c5aaaa581'; 106 | Ext01NameLbl: Label 'Take Order Sample'; 107 | Ext01PublisherLbl: Label 'BusinessCentralDemos'; 108 | Ext02IDLbl: Label '3c9a4c22-42f6-4e8f-8b96-744c7fef331f'; 109 | Ext02NameLbl: Label 'Dependency-Graph'; 110 | Ext02PublisherLbl: Label 'ANJ'; 111 | IdLbl: Label 'id'; 112 | IsInstalledLbl: Label 'isInstalled'; 113 | PackageIdLbl: Label 'packageId'; 114 | PublishedAsLbl: Label 'publishedAs'; 115 | PublisherLbl: Label 'publisher'; 116 | ScopeLbl: Label 'PTE'; 117 | SourceAppIDLbl: Label 'SourceAppID'; 118 | ValueLbl: Label 'value'; 119 | } -------------------------------------------------------------------------------- /Dependency-Graph.Test/src/Test/FillingProcessTablesMockANJ.EnumExt.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// EnumExtension FillingProcessingTablesMock_ANJ (ID 99990) extends Record FillingProcessingTables_ANJ. 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | enumextension 99990 FillingProcessTablesMock_ANJ extends FillingProcessingTables_ANJ 6 | { 7 | value(99990; Mock_ANJ) 8 | { 9 | Caption = 'Mock'; 10 | Implementation = IFillingProcessingTables_ANJ = FillingProTablesMock_ANJ; 11 | } 12 | } -------------------------------------------------------------------------------- /Dependency-Graph.Test/src/Test/GenerateFiguresTestANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "GenerateFiguresTest_ANJ" (ID 99991). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 99991 GenerateFiguresTest_ANJ 6 | { 7 | Access = Public; 8 | Subtype = Test; 9 | TestPermissions = Disabled; 10 | 11 | /// 12 | /// GenerateFigures. 13 | /// 14 | [Test] 15 | procedure GenerateFigures() 16 | var 17 | ScopeDevFigure: Text; 18 | ScopeGlobalFigure: Text; 19 | ScopePTEFigure: Text; 20 | begin 21 | // [Scenario] 22 | // Verify that the figures are being generated according to each of the extension scopes. 23 | 24 | // [Given] Setup: 25 | InitializeDependencyGraphSetup(); 26 | 27 | // [When] Exercise: 28 | GetFigureText(ScopeDevFigure, ScopeGlobalFigure, ScopePTEFigure); 29 | 30 | // [Then] Verify: 31 | LibraryAssert.AreEqual(ScopePTEFigure, StrSubstNo(SquareRectangleFigureLbl, IdentityLbl, AppNameLbl), GenerateFigureErr); 32 | LibraryAssert.AreEqual(ScopeGlobalFigure, StrSubstNo(CircleFigureLbl, IdentityLbl, AppNameLbl), GenerateFigureErr); 33 | LibraryAssert.AreEqual(ScopeDevFigure, StrSubstNo(RhombusFigureLbl, IdentityLbl, AppNameLbl), GenerateFigureErr); 34 | end; 35 | 36 | /// 37 | /// InitializeDependencyGraphSetup 38 | /// 39 | local procedure InitializeDependencyGraphSetup() 40 | var 41 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 42 | begin 43 | DependencyGraphSetup.GetInstance(); 44 | DependencyGraphSetup.Validate(ScopePTEFigure, Enum::GeometricFigure_ANJ::SquareRectangle); 45 | DependencyGraphSetup.Validate(ScopeGlobalFigure, Enum::GeometricFigure_ANJ::Circle); 46 | DependencyGraphSetup.Validate(ScopeDevFigure, Enum::GeometricFigure_ANJ::Rhombus); 47 | DependencyGraphSetup.Modify(true); 48 | end; 49 | 50 | /// 51 | /// GetFigureText. 52 | /// 53 | /// VAR Text. 54 | /// VAR Text. 55 | /// VAR Text. 56 | local procedure GetFigureText(var ScopeDevFigure: Text; var ScopeGlobalFigure: Text; var ScopePTEFigure: Text) 57 | begin 58 | ScopePTEFigure := DependencyGraphFacadeANJ.GenerateFigures(Enum::ExtensionScope_ANJ::PTE, IdentityLbl, AppNameLbl); 59 | ScopeGlobalFigure := DependencyGraphFacadeANJ.GenerateFigures(Enum::ExtensionScope_ANJ::Global, IdentityLbl, AppNameLbl); 60 | ScopeDevFigure := DependencyGraphFacadeANJ.GenerateFigures(Enum::ExtensionScope_ANJ::Dev, IdentityLbl, AppNameLbl); 61 | end; 62 | 63 | var 64 | DependencyGraphFacadeANJ: Codeunit DependencyGraphFacade_ANJ; 65 | LibraryAssert: Codeunit System.TestLibraries.Utilities."Library Assert"; 66 | AppNameLbl: Label 'App'; 67 | CircleFigureLbl: Label '%1((%2))', Comment = 'Placeholder %1 represents the Identity, and Placeholder %2 represents the Content.'; 68 | GenerateFigureErr: Label 'Error generating figures.'; 69 | IdentityLbl: Label 'E1'; 70 | RhombusFigureLbl: Label '%1{%2}', Comment = 'Placeholder %1 represents the Identity, and Placeholder %2 represents the Content.'; 71 | SquareRectangleFigureLbl: Label '%1[%2]', Comment = 'Placeholder %1 represents the Identity, and Placeholder %2 represents the Content.'; 72 | } -------------------------------------------------------------------------------- /Dependency-Graph.Test/src/Test/NumberSequenceTestANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit NumberSequenceTest_ANJ (ID 99990). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 99990 NumberSequenceTest_ANJ 6 | { 7 | Access = Public; 8 | Subtype = Test; 9 | 10 | /// 11 | /// TestNumberSequence. 12 | /// 13 | [Test] 14 | procedure TestNumberSequence() 15 | var 16 | FirstRequest: Text; 17 | SecondRequest: Text; 18 | ThirdRequest: Text; 19 | begin 20 | // [Scenario] 21 | // Verify that it is initialized and a number series is created correctly. 22 | 23 | // [Given] Setup: 24 | LibraryLowerPermissions.SetO365BusFull(); 25 | 26 | // [When] Exercise: 27 | CheckInitializeAndCreateSomeNumberSeries(FirstRequest, SecondRequest, ThirdRequest); 28 | 29 | // [Then] Verify: 30 | LibraryAssert.AreEqual(FirstRequest, FirstRequestLbl, NumberSequenceErr); 31 | LibraryAssert.AreEqual(SecondRequest, SecondRequestLbl, NumberSequenceErr); 32 | LibraryAssert.AreEqual(ThirdRequest, ThirdRequestLbl, NumberSequenceErr); 33 | end; 34 | 35 | /// 36 | /// CheckInitializeAndCreateSomeNumberSeries. 37 | /// 38 | /// VAR Text. 39 | /// VAR Text. 40 | /// VAR Text. 41 | local procedure CheckInitializeAndCreateSomeNumberSeries(var FirstRequest: Text; var SecondRequest: Text; var ThirdRequest: Text) 42 | begin 43 | DependencyGraphFacadeANJ.InitializeNumberSequence(); 44 | FirstRequest := DependencyGraphFacadeANJ.GetNextNumberSequence(); 45 | SecondRequest := DependencyGraphFacadeANJ.GetNextNumberSequence(); 46 | ThirdRequest := DependencyGraphFacadeANJ.GetNextNumberSequence(); 47 | end; 48 | 49 | var 50 | DependencyGraphFacadeANJ: Codeunit DependencyGraphFacade_ANJ; 51 | LibraryLowerPermissions: Codeunit "Library - Lower Permissions"; 52 | LibraryAssert: Codeunit System.TestLibraries.Utilities."Library Assert"; 53 | FirstRequestLbl: Label 'E1'; 54 | NumberSequenceErr: Label 'The number secuence does not correspond to the expected one'; 55 | SecondRequestLbl: Label 'E2'; 56 | ThirdRequestLbl: Label 'E3'; 57 | } -------------------------------------------------------------------------------- /Dependency-Graph.Test/src/Test/TemporaryTablesTestANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "TemporaryTablesTest_ANJ" (ID 99992). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 99992 TemporaryTablesTest_ANJ 6 | { 7 | Access = Public; 8 | Subtype = Test; 9 | TestPermissions = Disabled; 10 | 11 | /// 12 | /// GenerateFigures. 13 | /// 14 | [Test] 15 | procedure GenerateFigures() 16 | var 17 | ExtensionRecords: Integer; 18 | RelationsRecords: Integer; 19 | ExpectedMarkdownMermaidText: Text; 20 | ExpectedMarkdownText: Text; 21 | MarkdownMermaidText: Text; 22 | MarkdownText: Text; 23 | begin 24 | // [Scenario] 25 | // A single test is carried out with the entire cycle. 26 | 27 | // [Given] Setup: 28 | InitializeDependencyGraphSetup(); 29 | 30 | // [When] Exercise: 31 | DependencyGraphFacade.GenerateAllTables(true); 32 | ExtensionRecords := GetExtensionRecords(); 33 | RelationsRecords := GetRelationsRecords(); 34 | 35 | GetMarkdownTexts(MarkdownText, MarkdownMermaidText); 36 | ExpectedMarkdownText := ExpectedValues.GetExpectedMarkdownText(); 37 | ExpectedMarkdownMermaidText := ExpectedValues.GetExpectedMarkdownMermaidText(); 38 | // [Then] Verify: 39 | LibraryAssert.AreEqual(ExtensionRecords, 2, StrSubstNo(DiferentNumberErr, Format(Database::Extensions_ANJ))); 40 | LibraryAssert.AreEqual(RelationsRecords, 1, StrSubstNo(DiferentNumberErr, Format(Database::Relations_ANJ))); 41 | LibraryAssert.AreEqual(MarkdownText, ExpectedMarkdownText, MarkdownTextErr); 42 | LibraryAssert.AreEqual(MarkdownMermaidText, ExpectedMarkdownMermaidText, MarkdownTextErr); 43 | end; 44 | 45 | /// 46 | /// InitializeDependencyGraphSetup 47 | /// 48 | local procedure InitializeDependencyGraphSetup() 49 | var 50 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 51 | begin 52 | DependencyGraphSetup.GetInstance(); 53 | DependencyGraphSetup.Validate(ScopePTEFigure, Enum::GeometricFigure_ANJ::SquareRectangle); 54 | DependencyGraphSetup.Validate(FillingProcessingTables, Enum::FillingProcessingTables_ANJ::Mock_ANJ); 55 | DependencyGraphSetup.Modify(true); 56 | end; 57 | 58 | /// 59 | /// GetExtensionRecords. 60 | /// 61 | /// Return value of type Integer. 62 | local procedure GetExtensionRecords(): Integer 63 | var 64 | Extensions: Record Extensions_ANJ; 65 | begin 66 | exit(Extensions.Count()); 67 | end; 68 | 69 | /// 70 | /// GetRelationsRecords. 71 | /// 72 | /// Return value of type Integer. 73 | local procedure GetRelationsRecords(): Integer 74 | var 75 | Relations: Record Relations_ANJ; 76 | begin 77 | exit(Relations.Count()); 78 | end; 79 | 80 | /// 81 | /// GetMarkdownTexts. 82 | /// 83 | /// VAR Text. 84 | /// VAR Text. 85 | local procedure GetMarkdownTexts(var MarkdownText: Text; var MarkdownMermaidText: Text) 86 | var 87 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 88 | begin 89 | DependencyGraphFacade.GenerateGraph(); 90 | MarkdownText := DependencyGraphFacade.GetMarkdownText(DependencyGraphSetup.FieldNo(Markdown)); 91 | MarkdownMermaidText := DependencyGraphFacade.GetMarkdownText(DependencyGraphSetup.FieldNo(MarkdownMermaid)); 92 | end; 93 | 94 | var 95 | DependencyGraphFacade: Codeunit DependencyGraphFacade_ANJ; 96 | ExpectedValues: Codeunit ExpectedValues_ANJ; 97 | LibraryAssert: Codeunit System.TestLibraries.Utilities."Library Assert"; 98 | DiferentNumberErr: Label 'Diferent number of records in table %1 than expected', Comment = 'Error message for different number of records in a table %1.'; 99 | MarkdownTextErr: Label 'Markdown text is different than expected'; 100 | } -------------------------------------------------------------------------------- /Dependency-Graph.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".AL-Go" 5 | }, 6 | { 7 | "path": "Dependency-Graph" 8 | }, 9 | { 10 | "path": "Dependency-Graph.Test" 11 | } 12 | ], 13 | "settings": {} 14 | } 15 | -------------------------------------------------------------------------------- /Dependency-Graph/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "humao.rest-client" 4 | ] 5 | } -------------------------------------------------------------------------------- /Dependency-Graph/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "CDX - Development Sandbox", 6 | "request": "launch", 7 | "type": "al", 8 | "environmentType": "Sandbox", 9 | "environmentName": "DEVELOPMENT", 10 | "startupObjectId": 80800, 11 | "startupObjectType": "Page", 12 | "breakOnError": "All", 13 | "launchBrowser": true, 14 | "enableLongRunningSqlStatements": true, 15 | "enableSqlInformationDebugger": true, 16 | "tenant": "6de27021-dcdb-4539-b0e1-c5ecb27c4e48" 17 | }, 18 | { 19 | "name": "Docker - DependencyGraph", 20 | "request": "launch", 21 | "type": "al", 22 | "environmentType": "OnPrem", 23 | "server": "http://DependencyGraph", 24 | "serverInstance": "BC", 25 | "authentication": "UserPassword", 26 | "startupObjectId": 22, 27 | "startupObjectType": "Page", 28 | "breakOnError": "All", 29 | "launchBrowser": true, 30 | "enableLongRunningSqlStatements": true, 31 | "enableSqlInformationDebugger": true, 32 | "tenant": "default", 33 | "usePublicURLFromServer": true 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /Dependency-Graph/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "al.enableCodeAnalysis": true, 3 | "al.backgroundCodeAnalysis": "Project", 4 | "al.codeAnalyzers": [ 5 | "${CodeCop}", 6 | "${UICop}", 7 | "${PerTenantExtensionCop}", 8 | "${AppSourceCop}", 9 | "${analyzerFolder}BusinessCentral.LinterCop.dll" 10 | ], 11 | "al.enableCodeActions": true, 12 | "al.ruleSetPath": "./.vscode/ruleset.json", 13 | "CRS.FileNamePattern": "..al", 14 | "CRS.FileNamePatternExtensions": "..al", 15 | "CRS.FileNamePatternPageCustomizations": "..al", 16 | "CRS.OnSaveAlFileAction": "DoNothing", 17 | "CRS.RenameWithGit": true, 18 | "editor.formatOnSave": true, 19 | "CRS.ObjectNameSuffix": "_ANJ", 20 | "rest-client.rememberCookiesForSubsequentRequests": false, 21 | "editor.codeActionsOnSave": { 22 | "source.fixAll.al": "explicit" 23 | }, 24 | "alOutline.codeActionsOnSave": [ 25 | "SortProperties", 26 | "SortVariables", 27 | "FormatDocument" 28 | ] 29 | } -------------------------------------------------------------------------------- /Dependency-Graph/AppSourceCop.json: -------------------------------------------------------------------------------- 1 | { 2 | "supportedCountries": 3 | ["AL", "DZ", "AD", "AO", "AR", "AM", "AU", "AT", "AZ", "BS", "BH", "BD", "BE", "BM", "BA", "BW", "VG", "BR", "BG", "KH", "CM", "CA", "KY", "CL", "CO", "CR", 4 | "HR", "CY", "CZ", "DK", "DO", "EC", "SV", "EG", "EE", "ET", "DK", "FJ", "FI", "FR", "GE", "DE", "GH", "HN", "HK", "HU", "IS", "IN", "ID", "IE", "IM", "IL", 5 | "IT", "JM", "JP", "JE", "JO", "KZ", "KE", "KW", "LB", "LV", "LT", "LU", "MO", "MK", "MG", "MW", "MY", "MV", "MT", "MU", "MX", "ME", "MA", "MZ", "MM", "NA", 6 | "NP", "NL", "NZ", "NI", "NG", "OM", "PK", "PA", "PE", "PH", "PL", "PT", "PR", "QA", "RO", "RU", "SA", "SN", "SG", "SK", "ZA", "KR", "ES", "LK", "SE", "CH", 7 | "TW", "TZ", "TH", "TT", "TN", "TR", "UG", "UA", "AE", "GB", "US", "UY", "VN", "VG", "GB", "US", "UY", "ZM", "ZW"], 8 | "mandatorySuffix": "_ANJ" 9 | } -------------------------------------------------------------------------------- /Dependency-Graph/DependencyGraphANJ.permissionset.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// permissionset DependencyGraph_ANJ (ID 80800). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | permissionset 80800 DependencyGraph_ANJ 6 | { 7 | Access = Public; 8 | Assignable = true; 9 | Caption = 'DependencyGraph', Locked = true; 10 | Permissions = tabledata DependencyGraphSetup_ANJ = RIMD, 11 | table DependencyGraphSetup_ANJ = X, 12 | codeunit CircleIMP_ANJ = X, 13 | codeunit GenerateFigure_ANJ = X, 14 | codeunit RhombusIMP_ANJ = X, 15 | codeunit RoundRectangleIMP_ANJ = X, 16 | codeunit SquareRectangleIMP_ANJ = X, 17 | page DependencyGraphSetup_ANJ = X, 18 | tabledata Extensions_ANJ = RIMD, 19 | table Extensions_ANJ = X, 20 | page Extensions_ANJ = X, 21 | codeunit GenerateExtensionTable_ANJ = X, 22 | page ShowInGraph_ANJ = X, 23 | codeunit NumberSequenceMgmt_ANJ = X, 24 | tabledata Relations_ANJ = RIMD, 25 | table Relations_ANJ = X, 26 | page Relations_ANJ = X, 27 | codeunit GenerateTables_ANJ = X, 28 | codeunit GenerateRelationsTable_ANJ = X, 29 | codeunit MarkdownMgmt_ANJ = X, 30 | page MarkdownViewer_ANJ = X, 31 | codeunit DependencyGraphFacade_ANJ = X, 32 | page MarkdownFactbox_ANJ = X, 33 | page MarkdownViewerFull_ANJ = X, 34 | codeunit CleanTemporaryTables_ANJ = X, 35 | codeunit JSONMethods_ANJ = X, 36 | codeunit WSAndMDInfoImp_ANJ = X, 37 | codeunit DefaultFigureIMP_ANJ = X, 38 | codeunit DefaultFillProcessTabImp_ANJ = X, 39 | codeunit DataUpgrades_ANJ = X, 40 | codeunit UpgradeCodeunit_ANJ = X; 41 | } -------------------------------------------------------------------------------- /Dependency-Graph/Test Odata/Test API.http: -------------------------------------------------------------------------------- 1 | @TenantId = {{$processEnv DependencyGraphTenantId}} 2 | @AppId = {{$processEnv DependencyGraphAppId}} 3 | @Secret = {{$processEnv DependencyGraphSecret}} 4 | @Environment = {{$processEnv DependencyGraphEnvironment}} 5 | 6 | # @name AuthResponse 7 | POST https://login.microsoftonline.com/{{TenantId}}/oauth2/v2.0/token 8 | Content-type: application/x-www-form-urlencoded 9 | 10 | grant_type=client_credentials 11 | &client_id={{AppId}} 12 | &client_secret={{Secret}} 13 | &scope=https://api.businesscentral.dynamics.com/.default 14 | 15 | #### Token 16 | @AuthHeader = Bearer {{AuthResponse.response.body.$.access_token}} 17 | 18 | ### Metadata ODataV4 ### 19 | GET https://api.businesscentral.dynamics.com/v2.0/{{TenantId}}/{{Environment}}/ODataV4/$metadata 20 | Authorization: {{AuthHeader}} 21 | ### 22 | 23 | ### Companies ### 24 | GET https://api.businesscentral.dynamics.com/v2.0/{{TenantId}}/{{Environment}}/api/v2.0/companies 25 | Authorization: {{AuthHeader}} 26 | ### 27 | 28 | ### Get All extensiones 29 | @CompanyID = {{$processEnv DependencyGraphCompanyID}} 30 | GET https://api.businesscentral.dynamics.com/v2.0/{{Environment}}/api/microsoft/automation/v2.0/companies({{CompanyID}})/extensions 31 | Authorization: {{AuthHeader}} 32 | 33 | ### Get All extensiones with filter 34 | GET https://api.businesscentral.dynamics.com/v2.0/{{Environment}}/api/microsoft/automation/v2.0/companies({{CompanyID}})/extensions?$filter=publisher ne 'Microsoft' 35 | Authorization: {{AuthHeader}} 36 | -------------------------------------------------------------------------------- /Dependency-Graph/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3c9a4c22-42f6-4e8f-8b96-744c7fef331f", 3 | "name": "Dependency-Graph", 4 | "publisher": "ANJ", 5 | "version": "4.17.0.0", 6 | "brief": "App to generate the dependency tree", 7 | "description": "App to generate the dependency tree of the extensions we have installed.", 8 | "privacyStatement": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 9 | "EULA": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 10 | "help": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 11 | "url": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 12 | "logo": "res/logo.png", 13 | "contextSensitiveHelpUrl": "https://github.com/NovoaDev/Dependency-Graph-BCExt", 14 | "dependencies": [], 15 | "screenshots": [], 16 | "platform": "1.0.0.0", 17 | "application": "25.0.0.0", 18 | "idRanges": [ 19 | { 20 | "from": 80800, 21 | "to": 80849 22 | } 23 | ], 24 | "features": [ 25 | "NoImplicitWith", 26 | "TranslationFile" 27 | ], 28 | "resourceExposurePolicy": { 29 | "allowDebugging": true, 30 | "allowDownloadingSource": false, 31 | "includeSourceInSymbolFile": false 32 | }, 33 | "runtime": "14.0" 34 | } 35 | -------------------------------------------------------------------------------- /Dependency-Graph/doc/Dependency-Graph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/Dependency-Graph/doc/Dependency-Graph.pdf -------------------------------------------------------------------------------- /Dependency-Graph/res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/Dependency-Graph/res/logo.png -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/CleanTemporaryTablesANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "CleanTemporaryTables_ANJ" (ID 80811). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80811 CleanTemporaryTables_ANJ 6 | { 7 | Access = Public; 8 | Permissions = tabledata Extensions_ANJ = RD, tabledata Relations_ANJ = RD; 9 | 10 | /// 11 | /// Clean. 12 | /// 13 | /// Boolean. 14 | /// Boolean. 15 | internal procedure Clean(ExtensionsTable: Boolean; RelationsTable: Boolean) 16 | var 17 | IsHandled: Boolean; 18 | begin 19 | OnBeforeClean(ExtensionsTable, RelationsTable, IsHandled); 20 | DoClean(ExtensionsTable, RelationsTable, IsHandled); 21 | OnAfterClean(ExtensionsTable, RelationsTable); 22 | end; 23 | 24 | /// 25 | /// DoClean. 26 | /// 27 | /// Boolean. 28 | /// Boolean. 29 | /// Boolean. 30 | local procedure DoClean(ExtensionsTable: Boolean; RelationsTable: Boolean; IsHandled: Boolean) 31 | begin 32 | if IsHandled then 33 | exit; 34 | 35 | if ExtensionsTable then 36 | CleanExtensionsTable(); 37 | 38 | if RelationsTable then 39 | CleanRelationsTable(); 40 | end; 41 | 42 | /// 43 | /// CleanRelationsTable. 44 | /// 45 | local procedure CleanExtensionsTable() 46 | var 47 | Extensions: Record Extensions_ANJ; 48 | begin 49 | if not Extensions.IsEmpty() then 50 | Extensions.DeleteAll(true); 51 | end; 52 | 53 | /// 54 | /// CleanRelationsTable. 55 | /// 56 | local procedure CleanRelationsTable() 57 | var 58 | Relations: Record Relations_ANJ; 59 | begin 60 | if not Relations.IsEmpty() then 61 | Relations.DeleteAll(true); 62 | end; 63 | 64 | /// 65 | /// OnBeforeClean. 66 | /// 67 | /// VAR Boolean. 68 | /// VAR Boolean. 69 | /// VAR Boolean. 70 | [IntegrationEvent(false, false)] 71 | local procedure OnBeforeClean(var ExtensionsTable: Boolean; var RelationsTable: Boolean; var IsHandled: Boolean) 72 | begin 73 | end; 74 | 75 | /// 76 | /// OnAfterClean. 77 | /// 78 | /// Boolean. 79 | /// Boolean. 80 | [IntegrationEvent(false, false)] 81 | local procedure OnAfterClean(ExtensionsTable: Boolean; RelationsTable: Boolean) 82 | begin 83 | end; 84 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/DefaultFillProcessTabImpANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "DefaultFillProcessTabImp_ANJ" (ID 80815) implements Interface FillingProcessingTables_ANJ. 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80815 DefaultFillProcessTabImp_ANJ implements IFillingProcessingTables_ANJ 6 | { 7 | Access = Internal; 8 | 9 | /// 10 | /// GetExtensions. 11 | /// 12 | /// Return value of type Text. 13 | procedure GetExtensions(): Text 14 | begin 15 | Error(ImpErr); 16 | end; 17 | 18 | /// 19 | /// GetRelations. 20 | /// 21 | /// Return variable JsonText of type Text. 22 | procedure GetRelations() JsonText: Text 23 | begin 24 | Error(ImpErr); 25 | end; 26 | 27 | var 28 | ImpErr: Label 'An implementation for the generation of related relationship tables must be specified.'; 29 | 30 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/DependencyGraphFacadeANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "DependencyGraphFacade_ANJ" (ID 80810). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80810 DependencyGraphFacade_ANJ 6 | { 7 | Access = Public; 8 | 9 | #region codeunit 80806 NumberSequenceMgmt_ANJ 10 | /// 11 | /// InitializeNumberSequence 12 | /// 13 | procedure InitializeNumberSequence() 14 | begin 15 | NumberSequenceMgmt.Initialize(); 16 | end; 17 | 18 | /// 19 | /// GetNextNumberSequence. 20 | /// 21 | /// Return value of type Text. 22 | procedure GetNextNumberSequence(): Text 23 | begin 24 | exit(NumberSequenceMgmt.GetNextNo()); 25 | end; 26 | #endregion 27 | 28 | #region codeunit 80804 GenerateFigure_ANJ 29 | /// 30 | /// GenerateFigures. 31 | /// 32 | /// Enum ExtensionScope_ANJ. 33 | /// Text. 34 | /// Text. 35 | /// Return value of type Text. 36 | procedure GenerateFigures(ExtensionScope: Enum ExtensionScope_ANJ; Identity: Text; AppName: Text): Text 37 | begin 38 | exit(GenerateFigure.Generate(ExtensionScope, Identity, AppName)); 39 | end; 40 | #endregion 41 | 42 | #region codeunit 80807 GenerateTables_ANJ 43 | /// 44 | /// GenerateAllTables. 45 | /// 46 | /// Boolean. 47 | procedure GenerateAllTables(HideDialog: Boolean) 48 | begin 49 | GenerateTables.Generate(HideDialog); 50 | end; 51 | #endregion 52 | 53 | #region codeunit 80805 GenerateExtensionTable_ANJ 54 | /// 55 | /// GenerateExtensionsTable. 56 | /// 57 | procedure GenerateExtensionsTable() 58 | begin 59 | GenerateExtensionTable.Generate(); 60 | end; 61 | #endregion 62 | 63 | #region codeunit 80808 GenerateRelationsTable_ANJ 64 | /// 65 | /// GenerateRelationsTable. 66 | /// 67 | procedure GenerateRelationTable() 68 | begin 69 | GenerateRelationsTable.Generate(); 70 | end; 71 | #endregion 72 | 73 | #region 80811 CleanTemporaryTables_ANJ 74 | /// 75 | /// CleanExtensionsTable. 76 | /// 77 | procedure CleanExtensionsTable() 78 | begin 79 | CleanTemporaryTables.Clean(true, false); 80 | end; 81 | 82 | /// 83 | /// CleanRelationsTable. 84 | /// 85 | procedure CleanRelationsTable() 86 | begin 87 | CleanTemporaryTables.Clean(false, true); 88 | end; 89 | #endregion 90 | 91 | #region codeunit 80809 MarkdownMgmt_ANJ 92 | /// 93 | /// GenerateGraph. 94 | /// 95 | procedure GenerateGraph() 96 | begin 97 | MarkdownMgmt.GenerateGraph(); 98 | end; 99 | 100 | /// 101 | /// GetMarkdownText. 102 | /// 103 | /// Integer. 104 | /// Return value of type Text. 105 | procedure GetMarkdownText(FieldNo: Integer): Text 106 | begin 107 | exit(MarkdownMgmt.GetMarkdown(FieldNo)); 108 | end; 109 | #endregion 110 | 111 | /// 112 | /// GetInterfaceFillProcessingTables. 113 | /// 114 | /// VAR Interface FillingProcessingTables_ANJ. 115 | procedure GetInterfaceFillProcessingTables(var FillingProcessingTables: Interface IFillingProcessingTables_ANJ) 116 | var 117 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 118 | begin 119 | DependencyGraphSetup.SetLoadFields(FillingProcessingTables); 120 | DependencyGraphSetup.GetInstance(); 121 | FillingProcessingTables := DependencyGraphSetup.FillingProcessingTables; 122 | OnAfterGetFillingProcessingTables(FillingProcessingTables); 123 | end; 124 | 125 | /// 126 | /// OnAfterGetFillingProcessingTables 127 | /// 128 | /// 129 | [IntegrationEvent(false, false)] 130 | local procedure OnAfterGetFillingProcessingTables(var FillingProcessingTables: Interface IFillingProcessingTables_ANJ) 131 | begin 132 | end; 133 | 134 | var 135 | CleanTemporaryTables: Codeunit CleanTemporaryTables_ANJ; 136 | GenerateExtensionTable: Codeunit GenerateExtensionTable_ANJ; 137 | GenerateFigure: Codeunit GenerateFigure_ANJ; 138 | GenerateRelationsTable: Codeunit GenerateRelationsTable_ANJ; 139 | GenerateTables: Codeunit GenerateTables_ANJ; 140 | MarkdownMgmt: Codeunit MarkdownMgmt_ANJ; 141 | NumberSequenceMgmt: Codeunit NumberSequenceMgmt_ANJ; 142 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/FillingProcessingTablesANJ.Enum.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Enum FillingProcessingTables_ANJ (ID 80802). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | enum 80802 FillingProcessingTables_ANJ implements IFillingProcessingTables_ANJ 6 | { 7 | Access = Public; 8 | Caption = 'Filling Processing Tables'; 9 | DefaultImplementation = IFillingProcessingTables_ANJ = DefaultFillProcessTabImp_ANJ; 10 | Extensible = true; 11 | UnknownValueImplementation = IFillingProcessingTables_ANJ = DefaultFillProcessTabImp_ANJ; 12 | 13 | value(1; WSAndModuleDependencyInfo) 14 | { 15 | Caption = 'WS And ModuleDependencyInfo'; 16 | Implementation = IFillingProcessingTables_ANJ = WSAndMDInfoImp_ANJ; 17 | } 18 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/GenerateTablesANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit GenerateTables_ANJ (ID 80807) 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80807 GenerateTables_ANJ 6 | { 7 | Access = Public; 8 | Permissions = tabledata Extensions_ANJ = RD, tabledata Relations_ANJ = RD, tabledata DependencyGraphSetup_ANJ = RM; 9 | 10 | /// 11 | /// GenerateTables. 12 | /// 13 | /// Boolean. 14 | procedure Generate(HideDialog: Boolean) 15 | var 16 | Extensions: Record Extensions_ANJ; 17 | IsHandled: Boolean; 18 | begin 19 | if not Extensions.IsEmpty() then 20 | if not ConfirmGenerateTables(HideDialog) then 21 | exit; 22 | 23 | OnBeforeGenerateTables(IsHandled); 24 | DoGenerateTables(IsHandled); 25 | OnAfterGenerateTables(); 26 | UpdateSetupTable(); 27 | AcknowledgeGenerateTables(HideDialog); 28 | end; 29 | /// 30 | /// ConfirmGenerateTables. 31 | /// 32 | /// Boolean. 33 | /// Return variable Answer of type Boolean. 34 | local procedure ConfirmGenerateTables(HideDialog: Boolean) Answer: Boolean 35 | begin 36 | Answer := true; 37 | if not HideDialog then 38 | Answer := ConfirmManagement.GetResponseOrDefault(ConfirmQst, Answer); 39 | end; 40 | 41 | /// 42 | /// DoGenerateTables. 43 | /// 44 | /// Boolean. 45 | local procedure DoGenerateTables(IsHandled: Boolean) 46 | begin 47 | if IsHandled then 48 | exit; 49 | 50 | DependencyGraphFacade.CleanExtensionsTable(); 51 | DependencyGraphFacade.CleanRelationsTable(); 52 | 53 | DependencyGraphFacade.GenerateExtensionsTable(); 54 | DependencyGraphFacade.GenerateRelationTable(); 55 | end; 56 | 57 | /// 58 | /// UpdateSetupTable. 59 | /// 60 | local procedure UpdateSetupTable() 61 | var 62 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 63 | begin 64 | DependencyGraphSetup.GetInstance(); 65 | DependencyGraphSetup.Validate(DateLastGeneration, Today()); 66 | DependencyGraphSetup.Validate(TimeLastGeneration, Time()); 67 | DependencyGraphSetup.Modify(true); 68 | end; 69 | 70 | /// 71 | /// AcknowledgeGenerateTables. 72 | /// 73 | /// Boolean. 74 | local procedure AcknowledgeGenerateTables(HideDialog: Boolean) 75 | begin 76 | if (not GuiAllowed()) or (HideDialog) then 77 | exit; 78 | 79 | Message(ProcessFinishMsg); 80 | end; 81 | 82 | /// 83 | /// OnBeforeGenerateTables. 84 | /// 85 | /// VAR Boolean. 86 | [IntegrationEvent(false, false)] 87 | local procedure OnBeforeGenerateTables(var IsHandled: Boolean) 88 | begin 89 | end; 90 | 91 | /// 92 | /// OnAfterGenerateTables. 93 | /// 94 | [IntegrationEvent(false, false)] 95 | local procedure OnAfterGenerateTables() 96 | begin 97 | end; 98 | 99 | var 100 | DependencyGraphFacade: Codeunit DependencyGraphFacade_ANJ; 101 | ConfirmManagement: Codeunit System.Utilities."Confirm Management"; 102 | ConfirmQst: Label 'When generating the tables again, custom data will be deleted, do you want to continue?'; 103 | ProcessFinishMsg: Label 'The tables have been updated correctly.'; 104 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/IFillingProcessingTablesANJ.Interface.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Interface "FillingProcessingTables_ANJ." 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | interface IFillingProcessingTables_ANJ 6 | { 7 | Access = Public; 8 | 9 | /// 10 | /// GetExtensions. 11 | /// 12 | /// Return value of type Text. 13 | procedure GetExtensions(): Text 14 | 15 | /// 16 | /// GetRelations. 17 | /// 18 | /// Return value of type Text. 19 | procedure GetRelations() JsonText: Text 20 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/JSONMethodsANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "JSONMethods_ANJ" (ID 80813). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80813 JSONMethods_ANJ 6 | { 7 | Access = Internal; 8 | 9 | /// 10 | /// GetJsonValue. 11 | /// 12 | /// Text. 13 | /// Text. 14 | /// Return value of type Boolean. 15 | internal procedure GetJsonValue(JsonKey: Text; ResponseText: Text): Text 16 | var 17 | AuxJsonObject: JsonObject; 18 | AuxJsonToken: JsonToken; 19 | JsonText: Text; 20 | begin 21 | if not AuxJsonObject.ReadFrom(ResponseText) then 22 | Error(ReadingJsonErr); 23 | 24 | if not AuxJsonObject.Get(JsonKey, AuxJsonToken) then 25 | Error(ReadingJsonErr); 26 | 27 | if (not AuxJsonToken.IsValue()) and (not AuxJsonToken.IsArray()) then 28 | Error(ReadingJsonErr); 29 | 30 | if AuxJsonToken.IsValue() then 31 | JsonText := AuxJsonToken.AsValue().AsText() 32 | else 33 | AuxJsonToken.AsArray().WriteTo(JsonText); 34 | 35 | exit(JsonText.Trim()); 36 | end; 37 | 38 | var 39 | ReadingJsonErr: Label 'Error reading JSON response.'; 40 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/MarkdownFactboxANJ.Page.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Page MarkdownFactbox_ANJ (ID 80805). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | page 80805 MarkdownFactbox_ANJ 6 | { 7 | ApplicationArea = All; 8 | Caption = 'Markdown Text'; 9 | Editable = false; 10 | Extensible = true; 11 | PageType = CardPart; 12 | UsageCategory = None; 13 | 14 | layout 15 | { 16 | area(Content) 17 | { 18 | field(MarkdownText; MarkdownText) 19 | { 20 | Editable = false; 21 | MultiLine = true; 22 | ShowCaption = false; 23 | } 24 | } 25 | } 26 | actions 27 | { 28 | area(Processing) 29 | { 30 | action(DownloadMarkdown) 31 | { 32 | ApplicationArea = All; 33 | Caption = 'Download Markdown as .md'; 34 | Image = Download; 35 | ToolTip = 'Executes the Download Markdown as .md action.'; 36 | 37 | trigger OnAction() 38 | begin 39 | MarkdownMgmt.DownloadMarkdown(); 40 | end; 41 | } 42 | } 43 | } 44 | 45 | /// 46 | /// SetMarkdownText. 47 | /// 48 | /// Text. 49 | internal procedure SetMarkdownText(AuxMarkdownText: Text) 50 | begin 51 | MarkdownText := AuxMarkdownText; 52 | end; 53 | 54 | var 55 | MarkdownMgmt: Codeunit MarkdownMgmt_ANJ; 56 | MarkdownText: Text; 57 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/ShowInGraphANJ.Page.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Page "ShowInGraph_ANJ" (ID 80802). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | page 80802 ShowInGraph_ANJ 6 | { 7 | ApplicationArea = All; 8 | Caption = 'Show In Dependency Graph'; 9 | Extensible = true; 10 | PageType = Document; 11 | UsageCategory = None; 12 | 13 | layout 14 | { 15 | area(Content) 16 | { 17 | part(Extensions_ANJ; Extensions_ANJ) 18 | { 19 | UpdatePropagation = Both; 20 | } 21 | part(Relations_ANJ; Relations_ANJ) 22 | { 23 | UpdatePropagation = Both; 24 | } 25 | } 26 | area(FactBoxes) 27 | { 28 | part(MarkdownFactbox_ANJ; MarkdownFactbox_ANJ) 29 | { 30 | } 31 | part(MarkdownViewer_ANJ; MarkdownViewer_ANJ) 32 | { 33 | } 34 | } 35 | } 36 | 37 | actions 38 | { 39 | area(Processing) 40 | { 41 | action(GenTables) 42 | { 43 | ApplicationArea = All; 44 | Caption = 'Generate data'; 45 | Image = NewSparkle; 46 | Promoted = true; 47 | PromotedCategory = Process; 48 | PromotedOnly = true; 49 | ToolTip = 'Executes the Generate data action.'; 50 | 51 | trigger OnAction() 52 | begin 53 | DependencyGraphFacade.GenerateAllTables(false); 54 | end; 55 | } 56 | } 57 | } 58 | 59 | trigger OnAfterGetCurrRecord() 60 | var 61 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 62 | begin 63 | DependencyGraphFacade.GenerateGraph(); 64 | CurrPage.MarkdownFactbox_ANJ.Page.SetMarkdownText(DependencyGraphFacade.GetMarkdownText(DependencyGraphSetup.FieldNo(Markdown))); 65 | CurrPage.MarkdownViewer_ANJ.Page.SetMarkdown(DependencyGraphFacade.GetMarkdownText(DependencyGraphSetup.FieldNo(MarkdownMermaid))); 66 | end; 67 | 68 | var 69 | DependencyGraphFacade: Codeunit DependencyGraphFacade_ANJ; 70 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Base/WSAndMDInfoImpANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "WSAndMDInfoImp_ANJ" (ID 80812) implements Interface FillingProcessingTables_ANJ. 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80812 WSAndMDInfoImp_ANJ implements IFillingProcessingTables_ANJ 6 | { 7 | Access = Internal; 8 | Permissions = tabledata Extensions_ANJ = RD, tabledata Relations_ANJ = RD; 9 | 10 | /// 11 | /// GetExtensions. 12 | /// 13 | /// Return value of type Text. 14 | procedure GetExtensions(): Text 15 | var 16 | AccessToken: Text; 17 | begin 18 | AccessToken := GetAccessToken(); 19 | if AccessToken = '' then 20 | Error(UnableToCommunicateWSErr); 21 | 22 | exit(DoGetExtensions(AccessToken)); 23 | end; 24 | 25 | /// 26 | /// GetAccessToken. 27 | /// 28 | /// Return value of type Text. 29 | local procedure GetAccessToken(): Text 30 | var 31 | AuxHttpClient: HttpClient; 32 | RequestHttpContent: HttpContent; 33 | Headers: HttpHeaders; 34 | ResponseHttpResponseMessage: HttpResponseMessage; 35 | ResponseText: Text; 36 | begin 37 | RequestHttpContent.WriteFrom(GetRequestAccessTokenContent()); 38 | RequestHttpContent.GetHeaders(Headers); 39 | Headers.Remove(ContentTypeTok); 40 | Headers.Add(ContentTypeTok, UrlencodedTok); 41 | 42 | if not AuxHttpClient.Post(GetAccessTokenAPIUrl(), RequestHttpContent, ResponseHttpResponseMessage) then 43 | Error(UnableToCommunicateWSErr); 44 | 45 | GetResponseMessageText(ResponseHttpResponseMessage, ResponseText); 46 | exit(JSONMethods.GetJsonValue(AccessTokenTok, ResponseText)); 47 | end; 48 | 49 | /// 50 | /// GetRequestAccessTokenContent. 51 | /// 52 | /// Return value of type Text. 53 | local procedure GetRequestAccessTokenContent(): Text 54 | var 55 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 56 | ContentTextBuilder: TextBuilder; 57 | begin 58 | DependencyGraphSetup.SetLoadFields(ClientID, Secret); 59 | DependencyGraphSetup.GetInstance(); 60 | 61 | ContentTextBuilder.AppendLine(GrantTypeTok); 62 | ContentTextBuilder.AppendLine(StrSubstNo(ClientIdTok, DependencyGraphSetup.ClientID)); 63 | ContentTextBuilder.AppendLine(StrSubstNo(ClientSecretTok, DependencyGraphSetup.Secret)); 64 | ContentTextBuilder.AppendLine(ScopeTok); 65 | exit(ContentTextBuilder.ToText()); 66 | end; 67 | 68 | /// 69 | /// GetAccessTokenAPIUrl. 70 | /// 71 | /// Return value of type Text. 72 | local procedure GetAccessTokenAPIUrl(): Text 73 | begin 74 | exit(StrSubstNo(AccessTokenUrlTok, GetTenantId())); 75 | end; 76 | 77 | /// 78 | /// GetTenantId. 79 | /// 80 | /// Return value of type Text. 81 | local procedure GetTenantId(): Text 82 | begin 83 | exit(AzureADTenant.GetAadTenantId()); 84 | end; 85 | 86 | /// 87 | /// DoGetExtensions. 88 | /// 89 | /// Text. 90 | /// Return value of type Text. 91 | local procedure DoGetExtensions(AccessToken: Text): Text 92 | var 93 | AuxHttpClient: HttpClient; 94 | ResponseHttpResponseMessage: HttpResponseMessage; 95 | ResponseText: Text; 96 | begin 97 | AuxHttpClient.DefaultRequestHeaders().Add(AuthorizationTok, StrSubstNo(AuthorizationValueTok, AccessToken)); 98 | 99 | if not AuxHttpClient.Get(GetExtensionsAPIUrl(), ResponseHttpResponseMessage) then 100 | Error(UnableToCommunicateWSErr); 101 | 102 | GetResponseMessageText(ResponseHttpResponseMessage, ResponseText); 103 | exit(ResponseText); 104 | end; 105 | 106 | /// 107 | /// GetExtensionsAPIUrl. 108 | /// 109 | /// Return value of type Text. 110 | local procedure GetExtensionsAPIUrl(): Text 111 | var 112 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 113 | ExtensionsUrl: Text; 114 | begin 115 | DependencyGraphSetup.SetLoadFields(IncludeMicrosoftApps); 116 | DependencyGraphSetup.GetInstance(); 117 | 118 | ExtensionsUrl := StrSubstNo(ExtensionsUrlTok, GetEnvironmentName(), GetCompanyId()); 119 | if not DependencyGraphSetup.IncludeMicrosoftApps then 120 | ExtensionsUrl += FilterMSAppsTok + '''Microsoft'''; 121 | // Hardcode of value 'Microsoft' because the single-quote escaping gives error when generating translations with the third-party extension. 122 | 123 | exit(ExtensionsUrl); 124 | end; 125 | 126 | /// 127 | /// GetEnvironmentName. 128 | /// 129 | /// Return value of type Text. 130 | local procedure GetEnvironmentName(): Text 131 | var 132 | EnvironmentInformation: Codeunit System.Environment."Environment Information"; 133 | begin 134 | exit(EnvironmentInformation.GetEnvironmentName()); 135 | end; 136 | 137 | /// 138 | /// GetCompanyId. 139 | /// 140 | /// Return value of type Boolean. 141 | local procedure GetCompanyId(): Text 142 | var 143 | Company: Record System.Environment.Company; 144 | begin 145 | Company.SetLoadFields(Id); 146 | if not Company.Get(CompanyName()) then 147 | exit; 148 | 149 | exit(Format(Company.Id, 0, 4).ToLower()); 150 | end; 151 | 152 | /// 153 | /// GetResponseMessageText. 154 | /// 155 | /// VAR HttpResponseMessage. 156 | /// VAR Text. 157 | local procedure GetResponseMessageText(var ResponseHttpResponseMessage: HttpResponseMessage; var ResponseText: Text) 158 | begin 159 | ResponseHttpResponseMessage.Content().ReadAs(ResponseText); 160 | if not ResponseHttpResponseMessage.IsSuccessStatusCode() then 161 | Error(WSStatusCodeErr, ResponseHttpResponseMessage.HttpStatusCode(), ResponseText); 162 | end; 163 | 164 | /// 165 | /// GetRelations. 166 | /// 167 | /// Return value of type Text. 168 | procedure GetRelations() JsonText: Text 169 | var 170 | Extensions: Record Extensions_ANJ; 171 | RelationsArry: JsonArray; 172 | begin 173 | Extensions.SetLoadFields(AppID); 174 | Extensions.SetRange(ShowInGraph, true); 175 | if Extensions.IsEmpty() then 176 | exit; 177 | 178 | if Extensions.FindSet(false) then 179 | repeat 180 | CheckDependencies(Extensions.AppID, RelationsArry); 181 | until Extensions.Next() = 0; 182 | RelationsArry.WriteTo(JsonText); 183 | end; 184 | 185 | /// 186 | /// CheckDependencies. 187 | /// 188 | /// Guid. 189 | /// VAR JsonArray. 190 | local procedure CheckDependencies(AppId: Guid; var RelationsArry: JsonArray) 191 | var 192 | DestinationAppID: Guid; 193 | ModuleDependencyInfoList: List of [ModuleDependencyInfo]; 194 | SingleModuleDependencyInfo: ModuleDependencyInfo; 195 | AuxModuleInfo: ModuleInfo; 196 | begin 197 | if not NavApp.GetModuleInfo(AppId, AuxModuleInfo) then 198 | exit; 199 | 200 | ModuleDependencyInfoList := AuxModuleInfo.Dependencies; 201 | 202 | foreach SingleModuleDependencyInfo in ModuleDependencyInfoList do begin 203 | DestinationAppID := SingleModuleDependencyInfo.Id; 204 | if CheckDestinationAppIDShowInGraph(DestinationAppID) then 205 | AddNewRelationToJsonArry(RelationsArry, AppId, DestinationAppID); 206 | end; 207 | end; 208 | 209 | /// 210 | /// AddNewRelationToJsonArry. 211 | /// 212 | /// VAR JsonArray. 213 | /// Guid. 214 | /// Guid. 215 | local procedure AddNewRelationToJsonArry(var RelationsArry: JsonArray; SourceAppID: Guid; DestinationAppID: Guid) 216 | var 217 | RelationJsonObject: JsonObject; 218 | begin 219 | RelationJsonObject.Add(SourceAppIDTok, SourceAppID); 220 | RelationJsonObject.Add(DestinationAppIDTok, DestinationAppID); 221 | 222 | RelationsArry.Add(RelationJsonObject); 223 | end; 224 | 225 | /// 226 | /// CheckDestinationAppIDShowInGraph. 227 | /// 228 | /// Guid. 229 | /// Return value of type Boolean. 230 | local procedure CheckDestinationAppIDShowInGraph(AppID: Guid): Boolean 231 | var 232 | Extensions: Record Extensions_ANJ; 233 | begin 234 | Extensions.SetRange(AppID, AppID); 235 | Extensions.SetRange(ShowInGraph, true); 236 | exit(not Extensions.IsEmpty()); 237 | end; 238 | 239 | var 240 | JSONMethods: Codeunit JSONMethods_ANJ; 241 | AzureADTenant: Codeunit System.Azure.Identity."Azure AD Tenant"; 242 | AccessTokenTok: Label 'access_token', Locked = true; 243 | AccessTokenUrlTok: Label 'https://login.microsoftonline.com/%1/oauth2/v2.0/token', Locked = true; 244 | AuthorizationTok: Label 'Authorization', Locked = true; 245 | AuthorizationValueTok: Label 'Bearer %1', Locked = true; 246 | ClientIdTok: Label '&client_id=%1', Locked = true; 247 | ClientSecretTok: Label '&client_secret=%1', Locked = true; 248 | ContentTypeTok: Label 'Content-Type', Locked = true; 249 | DestinationAppIDTok: Label 'DestinationAppID', Locked = true; 250 | ExtensionsUrlTok: Label 'https://api.businesscentral.dynamics.com/v2.0/%1/api/microsoft/automation/v2.0/companies(%2)/extensions', Locked = true; 251 | FilterMSAppsTok: Label '?$filter=publisher ne ', Locked = true; 252 | GrantTypeTok: Label 'grant_type=client_credentials', Locked = true; 253 | ScopeTok: Label '&scope=https://api.businesscentral.dynamics.com/.default', Locked = true; 254 | SourceAppIDTok: Label 'SourceAppID', Locked = true; 255 | UnableToCommunicateWSErr: Label 'Unable to communicate with the web service.'; 256 | UrlencodedTok: Label 'application/x-www-form-urlencoded', Locked = true; 257 | WSStatusCodeErr: Label 'Error - Status code: %1 Description: %2.', Comment = 'Placeholder %1: Status code, Placeholder %2: Description.'; 258 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/ControlAddinViewer/MarkdownViewerANJ.ControlAddin.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// ControlAddIn "MarkdownViewer_ANJ." 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | controladdin MarkdownViewer_ANJ 6 | { 7 | HorizontalShrink = true; 8 | RequestedHeight = 600; 9 | RequestedWidth = 650; 10 | Scripts = 'src\ControlAddinViewer\Scripts.js', 'https://cdnjs.cloudflare.com/ajax/libs/mermaid/9.3.0/mermaid.min.js'; 11 | StartupScript = 'src\ControlAddinViewer\Start.js'; 12 | StyleSheets = 'src\ControlAddinViewer\Style.css'; 13 | VerticalShrink = true; 14 | 15 | /// 16 | /// Ready. 17 | /// 18 | event Ready(); 19 | 20 | /// 21 | /// Draw. 22 | /// 23 | /// Text. 24 | procedure Draw(Markdown: Text) 25 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/ControlAddinViewer/MarkdownViewerFSANJ.ControlAddin.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// ControlAddIn "MarkdownViewer_ANJ._ANJ" 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | controladdin MarkdownViewerFS_ANJ 6 | { 7 | HorizontalStretch = true; 8 | Scripts = 'src\ControlAddinViewer\Scripts.js', 'https://cdnjs.cloudflare.com/ajax/libs/mermaid/9.3.0/mermaid.min.js'; 9 | StartupScript = 'src\ControlAddinViewer\Start.js'; 10 | VerticalStretch = true; 11 | 12 | /// 13 | /// Ready. 14 | /// 15 | event Ready(); 16 | 17 | /// 18 | /// Draw. 19 | /// 20 | /// Text. 21 | procedure Draw(Markdown: Text) 22 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/ControlAddinViewer/Scripts.js: -------------------------------------------------------------------------------- 1 | /* 2 | Embed mermaid in Javascript learned from video by Erick houguard 3 | https://www.youtube.com/watch?v=OIVVpFSJLmw 4 | https://www.hougaard.com/ 5 | 6 | - Thank you very much! - 7 | */ 8 | 9 | function Draw(Markdown) { 10 | try { 11 | const insertSvg = function (svgCode, bindFunctions) { 12 | document.getElementById('controlAddIn').innerHTML = '
' + svgCode + '
'; 13 | }; 14 | mermaid.mermaidAPI.render('chart', Markdown, insertSvg); 15 | } 16 | catch (e) { 17 | console.log(e); 18 | } 19 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/ControlAddinViewer/Start.js: -------------------------------------------------------------------------------- 1 | HTMLContainer = document.getElementById("controlAddIn"); 2 | mermaid.initialize({ startOnLoad: false }); 3 | Microsoft.Dynamics.NAV.InvokeExtensibilityMethod("Ready", []); -------------------------------------------------------------------------------- /Dependency-Graph/src/ControlAddinViewer/Style.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | margin: 0 auto; 3 | } 4 | 5 | .MermaidDiv { 6 | width: 100%; 7 | height: auto; 8 | word-wrap: break-word; 9 | padding-left: 1%; 10 | padding-right: 1; 11 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/ExtensionTable/ExtensionsANJ.Page.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Page Extensions_ANJ (ID 80801). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | page 80801 Extensions_ANJ 6 | { 7 | ApplicationArea = All; 8 | Caption = 'Extensions'; 9 | DeleteAllowed = false; 10 | Extensible = true; 11 | InsertAllowed = false; 12 | PageType = ListPart; 13 | SourceTable = Extensions_ANJ; 14 | UsageCategory = None; 15 | 16 | layout 17 | { 18 | area(Content) 19 | { 20 | repeater(General) 21 | { 22 | field(Name; Rec.Name) 23 | { 24 | Editable = false; 25 | } 26 | field(DisplayName; Rec.DisplayName) 27 | { 28 | trigger OnValidate() 29 | begin 30 | DoGenerateRelationsTable(); 31 | end; 32 | } 33 | field(Publisher; Rec.Publisher) 34 | { 35 | Editable = false; 36 | } 37 | field(PublishedAs; Rec.PublishedAs) 38 | { 39 | Editable = false; 40 | } 41 | field(ShowInGraph; Rec.ShowInGraph) 42 | { 43 | 44 | trigger OnValidate() 45 | begin 46 | DoGenerateRelationsTable(); 47 | end; 48 | } 49 | } 50 | } 51 | } 52 | 53 | /// 54 | /// DoGenerateRelationsTable. 55 | /// 56 | local procedure DoGenerateRelationsTable() 57 | begin 58 | CurrPage.SaveRecord(); 59 | Rec.UpdateRelationTable(); 60 | CurrPage.Update(false); 61 | end; 62 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/ExtensionTable/ExtensionsANJ.Table.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Table "Extensions_ANJ" (ID 80801). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | table 80801 Extensions_ANJ 6 | { 7 | Access = Public; 8 | Caption = 'Extensions'; 9 | DataClassification = CustomerContent; 10 | DrillDownPageId = Extensions_ANJ; 11 | Extensible = true; 12 | LookupPageId = Extensions_ANJ; 13 | 14 | fields 15 | { 16 | field(1; AppID; Guid) 17 | { 18 | AllowInCustomizations = Never; 19 | Caption = 'App ID'; 20 | } 21 | field(2; Name; Text[2048]) 22 | { 23 | Caption = 'Name'; 24 | ToolTip = 'Specifies the value of the Name field.'; 25 | } 26 | field(3; DisplayName; Text[2048]) 27 | { 28 | Caption = 'Display Name'; 29 | ToolTip = 'Specifies the value of the Display Name field.'; 30 | 31 | trigger OnValidate() 32 | begin 33 | UpdateFigure(); 34 | end; 35 | } 36 | field(4; Publisher; Text[2048]) 37 | { 38 | Caption = 'Publisher'; 39 | ToolTip = 'Specifies the value of the Publisher field.'; 40 | } 41 | field(5; PublishedAs; Enum ExtensionScope_ANJ) 42 | { 43 | Caption = 'Published As'; 44 | InitValue = PTE; 45 | ToolTip = 'Specifies the value of the Published As field.'; 46 | } 47 | field(6; Identity; Text[2048]) 48 | { 49 | AllowInCustomizations = Never; 50 | Caption = 'Identity'; 51 | 52 | trigger OnValidate() 53 | begin 54 | UpdateFigure(); 55 | end; 56 | } 57 | field(7; ShowInGraph; Boolean) 58 | { 59 | Caption = 'Show In Graph'; 60 | ToolTip = 'Specifies the value of the ShowInGraph field.'; 61 | } 62 | field(8; Figure; Text[2048]) 63 | { 64 | AllowInCustomizations = Never; 65 | Caption = 'Figure'; 66 | } 67 | field(9; HasStartRelationships; Boolean) 68 | { 69 | AllowInCustomizations = Never; 70 | CalcFormula = exist(Relations_ANJ where(SourceAppID = field(AppID))); 71 | Caption = 'Has Relationships'; 72 | Editable = false; 73 | FieldClass = FlowField; 74 | } 75 | field(10; HasRelationships; Boolean) 76 | { 77 | AllowInCustomizations = Never; 78 | CalcFormula = exist(Relations_ANJ where(DestinationAppID = field(AppID))); 79 | Caption = 'Has Relationships'; 80 | Editable = false; 81 | FieldClass = FlowField; 82 | } 83 | } 84 | keys 85 | { 86 | key(Key1; AppID) 87 | { 88 | Clustered = true; 89 | } 90 | } 91 | fieldgroups 92 | { 93 | fieldgroup(DropDown; DisplayName, Publisher, PublishedAs) 94 | { 95 | } 96 | fieldgroup(Brick; DisplayName, Publisher, PublishedAs) 97 | { 98 | } 99 | } 100 | 101 | /// 102 | /// UpdateFigure 103 | /// 104 | local procedure UpdateFigure() 105 | begin 106 | Validate(Figure, CopyStr(DependencyGraphFacade.GenerateFigures(PublishedAs, Identity, DisplayName), 1, 2048)); 107 | end; 108 | 109 | /// 110 | /// UpdateRelationTable. 111 | /// 112 | internal procedure UpdateRelationTable() 113 | begin 114 | DependencyGraphFacade.CleanRelationsTable(); 115 | DependencyGraphFacade.GenerateRelationTable(); 116 | end; 117 | 118 | var 119 | DependencyGraphFacade: Codeunit DependencyGraphFacade_ANJ; 120 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/ExtensionTable/GenerateExtensionTableANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "GenerateExtensionTable_ANJ" (ID 80805). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80805 GenerateExtensionTable_ANJ 6 | { 7 | Access = Public; 8 | Permissions = tabledata Extensions_ANJ = RIM; 9 | 10 | /// 11 | /// Generate. 12 | /// 13 | internal procedure Generate() 14 | var 15 | Extensions: Record Extensions_ANJ; 16 | IsHandled: Boolean; 17 | begin 18 | OnBeforeGenerateExtensionTable(Extensions, IsHandled); 19 | DoGenerateExtensionTable(IsHandled); 20 | OnAfterGenerateExtensionTable(Extensions); 21 | end; 22 | 23 | /// 24 | /// DoGenerateExtensionTable. 25 | /// 26 | /// Boolean. 27 | local procedure DoGenerateExtensionTable(IsHandled: Boolean) 28 | var 29 | FillingProcessingTables: Interface IFillingProcessingTables_ANJ; 30 | ResponseText: Text; 31 | begin 32 | if IsHandled then 33 | exit; 34 | 35 | DependencyGraphFacade.GetInterfaceFillProcessingTables(FillingProcessingTables); 36 | ResponseText := FillingProcessingTables.GetExtensions(); 37 | if ResponseText <> '' then 38 | PopulateResponse(ResponseText); 39 | end; 40 | 41 | /// 42 | /// PopulateResponse. 43 | /// 44 | /// Text. 45 | local procedure PopulateResponse(ResponseText: Text) 46 | var 47 | ResponseJsonArray: JsonArray; 48 | SingleJsonObject: JsonToken; 49 | JsonValueArry: Text; 50 | begin 51 | JsonValueArry := JSONMethods.GetJsonValue(ValueJsonArryTok, ResponseText); 52 | if not ResponseJsonArray.ReadFrom(JsonValueArry) then 53 | Error(ReadingJsonErr); 54 | 55 | DependencyGraphFacade.InitializeNumberSequence(); 56 | foreach SingleJsonObject in ResponseJsonArray do 57 | InsertTableLines(SingleJsonObject); 58 | end; 59 | 60 | /// 61 | /// InsertTableLines. 62 | /// 63 | /// JsonToken. 64 | local procedure InsertTableLines(SingleJsonObject: JsonToken) 65 | var 66 | Extensions: Record Extensions_ANJ; 67 | AuxiliaryText: Text; 68 | Name: Text; 69 | begin 70 | if not SingleJsonObject.WriteTo(AuxiliaryText) then 71 | Error(ReadingJsonErr); 72 | 73 | if JSONMethods.GetJsonValue(IsInstalledTok, AuxiliaryText) = FalseTok then 74 | exit; 75 | 76 | Extensions.Init(); 77 | Extensions.Validate(AppID, JSONMethods.GetJsonValue(IdTok, AuxiliaryText)); 78 | Extensions.Insert(true); 79 | Name := JSONMethods.GetJsonValue(DisplayNameTok, AuxiliaryText); 80 | Extensions.Validate(Name, CopyStr(Name, 1, 2048)); 81 | Extensions.Validate(DisplayName, CopyStr(Name, 1, 2048)); 82 | Extensions.Validate(Publisher, CopyStr(JSONMethods.GetJsonValue(PublisherTok, AuxiliaryText), 1, 2048)); 83 | 84 | case JSONMethods.GetJsonValue(PublishedAsTok, AuxiliaryText) of 85 | Format(Enum::ExtensionScope_ANJ::PTE): 86 | Extensions.Validate(PublishedAs, Enum::ExtensionScope_ANJ::PTE); 87 | Format(Enum::ExtensionScope_ANJ::Global): 88 | Extensions.Validate(PublishedAs, Enum::ExtensionScope_ANJ::Global); 89 | Format(Enum::ExtensionScope_ANJ::Dev): 90 | Extensions.Validate(PublishedAs, Enum::ExtensionScope_ANJ::Dev); 91 | end; 92 | 93 | Extensions.Validate(ShowInGraph, true); 94 | Extensions.Validate(Identity, CopyStr(DependencyGraphFacade.GetNextNumberSequence(), 1, 2048)); 95 | Extensions.Modify(true); 96 | end; 97 | 98 | /// 99 | /// OnBeforeGenerateExtensionTable. 100 | /// 101 | /// Record Extensions_ANJ. 102 | /// VAR Boolean. 103 | [IntegrationEvent(false, false)] 104 | local procedure OnBeforeGenerateExtensionTable(Extensions: Record Extensions_ANJ; var IsHandled: Boolean) 105 | begin 106 | end; 107 | 108 | /// 109 | /// OnAfterGenerateExtensionTable. 110 | /// 111 | /// Record Extensions_ANJ. 112 | [IntegrationEvent(false, false)] 113 | local procedure OnAfterGenerateExtensionTable(Extensions: Record Extensions_ANJ) 114 | begin 115 | end; 116 | 117 | var 118 | DependencyGraphFacade: Codeunit DependencyGraphFacade_ANJ; 119 | JSONMethods: Codeunit JSONMethods_ANJ; 120 | DisplayNameTok: Label 'displayName', Locked = true; 121 | FalseTok: Label 'false', Locked = true; 122 | IdTok: Label 'id', Locked = true; 123 | IsInstalledTok: Label 'isInstalled', Locked = true; 124 | PublishedAsTok: Label 'publishedAs', Locked = true; 125 | PublisherTok: Label 'publisher', Locked = true; 126 | ReadingJsonErr: Label 'Error reading JSON response.'; 127 | ValueJsonArryTok: Label 'value', Locked = true; 128 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/ExtensionTable/NumberSequenceMgmtANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "NumberSequenceMgmt_ANJ" (ID 80806). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80806 NumberSequenceMgmt_ANJ 6 | { 7 | Access = Public; 8 | 9 | /// 10 | /// Initialize 11 | /// 12 | internal procedure Initialize() 13 | begin 14 | if NumberSequence.Exists(NumberSequenceNameTok, true) then 15 | NumberSequence.Delete(NumberSequenceNameTok, true); 16 | 17 | NumberSequence.Insert(NumberSequenceNameTok, 0001, 1, true); 18 | end; 19 | 20 | /// 21 | /// GetNextNo. 22 | /// 23 | /// Return value of type Text. 24 | internal procedure GetNextNo() NewIdentity: Text 25 | var 26 | IsHandled: Boolean; 27 | begin 28 | OnBeforeBuildIdentity(IsHandled); 29 | NewIdentity := DoBuildIdentity(IsHandled); 30 | OnAfterBuildIdentity(NewIdentity); 31 | end; 32 | 33 | /// 34 | /// DoBuildIdentity. 35 | /// 36 | /// Boolean. 37 | /// Return value of type Text. 38 | local procedure DoBuildIdentity(IsHandled: Boolean): Text 39 | var 40 | NewIdentity: Text; 41 | begin 42 | if IsHandled then 43 | exit; 44 | 45 | NewIdentity := StrSubstNo(IdentityLbl, Format(NumberSequence.Next(NumberSequenceNameTok, true))); 46 | exit(NewIdentity); 47 | end; 48 | 49 | /// 50 | /// OnBeforeBuildIdentity. 51 | /// 52 | /// VAR Boolean. 53 | [IntegrationEvent(false, false)] 54 | local procedure OnBeforeBuildIdentity(var IsHandled: Boolean) 55 | begin 56 | end; 57 | 58 | /// 59 | /// OnAfterBuildIdentity. 60 | /// 61 | /// VAR Text. 62 | [IntegrationEvent(false, false)] 63 | local procedure OnAfterBuildIdentity(var NewIdentity: Text) 64 | begin 65 | end; 66 | 67 | var 68 | IdentityLbl: Label 'E%1', Comment = 'Placeholder %1 for the identity label'; 69 | NumberSequenceNameTok: Label 'DependencyGraph', Locked = true; 70 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/GenerateFigure/CircleIMPANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "CircleIMP_ANJ" (ID 80801) implements Interface FigureInGraph_ANJ. 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80801 CircleIMP_ANJ implements IFigureInGraph_ANJ 6 | { 7 | Access = Internal; 8 | 9 | /// 10 | /// GenerateFigureText. 11 | /// 12 | /// Text. 13 | /// Text. 14 | /// Return value of type Text. 15 | procedure GenerateFigureText(Identity: Text; Content: Text): Text 16 | begin 17 | exit(StrSubstNo(FigureLbl, Identity, Content)); 18 | end; 19 | 20 | var 21 | FigureLbl: Label '%1((%2))', Comment = 'Placeholder %1 represents the Identity, and Placeholder %2 represents the Content.'; 22 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/GenerateFigure/DefaultFigureIMPANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "DefaultFigureIMP_ANJ" (ID 80814) implements Interface FigureInGraph_ANJ. 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80814 DefaultFigureIMP_ANJ implements IFigureInGraph_ANJ 6 | { 7 | Access = Internal; 8 | 9 | /// 10 | /// GenerateFigureText. 11 | /// 12 | /// Text. 13 | /// Text. 14 | /// Return value of type Text. 15 | procedure GenerateFigureText(Identity: Text; Content: Text): Text 16 | begin 17 | Error(ImpErr); 18 | end; 19 | 20 | var 21 | ImpErr: Label 'An implementation should be specified to the related figure.'; 22 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/GenerateFigure/ExtensionScopeANJ.Enum.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Enum ExtensionScope_ANJ (ID 80801) 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | enum 80801 ExtensionScope_ANJ 6 | { 7 | Access = Public; 8 | Caption = 'Extension Scope'; 9 | Extensible = false; 10 | 11 | value(1; Global) 12 | { 13 | Caption = 'Global'; 14 | } 15 | value(2; Dev) 16 | { 17 | Caption = 'Dev'; 18 | } 19 | value(3; PTE) 20 | { 21 | Caption = 'PTE'; 22 | } 23 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/GenerateFigure/GenerateFigureANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit GenerateFigure_ANJ (ID 80804). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80804 GenerateFigure_ANJ 6 | { 7 | Access = Public; 8 | 9 | /// 10 | /// Generate. 11 | /// 12 | /// Enum ExtensionScope_ANJ. 13 | /// Text. 14 | /// Text. 15 | /// Return variable ReturnText of type Text. 16 | internal procedure Generate(ExtensionScope: Enum ExtensionScope_ANJ; Identity: Text; AppName: Text) ReturnText: Text 17 | var 18 | IsHandled: Boolean; 19 | begin 20 | OnBeforeGenerate(ExtensionScope, Identity, AppName, IsHandled); 21 | ReturnText := DoGenerate(ExtensionScope, Identity, AppName, IsHandled); 22 | OnAfterGenerate(ReturnText); 23 | end; 24 | 25 | /// 26 | /// DoGenerate. 27 | /// 28 | /// Enum ExtensionScope_ANJ. 29 | /// Text. 30 | /// Text. 31 | /// Boolean. 32 | /// Return value of type Text. 33 | local procedure DoGenerate(ExtensionScope: Enum ExtensionScope_ANJ; Identity: Text; AppName: Text; IsHandled: Boolean): Text 34 | begin 35 | if IsHandled then 36 | exit; 37 | 38 | exit(GenerateFullFigure(ExtensionScope, Identity, AppName)); 39 | end; 40 | 41 | /// 42 | /// GenerateFullFigure. 43 | /// 44 | /// Enum ExtensionScope_ANJ. 45 | /// Text. 46 | /// Text. 47 | /// Return value of type Text. 48 | local procedure GenerateFullFigure(ExtensionScope: Enum ExtensionScope_ANJ; Identity: Text; AppName: Text): Text 49 | var 50 | FigureInGraph: Interface IFigureInGraph_ANJ; 51 | begin 52 | FigureInGraph := GetGeometricFigure(ExtensionScope); 53 | RemoveDisallowedCharacters(AppName); 54 | exit(FigureInGraph.GenerateFigureText(Identity, AppName)); 55 | end; 56 | 57 | /// 58 | /// RemoveDisallowedCharacters. 59 | /// 60 | /// VAR Text. 61 | local procedure RemoveDisallowedCharacters(var AppName: Text) 62 | begin 63 | AppName := AppName.Replace('-', ''); 64 | AppName := AppName.Replace('(', ''); 65 | AppName := AppName.Replace(')', ''); 66 | AppName := AppName.Replace('{', ''); 67 | AppName := AppName.Replace('}', ''); 68 | AppName := AppName.Replace('[', ''); 69 | AppName := AppName.Replace(']', ''); 70 | end; 71 | 72 | /// 73 | /// GetGeometricFigure. 74 | /// 75 | /// Enum ExtensionScope_ANJ. 76 | /// Return variable GeometricFigure of type Enum GeometricFigure_ANJ. 77 | local procedure GetGeometricFigure(ExtensionScope: Enum ExtensionScope_ANJ) GeometricFigure: Enum GeometricFigure_ANJ 78 | var 79 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 80 | begin 81 | DependencyGraphSetup.SetLoadFields(ScopePTEFigure, ScopeGlobalFigure, ScopeDevFigure); 82 | DependencyGraphSetup.GetInstance(); 83 | 84 | case ExtensionScope of 85 | ExtensionScope::PTE: 86 | GeometricFigure := DependencyGraphSetup.ScopePTEFigure; 87 | ExtensionScope::Global: 88 | GeometricFigure := DependencyGraphSetup.ScopeGlobalFigure; 89 | ExtensionScope::Dev: 90 | GeometricFigure := DependencyGraphSetup.ScopeDevFigure; 91 | end; 92 | end; 93 | 94 | /// 95 | /// OnBeforeGenerate 96 | /// 97 | /// 98 | /// 99 | /// 100 | /// 101 | [IntegrationEvent(false, false)] 102 | local procedure OnBeforeGenerate(ExtensionScope: Enum ExtensionScope_ANJ; Identity: Text; AppName: Text; var IsHandled: Boolean) 103 | begin 104 | end; 105 | 106 | /// 107 | /// OnAfterGenerate. 108 | /// 109 | /// VAR Text. 110 | [IntegrationEvent(false, false)] 111 | local procedure OnAfterGenerate(var ReturnText: Text) 112 | begin 113 | end; 114 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/GenerateFigure/GeometricFigureANJ.Enum.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Enum GeometricFigure_ANJ (ID 80800) implements Interface FigureInGraph_ANJ. 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | enum 80800 GeometricFigure_ANJ implements IFigureInGraph_ANJ 6 | { 7 | Access = Public; 8 | Caption = 'Geometric Figure'; 9 | DefaultImplementation = IFigureInGraph_ANJ = DefaultFigureIMP_ANJ; 10 | Extensible = true; 11 | UnknownValueImplementation = IFigureInGraph_ANJ = DefaultFigureIMP_ANJ; 12 | 13 | value(1; Circle) 14 | { 15 | Caption = 'Circle'; 16 | Implementation = IFigureInGraph_ANJ = CircleIMP_ANJ; 17 | } 18 | value(2; RoundRectangle) 19 | { 20 | Caption = 'Round Rectangle'; 21 | Implementation = IFigureInGraph_ANJ = RoundRectangleIMP_ANJ; 22 | } 23 | value(3; Rhombus) 24 | { 25 | Caption = 'Rhombus'; 26 | Implementation = IFigureInGraph_ANJ = RhombusIMP_ANJ; 27 | } 28 | value(4; SquareRectangle) 29 | { 30 | Caption = 'Square/Rectangle'; 31 | Implementation = IFigureInGraph_ANJ = SquareRectangleIMP_ANJ; 32 | } 33 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/GenerateFigure/IFigureInGraphANJ.Interface.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Interface "FigureInGraph_ANJ." 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | interface IFigureInGraph_ANJ 6 | { 7 | Access = Public; 8 | 9 | /// 10 | /// GenerateFigureText. 11 | /// 12 | /// Text. 13 | /// Text. 14 | /// Return value of type Text. 15 | procedure GenerateFigureText(Identity: Text; Content: Text): Text 16 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/GenerateFigure/RhombusIMPANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "RhombusIMP_ANJ" (ID 80803) implements Interface FigureInGraph_ANJ. 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80803 RhombusIMP_ANJ implements IFigureInGraph_ANJ 6 | { 7 | Access = Internal; 8 | 9 | /// 10 | /// GenerateFigureText. 11 | /// 12 | /// Text. 13 | /// Text. 14 | /// Return value of type Text. 15 | procedure GenerateFigureText(Identity: Text; Content: Text): Text 16 | begin 17 | exit(StrSubstNo(FigureLbl, Identity, Content)); 18 | end; 19 | 20 | var 21 | FigureLbl: Label '%1{%2}', Comment = 'Placeholder %1 represents the Identity, and Placeholder %2 represents the Content.'; 22 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/GenerateFigure/RoundRectangleIMPANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "RoundRectangleIMP_ANJ" (ID 80802) implements Interface FigureInGraph_ANJ. 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80802 RoundRectangleIMP_ANJ implements IFigureInGraph_ANJ 6 | { 7 | Access = Internal; 8 | 9 | /// 10 | /// GenerateFigureText. 11 | /// 12 | /// Text. 13 | /// Text. 14 | /// Return value of type Text. 15 | procedure GenerateFigureText(Identity: Text; Content: Text): Text 16 | begin 17 | exit(StrSubstNo(FigureLbl, Identity, Content)); 18 | end; 19 | 20 | var 21 | FigureLbl: Label '%1(%2)', Comment = 'Placeholder %1 represents the Identity, and Placeholder %2 represents the Content.'; 22 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/GenerateFigure/SquareRectangleIMPANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "SquareRectangleIMP_ANJ" (ID 80800) implements Interface FigureInGraph_ANJ. 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80800 SquareRectangleIMP_ANJ implements IFigureInGraph_ANJ 6 | { 7 | Access = Internal; 8 | 9 | /// 10 | /// GenerateFigureText. 11 | /// 12 | /// Text. 13 | /// Text. 14 | /// Return value of type Text. 15 | procedure GenerateFigureText(Identity: Text; Content: Text): Text 16 | begin 17 | exit(StrSubstNo(FigureLbl, Identity, Content)); 18 | end; 19 | 20 | var 21 | FigureLbl: Label '%1[%2]', Comment = 'Placeholder %1 represents the Identity, and Placeholder %2 represents the Content.'; 22 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Markdown/MarkdownMgmtANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit MarkdownMgmt_ANJ (ID 80809). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80809 MarkdownMgmt_ANJ 6 | { 7 | Access = Public; 8 | Permissions = tabledata Relations_ANJ = RD, tabledata Extensions_ANJ = RD, tabledata DependencyGraphSetup_ANJ = RM; 9 | 10 | /// 11 | /// GenerateGraph. 12 | /// 13 | internal procedure GenerateGraph() 14 | var 15 | IsHandled: Boolean; 16 | begin 17 | OnBeforeGenerateMarkdown(IsHandled); 18 | DoGenerateMarkdown(IsHandled); 19 | OnAfterGenerateMarkdown(); 20 | end; 21 | 22 | /// 23 | /// DoGenerateMarkdown. 24 | /// 25 | /// Boolean. 26 | local procedure DoGenerateMarkdown(IsHandled: Boolean) 27 | var 28 | GrapTextBuilder: TextBuilder; 29 | begin 30 | if IsHandled then 31 | exit; 32 | 33 | GrapTextBuilder.AppendLine(Header2Tok); 34 | InsertRelationships(GrapTextBuilder); 35 | InsertAppWithoutRelationships(GrapTextBuilder); 36 | 37 | UpdateSetupTable(GrapTextBuilder.ToText()) 38 | end; 39 | 40 | /// 41 | /// InsertRelationships. 42 | /// 43 | /// VAR TextBuilder. 44 | local procedure InsertRelationships(var GrapTextBuilder: TextBuilder) 45 | var 46 | Relations: Record Relations_ANJ; 47 | AppIdList: List of [Guid]; 48 | begin 49 | Relations.SetLoadFields(SourceAppID, DestinationAppID, LinkText); 50 | Relations.SetRange(ShowInGraph, true); 51 | if Relations.IsEmpty() then 52 | exit; 53 | 54 | if Relations.FindSet(false) then 55 | repeat 56 | GrapTextBuilder.Append(GetExtensionText(Relations.SourceAppID, AppIdList)); 57 | InsertLinkText(GrapTextBuilder, Relations.LinkText); 58 | GrapTextBuilder.AppendLine(GetExtensionText(Relations.DestinationAppID, AppIdList)); 59 | until Relations.Next() = 0; 60 | end; 61 | 62 | /// 63 | /// GetExtensionText. 64 | /// 65 | /// Guid. 66 | /// VAR List of [Guid]. 67 | /// Return value of type Text. 68 | local procedure GetExtensionText(AppGuid: Guid; var AppIdList: List of [Guid]): Text 69 | var 70 | Extensions: Record Extensions_ANJ; 71 | begin 72 | 73 | Extensions.SetLoadFields(Identity, Figure); 74 | if not Extensions.Get(AppGuid) then 75 | exit; 76 | 77 | if AppIdList.Contains(AppGuid) then 78 | exit(Extensions.Identity); 79 | 80 | AppIdList.Add(AppGuid); 81 | exit(Extensions.Figure); 82 | end; 83 | 84 | /// 85 | /// InsertLinkText. 86 | /// 87 | /// VAR TextBuilder. 88 | /// Text. 89 | local procedure InsertLinkText(var GrapTextBuilder: TextBuilder; LinkText: Text) 90 | begin 91 | if LinkText = '' then begin 92 | GrapTextBuilder.Append(ArrowLbl); 93 | exit; 94 | end; 95 | 96 | GrapTextBuilder.Append(StrSubstNo(LinkArrowLbl, LinkText)); 97 | end; 98 | 99 | /// 100 | /// InsertAppWithoutRelationships. 101 | /// 102 | /// VAR TextBuilder. 103 | local procedure InsertAppWithoutRelationships(var GrapTextBuilder: TextBuilder) 104 | var 105 | Extensions: Record Extensions_ANJ; 106 | begin 107 | Extensions.SetRange(ShowInGraph, true); 108 | Extensions.SetRange(HasStartRelationships, false); 109 | Extensions.SetRange(HasRelationships, false); 110 | if Extensions.IsEmpty() then 111 | exit; 112 | 113 | if Extensions.FindSet(false) then 114 | repeat 115 | GrapTextBuilder.AppendLine(Extensions.Figure); 116 | until Extensions.Next() = 0; 117 | end; 118 | 119 | /// 120 | /// UpdateSetupTable. 121 | /// 122 | /// Text. 123 | local procedure UpdateSetupTable(MarkDownText: Text) 124 | var 125 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 126 | GraphTextBuilder: TextBuilder; 127 | begin 128 | if MarkDownText = '' then 129 | exit; 130 | 131 | GraphTextBuilder.AppendLine(Header1Tok); 132 | GraphTextBuilder.AppendLine(MarkDownText); 133 | GraphTextBuilder.AppendLine(FooterTok); 134 | 135 | DependencyGraphSetup.GetInstance(); 136 | DependencyGraphSetup.SetMarkdown(GraphTextBuilder.ToText(), DependencyGraphSetup.FieldNo(Markdown)); 137 | DependencyGraphSetup.SetMarkdown(MarkDownText, DependencyGraphSetup.FieldNo(MarkdownMermaid)); 138 | DependencyGraphSetup.Validate(DateLastGenerationMarkdown, Today()); 139 | DependencyGraphSetup.Validate(TimeLastGenerationMarkdown, Time()); 140 | DependencyGraphSetup.Modify(true); 141 | end; 142 | 143 | /// 144 | /// DownloadMarkdown. 145 | /// 146 | internal procedure DownloadMarkdown() 147 | var 148 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 149 | AuxInStream: InStream; 150 | FileName: Text; 151 | begin 152 | DependencyGraphSetup.SetLoadFields(Markdown); 153 | DependencyGraphSetup.SetAutoCalcFields(Markdown); 154 | DependencyGraphSetup.GetInstance(); 155 | DependencyGraphSetup.Markdown.CreateInStream(AuxInStream); 156 | 157 | if not DependencyGraphSetup.Markdown.HasValue() then 158 | exit; 159 | 160 | FileName := FileNameLbl; 161 | DownloadFromStream(AuxInStream, '', '', '', FileName); 162 | end; 163 | 164 | /// 165 | /// GetMarkdown. 166 | /// 167 | /// Integer. 168 | /// Return value of type Text. 169 | internal procedure GetMarkdown(FieldNo: Integer): Text 170 | var 171 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 172 | AuxInStream: InStream; 173 | AuxText: Text; 174 | begin 175 | DependencyGraphSetup.SetLoadFields(Markdown, MarkdownMermaid); 176 | DependencyGraphSetup.SetAutoCalcFields(Markdown, MarkdownMermaid); 177 | DependencyGraphSetup.GetInstance(); 178 | case FieldNo of 179 | DependencyGraphSetup.FieldNo(Markdown): 180 | DependencyGraphSetup.Markdown.CreateInStream(AuxInStream); 181 | DependencyGraphSetup.FieldNo(MarkdownMermaid): 182 | DependencyGraphSetup.MarkdownMermaid.CreateInStream(AuxInStream); 183 | end; 184 | 185 | AuxInStream.Read(AuxText); 186 | exit(AuxText); 187 | end; 188 | 189 | /// 190 | /// OnBeforeGenerateMarkdown. 191 | /// 192 | /// 193 | [IntegrationEvent(false, false)] 194 | local procedure OnBeforeGenerateMarkdown(var IsHandled: Boolean) 195 | begin 196 | end; 197 | 198 | /// 199 | /// OnAfterGenerateMarkdown. 200 | /// 201 | [IntegrationEvent(false, false)] 202 | local procedure OnAfterGenerateMarkdown() 203 | begin 204 | end; 205 | 206 | var 207 | ArrowLbl: Label ' --> '; 208 | FileNameLbl: Label 'DependencyGraph.md'; 209 | FooterTok: Label '```', Locked = true; 210 | Header1Tok: Label '```mermaid', Locked = true; 211 | Header2Tok: Label 'graph BT', Locked = true; 212 | LinkArrowLbl: Label ' -- %1 --> ', Comment = 'Placeholder %1 for the link text'; 213 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Markdown/MarkdownViewerANJ.Page.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Page "MarkdownViewer_ANJ" (ID 80804). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | page 80804 MarkdownViewer_ANJ 6 | { 7 | ApplicationArea = All; 8 | Caption = 'Markdown Viewer'; 9 | Extensible = false; 10 | PageType = CardPart; 11 | UsageCategory = None; 12 | 13 | layout 14 | { 15 | area(Content) 16 | { 17 | usercontrol(MarkdownViewer; MarkdownViewer_ANJ) 18 | { 19 | } 20 | } 21 | } 22 | actions 23 | { 24 | area(Processing) 25 | { 26 | action(GenerateMarkdown) 27 | { 28 | ApplicationArea = All; 29 | Caption = 'Display dependency graph'; 30 | Image = View; 31 | ToolTip = 'Executes the Display dependency graph action.'; 32 | 33 | trigger OnAction() 34 | begin 35 | Page.Run(Page::MarkdownViewerFull_ANJ); 36 | end; 37 | } 38 | } 39 | } 40 | 41 | trigger OnAfterGetCurrRecord() 42 | begin 43 | CurrPage.MarkdownViewer.Draw(Markdown); 44 | end; 45 | 46 | /// 47 | /// SetMarkdown. 48 | /// 49 | /// Text. 50 | internal procedure SetMarkdown(AuxMarkdown: Text) 51 | begin 52 | Markdown := AuxMarkdown; 53 | end; 54 | 55 | var 56 | Markdown: Text; 57 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Markdown/MarkdownViewerFullANJ.Page.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Page "MarkdownViewerFull_ANJ" (ID 80804). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | page 80806 MarkdownViewerFull_ANJ 6 | { 7 | ApplicationArea = All; 8 | Caption = 'Markdown Viewer'; 9 | Extensible = false; 10 | PageType = Card; 11 | UsageCategory = None; 12 | 13 | layout 14 | { 15 | area(Content) 16 | { 17 | usercontrol(MarkdownViewerFS; MarkdownViewerFS_ANJ) 18 | { 19 | } 20 | } 21 | } 22 | 23 | trigger OnAfterGetCurrRecord() 24 | var 25 | MarkdownMermaidFieldID: Integer; 26 | begin 27 | MarkdownMermaidFieldID := 15; 28 | CurrPage.MarkdownViewerFS.Draw(DependencyGraphFacade.GetMarkdownText(MarkdownMermaidFieldID)); 29 | end; 30 | 31 | var 32 | DependencyGraphFacade: Codeunit DependencyGraphFacade_ANJ; 33 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Relations/GenerateRelationsTableANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "GenerateRelationsTable_ANJ" (ID 80808). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80808 GenerateRelationsTable_ANJ 6 | { 7 | Access = Public; 8 | Permissions = tabledata Relations_ANJ = RIM; 9 | /// 10 | /// Generate. 11 | /// 12 | internal procedure Generate() 13 | var 14 | Relations: Record Relations_ANJ; 15 | IsHandled: Boolean; 16 | begin 17 | OnBeforeGenerateRelationsTable(Relations, IsHandled); 18 | DoGenerateRelationsTable(IsHandled); 19 | OnAfterGenerateRelationsTable(Relations); 20 | end; 21 | 22 | /// 23 | /// DoGenerateRelationsTable. 24 | /// 25 | /// Boolean. 26 | local procedure DoGenerateRelationsTable(IsHandled: Boolean) 27 | var 28 | FillingProcessingTables: Interface IFillingProcessingTables_ANJ; 29 | ResponseText: Text; 30 | begin 31 | if IsHandled then 32 | exit; 33 | 34 | DependencyGraphFacade.GetInterfaceFillProcessingTables(FillingProcessingTables); 35 | ResponseText := FillingProcessingTables.GetRelations(); 36 | ProcessRelationsAndInsertLines(ResponseText); 37 | end; 38 | 39 | /// 40 | /// ProcessRelationsAndInsertLines. 41 | /// 42 | /// Text. 43 | local procedure ProcessRelationsAndInsertLines(ResponseText: Text) 44 | var 45 | RelationsArry: JsonArray; 46 | RelationJsonToken: JsonToken; 47 | JsonText: Text; 48 | begin 49 | if ResponseText = '' then 50 | exit; 51 | 52 | if not RelationsArry.ReadFrom(ResponseText) then 53 | exit; 54 | 55 | foreach RelationJsonToken in RelationsArry do begin 56 | RelationJsonToken.WriteTo(JsonText); 57 | InsertNewRelation(JSONMethods.GetJsonValue(SourceAppIDTok, JsonText), JSONMethods.GetJsonValue(DestinationAppIDTok, JsonText)); 58 | end; 59 | end; 60 | 61 | /// 62 | /// InsertNewRelation 63 | /// 64 | /// 65 | /// 66 | local procedure InsertNewRelation(SourceAppID: Guid; DestinationAppID: Guid) 67 | var 68 | Relations: Record Relations_ANJ; 69 | begin 70 | Clear(Relations); 71 | Relations.Init(); 72 | Relations.Validate(RelationNo, GetNewRelationsLineNo()); 73 | Relations.Insert(true); 74 | Relations.Validate(SourceAppID, SourceAppID); 75 | Relations.Validate(DestinationAppID, DestinationAppID); 76 | Relations.Validate(ShowInGraph, true); 77 | Relations.Modify(true); 78 | end; 79 | 80 | /// 81 | /// GetNewRelationsLineNo. 82 | /// /// 83 | /// Return variable NewRelationsLine of type Integer. 84 | local procedure GetNewRelationsLineNo() NewRelationsLine: Integer 85 | var 86 | Relations: Record Relations_ANJ; 87 | begin 88 | NewRelationsLine := 10000; 89 | if Relations.FindLast() then 90 | NewRelationsLine += Relations.RelationNo; 91 | end; 92 | 93 | /// 94 | /// OnBeforeGenerateRelationsTable. 95 | /// 96 | /// VAR Record Relations_ANJ. 97 | /// VAR Boolean. 98 | [IntegrationEvent(false, false)] 99 | local procedure OnBeforeGenerateRelationsTable(var Relations: Record Relations_ANJ; var IsHandled: Boolean) 100 | begin 101 | end; 102 | 103 | /// 104 | /// OnAfterGenerateRelationsTable. 105 | /// 106 | /// VAR Record Relations_ANJ. 107 | 108 | [IntegrationEvent(false, false)] 109 | local procedure OnAfterGenerateRelationsTable(var Relations: Record Relations_ANJ) 110 | begin 111 | end; 112 | 113 | var 114 | DependencyGraphFacade: Codeunit DependencyGraphFacade_ANJ; 115 | JSONMethods: Codeunit JSONMethods_ANJ; 116 | DestinationAppIDTok: Label 'DestinationAppID', Locked = true; 117 | SourceAppIDTok: Label 'SourceAppID', Locked = true; 118 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Relations/RelationsANJ.Page.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Page Relations_ANJ (ID 80803). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | page 80803 Relations_ANJ 6 | { 7 | ApplicationArea = All; 8 | Caption = 'Relations'; 9 | DeleteAllowed = false; 10 | Extensible = true; 11 | InsertAllowed = false; 12 | PageType = ListPart; 13 | SourceTable = Relations_ANJ; 14 | UsageCategory = None; 15 | 16 | layout 17 | { 18 | area(Content) 19 | { 20 | repeater(General) 21 | { 22 | field(SourceAppName; Rec.SourceAppName) 23 | { 24 | } 25 | field(LinkText; Rec.LinkText) 26 | { 27 | trigger OnValidate() 28 | begin 29 | CurrPage.Update(true); 30 | end; 31 | } 32 | field(DestinationAppName; Rec.DestinationAppName) 33 | { 34 | } 35 | field(ShowInGraph; Rec.ShowInGraph) 36 | { 37 | trigger OnValidate() 38 | begin 39 | CurrPage.Update(true); 40 | end; 41 | } 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Relations/RelationsANJ.Table.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Table Relations_ANJ (ID 80802). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | table 80802 Relations_ANJ 6 | { 7 | Access = Public; 8 | Caption = 'Relations'; 9 | DataClassification = CustomerContent; 10 | DrillDownPageId = Relations_ANJ; 11 | Extensible = true; 12 | LookupPageId = Relations_ANJ; 13 | 14 | fields 15 | { 16 | field(1; RelationNo; Integer) 17 | { 18 | AllowInCustomizations = Never; 19 | Caption = 'Relation No.'; 20 | } 21 | field(2; SourceAppID; Guid) 22 | { 23 | AllowInCustomizations = Never; 24 | Caption = 'Source App ID'; 25 | } 26 | field(3; SourceAppName; Text[2048]) 27 | { 28 | CalcFormula = lookup(Extensions_ANJ.DisplayName where(AppID = field(SourceAppID))); 29 | Caption = 'Source App Name'; 30 | Editable = false; 31 | FieldClass = FlowField; 32 | ToolTip = 'Specifies the value of the Source App Name field.'; 33 | } 34 | field(4; DestinationAppID; Guid) 35 | { 36 | AllowInCustomizations = Never; 37 | Caption = 'Destination App ID'; 38 | } 39 | field(5; DestinationAppName; Text[2048]) 40 | { 41 | CalcFormula = lookup(Extensions_ANJ.DisplayName where(AppID = field(DestinationAppID))); 42 | Caption = 'Destination App Name'; 43 | Editable = false; 44 | FieldClass = FlowField; 45 | ToolTip = 'Specifies the value of the Destination App Name field.'; 46 | } 47 | field(6; LinkText; Text[2048]) 48 | { 49 | Caption = 'Link Text'; 50 | ToolTip = 'Specifies the value of the Link Text field.'; 51 | } 52 | field(7; ShowInGraph; Boolean) 53 | { 54 | Caption = 'Show In Graph'; 55 | ToolTip = 'Specifies the value of the Show In Graph field.'; 56 | } 57 | } 58 | keys 59 | { 60 | key(Key1; RelationNo) 61 | { 62 | Clustered = true; 63 | } 64 | } 65 | fieldgroups 66 | { 67 | fieldgroup(DropDown; SourceAppName, DestinationAppName) 68 | { 69 | } 70 | fieldgroup(Brick; SourceAppName, DestinationAppName) 71 | { 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Setup/DependencyGraphSetupANJ.Page.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Page DependencyGraphSetup_ANJ (ID 80800). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | page 80800 DependencyGraphSetup_ANJ 6 | { 7 | ApplicationArea = All; 8 | Caption = 'Dependency Graph Setup'; 9 | Extensible = true; 10 | PageType = Card; 11 | SourceTable = DependencyGraphSetup_ANJ; 12 | UsageCategory = Administration; 13 | 14 | layout 15 | { 16 | area(Content) 17 | { 18 | group(Auth) 19 | { 20 | Caption = 'App registrations - Azure'; 21 | 22 | field(ClientID; Rec.ClientID) 23 | { 24 | } 25 | field(Secret; Rec.Secret) 26 | { 27 | ExtendedDatatype = Masked; 28 | } 29 | } 30 | group(Figure) 31 | { 32 | Caption = 'App shapes inside the diagram'; 33 | 34 | field(ScopePTEFigure; Rec.ScopePTEFigure) 35 | { 36 | } 37 | field(ScopeGlobalFigure; Rec.ScopeGlobalFigure) 38 | { 39 | } 40 | field(ScopeDevFigure; Rec.ScopeDevFigure) 41 | { 42 | } 43 | } 44 | group(Include) 45 | { 46 | Caption = 'Include when generating the graph'; 47 | 48 | field(IncludeMicrosoftApps; Rec.IncludeMicrosoftApps) 49 | { 50 | Caption = 'Microsoft Apps'; 51 | } 52 | field(IncludeLinkText; Rec.IncludeLinkText) 53 | { 54 | Caption = 'Link text between dependencies'; 55 | } 56 | } 57 | group(FillTables) 58 | { 59 | Caption = 'Fill tables'; 60 | 61 | field(FillingProcessingTables; Rec.FillingProcessingTables) 62 | { 63 | Editable = false; 64 | } 65 | } 66 | group(LastGeneration) 67 | { 68 | Caption = 'Last time it was generated'; 69 | 70 | group(Tables) 71 | { 72 | Caption = 'Tables to customize the graph'; 73 | 74 | field(DateLastGeneration; Rec.DateLastGeneration) 75 | { 76 | Editable = false; 77 | } 78 | field(TimeLastGeneration; Rec.TimeLastGeneration) 79 | { 80 | Editable = false; 81 | } 82 | } 83 | group(Markdown) 84 | { 85 | Caption = 'Dependency graph'; 86 | 87 | field(DateLastGenerationMarkdown; Rec.DateLastGenerationMarkdown) 88 | { 89 | Editable = false; 90 | } 91 | field(TimeLastGenerationMarkdown; Rec.TimeLastGenerationMarkdown) 92 | { 93 | Editable = false; 94 | } 95 | } 96 | } 97 | } 98 | } 99 | actions 100 | { 101 | area(Processing) 102 | { 103 | action(Extensions) 104 | { 105 | ApplicationArea = All; 106 | Caption = 'Dependency Graph'; 107 | Image = Table; 108 | Promoted = true; 109 | PromotedCategory = Process; 110 | PromotedOnly = true; 111 | RunObject = page ShowInGraph_ANJ; 112 | ToolTip = 'Executes the Show In Graph action.'; 113 | } 114 | } 115 | } 116 | 117 | trigger OnOpenPage() 118 | begin 119 | Rec.GetInstance(); 120 | end; 121 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/Setup/DependencyGraphSetupANJ.Table.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Table DependencyGraphSetup_ANJ (ID 80800). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | table 80800 DependencyGraphSetup_ANJ 6 | { 7 | Access = Public; 8 | Caption = 'Dependency Graph Setup'; 9 | DataClassification = CustomerContent; 10 | DrillDownPageId = DependencyGraphSetup_ANJ; 11 | Extensible = true; 12 | LookupPageId = DependencyGraphSetup_ANJ; 13 | 14 | fields 15 | { 16 | field(1; PrimaryKey; Code[10]) 17 | { 18 | AllowInCustomizations = Never; 19 | Caption = 'Primary Key'; 20 | NotBlank = false; 21 | } 22 | field(2; ClientID; Text[2048]) 23 | { 24 | Caption = 'Client ID'; 25 | ToolTip = 'Specifies the value of the Client ID field.'; 26 | } 27 | field(3; Secret; Text[2048]) 28 | { 29 | Caption = 'Secret'; 30 | ToolTip = 'Specifies the value of the Secret field.'; 31 | } 32 | field(4; ScopePTEFigure; Enum GeometricFigure_ANJ) 33 | { 34 | Caption = 'PTE Scope'; 35 | InitValue = SquareRectangle; 36 | ToolTip = 'Specifies the value of the Scope PTE Figure field.'; 37 | } 38 | field(5; ScopeGlobalFigure; Enum GeometricFigure_ANJ) 39 | { 40 | Caption = 'Global Scope'; 41 | InitValue = SquareRectangle; 42 | ToolTip = 'Specifies the value of the Scope Global Figure field.'; 43 | } 44 | field(6; ScopeDevFigure; Enum GeometricFigure_ANJ) 45 | { 46 | Caption = 'Dev Scope'; 47 | InitValue = SquareRectangle; 48 | ToolTip = 'Specifies the value of the Scope Dev Figure field.'; 49 | } 50 | field(7; IncludeMicrosoftApps; Boolean) 51 | { 52 | Caption = 'Include Microsoft Apps'; 53 | ToolTip = 'Specifies the value of the Include Microsoft Apps field.'; 54 | } 55 | field(8; DateLastGeneration; Date) 56 | { 57 | Caption = 'Date'; 58 | ToolTip = 'Specifies the value of the Date Last Generation field.'; 59 | } 60 | field(9; TimeLastGeneration; Time) 61 | { 62 | Caption = 'Time'; 63 | ToolTip = 'Specifies the value of the Time Last Generation field.'; 64 | } 65 | field(10; Markdown; Blob) 66 | { 67 | Caption = 'Content Text'; 68 | } 69 | field(11; Picture; Media) 70 | { 71 | Caption = 'Picture'; 72 | } 73 | field(12; IncludeLinkText; Boolean) 74 | { 75 | Caption = 'Include Link Text'; 76 | ToolTip = 'Specifies the value of the Include Link text field.'; 77 | } 78 | field(13; DateLastGenerationMarkdown; Date) 79 | { 80 | Caption = 'Date'; 81 | ToolTip = 'Specifies the value of the Date Last Generation field.'; 82 | } 83 | field(14; TimeLastGenerationMarkdown; Time) 84 | { 85 | Caption = 'Time'; 86 | ToolTip = 'Specifies the value of the Time Last Generation field.'; 87 | } 88 | field(15; MarkdownMermaid; Blob) 89 | { 90 | Caption = 'Mermaid Content Text'; 91 | } 92 | field(16; FillingProcessingTables; Enum FillingProcessingTables_ANJ) 93 | { 94 | Caption = 'Filling Processing Tables'; 95 | InitValue = WSAndModuleDependencyInfo; 96 | ToolTip = 'Specifies the value of the Filling Processing Tables field.'; 97 | } 98 | } 99 | keys 100 | { 101 | key(PK; PrimaryKey) 102 | { 103 | Clustered = true; 104 | } 105 | } 106 | fieldgroups 107 | { 108 | fieldgroup(DropDown; ScopePTEFigure, ScopeGlobalFigure, ScopeDevFigure) 109 | { 110 | } 111 | fieldgroup(Brick; ScopePTEFigure, ScopeGlobalFigure, ScopeDevFigure) 112 | { 113 | } 114 | } 115 | 116 | /// 117 | /// SetMarkdown. 118 | /// 119 | /// Text. 120 | /// Integer. 121 | internal procedure SetMarkdown(AuxText: Text; FieldNo: Integer) 122 | var 123 | AuxOutStream: OutStream; 124 | begin 125 | Clear(HasBeenRead); 126 | case FieldNo of 127 | FieldNo(Markdown): 128 | Markdown.CreateOutStream(AuxOutStream); 129 | FieldNo(MarkdownMermaid): 130 | MarkdownMermaid.CreateOutStream(AuxOutStream); 131 | end; 132 | AuxOutStream.Write(AuxText); 133 | end; 134 | 135 | /// 136 | /// GetInstance. 137 | /// 138 | procedure GetInstance() 139 | begin 140 | if HasBeenRead then 141 | exit; 142 | 143 | if not Get() then begin 144 | Init(); 145 | Insert(true); 146 | end; 147 | HasBeenRead := true; 148 | end; 149 | 150 | var 151 | HasBeenRead: Boolean; 152 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/UpgradeTagsExample/DataUpgradesANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "DataUpgrades_ANJ" (ID 80816). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80816 DataUpgrades_ANJ 6 | { 7 | Access = Internal; 8 | Permissions = tabledata DependencyGraphSetup_ANJ = RM, tabledata Extensions_ANJ = RMD, tabledata Relations_ANJ = RD; 9 | 10 | /// 11 | /// OnGetPerCompanyUpgradeTags. 12 | /// 13 | /// VAR List of [Code[250]]. 14 | [EventSubscriber(ObjectType::Codeunit, Codeunit::System.Upgrade."Upgrade Tag", OnGetPerCompanyUpgradeTags, '', false, false)] 15 | local procedure OnGetPerCompanyUpgradeTags(var PerCompanyUpgradeTags: List of [Code[250]]) 16 | begin 17 | PerCompanyUpgradeTags.Add(GetUpgradeTagDataUpgrade1()); 18 | end; 19 | 20 | #region Data Upgrade 1 - Upgrade data from previous versions to version 4.15.0.0. - Change reserved ordinal 0 to another one. 21 | /// 22 | /// GetUpgradeTagDataUpgrade1. 23 | /// 24 | /// Return value of type Code[250]. 25 | internal procedure GetUpgradeTagDataUpgrade1(): Code[250] 26 | begin 27 | exit(DT1IdTok) 28 | end; 29 | 30 | /// 31 | /// RunDataUpgrade1. 32 | /// 33 | internal procedure RunDataUpgrade1() 34 | begin 35 | CheckAndUpdateDependencyGraphSetupFillingProcessingTablesEnum(); 36 | CheckAndUpdateExtensionsExtensionScopeEnum(); 37 | CheckAndUpdateDependencyGraphSetupGeometricFigureEnum(); 38 | end; 39 | 40 | /// 41 | /// CheckAndUpdateDependencyGraphSetupFillingProcessingTablesEnum. 42 | /// 43 | local procedure CheckAndUpdateDependencyGraphSetupFillingProcessingTablesEnum() 44 | var 45 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 46 | begin 47 | DependencyGraphSetup.GetInstance(); 48 | if DependencyGraphSetup.FillingProcessingTables.AsInteger() <> 0 then 49 | exit; 50 | 51 | DependencyGraphSetup.Validate(FillingProcessingTables, DependencyGraphSetup.FillingProcessingTables::WSAndModuleDependencyInfo); 52 | DependencyGraphSetup.Modify(false); 53 | end; 54 | 55 | /// 56 | /// CheckAndUpdateExtensionsExtensionScopeEnum. 57 | /// 58 | local procedure CheckAndUpdateExtensionsExtensionScopeEnum() 59 | var 60 | Extensions: Record Extensions_ANJ; 61 | begin 62 | if Extensions.IsEmpty() then 63 | exit; 64 | 65 | if Extensions.FindSet(true) then 66 | repeat 67 | if Extensions.PublishedAs.AsInteger() = 0 then begin 68 | Extensions.Validate(PublishedAs, Extensions.PublishedAs::PTE); 69 | Extensions.Modify(false) 70 | end; 71 | until Extensions.Next() = 0; 72 | end; 73 | 74 | /// 75 | /// CheckAndUpdateDependencyGraphSetupGeometricFigureEnum. 76 | /// 77 | local procedure CheckAndUpdateDependencyGraphSetupGeometricFigureEnum() 78 | var 79 | DependencyGraphSetup: Record DependencyGraphSetup_ANJ; 80 | begin 81 | DependencyGraphSetup.GetInstance(); 82 | if (DependencyGraphSetup.ScopePTEFigure.AsInteger() <> 0) and 83 | (DependencyGraphSetup.ScopeGlobalFigure.AsInteger() <> 0) and 84 | (DependencyGraphSetup.ScopeDevFigure.AsInteger() <> 0) then 85 | exit; 86 | 87 | if DependencyGraphSetup.ScopePTEFigure.AsInteger() = 0 then 88 | DependencyGraphSetup.Validate(ScopePTEFigure, DependencyGraphSetup.ScopePTEFigure::SquareRectangle); 89 | 90 | if DependencyGraphSetup.ScopeGlobalFigure.AsInteger() = 0 then 91 | DependencyGraphSetup.Validate(ScopeGlobalFigure, DependencyGraphSetup.ScopeGlobalFigure::SquareRectangle); 92 | 93 | if DependencyGraphSetup.ScopeDevFigure.AsInteger() = 0 then 94 | DependencyGraphSetup.Validate(ScopeDevFigure, DependencyGraphSetup.ScopeDevFigure::SquareRectangle); 95 | DependencyGraphSetup.Modify(false); 96 | end; 97 | #endregion 98 | 99 | var 100 | DT1IdTok: Label 'ANJ-41500-FixEnumsZeroOrdinal-20240303', Locked = true; 101 | } -------------------------------------------------------------------------------- /Dependency-Graph/src/UpgradeTagsExample/UpgradeCodeunitANJ.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Codeunit "UpgradeCodeunit_ANJ" (ID 80817). 3 | /// 4 | namespace ANJ.Tools.Graph; 5 | codeunit 80817 UpgradeCodeunit_ANJ 6 | { 7 | Access = Internal; 8 | Subtype = Upgrade; 9 | 10 | trigger OnUpgradePerCompany() 11 | begin 12 | DataUpgrade(); 13 | end; 14 | 15 | /// 16 | /// DataUpgrade. 17 | /// 18 | local procedure DataUpgrade() 19 | var 20 | DataUpgrades: Codeunit DataUpgrades_ANJ; 21 | UpgradeTag: Codeunit System.Upgrade."Upgrade Tag"; 22 | begin 23 | if not UpgradeTag.HasUpgradeTag(DataUpgrades.GetUpgradeTagDataUpgrade1()) then begin 24 | DataUpgrades.RunDataUpgrade1(); 25 | UpgradeTag.SetUpgradeTag(DataUpgrades.GetUpgradeTagDataUpgrade1()); 26 | end; 27 | end; 28 | } -------------------------------------------------------------------------------- /Environment.ps1: -------------------------------------------------------------------------------- 1 | # container 2 | $ContainerName = 'DependencyGraph' 3 | $licenseFile = 'C:\License\Dev.bclicense' 4 | 5 | # Image 6 | $artifactUrl = Get-BCArtifactUrl -country "es" -version 25 7 | 8 | # User 9 | $UserName = 'User' 10 | $Password = ConvertTo-SecureString "Pass123" -AsPlainText -Force 11 | $Credential = New-Object System.Management.Automation.PSCredential ($UserName, $Password) 12 | 13 | New-BCContainer -accept_eula ` 14 | -accept_outdated ` 15 | -updateHosts ` 16 | -containername $containername ` 17 | -artifactUrl $artifactUrl ` 18 | -auth NavUserPassword ` 19 | -Credential $credential ` 20 | -isolation hyperv ` 21 | -includeTestToolkit -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Antonio Novoa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dependency Graph 2 | Small tool designed to create a graph showing installed extensions and their respective dependencies, all from the **Business Central** environment in which the extension is running. In addition, it allows you to preview and download the generated text to easily add it to the client's documentation in Markdown format.

3 | **Usage video:**
4 | https://youtu.be/PsgThQVQAw4

5 | Very proud to be on Stefan Maron's Open Source Business Central Applications list:
6 | https://stefanmaron.com/opensourcealprojects/

7 | **PS:**
8 | Although there are numerous more advanced tools available, this one has been created for the simple purpose of practice, but it could prove useful for other users. 9 | 10 | ## Usage 11 | ![Usage.gif](/res/Usage.gif) 12 | 13 | ## Previous configuration 14 | ### Azure 15 | #### Create App registrations 16 | **Supported account types:**
17 | Organization accounts only.
18 | **Redirect URI:**
19 | "Web" https://businesscentral.dynamics.com/OAuthLanding.htm
20 | ![PortalAzure001](/res/PortalAzure001.png) 21 | 22 | #### Create Client Secret 23 | ![PortalAzure002](/res/PortalAzure002.png) 24 | 25 | #### Assign API permissions (With Administrator consent) 26 | API. ReadWrite.All
27 | Automation.ReadWrite.All
28 | ![PortalAzure003](/res/PortalAzure003.png) 29 | 30 | ### Business Central 31 | Create new record in **Azure Active Directory Applications** Indicating the application ID created in Azure.
32 | Add **D365 AUTOMATION** and **EXTEN MGT. -ADMIN** to user permission sets.
33 | Change status to **Enabled**
34 | **Grant consent**
35 | ![1AADBC](/res/1AADBC.png) 36 | 37 | ## Dependency Graph Settings page 38 | ![2DGSetup](/res/2DGSetup.png) 39 | 40 | - App registrations - **Azure**
41 | Configuration of the **Azure** Registry ID required for operation. 42 | 43 | - App shapes inside the diagram
44 | Select which shape an application will be represented by its scope. 45 | 46 | - Include when generating the graph
47 | Features to include for chart generation:
48 | - Microsoft applications. 49 | - Link text between dependencies. 50 | 51 | - Last time it was generated
52 | Historical data. 53 | 54 | - Dependency graph
55 | Action to access the data generation page for the graph. 56 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Reporting a vulnerability 2 | Contact me directly at anovoa@novoadev.com. 3 | -------------------------------------------------------------------------------- /res/1AADBC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/res/1AADBC.png -------------------------------------------------------------------------------- /res/1AADBCes-ES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/res/1AADBCes-ES.png -------------------------------------------------------------------------------- /res/2DGSetup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/res/2DGSetup.png -------------------------------------------------------------------------------- /res/2DGSetupes-ES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/res/2DGSetupes-ES.png -------------------------------------------------------------------------------- /res/PortalAzure001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/res/PortalAzure001.png -------------------------------------------------------------------------------- /res/PortalAzure002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/res/PortalAzure002.png -------------------------------------------------------------------------------- /res/PortalAzure003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/res/PortalAzure003.png -------------------------------------------------------------------------------- /res/Usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovoaDev/Dependency-Graph-BCExt/6bedc574c4dacd5c7c5c9a420c98746d2a67f5fd/res/Usage.gif --------------------------------------------------------------------------------