├── .azure └── pipelines │ ├── build-bravo.yaml │ ├── release-bravo.yaml │ ├── release-bravostore.yaml │ └── release-bravostorelauncher.yaml ├── .editorconfig ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── dependabot.yml ├── release.yml └── workflows │ └── ci.yml ├── .gitignore ├── Bravo.sln ├── LICENSE ├── README.md ├── TERMS.md ├── build.cmd ├── global.json ├── installer ├── .gitignore ├── msix │ ├── Bravo.Installer.Msix.StoreLauncher │ │ ├── App.config │ │ ├── Bravo.Installer.Msix.StoreLauncher.csproj │ │ ├── Program.cs │ │ └── Properties │ │ │ └── AssemblyInfo.cs │ ├── Bravo.Installer.Msix.sln │ └── Bravo.Installer.Msix │ │ ├── Assets │ │ ├── LockScreenLogo.scale-200.png │ │ ├── SplashScreen.scale-200.png │ │ ├── Square150x150Logo.scale-200.png │ │ ├── Square44x44Logo.scale-200.png │ │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ │ ├── StoreLogo.png │ │ ├── Wide310x150Logo.scale-200.png │ │ └── icon-256x256.png │ │ ├── Bravo.Installer.Msix.wapproj │ │ └── Package.appxmanifest └── wix │ ├── extensions │ ├── Bravo.Installer.Wix.Tests │ │ ├── Bravo.Installer.Wix.Tests.csproj │ │ ├── HelpersTests.cs │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ └── packages.config │ ├── Bravo.Installer.Wix.sln │ └── Bravo.Installer.Wix │ │ ├── Bravo.Installer.Wix.csproj │ │ ├── CustomAction.config │ │ ├── CustomAction.cs │ │ ├── Helpers.cs │ │ ├── Properties │ │ └── AssemblyInfo.cs │ │ └── app.config │ └── src │ ├── Bravo │ ├── Bravo-en-us.wxl │ ├── Bravo-perUser.wxs │ ├── Bravo.wxs │ └── Bravo.xslt │ ├── BravoStoreLauncher │ ├── BravoStoreLauncher-en-us.wxl │ └── BravoStoreLauncher.wxs │ └── assets │ ├── Bravo.Installer.Wix.CA.dll │ ├── background.bmp │ ├── background.png │ ├── banner.bmp │ ├── banner.png │ ├── bravo.pbitool.json │ ├── bravostore.pbitool.json │ └── license.rtf ├── nuget.config ├── publish.cmd ├── src ├── Assets │ ├── ManageDates │ │ ├── Schemas │ │ │ ├── date-template.schema.json │ │ │ ├── engine-configuration.schema.json │ │ │ ├── holidays-definition.schema.json │ │ │ └── measures-template.schema.json │ │ └── Templates │ │ │ ├── Config-01.template.json │ │ │ ├── Config-02.template.json │ │ │ ├── Config-03.template.json │ │ │ ├── Config-04.template.json │ │ │ ├── Config-05.template.json │ │ │ ├── Config-06.template.json │ │ │ ├── Config-07.template.json │ │ │ ├── DateLocalization-01.json │ │ │ ├── DateLocalization-02.json │ │ │ ├── DateLocalization-03.json │ │ │ ├── DateLocalization-04.json │ │ │ ├── DateLocalization-05.json │ │ │ ├── DateLocalization-06.json │ │ │ ├── DateLocalization-07.json │ │ │ ├── DateTemplate-01.json │ │ │ ├── DateTemplate-02.json │ │ │ ├── DateTemplate-03.json │ │ │ ├── DateTemplate-04.json │ │ │ ├── DateTemplate-05.json │ │ │ ├── DateTemplate-06.json │ │ │ ├── DateTemplate-07.json │ │ │ ├── DateTemplate-95.json │ │ │ ├── HolidaysDefinition.json │ │ │ ├── TimeIntelligence-01.json │ │ │ ├── TimeIntelligence-02.json │ │ │ ├── TimeIntelligence-03.json │ │ │ ├── TimeIntelligence-04.json │ │ │ ├── TimeIntelligence-05.json │ │ │ ├── TimeIntelligence-06.json │ │ │ └── TimeIntelligence-07.json │ ├── bravo.ico │ ├── bravo.png │ └── lib │ │ ├── Antlr4.Runtime.dll │ │ └── TOMWrapper.dll ├── Bravo.csproj ├── Controllers │ ├── AnalyzeModelController.cs │ ├── ApplicationController.cs │ ├── AuthenticationController.cs │ ├── ExportDataController.cs │ ├── FormatDaxController.cs │ ├── ManageDatesController.cs │ └── TemplateDevelopmentController.cs ├── GlobalSuppressions.cs ├── Infrastructure │ ├── AppEnvironment.cs │ ├── AppExceptions.cs │ ├── AppInstance.cs │ ├── AppTelemetry.cs │ ├── AppWindow.Designer.cs │ ├── AppWindow.cs │ ├── AppWindow.resx │ ├── AppWindowSubclass.cs │ ├── Authentication │ │ ├── AppAuthenticationHandler.cs │ │ ├── AppAuthenticationSchemeOptions.cs │ │ └── MsalSystemWebViewOptions.cs │ ├── Configuration │ │ ├── Options │ │ │ ├── IWritableOptions.cs │ │ │ └── WritableOptions.cs │ │ ├── Settings │ │ │ ├── ExperimentalSettings.cs │ │ │ ├── ProxySettings.cs │ │ │ ├── StartupSettings.cs │ │ │ └── UserSettings.cs │ │ ├── StartupConfiguration.cs │ │ └── UserPreferences.cs │ ├── Contracts │ │ ├── PBICloud │ │ │ ├── CloudModel.cs │ │ │ ├── CloudOrganizationalGalleryItem.cs │ │ │ ├── CloudOrganizationalGalleryItemStatus.cs │ │ │ ├── CloudPermissions.cs │ │ │ ├── CloudPromotionalStage.cs │ │ │ ├── CloudSharedModel.cs │ │ │ ├── CloudSharedModelWorkspaceType.cs │ │ │ ├── CloudUser.cs │ │ │ ├── CloudWorkspace.cs │ │ │ ├── CloudWorkspaceCapacitySkuType.cs │ │ │ ├── CloudWorkspaceType.cs │ │ │ ├── GlobalService.cs │ │ │ ├── GlobalServiceEnvironment.cs │ │ │ ├── GlobalServiceEnvironmentClient.cs │ │ │ ├── GlobalServiceEnvironmentService.cs │ │ │ └── TenantCluster.cs │ │ └── PBIDesktop │ │ │ └── LocalClientSite.cs │ ├── Extensions │ │ ├── CommonExtensions.cs │ │ ├── DataReaderExtensions.cs │ │ ├── DynamicLinq │ │ │ ├── ClassFactory.cs │ │ │ ├── DynamicClass.cs │ │ │ ├── DynamicExpression.cs │ │ │ ├── DynamicOrdering.cs │ │ │ ├── DynamicProperty.cs │ │ │ ├── DynamicQueryable.cs │ │ │ ├── Parser │ │ │ │ ├── ExpressionParser.cs │ │ │ │ ├── ParseConstants.cs │ │ │ │ └── ParseException.cs │ │ │ └── Signature.cs │ │ ├── EnumerableExtensions.cs │ │ ├── HostingExtensions.cs │ │ ├── ProcessExtensions.cs │ │ ├── RegistryExtensions.cs │ │ ├── StringExtensions.cs │ │ └── TabularModelExtensions.cs │ ├── Helpers │ │ ├── CommonHelper.cs │ │ ├── ConnectionStringHelper.cs │ │ ├── DesktopBridgeHelper.cs │ │ ├── ExceptionHelper.cs │ │ ├── MsalHelper.cs │ │ ├── NetworkHelper.cs │ │ ├── ProcessHelper.cs │ │ ├── TabularModelHelper.cs │ │ ├── TelemetryHelper.cs │ │ ├── ThemeHelper.cs │ │ ├── TokenCacheHelper.cs │ │ ├── VpaxHelper.cs │ │ ├── WebView2Helper.cs │ │ └── WindowDialogHelper.cs │ ├── Messages │ │ ├── AppInstanceStartupMessage.cs │ │ ├── WebMessageType.cs │ │ └── WebMessages.cs │ ├── Models │ │ ├── IAuthenticationResult.cs │ │ ├── IDataModel.cs │ │ └── PBICloud │ │ │ ├── PBICloudAuthenticationRequest.cs │ │ │ ├── PBICloudAuthenticationResult.cs │ │ │ ├── PBICloudEnvironment.cs │ │ │ └── PBICloudEnvironmentType.cs │ ├── Security │ │ ├── CredentialDialog.cs │ │ ├── CredentialManager.cs │ │ ├── Cryptography.cs │ │ └── Policies │ │ │ ├── ADMX │ │ │ ├── bravo.admx │ │ │ ├── en-US │ │ │ │ ├── bravo.adml │ │ │ │ └── sqlbi.adml │ │ │ └── sqlbi.admx │ │ │ ├── PolicyManager.cs │ │ │ └── PolicyStatus.cs │ ├── Services │ │ ├── BestPracticeAnalyzer │ │ │ ├── Analyzer.cs │ │ │ ├── AnalyzerResult.cs │ │ │ ├── BestPracticeCollection.cs │ │ │ ├── BestPracticeRule.cs │ │ │ ├── IRuleDefinition.cs │ │ │ ├── RuleScope.cs │ │ │ └── RuleScopeConverter.cs │ │ ├── ConnectionWrapper.cs │ │ ├── DaxTemplate │ │ │ └── DaxTemplateManager.cs │ │ ├── ExportData │ │ │ └── ExportDataJobMap.cs │ │ ├── PowerBI │ │ │ ├── PBICloudAuthenticationService.cs │ │ │ ├── PBICloudService.cs │ │ │ ├── PBICloudSettingsService.cs │ │ │ └── PBIDesktopService.cs │ │ └── WebProxyWrapper.cs │ └── Windows │ │ ├── Interop │ │ ├── Advapi32.cs │ │ ├── COLORREF.cs │ │ ├── Comctl32.cs │ │ ├── Comdlg32.cs │ │ ├── Credui.cs │ │ ├── Dwmapi.cs │ │ ├── ExternDll.cs │ │ ├── HRESULT.cs │ │ ├── Iphlpapi.cs │ │ ├── Kernel32.cs │ │ ├── NativeMethods.cs │ │ ├── Ntdll.cs │ │ ├── Ole32.cs │ │ ├── User32.cs │ │ ├── Uxtheme.cs │ │ ├── Win32Constant.cs │ │ └── WindowMessage.cs │ │ ├── Win32WindowWrapper.cs │ │ ├── WindowDialogs.cs │ │ └── WindowSubclass.cs ├── Models │ ├── AnalyzeModel │ │ ├── ExportVpaxMode.cs │ │ ├── TabularColumn.cs │ │ ├── TabularDatabase.cs │ │ ├── TabularMeasure.cs │ │ └── TabularTable.cs │ ├── BravoAccount.cs │ ├── BravoOptions.cs │ ├── BravoPolicies.cs │ ├── BravoUpdate.cs │ ├── DiagnosticMessage.cs │ ├── ExportData │ │ ├── ExportDataEntity.cs │ │ ├── ExportDataSettings.cs │ │ ├── ExportDataStatus.cs │ │ ├── ExportDelimitedTextRequest.cs │ │ └── ExportExcelRequest.cs │ ├── FormatDax │ │ ├── DatabaseUpdateResult.cs │ │ ├── FormatDaxRequest.cs │ │ ├── FormatDaxResponse.cs │ │ ├── UpdatePBICloudDatasetRequest.cs │ │ └── UpdatePBIDesktopReportRequest.cs │ ├── ManageDates │ │ ├── ApplyConfigurationRequest.cs │ │ ├── CustomPackage.cs │ │ ├── DateConfiguration.cs │ │ ├── DateDefaults.cs │ │ └── PreviewChangesRequest.cs │ ├── PBICloudDataset.cs │ ├── PBIDesktopReport.cs │ └── TemplateDevelopment │ │ ├── CreateWorkspaceRequest.cs │ │ └── WorkspacePreviewChangesRequest.cs ├── Program.Host.cs ├── Program.cs ├── Properties │ ├── Resources.Designer.cs │ ├── Resources.resx │ └── launchSettings.json ├── Scripts │ ├── @types │ │ ├── global.d.ts │ │ ├── split.js │ │ │ └── index.d.ts │ │ └── tabulator-tables │ │ │ └── index.d.ts │ ├── controllers │ │ ├── app.ts │ │ ├── auth.ts │ │ ├── cache.ts │ │ ├── debug.ts │ │ ├── host.ts │ │ ├── logger.ts │ │ ├── notifications.ts │ │ ├── options.ts │ │ ├── page.ts │ │ ├── pbi-desktop.ts │ │ ├── sheet.ts │ │ ├── telemetry.ts │ │ └── theme.ts │ ├── css │ │ ├── analyze-model.less │ │ ├── best-practices.less │ │ ├── codemirror.less │ │ ├── colors.less │ │ ├── common.less │ │ ├── contextmenu.less │ │ ├── dax-editor.less │ │ ├── dax-formatter.less │ │ ├── diagnostic-pane.less │ │ ├── dialog.less │ │ ├── export-data.less │ │ ├── flex.less │ │ ├── fonts.less │ │ ├── images.less │ │ ├── json-formatter.less │ │ ├── main.less │ │ ├── manage-dates.less │ │ ├── media.less │ │ ├── menu.less │ │ ├── mixins.less │ │ ├── multiview-pane.less │ │ ├── normalize.less │ │ ├── notification-sidebar.less │ │ ├── root.less │ │ ├── sheet.less │ │ ├── sidebar.less │ │ ├── tabs.less │ │ ├── tabular-browser.less │ │ ├── tabulator.less │ │ ├── unsupported.less │ │ └── welcome.less │ ├── helpers │ │ ├── contextmenu.ts │ │ ├── dispatchable.ts │ │ ├── idle.ts │ │ ├── loader.ts │ │ ├── renderer.ts │ │ └── utils.ts │ ├── main.ts │ ├── model │ │ ├── dates.ts │ │ ├── doc.ts │ │ ├── exceptions.ts │ │ ├── extend-tabulator.ts │ │ ├── help.ts │ │ ├── i18n.ts │ │ ├── i18n │ │ │ ├── cz.ts │ │ │ ├── da.ts │ │ │ ├── de.ts │ │ │ ├── en.ts │ │ │ ├── es.ts │ │ │ ├── fa.ts │ │ │ ├── fr.ts │ │ │ ├── gr.ts │ │ │ ├── it.ts │ │ │ ├── locales.ts │ │ │ ├── nl.ts │ │ │ ├── pl.ts │ │ │ ├── pt.ts │ │ │ ├── ru.ts │ │ │ ├── tr.ts │ │ │ ├── uk.ts │ │ │ └── zh.ts │ │ ├── message.ts │ │ ├── model-changes.ts │ │ ├── pbi-cloud.ts │ │ ├── pbi-dataset.ts │ │ ├── pbi-report.ts │ │ ├── pii.ts │ │ ├── strings.ts │ │ └── tabular.ts │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.json │ ├── view │ │ ├── alert.ts │ │ ├── chrome-tabs.ts │ │ ├── confirm.ts │ │ ├── connect-file.ts │ │ ├── connect-item.ts │ │ ├── connect-local.ts │ │ ├── connect-remote.ts │ │ ├── connect.ts │ │ ├── control.ts │ │ ├── create-template.ts │ │ ├── dax-editor.ts │ │ ├── diagnostic-pane.ts │ │ ├── dialog.ts │ │ ├── error-alert.ts │ │ ├── help-dialog.ts │ │ ├── menu.ts │ │ ├── multiview-pane.ts │ │ ├── notification-sidebar.ts │ │ ├── options-dialog-about.ts │ │ ├── options-dialog-dev.ts │ │ ├── options-dialog-formatting.ts │ │ ├── options-dialog-general.ts │ │ ├── options-dialog-proxy.ts │ │ ├── options-dialog-telemetry.ts │ │ ├── options-dialog.ts │ │ ├── powerbi-signin.ts │ │ ├── scene-analyze-model.ts │ │ ├── scene-back.ts │ │ ├── scene-best-practices.ts │ │ ├── scene-dax-formatter.ts │ │ ├── scene-doc.ts │ │ ├── scene-error.ts │ │ ├── scene-export-data.ts │ │ ├── scene-exported.ts │ │ ├── scene-exporting.ts │ │ ├── scene-loader.ts │ │ ├── scene-manage-dates-calendar.ts │ │ ├── scene-manage-dates-dates.ts │ │ ├── scene-manage-dates-holidays.ts │ │ ├── scene-manage-dates-interval.ts │ │ ├── scene-manage-dates-pane.ts │ │ ├── scene-manage-dates-preview.ts │ │ ├── scene-manage-dates-time-intelligence.ts │ │ ├── scene-manage-dates.ts │ │ ├── scene-navigator.ts │ │ ├── scene-success.ts │ │ ├── scene-unsupported.ts │ │ ├── scene-welcome.ts │ │ ├── scene.ts │ │ ├── sidebar.ts │ │ ├── tabs.ts │ │ ├── tabular-browser.ts │ │ ├── toast.ts │ │ └── view.ts │ └── webpack.config.js ├── Services │ ├── AnalyzeModelService.cs │ ├── AuthenticationService.cs │ ├── BestPracticeAnalyzerService.cs │ ├── ExportDataService.cs │ ├── FormatDaxService.cs │ ├── ManageDatesService.cs │ └── TemplateDevelopmentService.cs ├── Startup.cs └── wwwroot │ ├── auth-msalerror.html │ ├── auth-msalsuccess.html │ ├── favicons │ ├── icon-120.png │ ├── icon-128.png │ ├── icon-152.png │ ├── icon-16.png │ ├── icon-167.png │ ├── icon-180.png │ ├── icon-196.png │ ├── icon-228.png │ ├── icon-32.png │ └── icon-96.png │ ├── fonts │ ├── bravo.eot │ ├── bravo.svg │ ├── bravo.ttf │ └── bravo.woff │ ├── images │ ├── attach-pbi.svg │ ├── bravo-hero.svg │ ├── bravo-shadows.svg │ ├── bravo.svg │ ├── connect-pbi.svg │ ├── dax-formatter-dark.svg │ ├── dax-formatter.svg │ ├── measure-dark.svg │ ├── measure-light.svg │ ├── notifications-dark.svg │ ├── notifications-light.svg │ ├── preview-dark.svg │ ├── preview-light.svg │ ├── sqlbi-invert.svg │ ├── sqlbi.svg │ ├── user.svg │ ├── vertipaq.svg │ └── vscode.svg │ └── index.html └── test └── Bravo.Tests ├── Bravo.Tests.csproj ├── ClassFixture.cs ├── Infrastructure ├── Extensions │ ├── CommonExtensionsTests.cs │ └── StringExtensionsTests.cs ├── Helpers │ └── CommonHelperTests.cs ├── Security │ ├── CryptographyExtensionsTests.cs │ └── CryptographyTests.cs └── Services │ └── BestPracticeAnalyzer │ ├── AnalyzerTests.cs │ └── BestPracticeCollectionTests.cs └── _data ├── BestPracticeAnalyzer ├── TabularEditor_BPARules-PowerBI.AdventureWorks.TE2Results.json ├── TabularEditor_BPARules-PowerBI.json ├── TabularEditor_BPARules-standard.AdventureWorks.TE2Results.json └── TabularEditor_BPARules-standard.json └── Models └── AdventureWorks.bim /.azure/pipelines/release-bravostore.yaml: -------------------------------------------------------------------------------- 1 | trigger: none 2 | 3 | pool: 4 | vmImage: 'windows-2022' 5 | 6 | variables: 7 | arch: 'x64' 8 | configuration: 'Release' 9 | artifact: 'BravoStore-$(arch)' 10 | major: 1 11 | minor: 0 12 | build: 0 13 | 14 | steps: 15 | - task: UseDotNet@2 16 | displayName: 'Use .NET 5.0 SDK' 17 | inputs: 18 | packageType: sdk 19 | version: 5.0.x 20 | - task: DotNetCoreCLI@2 21 | displayName: '.NET restore' 22 | inputs: 23 | command: 'restore' 24 | projects: 'src\Bravo.csproj' 25 | feedsToUse: 'select' 26 | verbosityRestore: 'Normal' 27 | - task: PowerShell@2 28 | displayName: "Update appxmanifest version" 29 | inputs: 30 | targetType: 'inline' 31 | script: | 32 | $version = "$(major).$(minor).$(build).0" 33 | Write-Host "Updating to version [$version]" 34 | [xml]$manifest = Get-Content ".\Package.appxmanifest" 35 | $manifest.Package.Identity.Version = "$version" 36 | $manifest.Save("./Package.appxmanifest") 37 | warningPreference: 'stop' 38 | failOnStderr: true 39 | showWarnings: true 40 | workingDirectory: '$(Build.SourcesDirectory)\installer\msix\Bravo.Installer.Msix' 41 | - task: MSBuild@1 42 | displayName: "MSBuild MSIX" 43 | inputs: 44 | solution: 'installer/msix/Bravo.Installer.Msix/Bravo.Installer.Msix.wapproj' 45 | platform: '$(arch)' 46 | configuration: '$(configuration)' 47 | msbuildArguments: ' 48 | /p:UapAppxPackageBuildMode=SideLoadOnly 49 | /p:AppxBundle=Never 50 | /p:AppxPackageOutput=$(Build.ArtifactStagingDirectory)\$(artifact).msix 51 | /p:AppxPackageSigningEnabled=false' 52 | clean: true 53 | - task: DotNetCoreCLI@2 54 | displayName: 'Install AzureSignTool' 55 | inputs: 56 | command: 'custom' 57 | custom: 'tool' 58 | arguments: 'update --global azuresigntool' 59 | - task: CmdLine@2 60 | displayName: 'Code signing MSIX' 61 | inputs: 62 | script: 'AzureSignTool sign -kvu "$(SigningVaultURL)" -kvt "$(SigningTenantId)" -kvi "$(SigningClientId)" -kvs "$(SigningClientSecret)" -kvc "$(SigningCertName)" -tr http://timestamp.digicert.com -v "$(Build.ArtifactStagingDirectory)\$(artifact).msix"' 63 | failOnStderr: true 64 | - task: PublishBuildArtifacts@1 65 | inputs: 66 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 67 | ArtifactName: 'drop' 68 | publishLocation: 'Container' 69 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @albertospelta 2 | 3 | .azure/ @albertospelta 4 | .github/ @albertospelta -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 💬 General Question 4 | url: https://github.com/sql-bi/bravo/discussions/categories/q-a 5 | about: Please ask and answer questions as a discussion thread 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 💡 Feature Request 2 | labels: ["enhancement", "untriaged"] 3 | description: Suggest an idea for this project 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for submitting a feature request! 9 | 10 | --- 11 | - type: textarea 12 | id: the-feature-request 13 | attributes: 14 | label: The feature request 15 | description: 16 | Write a clear and concise description of what the feature or problem is. 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: proposed-solution 21 | attributes: 22 | label: Proposed solution 23 | description: Share how this will benefit Bravo for Power BI and its users. 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: additional-context 28 | attributes: 29 | label: Additional context 30 | description: 31 | Please include any other context, like screenshots or mockups, if applicable. 32 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Closes #[issue number] 2 | 3 | ## Description 4 | - 5 | 6 | Notes: -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "nuget" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 10 8 | assignees: 9 | - "albertospelta" 10 | 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | schedule: 14 | interval: "weekly" 15 | open-pull-requests-limit: 10 16 | assignees: 17 | - "albertospelta" 18 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: Breaking Changes 4 | labels: 5 | - breaking-change 6 | - title: New Features 7 | labels: 8 | - feature 9 | - title: Bug fixes 10 | labels: 11 | - bug 12 | - title: Dependencies 13 | labels: 14 | - dependencies 15 | - title: Other Changes 16 | labels: 17 | - "*" -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | workflow_dispatch: 5 | jobs: 6 | build-and-test: 7 | runs-on: windows-latest 8 | strategy: 9 | matrix: 10 | framework: [net6.0-windows, net8.0-windows] 11 | steps: 12 | - name: checkout 13 | uses: actions/checkout@v4 14 | - name: setup dotnet 15 | uses: actions/setup-dotnet@v4 16 | with: 17 | global-json-file: global.json 18 | - name: dotnet restore 19 | run: dotnet restore Bravo.sln 20 | - name: dotnet build 21 | run: dotnet build Bravo.sln --configuration Release --framework ${{ matrix.framework }} --no-restore 22 | - name: dotnet test 23 | run: dotnet test Bravo.sln --configuration Release --framework ${{ matrix.framework }} --no-build --verbosity normal -------------------------------------------------------------------------------- /Bravo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32014.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bravo", "src\Bravo.csproj", "{3D49664E-E100-4EFF-A709-A59D03F4663E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bravo.Tests", "test\Bravo.Tests\Bravo.Tests.csproj", "{6EECEE30-9DD6-4CF4-8E0E-7773C2DED2CF}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug_wwwroot|Any CPU = Debug_wwwroot|Any CPU 13 | Debug|Any CPU = Debug|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {3D49664E-E100-4EFF-A709-A59D03F4663E}.Debug_wwwroot|Any CPU.ActiveCfg = Debug_wwwroot|Any CPU 18 | {3D49664E-E100-4EFF-A709-A59D03F4663E}.Debug_wwwroot|Any CPU.Build.0 = Debug_wwwroot|Any CPU 19 | {3D49664E-E100-4EFF-A709-A59D03F4663E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {3D49664E-E100-4EFF-A709-A59D03F4663E}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {3D49664E-E100-4EFF-A709-A59D03F4663E}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {3D49664E-E100-4EFF-A709-A59D03F4663E}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {6EECEE30-9DD6-4CF4-8E0E-7773C2DED2CF}.Debug_wwwroot|Any CPU.ActiveCfg = Debug_wwwroot|Any CPU 24 | {6EECEE30-9DD6-4CF4-8E0E-7773C2DED2CF}.Debug_wwwroot|Any CPU.Build.0 = Debug_wwwroot|Any CPU 25 | {6EECEE30-9DD6-4CF4-8E0E-7773C2DED2CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {6EECEE30-9DD6-4CF4-8E0E-7773C2DED2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {6EECEE30-9DD6-4CF4-8E0E-7773C2DED2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {6EECEE30-9DD6-4CF4-8E0E-7773C2DED2CF}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {B376565F-DE6F-4510-8C84-76915632F3C3} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) SQLBI Corporation 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 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | dotnet build Bravo.sln 4 | dotnet test .\test\Bravo.Tests\Bravo.Tests.csproj -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.400", 4 | "allowPrerelease": false, 5 | "rollForward": "latestFeature" 6 | } 7 | } -------------------------------------------------------------------------------- /installer/.gitignore: -------------------------------------------------------------------------------- 1 | !wix/bin 2 | !wix/bin/* 3 | wix/**/*.wixobj 4 | wix/**/*.wixpdb 5 | wix/**/*.msi 6 | wix/**/Components.wxs -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix.StoreLauncher/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix.StoreLauncher/Bravo.Installer.Msix.StoreLauncher.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {742A2671-6C1D-475A-9A8D-102183B58937} 8 | WinExe 9 | Sqlbi.Bravo.Installer.Msix.StoreLauncher 10 | BravoStoreLauncher 11 | v4.6.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix.StoreLauncher/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Sqlbi.Bravo.StoreLauncher")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Sqlbi.Bravo.StoreLauncher")] 13 | [assembly: AssemblyCopyright("Copyright © 2021")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("742a2671-6c1d-475a-9a8d-102183b58937")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/msix/Bravo.Installer.Msix/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/msix/Bravo.Installer.Msix/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/msix/Bravo.Installer.Msix/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/msix/Bravo.Installer.Msix/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/msix/Bravo.Installer.Msix/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/msix/Bravo.Installer.Msix/Assets/StoreLogo.png -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/msix/Bravo.Installer.Msix/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /installer/msix/Bravo.Installer.Msix/Assets/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/msix/Bravo.Installer.Msix/Assets/icon-256x256.png -------------------------------------------------------------------------------- /installer/wix/extensions/Bravo.Installer.Wix.Tests/HelpersTests.cs: -------------------------------------------------------------------------------- 1 | namespace Bravo.Installer.WiX.Tests 2 | { 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Sqlbi.Bravo.Installer.Wix; 5 | 6 | [TestClass] 7 | public class HelpersTests 8 | { 9 | [TestMethod] 10 | public void ToSHA256Hash_Test1() 11 | { 12 | var actual = Helpers.ToSHA256Hash("Bravo"); 13 | var expected = "8123f58e72483f148509ae2da7feda62076dbe2ae3a045323bea4458a62d0952"; 14 | 15 | Assert.AreEqual(expected, actual); 16 | } 17 | 18 | [TestMethod] 19 | public void ToSHA256Hash_Test2() 20 | { 21 | var actual = Helpers.ToSHA256Hash("LAPTOP-12C9A7VU\\SYSTEM"); 22 | var expected = "e4f87d099028e128ea2b413f4fa6fc741426bef314de588b0920e89f22015bd0"; 23 | 24 | Assert.AreEqual(expected, actual); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /installer/wix/extensions/Bravo.Installer.Wix.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Bravo.Installer.WiX.Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Bravo.Installer.WiX.Tests")] 10 | [assembly: AssemblyCopyright("Copyright © 2022")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("296dd0ff-6dab-4910-95bb-c029f79394b4")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /installer/wix/extensions/Bravo.Installer.Wix.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /installer/wix/extensions/Bravo.Installer.Wix.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.32002.261 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bravo.Installer.Wix", "Bravo.Installer.Wix\Bravo.Installer.Wix.csproj", "{1862B616-A7DE-4CD9-9779-374E96193B78}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bravo.Installer.Wix.Tests", "Bravo.Installer.WiX.Tests\Bravo.Installer.Wix.Tests.csproj", "{296DD0FF-6DAB-4910-95BB-C029F79394B4}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x86 = Debug|x86 14 | Release|Any CPU = Release|Any CPU 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {1862B616-A7DE-4CD9-9779-374E96193B78}.Debug|Any CPU.ActiveCfg = Debug|x86 19 | {1862B616-A7DE-4CD9-9779-374E96193B78}.Debug|x86.ActiveCfg = Debug|x86 20 | {1862B616-A7DE-4CD9-9779-374E96193B78}.Debug|x86.Build.0 = Debug|x86 21 | {1862B616-A7DE-4CD9-9779-374E96193B78}.Release|Any CPU.ActiveCfg = Release|x86 22 | {1862B616-A7DE-4CD9-9779-374E96193B78}.Release|x86.ActiveCfg = Release|x86 23 | {1862B616-A7DE-4CD9-9779-374E96193B78}.Release|x86.Build.0 = Release|x86 24 | {296DD0FF-6DAB-4910-95BB-C029F79394B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {296DD0FF-6DAB-4910-95BB-C029F79394B4}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {296DD0FF-6DAB-4910-95BB-C029F79394B4}.Debug|x86.ActiveCfg = Debug|Any CPU 27 | {296DD0FF-6DAB-4910-95BB-C029F79394B4}.Debug|x86.Build.0 = Debug|Any CPU 28 | {296DD0FF-6DAB-4910-95BB-C029F79394B4}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {296DD0FF-6DAB-4910-95BB-C029F79394B4}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {296DD0FF-6DAB-4910-95BB-C029F79394B4}.Release|x86.ActiveCfg = Release|Any CPU 31 | {296DD0FF-6DAB-4910-95BB-C029F79394B4}.Release|x86.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(ExtensibilityGlobals) = postSolution 37 | SolutionGuid = {B4376BC7-FC9C-4344-8FFC-A10F1452BD57} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /installer/wix/extensions/Bravo.Installer.Wix/CustomAction.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /installer/wix/extensions/Bravo.Installer.Wix/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Sqlbi.Bravo.Installer.Wix")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Sqlbi.Bravo.Installer")] 12 | [assembly: AssemblyCopyright("Copyright © 2021")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("1862b616-a7de-4cd9-9779-374e96193b78")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: InternalsVisibleTo("Bravo.Installer.Wix.Tests")] 36 | -------------------------------------------------------------------------------- /installer/wix/extensions/Bravo.Installer.Wix/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /installer/wix/src/Bravo/Bravo-en-us.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Bravo for Power BI 5 | 1033 6 | Bravo for Power BI 7 | This installer contains the logic and data required to install Bravo for Power BI 8 | A newer version of this application is already installed on this computer 9 | Create Desktop Shortcut 10 | Create Program Menu Shortcut 11 | Telemetry and error reports: 12 | Bravo for Power BI collects anonymous telemetry, which helps us improve the product. You can opt-out at any time by launching Bravo and navigating to Settings > Telemetry. 13 | Enable Installer Telemetry 14 | Enable Application Telemetry 15 | This application is only supported on Windows series operating systems - Windows Client version 7 SP1 or higher - Windows Server version 2012 or higher. 16 | 17 | 18 | Click next to install to the default location or change it below 19 | 20 | 21 | -------------------------------------------------------------------------------- /installer/wix/src/BravoStoreLauncher/BravoStoreLauncher-en-us.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Bravo for Power BI (Store Launcher) 5 | 1033 6 | Bravo for Power BI (Store Launcher) 7 | This installer contains the logic and data required to install Bravo for Power BI (Store Launcher) 8 | A newer version of this application is already installed on this computer 9 | 10 | 11 | -------------------------------------------------------------------------------- /installer/wix/src/assets/Bravo.Installer.Wix.CA.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/wix/src/assets/Bravo.Installer.Wix.CA.dll -------------------------------------------------------------------------------- /installer/wix/src/assets/background.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/wix/src/assets/background.bmp -------------------------------------------------------------------------------- /installer/wix/src/assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/wix/src/assets/background.png -------------------------------------------------------------------------------- /installer/wix/src/assets/banner.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/wix/src/assets/banner.bmp -------------------------------------------------------------------------------- /installer/wix/src/assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/installer/wix/src/assets/banner.png -------------------------------------------------------------------------------- /installer/wix/src/assets/license.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1040\deflangfe1040\deftab708{\fonttbl{\f0\fmodern\fprq1\fcharset0 Consolas;}} 2 | {\*\generator Riched20 10.0.22000}{\*\mmathPr\mnaryLim0\mdispDef1\mwrapIndent1440 }\viewkind4\uc1 3 | \pard\widctlpar\sa160\sl252\slmult1\b\f0\fs36\lang1033 Bravo for Power BI\b0\fs22\par 4 | \ul Copyright 2022 SQLBI Corp.\ulnone\par 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\par 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\par 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\par 8 | \ul\lang1040\par 9 | } 10 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Assets/ManageDates/Templates/Config-01.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sql-bi/Bravo/main/src/Assets/ManageDates/Schemas/engine-configuration.schema.json", 3 | "Description": "Standard gregorian calendar", 4 | "Templates": [ 5 | { 6 | "Class": "HolidaysDefinitionTable", 7 | "Table": "HolidaysDefinition", 8 | "Template": "HolidaysDefinition.json", 9 | "IsHidden": true 10 | }, 11 | { 12 | "Class": "HolidaysTable", 13 | "Table": "Holidays", 14 | "Template": null, 15 | "IsHidden": true 16 | }, 17 | { 18 | "Class": "CustomDateTable", 19 | "Table": "Date", 20 | "ReferenceTable": "DateAutoTemplate", 21 | "Template": "DateTemplate-01.json", 22 | "LocalizationFiles": [ 23 | ] 24 | }, 25 | { 26 | "Class": "MeasuresTemplate", 27 | "Table": null, 28 | "Template": "TimeIntelligence-01.json", 29 | "Properties": { 30 | "__DisplayFolderRule": "Time intelligence\\@_MEASURE_@\\@_TEMPLATEFOLDER_@", 31 | "_DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_TEMPLATE_@", 32 | "___DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASURE_@", 33 | "DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASUREFOLDER_@\\@_MEASURE_@", 34 | "DisplayFolderRuleSingleInstanceMeasures": "Hidden Time Intelligence" 35 | }, 36 | "_comment": "TargetMeasures can override the default setting" 37 | } 38 | ], 39 | "IsoTranslation": null, 40 | "IsoFormat": null, 41 | "LocalizationFiles": [ 42 | "DateLocalization-01.json" 43 | ], 44 | "OnlyTablesColumns": [], 45 | "ExceptTablesColumns": [], 46 | "FirstYearMin": null, 47 | "FirstYearMax": null, 48 | "LastYearMin": null, 49 | "LastYearMax": null, 50 | "AutoScan": "Full", 51 | "DefaultVariables": { 52 | "__FirstDayOfWeek": "0" 53 | }, 54 | "IsoCountry": "US", 55 | "InLieuOfPrefix": "(in lieu of ", 56 | "InLieuOfSuffix": ")", 57 | "WorkingDays": "{ 2, 3, 4, 5, 6 }", 58 | "HolidaysDefinitionTable": "HolidaysDefinition", 59 | "HolidaysReference": { 60 | "TableName": "Holidays", 61 | "DateColumnName": "Holiday Date", 62 | "HolidayColumnName": "Holiday Name" 63 | }, 64 | "TableSingleInstanceMeasures": null, 65 | "AutoNaming": "Prefix", 66 | "TargetMeasures": [] 67 | } -------------------------------------------------------------------------------- /src/Assets/ManageDates/Templates/Config-02.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sql-bi/Bravo/main/src/Assets/ManageDates/Schemas/engine-configuration.schema.json", 3 | "Description": "Standard fiscal calendar", 4 | "Templates": [ 5 | { 6 | "Class": "HolidaysDefinitionTable", 7 | "Table": "HolidaysDefinition", 8 | "Template": "HolidaysDefinition.json", 9 | "IsHidden": true 10 | }, 11 | { 12 | "Class": "HolidaysTable", 13 | "Table": "Holidays", 14 | "Template": null, 15 | "IsHidden": true 16 | }, 17 | { 18 | "Class": "CustomDateTable", 19 | "Table": "Date", 20 | "ReferenceTable": "DateAutoTemplate", 21 | "Template": "DateTemplate-02.json", 22 | "LocalizationFiles": [ 23 | ] 24 | }, 25 | { 26 | "Class": "MeasuresTemplate", 27 | "Table": null, 28 | "Template": "TimeIntelligence-02.json", 29 | "Properties": { 30 | "__DisplayFolderRule": "Time intelligence\\@_MEASURE_@\\@_TEMPLATEFOLDER_@", 31 | "_DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_TEMPLATE_@", 32 | "___DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASURE_@", 33 | "DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASUREFOLDER_@\\@_MEASURE_@", 34 | "DisplayFolderRuleSingleInstanceMeasures": "Hidden Time Intelligence" 35 | }, 36 | "_comment": "TargetMeasures can override the default setting" 37 | } 38 | ], 39 | "IsoTranslation": null, 40 | "IsoFormat": null, 41 | "LocalizationFiles": [ 42 | "DateLocalization-02.json" 43 | ], 44 | "OnlyTablesColumns": [], 45 | "ExceptTablesColumns": [], 46 | "FirstYearMin": null, 47 | "FirstYearMax": null, 48 | "LastYearMin": null, 49 | "LastYearMax": null, 50 | "AutoScan": "Full", 51 | "DefaultVariables": { 52 | "__FirstFiscalMonth": "7", 53 | "__FirstDayOfWeek": "0" 54 | }, 55 | "IsoCountry": "US", 56 | "InLieuOfPrefix": "(in lieu of ", 57 | "InLieuOfSuffix": ")", 58 | "WorkingDays": "{ 2, 3, 4, 5, 6 }", 59 | "HolidaysDefinitionTable": "HolidaysDefinition", 60 | "HolidaysReference": { 61 | "TableName": "Holidays", 62 | "DateColumnName": "Holiday Date", 63 | "HolidayColumnName": "Holiday Name" 64 | }, 65 | "TableSingleInstanceMeasures": null, 66 | "AutoNaming": "Prefix", 67 | "TargetMeasures": [] 68 | } -------------------------------------------------------------------------------- /src/Assets/ManageDates/Templates/Config-03.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sql-bi/Bravo/main/src/Assets/ManageDates/Schemas/engine-configuration.schema.json", 3 | "Description": "Monthly gregorian calendar", 4 | "Templates": [ 5 | { 6 | "Class": "CustomDateTable", 7 | "Table": "Date", 8 | "ReferenceTable": "DateAutoTemplate", 9 | "Template": "DateTemplate-03.json", 10 | "LocalizationFiles": [ 11 | ] 12 | }, 13 | { 14 | "Class": "MeasuresTemplate", 15 | "Table": null, 16 | "Template": "TimeIntelligence-03.json", 17 | "Properties": { 18 | "__DisplayFolderRule": "Time intelligence\\@_MEASURE_@\\@_TEMPLATEFOLDER_@", 19 | "_DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_TEMPLATE_@", 20 | "___DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASURE_@", 21 | "DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASUREFOLDER_@\\@_MEASURE_@", 22 | "DisplayFolderRuleSingleInstanceMeasures": "Hidden Time Intelligence" 23 | }, 24 | "_comment": "TargetMeasures can override the default setting" 25 | } 26 | ], 27 | "IsoTranslation": null, 28 | "IsoFormat": null, 29 | "LocalizationFiles": [ 30 | "DateLocalization-03.json" 31 | ], 32 | "OnlyTablesColumns": [], 33 | "ExceptTablesColumns": [], 34 | "FirstYearMin": null, 35 | "FirstYearMax": null, 36 | "LastYearMin": null, 37 | "LastYearMax": null, 38 | "AutoScan": "Full", 39 | "DefaultVariables": { 40 | "__MonthsInYear": "12" 41 | }, 42 | "IsoCountry": "US", 43 | "AutoNaming": "Prefix", 44 | "TargetMeasures": [] 45 | } -------------------------------------------------------------------------------- /src/Assets/ManageDates/Templates/Config-04.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sql-bi/Bravo/main/src/Assets/ManageDates/Schemas/engine-configuration.schema.json", 3 | "Description": "Monthly fiscal calendar", 4 | "Templates": [ 5 | { 6 | "Class": "CustomDateTable", 7 | "Table": "Date", 8 | "ReferenceTable": "DateAutoTemplate", 9 | "Template": "DateTemplate-04.json", 10 | "LocalizationFiles": [ 11 | ] 12 | }, 13 | { 14 | "Class": "MeasuresTemplate", 15 | "Table": null, 16 | "Template": "TimeIntelligence-04.json", 17 | "Properties": { 18 | "__DisplayFolderRule": "Time intelligence\\@_MEASURE_@\\@_TEMPLATEFOLDER_@", 19 | "_DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_TEMPLATE_@", 20 | "___DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASURE_@", 21 | "DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASUREFOLDER_@\\@_MEASURE_@", 22 | "DisplayFolderRuleSingleInstanceMeasures": "Hidden Time Intelligence" 23 | }, 24 | "_comment": "TargetMeasures can override the default setting" 25 | } 26 | ], 27 | "IsoTranslation": null, 28 | "IsoFormat": null, 29 | "LocalizationFiles": [ 30 | "DateLocalization-04.json" 31 | ], 32 | "OnlyTablesColumns": [], 33 | "ExceptTablesColumns": [], 34 | "FirstYearMin": null, 35 | "FirstYearMax": null, 36 | "LastYearMin": null, 37 | "LastYearMax": null, 38 | "AutoScan": "Full", 39 | "DefaultVariables": { 40 | "__FirstFiscalMonth": "7", 41 | "__MonthsInYear": "12" 42 | }, 43 | "IsoCountry": "US", 44 | "AutoNaming": "Prefix", 45 | "TargetMeasures": [] 46 | } -------------------------------------------------------------------------------- /src/Assets/ManageDates/Templates/Config-05.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sql-bi/Bravo/main/src/Assets/ManageDates/Schemas/engine-configuration.schema.json", 3 | "Description": "Custom gregorian calendar", 4 | "Templates": [ 5 | { 6 | "Class": "HolidaysDefinitionTable", 7 | "Table": "HolidaysDefinition", 8 | "Template": "HolidaysDefinition.json", 9 | "IsHidden": true 10 | }, 11 | { 12 | "Class": "HolidaysTable", 13 | "Table": "Holidays", 14 | "Template": null, 15 | "IsHidden": true 16 | }, 17 | { 18 | "Class": "CustomDateTable", 19 | "Table": "Date", 20 | "ReferenceTable": "DateAutoTemplate", 21 | "Template": "DateTemplate-05.json", 22 | "LocalizationFiles": [] 23 | }, 24 | { 25 | "Class": "MeasuresTemplate", 26 | "Table": null, 27 | "Template": "TimeIntelligence-05.json", 28 | "Properties": { 29 | "__DisplayFolderRule": "Time intelligence\\@_MEASURE_@\\@_TEMPLATEFOLDER_@", 30 | "_DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_TEMPLATE_@", 31 | "___DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASURE_@", 32 | "DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASUREFOLDER_@\\@_MEASURE_@", 33 | "DisplayFolderRuleSingleInstanceMeasures": "Hidden Time Intelligence" 34 | }, 35 | "_comment": "TargetMeasures can override the default setting" 36 | } 37 | ], 38 | "IsoTranslation": null, 39 | "IsoFormat": null, 40 | "LocalizationFiles": [ 41 | "DateLocalization-05.json" 42 | ], 43 | "OnlyTablesColumns": [], 44 | "ExceptTablesColumns": [], 45 | "FirstYearMin": null, 46 | "FirstYearMax": null, 47 | "LastYearMin": null, 48 | "LastYearMax": null, 49 | "AutoScan": "Full", 50 | "DefaultVariables": { 51 | "__FirstDayOfWeek": "0" 52 | }, 53 | "IsoCountry": "US", 54 | "InLieuOfPrefix": "(in lieu of ", 55 | "InLieuOfSuffix": ")", 56 | "WorkingDays": "{ 2, 3, 4, 5, 6 }", 57 | "HolidaysDefinitionTable": "HolidaysDefinition", 58 | "HolidaysReference": { 59 | "TableName": "Holidays", 60 | "DateColumnName": "Holiday Date", 61 | "HolidayColumnName": "Holiday Name" 62 | }, 63 | "AutoNaming": "Prefix", 64 | "TargetMeasures": [] 65 | } -------------------------------------------------------------------------------- /src/Assets/ManageDates/Templates/Config-06.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sql-bi/Bravo/main/src/Assets/ManageDates/Schemas/engine-configuration.schema.json", 3 | "Description": "Custom fiscal calendar", 4 | "Templates": [ 5 | { 6 | "Class": "HolidaysDefinitionTable", 7 | "Table": "HolidaysDefinition", 8 | "Template": "HolidaysDefinition.json", 9 | "IsHidden": true 10 | }, 11 | { 12 | "Class": "HolidaysTable", 13 | "Table": "Holidays", 14 | "Template": null, 15 | "IsHidden": true 16 | }, 17 | { 18 | "Class": "CustomDateTable", 19 | "Table": "Date", 20 | "ReferenceTable": "DateAutoTemplate", 21 | "Template": "DateTemplate-06.json", 22 | "LocalizationFiles": [] 23 | }, 24 | { 25 | "Class": "MeasuresTemplate", 26 | "Table": null, 27 | "Template": "TimeIntelligence-06.json", 28 | "Properties": { 29 | "__DisplayFolderRule": "Time intelligence\\@_MEASURE_@\\@_TEMPLATEFOLDER_@", 30 | "_DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_TEMPLATE_@", 31 | "___DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASURE_@", 32 | "DisplayFolderRule": "Time intelligence\\@_TEMPLATEFOLDER_@\\@_MEASUREFOLDER_@\\@_MEASURE_@", 33 | "DisplayFolderRuleSingleInstanceMeasures": "Hidden Time Intelligence" 34 | }, 35 | "_comment": "TargetMeasures can override the default setting" 36 | } 37 | ], 38 | "IsoTranslation": null, 39 | "IsoFormat": null, 40 | "LocalizationFiles": [ 41 | "DateLocalization-06.json" 42 | ], 43 | "OnlyTablesColumns": [], 44 | "ExceptTablesColumns": [], 45 | "FirstYearMin": null, 46 | "FirstYearMax": null, 47 | "LastYearMin": null, 48 | "LastYearMax": null, 49 | "AutoScan": "Full", 50 | "DefaultVariables": { 51 | "__FirstFiscalMonth": "7", 52 | "__FirstDayOfWeek": "0" 53 | }, 54 | "IsoCountry": "US", 55 | "InLieuOfPrefix": "(in lieu of ", 56 | "InLieuOfSuffix": ")", 57 | "WorkingDays": "{ 2, 3, 4, 5, 6 }", 58 | "HolidaysDefinitionTable": "HolidaysDefinition", 59 | "HolidaysReference": { 60 | "TableName": "Holidays", 61 | "DateColumnName": "Holiday Date", 62 | "HolidayColumnName": "Holiday Name" 63 | }, 64 | "AutoNaming": "Prefix", 65 | "TargetMeasures": [] 66 | } -------------------------------------------------------------------------------- /src/Assets/bravo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/Assets/bravo.ico -------------------------------------------------------------------------------- /src/Assets/bravo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/Assets/bravo.png -------------------------------------------------------------------------------- /src/Assets/lib/Antlr4.Runtime.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/Assets/lib/Antlr4.Runtime.dll -------------------------------------------------------------------------------- /src/Assets/lib/TOMWrapper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/Assets/lib/TOMWrapper.dll -------------------------------------------------------------------------------- /src/Infrastructure/AppTelemetry.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure 2 | { 3 | using Microsoft.ApplicationInsights.Channel; 4 | using Microsoft.ApplicationInsights.Extensibility; 5 | using Sqlbi.Bravo.Infrastructure.Security; 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | internal class AppTelemetryInitializer : ITelemetryInitializer 10 | { 11 | public static readonly string DeviceOperatingSystem = Environment.OSVersion.ToString(); 12 | public static readonly string ComponentVersion = AppEnvironment.ApplicationProductVersion; 13 | public static readonly string SessionId = Guid.NewGuid().ToString(); 14 | public static readonly string? UserId = $"{ Environment.MachineName }\\{ Environment.UserName }".ToSHA256Hash(); 15 | public static readonly IReadOnlyDictionary GlobalProperties = new Dictionary 16 | { 17 | { "ProductName", AppEnvironment.ApplicationName }, 18 | { "Version", AppEnvironment.ApplicationProductVersion }, 19 | { "Build", AppEnvironment.ApplicationFileVersion }, 20 | { "PublishMode", AppEnvironment.PublishMode.ToString() }, 21 | { "InstallScope", AppEnvironment.DeploymentMode.ToString() }, 22 | { "WebView2Version", AppEnvironment.WebView2VersionInfo ?? string.Empty }, 23 | }; 24 | 25 | public void Initialize(ITelemetry telemetry) 26 | { 27 | // Keep telemetry context configuration synchronized with Sqlbi.Bravo.Installer.Wix.Helpers.GetTelemetryClient() 28 | 29 | telemetry.Context.Device.OperatingSystem = DeviceOperatingSystem; 30 | telemetry.Context.Component.Version = ComponentVersion; 31 | telemetry.Context.Session.Id = SessionId; 32 | telemetry.Context.User.Id = UserId; 33 | 34 | foreach (var property in GlobalProperties) 35 | { 36 | telemetry.Context.GlobalProperties.Add(property.Key, property.Value); 37 | } 38 | } 39 | } 40 | 41 | internal class AppTelemetryProcessor : ITelemetryProcessor 42 | { 43 | private readonly ITelemetryProcessor _next; 44 | 45 | public AppTelemetryProcessor(ITelemetryProcessor next) 46 | { 47 | _next = next; 48 | } 49 | 50 | public void Process(ITelemetry item) 51 | { 52 | _next.Process(item); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Infrastructure/Authentication/AppAuthenticationSchemeOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | 3 | namespace Sqlbi.Bravo.Infrastructure.Authentication 4 | { 5 | internal class AppAuthenticationSchemeOptions : AuthenticationSchemeOptions 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Infrastructure/Authentication/MsalSystemWebViewOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Authentication 2 | { 3 | using Microsoft.Identity.Client; 4 | using System.IO; 5 | 6 | internal class MsalSystemWebViewOptions : SystemWebViewOptions 7 | { 8 | public MsalSystemWebViewOptions(string webrootPath) 9 | { 10 | var succeessHtml = File.ReadAllText(Path.Combine(webrootPath, "auth-msalsuccess.html")); 11 | var errorHtml = File.ReadAllText(Path.Combine(webrootPath, "auth-msalerror.html")); 12 | 13 | HtmlMessageSuccess = succeessHtml; 14 | HtmlMessageError = errorHtml; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Infrastructure/Configuration/Options/IWritableOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System; 3 | 4 | namespace Sqlbi.Bravo.Infrastructure.Configuration.Options 5 | { 6 | public interface IWritableOptions : IOptions where T : class, new() 7 | { 8 | void Update(Action action); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Infrastructure/Configuration/Settings/ExperimentalSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Configuration.Settings 2 | { 3 | using System.Text.Json.Serialization; 4 | 5 | public class ExperimentalSettings 6 | { 7 | //[JsonPropertyName("useIntegratedWindowsAuthenticationSso")] 8 | //public bool? UseIntegratedWindowsAuthenticationSso { get; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/CloudOrganizationalGalleryItem.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | using System; 4 | using System.Text.Json.Serialization; 5 | 6 | public class CloudOrganizationalGalleryItem 7 | { 8 | [JsonPropertyName("id")] 9 | public int Id { get; set; } 10 | 11 | [JsonPropertyName("displayName")] 12 | public string? DisplayName { get; set; } 13 | 14 | [JsonPropertyName("description")] 15 | public string? Description { get; set; } 16 | 17 | [JsonPropertyName("status")] 18 | public CloudOrganizationalGalleryItemStatus Status { get; set; } 19 | 20 | [JsonPropertyName("stage")] 21 | public CloudPromotionalStage Stage { get; set; } 22 | 23 | [JsonPropertyName("iconUrl")] 24 | public string? IconUrl { get; set; } 25 | 26 | [JsonPropertyName("publishTime")] 27 | public DateTime PublishTime { get; set; } 28 | 29 | [JsonPropertyName("config")] 30 | public string? Config { get; set; } 31 | 32 | [JsonPropertyName("ownerGivenName")] 33 | public string? OwnerGivenName { get; set; } 34 | 35 | [JsonPropertyName("ownerFamilyName")] 36 | public string? OwnerFamilyName { get; set; } 37 | 38 | [JsonPropertyName("ownerEmailAddress")] 39 | public string? OwnerEmailAddress { get; set; } 40 | 41 | [JsonPropertyName("resourcePackageId")] 42 | public int ResourcePackageId { get; set; } 43 | 44 | [JsonPropertyName("objectId")] 45 | public Guid ObjectId { get; set; } 46 | 47 | [JsonPropertyName("disabled")] 48 | public bool? Disabled { get; set; } 49 | 50 | [JsonPropertyName("certifyingUser")] 51 | public CloudUser? CertifyingUser { get; set; } 52 | 53 | [JsonPropertyName("certificationTime")] 54 | public DateTime? CertificationTime { get; set; } 55 | 56 | [JsonPropertyName("isOutOfBox")] 57 | public bool? IsOutOfBox { get; set; } 58 | 59 | [JsonPropertyName("name")] 60 | public string? Name { get; set; } 61 | } 62 | } -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/CloudOrganizationalGalleryItemStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | public enum CloudOrganizationalGalleryItemStatus 4 | { 5 | Enabled, 6 | Disabled 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/CloudPermissions.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | using System; 4 | 5 | [Flags] 6 | public enum CloudPermissions 7 | { 8 | None = 0, 9 | Read = 1, 10 | Write = 2, 11 | ReShared = 4, 12 | Explore = 8 13 | } 14 | } -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/CloudPromotionalStage.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | public enum CloudPromotionalStage 4 | { 5 | None, 6 | Promoted, 7 | Certified, 8 | Master, 9 | Recommended 10 | } 11 | } -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/CloudSharedModel.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | using System; 4 | using System.Text.Json.Serialization; 5 | 6 | public sealed class CloudSharedModel 7 | { 8 | [JsonPropertyName("modelId")] 9 | public long ModelId { get; set; } 10 | 11 | [JsonPropertyName("workspaceId")] 12 | public long WorkspaceId { get; set; } 13 | 14 | [JsonPropertyName("workspaceObjectId")] 15 | public string? WorkspaceObjectId { get; set; } 16 | 17 | [JsonPropertyName("workspaceName")] 18 | public string? WorkspaceName { get; set; } 19 | 20 | [JsonPropertyName("workspaceType")] 21 | public CloudSharedModelWorkspaceType WorkspaceType { get; set; } 22 | 23 | [JsonPropertyName("permissions")] 24 | public CloudPermissions Permissions { get; set; } 25 | 26 | [JsonPropertyName("model")] 27 | public CloudModel? Model { get; set; } 28 | 29 | [JsonPropertyName("galleryItem")] 30 | public CloudOrganizationalGalleryItem? GalleryItem { get; set; } 31 | 32 | //[JsonPropertyName("artifactInformationProtection")] 33 | //public CloudArtifactInformationProtection ArtifactInformationProtection { get; set; } 34 | 35 | [JsonPropertyName("snapshotId")] 36 | public long? SnapshotId { get; set; } 37 | 38 | [JsonPropertyName("lastVisitedTimeUTC")] 39 | public DateTime? LastVisitedTimeUTC { get; set; } 40 | 41 | [JsonPropertyName("__objectId")] 42 | public string? ObjectId 43 | { 44 | get 45 | { 46 | if (IsOnPersonalWorkspace) 47 | { 48 | return CloudWorkspace.PersonalWorkspaceId; 49 | } 50 | 51 | return WorkspaceObjectId; 52 | } 53 | } 54 | 55 | [JsonPropertyName("__isOnPersonalWorkspace")] 56 | public bool IsOnPersonalWorkspace => WorkspaceType == CloudSharedModelWorkspaceType.PersonalGroup; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/CloudSharedModelWorkspaceType.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | public enum CloudSharedModelWorkspaceType 4 | { 5 | /// 6 | /// ??? 7 | /// 8 | Personal = 0, 9 | 10 | /// 11 | /// A modern workspace (the 'new' decoupled Workspace experience) 12 | /// 13 | Workspace = 1, 14 | 15 | /// 16 | /// A legacy workspace based on Office 365 groups (the 'old' Workspace experience where Workspaces are tightly coupled with O365 groups) 17 | /// 18 | Group = 2, 19 | 20 | /// 21 | /// Personal workspace of a Power BI user, a.k.a. "My Workspace" 22 | /// 23 | PersonalGroup = 3 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/CloudUser.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | using System.Text.Json.Serialization; 4 | 5 | public sealed class CloudUser 6 | { 7 | [JsonPropertyName("id")] 8 | public long Id { get; set; } 9 | 10 | [JsonPropertyName("givenName")] 11 | public string? GivenName { get; set; } 12 | 13 | [JsonPropertyName("familyName")] 14 | public string? FamilyName { get; set; } 15 | 16 | [JsonPropertyName("emailAddress")] 17 | public string? EmailAddress { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/CloudWorkspaceCapacitySkuType.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | public enum CloudWorkspaceCapacitySkuType 4 | { 5 | Unknown = 0, 6 | 7 | /// 8 | /// PremiumCapacitySku 9 | /// 10 | Premium, 11 | 12 | /// 13 | /// SharedCapacitySku 14 | /// 15 | Shared, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/CloudWorkspaceType.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | public enum CloudWorkspaceType 4 | { 5 | Unknown = 0, 6 | 7 | /// 8 | /// PersonalWorkspaceType 9 | /// 10 | User, 11 | 12 | /// 13 | /// GroupWorkspaceType 14 | /// 15 | Group, 16 | 17 | /// 18 | /// FolderWorkspaceType 19 | /// 20 | Folder, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/GlobalService.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | using System.Collections.Generic; 4 | using System.Text.Json.Serialization; 5 | 6 | public class GlobalService 7 | { 8 | [JsonPropertyName("environments")] 9 | public IEnumerable? Environments { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/GlobalServiceEnvironment.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Text.Json.Serialization; 6 | 7 | [DebuggerDisplay("{CloudName}")] 8 | public class GlobalServiceEnvironment 9 | { 10 | [JsonPropertyName("cloudName")] 11 | public string? CloudName { get; set; } 12 | 13 | [JsonPropertyName("services")] 14 | public IEnumerable? Services { get; set; } 15 | 16 | [JsonPropertyName("clients")] 17 | public IEnumerable? Clients { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/GlobalServiceEnvironmentClient.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | using System.Text.Json.Serialization; 4 | 5 | public class GlobalServiceEnvironmentClient 6 | { 7 | [JsonPropertyName("name")] 8 | public string? Name { get; set; } 9 | 10 | [JsonPropertyName("appId")] 11 | public string? AppId { get; set; } 12 | 13 | [JsonPropertyName("redirectUri")] 14 | public string? RedirectUri { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/GlobalServiceEnvironmentService.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | using System.Collections.Generic; 4 | using System.Text.Json.Serialization; 5 | 6 | public class GlobalServiceEnvironmentService 7 | { 8 | [JsonPropertyName("name")] 9 | public string? Name { get; set; } 10 | 11 | [JsonPropertyName("endpoint")] 12 | public string? Endpoint { get; set; } 13 | 14 | [JsonPropertyName("resourceId")] 15 | public string? ResourceId { get; set; } 16 | 17 | [JsonPropertyName("allowedDomains")] 18 | public IEnumerable? AllowedDomains { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Infrastructure/Contracts/PBICloud/TenantCluster.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud 2 | { 3 | using System.Text.Json.Serialization; 4 | 5 | public class TenantCluster 6 | { 7 | [JsonPropertyName("FixedClusterUri")] 8 | public string? FixedClusterUri { get; set; } 9 | 10 | //public string? PrivateLinkFixedClusterUri { get; set; } 11 | 12 | //public string? NewTenantId { get; set; } 13 | 14 | //public string? RuleDescription { get; set; } 15 | 16 | //public int? TTLSeconds { get; set; } 17 | 18 | //public string? TenantId { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Infrastructure/Extensions/CommonExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Extensions 2 | { 3 | using System; 4 | using System.Text.Json; 5 | 6 | internal static class EnumExtensions 7 | { 8 | public static TEnum? TryParseTo(this Enum? value) where TEnum : struct, Enum 9 | { 10 | if (value is not null) 11 | { 12 | var valueString = value.ToString(); 13 | var valueEnum = TryParseTo(valueString); 14 | 15 | return valueEnum; 16 | } 17 | 18 | return null; 19 | } 20 | 21 | public static TEnum? TryParseTo(this string? value) where TEnum : struct, Enum 22 | { 23 | if (value is not null) 24 | { 25 | if (Enum.TryParse(value, ignoreCase: true, out var valueEnum)) 26 | { 27 | return valueEnum; 28 | } 29 | } 30 | 31 | return null; 32 | } 33 | 34 | public static TEnum? TryParseTo(this int? value) where TEnum : struct, Enum 35 | { 36 | if (value is not null) 37 | { 38 | if (Enum.IsDefined(typeof(TEnum), value.Value)) 39 | { 40 | var @enum = (TEnum)Enum.ToObject(typeof(TEnum), value.Value); 41 | return @enum; 42 | } 43 | } 44 | 45 | return null; 46 | } 47 | 48 | public static T? JsonClone(this T value) 49 | { 50 | var json = JsonSerializer.Serialize(value); 51 | var instance = JsonSerializer.Deserialize(json); 52 | return instance; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Infrastructure/Extensions/DataReaderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Extensions 2 | { 3 | using System.Collections.Generic; 4 | using System; 5 | using System.Data; 6 | 7 | internal static class DataReaderExtensions 8 | { 9 | public static IEnumerable Select(this IDataReader reader, Func selector) 10 | { 11 | while (reader.Read()) 12 | { 13 | yield return selector(reader); 14 | } 15 | 16 | reader.Close(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Infrastructure/Extensions/DynamicLinq/DynamicClass.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Extensions.DynamicLinq 2 | { 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | internal abstract class DynamicClass 7 | { 8 | public override string ToString() 9 | { 10 | var properties = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); 11 | var builder = new StringBuilder(); 12 | 13 | builder.Append('{'); 14 | 15 | for (var i = 0; i < properties.Length; i++) 16 | { 17 | if (i > 0) 18 | builder.Append(", "); 19 | 20 | builder.Append(properties[i].Name); 21 | builder.Append('='); 22 | builder.Append(properties[i].GetValue(this, null)); 23 | } 24 | 25 | builder.Append('}'); 26 | 27 | return builder.ToString(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Infrastructure/Extensions/DynamicLinq/DynamicOrdering.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Extensions.DynamicLinq 2 | { 3 | using System.Linq.Expressions; 4 | 5 | internal class DynamicOrdering 6 | { 7 | public Expression? Selector; 8 | 9 | public bool Ascending; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Infrastructure/Extensions/DynamicLinq/DynamicProperty.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Extensions.DynamicLinq 2 | { 3 | using System; 4 | 5 | internal class DynamicProperty 6 | { 7 | private readonly string _name; 8 | private readonly Type _type; 9 | 10 | public DynamicProperty(string name, Type type) 11 | { 12 | _name = name ?? throw new ArgumentNullException(nameof(name)); 13 | _type = type ?? throw new ArgumentNullException(nameof(type)); 14 | } 15 | 16 | public string Name => _name; 17 | 18 | public Type Type => _type; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Infrastructure/Extensions/DynamicLinq/Parser/ParseException.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Extensions.DynamicLinq.Parser 2 | { 3 | using System; 4 | 5 | public sealed class ParseException : Exception 6 | { 7 | private readonly int _position; 8 | 9 | public ParseException(string message, int position) 10 | : base(message) 11 | { 12 | _position = position; 13 | } 14 | 15 | public int Position => _position; 16 | 17 | public override string ToString() 18 | { 19 | return string.Format(Res.ParseExceptionFormat, Message, Position); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Infrastructure/Extensions/DynamicLinq/Signature.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Extensions.DynamicLinq 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | internal class Signature : IEquatable 8 | { 9 | private readonly DynamicProperty[] _properties; 10 | private readonly int _hashCode; 11 | 12 | public Signature(IEnumerable properties) 13 | { 14 | _properties = properties.ToArray(); 15 | _hashCode = 0; 16 | 17 | foreach (DynamicProperty p in properties) 18 | { 19 | _hashCode ^= p.Name.GetHashCode() ^ p.Type.GetHashCode(); 20 | } 21 | } 22 | 23 | public DynamicProperty[] Properties => _properties; 24 | 25 | public override int GetHashCode() 26 | { 27 | return _hashCode; 28 | } 29 | 30 | public override bool Equals(object? obj) 31 | { 32 | return obj is Signature signature && Equals(signature); 33 | } 34 | 35 | public bool Equals(Signature? other) 36 | { 37 | if (Properties.Length != other?.Properties.Length) 38 | return false; 39 | 40 | for (var i = 0; i < Properties.Length; i++) 41 | { 42 | var @this = Properties[i]; 43 | if (@this.Name != other.Properties[i].Name || 44 | @this.Type != other.Properties[i].Type) 45 | { 46 | return false; 47 | } 48 | } 49 | return true; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Infrastructure/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Extensions 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Linq; 7 | 8 | internal static class EnumerableExtensions 9 | { 10 | public static IEnumerable<(T item, int index)> WithIndex(this IEnumerable source) 11 | { 12 | return source.Select((item, index) => (item, index)); 13 | } 14 | 15 | public static bool IsEmpty(this IEnumerable? source) 16 | { 17 | if (source is null) 18 | return true; 19 | 20 | return !source.Any(); 21 | } 22 | 23 | public static void ForEach(this IEnumerable source, Action action) 24 | { 25 | foreach (T item in source) 26 | { 27 | action(item); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Infrastructure/Extensions/RegistryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Extensions 2 | { 3 | using Microsoft.Win32; 4 | 5 | internal static class RegistryExtensions 6 | { 7 | public static bool SubKeyExists(this RegistryKey registryKey, string subkeyName) 8 | { 9 | using var registrySubKey = registryKey.OpenSubKey(subkeyName); 10 | return registrySubKey != null; 11 | } 12 | 13 | public static bool GetBoolValue(this RegistryKey registryKey, string subkeyName, string valueName) 14 | { 15 | var valueInt = GetIntValue(registryKey, subkeyName, valueName); 16 | return valueInt == 1; 17 | } 18 | 19 | public static int? GetIntValue(this RegistryKey registryKey, string subkeyName, string valueName) 20 | { 21 | var (value, valueKind) = GetRegistryValue(registryKey, subkeyName, valueName); 22 | 23 | if (value is not null && valueKind == RegistryValueKind.DWord) 24 | { 25 | var valueInt = (int)value; 26 | return valueInt; 27 | } 28 | 29 | return null; 30 | } 31 | 32 | public static string? GetStringValue(this RegistryKey registryKey, string subkeyName, string valueName) 33 | { 34 | var (value, valueKind) = GetRegistryValue(registryKey, subkeyName, valueName); 35 | 36 | if (value is not null && valueKind == RegistryValueKind.String) 37 | { 38 | var valueString = (string)value; 39 | return valueString; 40 | } 41 | 42 | return null; 43 | } 44 | 45 | private static (object? Value, RegistryValueKind ValueKind) GetRegistryValue(RegistryKey registryKey, string subkeyName, string valueName) 46 | { 47 | using var registrySubKey = registryKey.OpenSubKey(subkeyName); 48 | 49 | if (registrySubKey is not null) 50 | { 51 | var value = registrySubKey.GetValue(valueName, defaultValue: null, RegistryValueOptions.DoNotExpandEnvironmentNames); 52 | if (value is not null) 53 | { 54 | var valueKind = registrySubKey.GetValueKind(valueName); 55 | return (value, valueKind); 56 | } 57 | } 58 | 59 | return (null, RegistryValueKind.None); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/Infrastructure/Messages/WebMessageType.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Messages 2 | { 3 | internal enum WebMessageType 4 | { 5 | Unknown = 0, 6 | ReportOpen = 1, 7 | DatasetOpen = 2, 8 | VpaxOpen = 3, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Infrastructure/Models/IAuthenticationResult.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Models 2 | { 3 | using Sqlbi.Bravo.Models; 4 | 5 | public interface IAuthenticationResult 6 | { 7 | bool IsExpired { get; } 8 | 9 | string AccessToken { get; } 10 | 11 | IBravoAccount Account { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Infrastructure/Models/IDataModel.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Models 2 | { 3 | using System; 4 | 5 | internal interface IDataModel : IEquatable 6 | { 7 | public string? ServerName { get; set; } 8 | 9 | public string? DatabaseName { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Infrastructure/Models/PBICloud/PBICloudAuthenticationRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Models.PBICloud 2 | { 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | public class PBICloudAuthenticationRequest 7 | { 8 | [Required] 9 | [JsonPropertyName("userPrincipalName")] 10 | public string? UserPrincipalName { get; set; } 11 | 12 | [Required] 13 | [JsonPropertyName("environment")] 14 | public PBICloudEnvironment? Environment { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Infrastructure/Models/PBICloud/PBICloudAuthenticationResult.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Models.PBICloud 2 | { 3 | using Microsoft.Identity.Client; 4 | using Sqlbi.Bravo.Models; 5 | using System; 6 | using System.Diagnostics; 7 | 8 | [DebuggerDisplay($"{{{ nameof(GetDebuggerDisplay) }(),nq}}")] 9 | internal sealed class PBICloudAuthenticationResult : IAuthenticationResult 10 | { 11 | private readonly AuthenticationResult _authenticationResult; 12 | 13 | public PBICloudAuthenticationResult(AuthenticationResult authenticationResult) 14 | { 15 | _authenticationResult = authenticationResult; 16 | Account = new BravoAccount(authenticationResult); 17 | } 18 | 19 | public bool IsExpired => _authenticationResult.ExpiresOn < DateTimeOffset.UtcNow.AddMinutes(1); 20 | 21 | public string AccessToken => _authenticationResult.AccessToken; 22 | 23 | public IBravoAccount Account { get; private set; } 24 | 25 | #region IEquatable 26 | 27 | public override bool Equals(object? obj) 28 | { 29 | return Equals(obj as PBICloudAuthenticationResult); 30 | } 31 | 32 | public bool Equals(PBICloudAuthenticationResult? other) 33 | { 34 | return other != null && 35 | Account.Identifier == other.Account.Identifier; 36 | } 37 | 38 | public override int GetHashCode() 39 | { 40 | return HashCode.Combine(Account.Identifier); 41 | } 42 | 43 | #endregion 44 | 45 | private string GetDebuggerDisplay() 46 | { 47 | return _authenticationResult.Account.Username; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Infrastructure/Security/Policies/ADMX/en-US/sqlbi.adml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | SQLBI 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Infrastructure/Security/Policies/ADMX/sqlbi.admx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Infrastructure/Security/Policies/PolicyStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Security.Policies 2 | { 3 | public enum PolicyStatus 4 | { 5 | /// 6 | /// No policy has been enforced 7 | /// 8 | NotConfigured = 0, 9 | 10 | /// 11 | /// A policy has been applied for the property scope 12 | /// 13 | Forced = 1, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Infrastructure/Services/BestPracticeAnalyzer/Analyzer.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Services.BestPracticeAnalyzer 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using TabularEditor.TOMWrapper; 6 | 7 | internal class Analyzer 8 | { 9 | private readonly BestPracticeCollection _rules; 10 | private readonly Model _model; 11 | 12 | public Analyzer(Model model) 13 | : this(model, BestPracticeCollection.Empty) 14 | { 15 | } 16 | 17 | public Analyzer(Model model, BestPracticeCollection rules) 18 | { 19 | _model = model; 20 | _rules = rules; 21 | } 22 | 23 | public Model Model => _model; 24 | 25 | public BestPracticeCollection Rules => _rules; 26 | 27 | public IEnumerable Analyze() 28 | { 29 | var results = Analyze(Rules); 30 | return results; 31 | } 32 | 33 | public IEnumerable Analyze(BestPracticeRule rule) 34 | { 35 | var results = rule.Analyze(Model); 36 | return results; 37 | } 38 | 39 | public IEnumerable Analyze(IEnumerable rules) 40 | { 41 | var results = new List(); 42 | 43 | foreach (var rule in rules) 44 | { 45 | var ruleResults = rule.Analyze(Model); 46 | results.AddRange(ruleResults); 47 | } 48 | 49 | return results; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/Infrastructure/Services/BestPracticeAnalyzer/AnalyzerResult.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Services.BestPracticeAnalyzer 2 | { 3 | using TabularEditor.TOMWrapper; 4 | 5 | internal class AnalyzerResult 6 | { 7 | public bool RuleEnabled { get; set; } = true; 8 | 9 | public bool RuleHasError => RuleError is not null; 10 | 11 | public bool InvalidCompatibilityLevel { get; set; } = false; 12 | 13 | public string? RuleError { get; set; } 14 | 15 | public RuleScope RuleErrorScope { get; set; } 16 | 17 | public string? ObjectType => RuleHasError ? "Error" : Object?.GetTypeName(); 18 | 19 | public string? ObjectName 20 | { 21 | get 22 | { 23 | if (Object == null) 24 | return string.Empty; 25 | 26 | if (RuleHasError) 27 | return RuleError; 28 | 29 | if (Object is KPI kpi) 30 | return kpi.Measure.DaxObjectFullName + ".KPI"; 31 | 32 | return (Object as IDaxObject)?.DaxObjectFullName ?? Object.Name; 33 | } 34 | } 35 | 36 | public string? RuleName => Rule?.Name; 37 | 38 | public ITabularNamedObject? Object { get; set; } 39 | 40 | public BestPracticeRule? Rule { get; set; } 41 | 42 | public bool CanFix => Rule?.FixExpression != null; 43 | 44 | /// 45 | /// Indicates whether this rule should be ignored on this particular object 46 | /// 47 | public static bool Ignored 48 | { 49 | get 50 | { 51 | //var obj = Object as IAnnotationObject; 52 | //if (obj != null) 53 | //{ 54 | // var air = new AnalyzerIgnoreRules(obj); 55 | // return air.RuleIDs.Contains(Rule.ID); 56 | //} 57 | return false; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Infrastructure/Services/BestPracticeAnalyzer/IRuleDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Services.BestPracticeAnalyzer 2 | { 3 | using System.Collections.Generic; 4 | 5 | internal interface IRuleDefinition 6 | { 7 | bool Internal { get; } 8 | 9 | string? Name { get; } 10 | 11 | IEnumerable Rules { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Infrastructure/Services/BestPracticeAnalyzer/RuleScopeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Services.BestPracticeAnalyzer 2 | { 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using System; 6 | using System.Linq; 7 | 8 | public class RuleScopeConverter : StringEnumConverter 9 | { 10 | public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) 11 | { 12 | if (reader.ValueType == typeof(string) && objectType == typeof(RuleScope) && reader.Value is string readerValue) 13 | { 14 | var types = readerValue.Split(',').ToList(); 15 | 16 | // For backwards compatibility with rules created when "Column" existed as a RuleScope: 17 | if (types.Contains("Column")) 18 | { 19 | types.Remove("Column"); 20 | types.Add("DataColumn"); 21 | types.Add("CalculatedColumn"); 22 | types.Add("CalculatedTableColumn"); 23 | } 24 | 25 | // For backwards compatibility with rules created when "DataSource" existed as a RuleScope: 26 | if (types.Contains("DataSource")) 27 | { 28 | types.Remove("DataSource"); 29 | types.Add("ProviderDataSource"); 30 | } 31 | 32 | return types.Select(RuleScopeExtensions.GetScope).Combine(); 33 | } 34 | 35 | return base.ReadJson(reader, objectType, existingValue, serializer); 36 | } 37 | 38 | public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) 39 | { 40 | base.WriteJson(writer, value, serializer); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Infrastructure/Services/ExportData/ExportDataJobMap.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Services.ExportData 2 | { 3 | using Sqlbi.Bravo.Infrastructure.Models; 4 | using Sqlbi.Bravo.Models.ExportData; 5 | using System.Collections.Concurrent; 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | internal class ExportDataJobMap where T : class, IDataModel 9 | { 10 | private readonly ConcurrentDictionary _jobs; 11 | 12 | public ExportDataJobMap() 13 | { 14 | _jobs = new ConcurrentDictionary(); 15 | } 16 | 17 | public ExportDataJob AddNew(T datamodel, ExportDataSettings settings) 18 | { 19 | var job = ExportDataJob.CreateFrom(settings); 20 | 21 | var jobAdded = _jobs.TryAdd(datamodel, job); 22 | 23 | // Each IPBIDataModel is not allowed to start more than a single export job at a time 24 | BravoUnexpectedException.Assert(jobAdded); 25 | 26 | job.SetRunning(); 27 | 28 | return job; 29 | } 30 | 31 | public bool TryGet(T datamodel, [MaybeNullWhen(false)] out ExportDataJob job) 32 | { 33 | return _jobs.TryGetValue(datamodel, out job); 34 | } 35 | 36 | public void Remove(T datamodel) 37 | { 38 | var jobRemoved = _jobs.TryRemove(datamodel, out _); 39 | 40 | BravoUnexpectedException.Assert(jobRemoved); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/Interop/COLORREF.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Windows.Interop 2 | { 3 | using System.Drawing; 4 | using System.Runtime.InteropServices; 5 | 6 | [StructLayout(LayoutKind.Explicit, Size = 4)] 7 | internal struct COLORREF 8 | { 9 | [FieldOffset(0)] 10 | private readonly uint Value; 11 | 12 | [FieldOffset(0)] 13 | public byte R; 14 | 15 | [FieldOffset(1)] 16 | public byte G; 17 | 18 | [FieldOffset(2)] 19 | public byte B; 20 | 21 | //private const uint CLR_NONE = uint.MaxValue; 22 | 23 | //private const uint CLR_DEFAULT = 4278190080u; 24 | 25 | //public static COLORREF None = new(uint.MaxValue); 26 | 27 | //public static COLORREF Default = new(4278190080u); 28 | 29 | public COLORREF(byte r, byte g, byte b) 30 | { 31 | Value = 0u; 32 | R = r; 33 | G = g; 34 | B = b; 35 | } 36 | 37 | public COLORREF(uint value) 38 | { 39 | R = 0; 40 | G = 0; 41 | B = 0; 42 | Value = (value & 0xFFFFFF); 43 | } 44 | 45 | public COLORREF(Color color) 46 | : this(color.R, color.G, color.B) 47 | { 48 | if (color == Color.Transparent) 49 | { 50 | Value = uint.MaxValue; 51 | } 52 | } 53 | 54 | public static implicit operator Color(COLORREF colorRef) 55 | { 56 | if (colorRef.Value != uint.MaxValue) 57 | { 58 | return Color.FromArgb(colorRef.R, colorRef.G, colorRef.B); 59 | } 60 | 61 | return Color.Transparent; 62 | } 63 | 64 | public static implicit operator COLORREF(Color color) 65 | { 66 | return new COLORREF(color); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/Interop/Comctl32.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Windows.Interop 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | internal static class Comctl32 7 | { 8 | public delegate IntPtr SUBCLASSPROC(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, IntPtr dwRefData); 9 | 10 | [DllImport(ExternDll.Comctl32, CharSet = CharSet.Auto, SetLastError = true)] 11 | public static extern bool GetWindowSubclass(IntPtr hWnd, SUBCLASSPROC pfnSubclass, IntPtr uIdSubclass, ref IntPtr dwRefData); 12 | 13 | [DllImport(ExternDll.Comctl32, CharSet = CharSet.Auto, SetLastError = true)] 14 | public static extern bool SetWindowSubclass(IntPtr hWnd, SUBCLASSPROC pfnSubclass, IntPtr uIdSubclass, IntPtr dwRefData); 15 | 16 | [DllImport(ExternDll.Comctl32, CharSet = CharSet.Auto, SetLastError = true)] 17 | public static extern bool RemoveWindowSubclass(IntPtr hWnd, SUBCLASSPROC pfnSubclass, IntPtr uIdSubclass); 18 | 19 | [DllImport(ExternDll.Comctl32, CharSet = CharSet.Auto, SetLastError = true)] 20 | public static extern IntPtr DefSubclassProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/Interop/Dwmapi.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Windows.Interop 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | internal static class Dwmapi 7 | { 8 | public enum DWMWINDOWATTRIBUTE 9 | { 10 | DWMWA_NCRENDERING_ENABLED = 1, 11 | DWMWA_NCRENDERING_POLICY, 12 | DWMWA_TRANSITIONS_FORCEDISABLED, 13 | DWMWA_ALLOW_NCPAINT, 14 | DWMWA_CAPTION_BUTTON_BOUNDS, 15 | DWMWA_NONCLIENT_RTL_LAYOUT, 16 | DWMWA_FORCE_ICONIC_REPRESENTATION, 17 | DWMWA_FLIP3D_POLICY, 18 | DWMWA_EXTENDED_FRAME_BOUNDS, 19 | DWMWA_HAS_ICONIC_BITMAP, 20 | DWMWA_DISALLOW_PEEK, 21 | DWMWA_EXCLUDED_FROM_PEEK, 22 | DWMWA_CLOAK, 23 | DWMWA_CLOAKED, 24 | DWMWA_FREEZE_REPRESENTATION, 25 | DWMWA_PASSIVE_UPDATE_MODE, 26 | DWMWA_USE_HOSTBACKDROPBRUSH, 27 | DWMWA_USE_IMMERSIVE_DARK_MODE, 28 | DWMWA_WINDOW_CORNER_PREFERENCE, 29 | DWMWA_BORDER_COLOR, 30 | DWMWA_CAPTION_COLOR, 31 | DWMWA_TEXT_COLOR, 32 | DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, 33 | DWMWA_LAST 34 | } 35 | 36 | [DllImport(ExternDll.Dwmapi, ExactSpelling = true)] 37 | public static extern int DwmSetWindowAttribute(IntPtr hWnd, DWMWINDOWATTRIBUTE dwAttribute, [In] IntPtr pvAttribute, int cbAttribute); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/Interop/HRESULT.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Windows.Interop 2 | { 3 | /// 4 | /// https://github.com/dahall/Vanara/blob/master/PInvoke/Shared/WinError/HRESULT.Values.cs 5 | /// https://www.magnumdb.com/ 6 | /// 7 | internal class HRESULT 8 | { 9 | public const int S_OK = 0; 10 | 11 | //public const int ERROR_FILE_NOT_FOUND = -2147024894; 12 | 13 | public const int ERROR_INVALID_DATA = unchecked((int)0x8007000D); 14 | 15 | public const int E_NOINTERFACE = unchecked((int)0x80004002); 16 | 17 | public const int NTE_BAD_KEY_STATE = unchecked((int)0x8009000B); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/Interop/Kernel32.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Windows.Interop 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | internal static class Kernel32 8 | { 9 | public delegate IntPtr SUBCLASSPROC(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam, IntPtr id, IntPtr data); 10 | 11 | [DllImport(ExternDll.Kernel32, SetLastError = true)] 12 | public static extern int GetCurrentProcessId(); 13 | 14 | [DllImport(ExternDll.Kernel32, SetLastError = true)] 15 | public static extern int GetCurrentThreadId(); 16 | 17 | [DllImport(ExternDll.Kernel32, CharSet = CharSet.Unicode, SetLastError = true)] 18 | public static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/Interop/Ntdll.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Windows.Interop 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | internal static class Ntdll 7 | { 8 | internal enum NTSTATUS 9 | { 10 | STATUS_SUCCESS = 0 11 | } 12 | 13 | internal enum PROCESSINFOCLASS 14 | { 15 | ProcessBasicInformation = 0, 16 | //ProcessDebugPort = 7, 17 | //ProcessWow64Information = 26, 18 | //ProcessImageFileName = 27, 19 | //ProcessBreakOnTermination = 29, 20 | //ProcessSubsystemInformation = 75 21 | } 22 | 23 | internal struct PROCESS_BASIC_INFORMATION 24 | { 25 | public uint ExitStatus; 26 | 27 | public IntPtr PebBaseAddress; 28 | 29 | public UIntPtr AffinityMask; 30 | 31 | public int BasePriority; 32 | 33 | public UIntPtr UniqueProcessId; 34 | 35 | public UIntPtr InheritedFromUniqueProcessId; 36 | } 37 | 38 | [DllImport(ExternDll.Ntdll, ExactSpelling = true, SetLastError = true)] 39 | internal static extern int NtQueryInformationProcess(IntPtr processHandle, PROCESSINFOCLASS processInformationClass, out PROCESS_BASIC_INFORMATION processInformation, uint processInformationLength, out int returnLength); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/Interop/Ole32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Sqlbi.Bravo.Infrastructure.Windows.Interop 5 | { 6 | internal static class Ole32 7 | { 8 | [DllImport("ole32.dll", ExactSpelling = true)] 9 | public static extern HRESULT RevokeDragDrop(IntPtr hWnd); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/Interop/Win32Constant.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Windows.Interop 2 | { 3 | internal static class Win32Constant 4 | { 5 | public const int MAX_PATH = 260; 6 | 7 | public const int INFOTIPSIZE = 1024; 8 | 9 | public const int TRUE = 1; 10 | 11 | public const int FALSE = 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/Win32WindowWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace Sqlbi.Bravo.Infrastructure.Windows 5 | { 6 | internal class Win32WindowWrapper : IWin32Window 7 | { 8 | private Win32WindowWrapper(IntPtr handle) 9 | { 10 | Handle = handle; 11 | } 12 | 13 | public IntPtr Handle { get; private set; } 14 | 15 | public static Win32WindowWrapper CreateFrom(IntPtr handle) => new(handle); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Infrastructure/Windows/WindowSubclass.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Infrastructure.Windows 2 | { 3 | using Sqlbi.Bravo.Infrastructure.Windows.Interop; 4 | using System; 5 | 6 | /// 7 | /// Installs a window subclass callback to hook messages sent to the specified window 8 | /// 9 | internal abstract class WindowSubclass 10 | { 11 | private readonly Comctl32.SUBCLASSPROC _subclassProc; 12 | private readonly IntPtr _hWnd; 13 | 14 | public WindowSubclass(IntPtr hWnd) 15 | { 16 | _hWnd = hWnd; 17 | 18 | _subclassProc = SubclassProc; 19 | _ = Comctl32.SetWindowSubclass(hWnd, _subclassProc, IntPtr.Zero, IntPtr.Zero); 20 | } 21 | 22 | private IntPtr SubclassProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, IntPtr dwRefData) 23 | { 24 | return WndProc(hWnd, uMsg, wParam, lParam, uIdSubclass, dwRefData); 25 | } 26 | 27 | protected virtual IntPtr WndProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, IntPtr dwRefData) 28 | { 29 | if (uMsg == (uint)WindowMessage.WM_NCDESTROY) 30 | { 31 | // The subclass must be removed before the window being subclassed is destroyed 32 | // This is a permanent subclass so can call RemoveWindowSubclass inside the subclass procedure itself 33 | _ = Comctl32.RemoveWindowSubclass(_hWnd, _subclassProc, IntPtr.Zero); 34 | } 35 | 36 | return Comctl32.DefSubclassProc(hWnd, uMsg, wParam, lParam); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Models/AnalyzeModel/ExportVpaxMode.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.AnalyzeModel; 2 | 3 | public enum ExportVpaxMode 4 | { 5 | Default = 0, 6 | Obfuscate = 1, 7 | ObfuscateIncremental = 2 8 | } 9 | 10 | internal static class ExportVpaxModeExtensions 11 | { 12 | public static bool IsObfuscate(this ExportVpaxMode mode) 13 | { 14 | return mode == ExportVpaxMode.Obfuscate || mode == ExportVpaxMode.ObfuscateIncremental; 15 | } 16 | } -------------------------------------------------------------------------------- /src/Models/AnalyzeModel/TabularColumn.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.AnalyzeModel 2 | { 3 | using Dax.ViewModel; 4 | using Sqlbi.Bravo.Infrastructure.Extensions; 5 | using System.Diagnostics; 6 | using System.Text.Json.Serialization; 7 | using TOM = Microsoft.AnalysisServices.Tabular; 8 | 9 | [DebuggerDisplay("'{TableName}'[{Name}]")] 10 | public class TabularColumn 11 | { 12 | [JsonPropertyName("name")] 13 | public string? FullName => $"'{ TableName }'[{ Name }]"; 14 | 15 | [JsonPropertyName("columnName")] 16 | public string? Name { get; set; } 17 | 18 | [JsonPropertyName("tableName")] 19 | public string? TableName { get; set; } 20 | 21 | [JsonPropertyName("columnCardinality")] 22 | public long Cardinality { get; set; } 23 | 24 | [JsonPropertyName("size")] 25 | public long Size { get; set; } 26 | 27 | [JsonPropertyName("weight")] 28 | public double Weight { get; set; } 29 | 30 | [JsonPropertyName("isReferenced")] 31 | public bool IsReferenced { get; set; } 32 | 33 | [JsonPropertyName("dataType")] 34 | public string? DataType { get; set; } 35 | 36 | [JsonPropertyName("isHidden")] 37 | public bool IsHidden { get; set; } 38 | 39 | [JsonPropertyName("isQueryable")] 40 | public bool? IsQueryable { get; set; } 41 | 42 | internal static TabularColumn CreateFrom(VpaColumn vpaColumn, long databaseSize) 43 | { 44 | var column = new TabularColumn 45 | { 46 | Name = vpaColumn.ColumnName, 47 | TableName = vpaColumn.Table.TableName, 48 | Cardinality = vpaColumn.ColumnCardinality, 49 | Size = vpaColumn.TotalSize, 50 | Weight = (double)vpaColumn.TotalSize / databaseSize, 51 | IsReferenced = vpaColumn.IsReferenced, 52 | DataType = vpaColumn.DataType, 53 | IsHidden = vpaColumn.IsHidden, 54 | IsQueryable = vpaColumn.State.TryParseTo()?.IsQueryable(), 55 | }; 56 | 57 | return column; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Models/BravoAccount.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models 2 | { 3 | using Microsoft.Identity.Client; 4 | using System.Text.Json.Serialization; 5 | 6 | public interface IBravoAccount 7 | { 8 | string Identifier { get; set; } 9 | 10 | string UserPrincipalName { get; set; } 11 | 12 | string Username { get; set; } 13 | } 14 | 15 | internal sealed class BravoAccount : IBravoAccount 16 | { 17 | /// 18 | /// Unique identifier for the account 19 | /// 20 | [JsonPropertyName("id")] 21 | public string Identifier { get; set; } 22 | 23 | /// 24 | /// User name in UserPrincipalName (UPN) format - e.g. john.doe@contoso.com 25 | /// 26 | [JsonPropertyName("userPrincipalName")] 27 | public string UserPrincipalName { get; set; } 28 | 29 | /// 30 | /// Displayable user name (not guaranteed to be unique, it is mutable) 31 | /// 32 | [JsonPropertyName("username")] 33 | public string Username { get; set; } 34 | 35 | public BravoAccount(AuthenticationResult authenticationResult) 36 | { 37 | Identifier = authenticationResult.Account.HomeAccountId.Identifier; 38 | UserPrincipalName = authenticationResult.Account.Username; 39 | Username = authenticationResult.ClaimsPrincipal.FindFirst((claim) => claim.Type == "name")?.Value ?? ""; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Models/BravoUpdate.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models 2 | { 3 | using Sqlbi.Bravo.Infrastructure.Configuration.Settings; 4 | using System.Text.Json.Serialization; 5 | 6 | public interface IUpdateInfo 7 | { 8 | UpdateChannelType? UpdateChannel { get; set; } 9 | 10 | bool IsNewerVersion { get; set; } 11 | 12 | string? CurrentVersion { get; set; } 13 | 14 | string? InstalledVersion { get; set; } 15 | 16 | string? DownloadUrl { get; set; } 17 | 18 | string? ChangelogUrl { get; set; } 19 | } 20 | 21 | public class BravoUpdate : IUpdateInfo 22 | { 23 | [JsonPropertyName("updateChannel")] 24 | public UpdateChannelType? UpdateChannel { get; set; } 25 | 26 | [JsonPropertyName("isNewerVersion")] 27 | public bool IsNewerVersion { get; set; } = false; 28 | 29 | [JsonPropertyName("currentVersion")] 30 | public string? CurrentVersion { get; set; } 31 | 32 | [JsonPropertyName("installedVersion")] 33 | public string? InstalledVersion { get; set; } 34 | 35 | [JsonPropertyName("downloadUrl")] 36 | public string? DownloadUrl { get; set; } 37 | 38 | [JsonPropertyName("changelogUrl")] 39 | public string? ChangelogUrl { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Models/DiagnosticMessage.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models 2 | { 3 | using System; 4 | using System.Text.Json.Serialization; 5 | 6 | public class DiagnosticMessage 7 | { 8 | [JsonPropertyName("type")] 9 | public DiagnosticMessageType Type { get; set; } = DiagnosticMessageType.Text; 10 | 11 | [JsonPropertyName("severity")] 12 | public DiagnosticMessageSeverity Severity { get; set; } = DiagnosticMessageSeverity.None; 13 | 14 | [JsonPropertyName("name")] 15 | public string? Name { get; set; } 16 | 17 | [JsonPropertyName("content")] 18 | public string? Content { get; set; } 19 | 20 | [JsonPropertyName("timestamp")] 21 | public DateTime Timestamp { get; set; } = DateTime.UtcNow; 22 | 23 | [JsonIgnore] 24 | public DateTime? ReadTimestamp { get; set; } 25 | 26 | internal static DiagnosticMessage Create(DiagnosticMessageType type, DiagnosticMessageSeverity severity, string name, string content) 27 | { 28 | var message = new DiagnosticMessage 29 | { 30 | Type = type, 31 | Severity = severity, 32 | Name = $"[HOST] { name }", 33 | Content = content, 34 | ReadTimestamp = null 35 | }; 36 | 37 | return message; 38 | } 39 | } 40 | 41 | public enum DiagnosticMessageType 42 | { 43 | Text = 0, 44 | Json = 1, 45 | } 46 | 47 | public enum DiagnosticMessageSeverity 48 | { 49 | None = 0, 50 | Warning = 1, 51 | Error = 2, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Models/ExportData/ExportDataEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.ExportData 2 | { 3 | using System.Collections.Generic; 4 | using System.Text.Json.Serialization; 5 | 6 | public abstract class ExportDataEntity 7 | { 8 | [JsonPropertyName("status")] 9 | public ExportDataStatus Status { get; set; } 10 | 11 | public void SetRunning() => Status = ExportDataStatus.Running; 12 | 13 | public void SetCompleted() => Status = ExportDataStatus.Completed; 14 | 15 | } 16 | 17 | public class ExportDataJob : ExportDataEntity 18 | { 19 | [JsonPropertyName("path")] 20 | public string? Path { get; set; } 21 | 22 | [JsonPropertyName("tables")] 23 | public HashSet Tables { get; set; } = new(); 24 | 25 | public void SetCanceled() => Status = ExportDataStatus.Canceled; 26 | 27 | public void SetFailed() => Status = ExportDataStatus.Failed; 28 | 29 | public static ExportDataJob CreateFrom(ExportDataSettings settings) 30 | { 31 | var job = new ExportDataJob 32 | { 33 | Path = settings.ExportPath 34 | }; 35 | 36 | return job; 37 | } 38 | } 39 | 40 | public class ExportDataTable : ExportDataEntity 41 | { 42 | [JsonPropertyName("name")] 43 | public string? Name { get; set; } 44 | 45 | [JsonPropertyName("rows")] 46 | public int Rows { get; set; } = 0; 47 | 48 | [JsonPropertyName("columns")] 49 | public int Columns { get; set; } = 0; 50 | 51 | public void SetTruncated() => Status = ExportDataStatus.Truncated; 52 | } 53 | 54 | internal static class ExportDataJobExtensions 55 | { 56 | public static ExportDataTable AddNew(this ExportDataJob job, string name) 57 | { 58 | var table = new ExportDataTable 59 | { 60 | Name = name, 61 | }; 62 | 63 | job.Tables.Add(table); 64 | table.SetRunning(); 65 | 66 | return table; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Models/ExportData/ExportDataStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.ExportData 2 | { 3 | public enum ExportDataStatus 4 | { 5 | Unknown = 0, 6 | 7 | /// 8 | /// Data export is running. Applies to and 9 | /// 10 | Running = 1, 11 | 12 | /// 13 | /// Data export is completed. Applies to and 14 | /// 15 | Completed = 2, 16 | 17 | /// 18 | /// Data export was canceled. Only applies to 19 | /// 20 | Canceled = 3, 21 | 22 | /// 23 | /// Data export was failed due to an error. Only applies to 24 | /// 25 | Failed = 4, 26 | 27 | /// 28 | /// Data export was interrupted due to reaching the limit allowed by the data destination. Only applies 29 | /// 30 | /// Excel cannot exceed the limit of 1,048,576 rows 31 | Truncated = 5, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Models/ExportData/ExportDelimitedTextRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.ExportData 2 | { 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | public class ExportDelimitedTextRequest 7 | { 8 | [Required] 9 | [JsonPropertyName("settings")] 10 | public ExportDelimitedTextSettings? Settings { get; set; } 11 | } 12 | 13 | public class ExportDelimitedTextFromPBIReportRequest : ExportDelimitedTextRequest 14 | { 15 | [Required] 16 | [JsonPropertyName("report")] 17 | public PBIDesktopReport? Report { get; set; } 18 | } 19 | 20 | public class ExportDelimitedTextFromPBICloudDatasetRequest : ExportDelimitedTextRequest 21 | { 22 | [Required] 23 | [JsonPropertyName("dataset")] 24 | public PBICloudDataset? Dataset { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Models/ExportData/ExportExcelRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.ExportData 2 | { 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | public abstract class ExportExcelRequest 7 | { 8 | [Required] 9 | [JsonPropertyName("settings")] 10 | public ExportExcelSettings? Settings { get; set; } 11 | } 12 | 13 | public class ExportExcelFromPBIReportRequest : ExportExcelRequest 14 | { 15 | [Required] 16 | [JsonPropertyName("report")] 17 | public PBIDesktopReport? Report { get; set; } 18 | } 19 | 20 | public class ExportExcelFromPBICloudDatasetRequest : ExportExcelRequest 21 | { 22 | [Required] 23 | [JsonPropertyName("dataset")] 24 | public PBICloudDataset? Dataset { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Models/FormatDax/DatabaseUpdateResult.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.FormatDax 2 | { 3 | using System.Text.Json.Serialization; 4 | 5 | // TODO: rename to 'UpdateResponse' 6 | public class DatabaseUpdateResult 7 | { 8 | /// 9 | /// The unique identifier of the current version of the tabular model computed after the update 10 | /// 11 | [JsonPropertyName("etag")] 12 | public string? DatabaseETag { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Models/FormatDax/FormatDaxResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.FormatDax 2 | { 3 | using Dax.Formatter.Models; 4 | using Sqlbi.Bravo.Infrastructure; 5 | using System.Collections.Generic; 6 | using System.Text.Json.Serialization; 7 | 8 | public class FormatDaxResponse : List 9 | { 10 | // TODO: do not inherit from Generic.List, add instead a 'Measures' property 11 | //[JsonPropertyName("measures")] 12 | //public IEnumerable? Measures { get; set; } 13 | } 14 | 15 | public class FormattedMeasure 16 | { 17 | [JsonPropertyName("etag")] 18 | public string? ETag { get; set; } 19 | 20 | [JsonPropertyName("name")] 21 | public string? Name { get; set; } 22 | 23 | [JsonPropertyName("tableName")] 24 | public string? TableName { get; set; } 25 | 26 | [JsonPropertyName("expression")] 27 | public string? Expression { get; set; } 28 | 29 | [JsonPropertyName("lineBreakStyle")] 30 | public DaxLineBreakStyle LineBreakStyle { get; set; } = AppEnvironment.FormatDaxLineBreakDefault; 31 | 32 | [JsonPropertyName("errors")] 33 | public IEnumerable? Errors { get; set; } 34 | } 35 | 36 | public class FormatterError 37 | { 38 | [JsonPropertyName("line")] 39 | public int? Line { get; set; } 40 | 41 | [JsonPropertyName("column")] 42 | public int? Column { get; set; } 43 | 44 | [JsonPropertyName("message")] 45 | public string? Message { get; set; } 46 | 47 | public static FormatterError CreateFrom(DaxFormatterError error) 48 | { 49 | return new FormatterError 50 | { 51 | Line = error.Line, 52 | Column = error.Column, 53 | Message = error.Message 54 | }; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Models/FormatDax/UpdatePBICloudDatasetRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.FormatDax 2 | { 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Text.Json.Serialization; 6 | 7 | public class UpdatePBICloudDatasetRequest 8 | { 9 | [Required] 10 | [JsonPropertyName("dataset")] 11 | public PBICloudDataset? Dataset { get; set; } 12 | 13 | [Required] 14 | [JsonPropertyName("measures")] 15 | public IEnumerable? Measures { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Models/FormatDax/UpdatePBIDesktopReportRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.FormatDax 2 | { 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Text.Json.Serialization; 6 | 7 | public class UpdatePBIDesktopReportRequest 8 | { 9 | [Required] 10 | [JsonPropertyName("report")] 11 | public PBIDesktopReport? Report { get; set; } 12 | 13 | [Required] 14 | [JsonPropertyName("measures")] 15 | public IEnumerable? Measures { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Models/ManageDates/ApplyConfigurationRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.ManageDates 2 | { 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | public class ApplyConfigurationRequest 7 | { 8 | [Required] 9 | [JsonPropertyName("report")] 10 | public PBIDesktopReport? Report { get; set; } 11 | 12 | [Required] 13 | [JsonPropertyName("configuration")] 14 | public DateConfiguration? Configuration { get; set; } 15 | } 16 | 17 | public class ValidateConfigurationRequest : ApplyConfigurationRequest 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Models/ManageDates/CustomPackage.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.ManageDates 2 | { 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Diagnostics; 5 | using System.Text.Json.Serialization; 6 | 7 | public class CustomPackage 8 | { 9 | [Required] 10 | [JsonPropertyName("type")] 11 | public CustomPackageType? Type { get; set; } 12 | 13 | [JsonPropertyName("path")] 14 | public string? Path { get; set; } 15 | 16 | [JsonPropertyName("name")] 17 | public string? Name { get; set; } 18 | 19 | [JsonPropertyName("description")] 20 | public string? Description { get; set; } 21 | 22 | [JsonPropertyName("workspacePath")] 23 | public string? WorkspacePath { get; set; } 24 | 25 | [JsonPropertyName("workspaceName")] 26 | public string? WorkspaceName { get; set; } 27 | 28 | [JsonPropertyName("hasWorkspace")] 29 | public bool HasWorkspace { get; set; } 30 | 31 | [JsonPropertyName("hasPackage")] 32 | public bool HasPackage { get; set; } 33 | } 34 | 35 | public enum CustomPackageType 36 | { 37 | /// 38 | /// Custom template package from the user's local repository 39 | /// 40 | User = 0, 41 | 42 | /// 43 | /// Custom template package from the organization's shared repository 44 | /// 45 | Organization = 1, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Models/ManageDates/PreviewChangesRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.ManageDates 2 | { 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | public class PreviewChangesRequest 7 | { 8 | [Required] 9 | [JsonPropertyName("report")] 10 | public PBIDesktopReport? Report { get; set; } 11 | 12 | [Required] 13 | [JsonPropertyName("settings")] 14 | public PreviewChangesSettings? Settings { get; set; } 15 | } 16 | 17 | public class PreviewChangesSettings 18 | { 19 | /// 20 | /// to be applied 21 | /// 22 | [Required] 23 | [JsonPropertyName("configuration")] 24 | public DateConfiguration? Configuration { get; set; } 25 | 26 | /// 27 | /// Number of records generated as a preview of requested changes 28 | /// 29 | [Required] 30 | [JsonPropertyName("previewRows")] 31 | public int PreviewRows { get; set; } = 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Models/TemplateDevelopment/CreateWorkspaceRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.TemplateDevelopment 2 | { 3 | using Sqlbi.Bravo.Models.ManageDates; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Text.Json.Serialization; 6 | 7 | public class CreateWorkspaceRequest 8 | { 9 | [Required] 10 | [JsonPropertyName("name")] 11 | public string? Name { get; set; } 12 | 13 | 14 | [Required] 15 | [JsonPropertyName("configuration")] 16 | public DateConfiguration? Configuration { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Models/TemplateDevelopment/WorkspacePreviewChangesRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Models.TemplateDevelopment 2 | { 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | public class WorkspacePreviewChangesRequest 7 | { 8 | [Required] 9 | [JsonPropertyName("report")] 10 | public PBIDesktopReport? Report { get; set; } 11 | 12 | [Required] 13 | [JsonPropertyName("settings")] 14 | public WorkspacePreviewChangesSettings? Settings { get; set; } 15 | } 16 | 17 | public class WorkspacePreviewChangesSettings 18 | { 19 | /// 20 | /// Full path of the custom package to be applied 21 | /// 22 | [Required] 23 | [JsonPropertyName("customPackagePath")] 24 | public string? CustomPackagePath { get; set; } 25 | 26 | /// 27 | /// Number of records generated as a preview of requested changes 28 | /// 29 | [Required] 30 | [JsonPropertyName("previewRows")] 31 | public int PreviewRows { get; set; } = 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo 2 | { 3 | using Microsoft.ApplicationInsights; 4 | using Microsoft.Extensions.Hosting; 5 | using Sqlbi.Bravo.Infrastructure; 6 | using Sqlbi.Bravo.Infrastructure.Configuration; 7 | using Sqlbi.Bravo.Infrastructure.Helpers; 8 | using System; 9 | using System.Windows.Forms; 10 | 11 | internal partial class Program 12 | { 13 | [STAThread] 14 | public static void Main() 15 | { 16 | try 17 | { 18 | StartupConfiguration.Configure(); 19 | 20 | using var instance = new AppInstance(); 21 | if (instance.IsOwned) 22 | { 23 | using var host = CreateHost(); 24 | host.Start(); 25 | { 26 | var window = new AppWindow(host, instance); 27 | Application.Run(window); 28 | } 29 | host.StopAsync().GetAwaiter().GetResult(); 30 | } 31 | else 32 | { 33 | instance.NotifyOwner(); 34 | } 35 | } 36 | catch (Exception ex) 37 | { 38 | TelemetryHelper.TrackException(ex); 39 | ExceptionHelper.ShowDialog(ex); 40 | throw; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Bravo": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/Scripts/@types/global.d.ts: -------------------------------------------------------------------------------- 1 | import * as CodeMirror from 'codemirror'; 2 | import { Options, PolicyStatus } from '../controllers/options'; 3 | import { TelemetryConfig } from '../controllers/telemetry'; 4 | import { Dic } from '../helpers/utils'; 5 | 6 | declare global { 7 | var CONFIG: { 8 | debug?: boolean, 9 | address: string 10 | version: string, 11 | build: string 12 | options: Options, 13 | policies?: Dic, 14 | token?: string, 15 | telemetry?: TelemetryConfig 16 | culture: { 17 | ietfLanguageTag: string 18 | } 19 | }; 20 | 21 | interface CodeMirrorElement extends HTMLElement { 22 | CodeMirror: CodeMirror.Editor 23 | } 24 | } -------------------------------------------------------------------------------- /src/Scripts/@types/split.js/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for Split.js 2 | // Definitions by: Daniele Perilli 3 | 4 | declare module 'split.js' { 5 | type Partial = { [P in keyof T]?: T[P] } 6 | type CSSStyleDeclarationPartial = Partial 7 | export interface SplitOptions { 8 | sizes?: number[] 9 | minSize?: number[] | number 10 | maxSize?: number | number[] 11 | expandToMin?: boolean 12 | gutterSize?: number 13 | gutterAlign?: string 14 | snapOffset?: number 15 | dragInterval?: number 16 | direction?: "horizontal" | "vertical"; 17 | cursor?: string; 18 | gutter?: (index: number, direction: "horizontal" | "vertical") => HTMLElement; 19 | elementStyle?: (dimension: "width" | "height", elementSize: number, gutterSize: number, index: number) => CSSStyleDeclarationPartial; 20 | gutterStyle?: (dimension: "width" | "height", gutterSize: number, index: number) => CSSStyleDeclarationPartial; 21 | onDrag?: (sizes: number[]) => void; 22 | onDragStart?: (sizes: number[]) => void; 23 | onDragEnd?: (sizes: number[]) => void; 24 | } 25 | 26 | export interface SplitObject { 27 | setSizes: (sizes: number[]) => void; 28 | getSizes: () => number[]; 29 | collapse: (index: number) => void; 30 | destroy: (preserveStyles?: boolean, preserveGutters?: boolean) => void; 31 | } 32 | 33 | function Split(elements: HTMLElement | string[], options?: SplitOptions): SplitObject; 34 | 35 | export default Split; 36 | } -------------------------------------------------------------------------------- /src/Scripts/controllers/cache.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { Dic } from '../helpers/utils'; 8 | 9 | export class CacheHelper{ 10 | 11 | storageName: string; 12 | 13 | constructor(storageName: string) { 14 | this.storageName = storageName; 15 | } 16 | 17 | getCache(): Dic { 18 | 19 | let cache = {}; 20 | const rawData = localStorage.getItem(this.storageName); 21 | if (rawData) { 22 | try { 23 | cache = JSON.parse(rawData); 24 | } catch(ignore){} 25 | } 26 | return cache; 27 | } 28 | 29 | saveCache(cache: Dic) { 30 | try { 31 | localStorage.setItem(this.storageName, JSON.stringify(cache)); 32 | } catch(ignore){} 33 | } 34 | 35 | setItem(id: string, item: T) { 36 | 37 | let cache = this.getCache(); 38 | cache[id] = item; 39 | this.saveCache(cache); 40 | } 41 | 42 | getItem(id: string): T { 43 | let cache = this.getCache(); 44 | if (id in cache) 45 | return cache[id]; 46 | 47 | return null; 48 | } 49 | 50 | removeItem(id: string) { 51 | let cache = this.getCache(); 52 | delete cache[id]; 53 | this.saveCache(cache); 54 | } 55 | 56 | 57 | } -------------------------------------------------------------------------------- /src/Scripts/controllers/pbi-desktop.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { deepEqual } from 'fast-equals'; 8 | import { Dispatchable } from '../helpers/dispatchable'; 9 | import { host } from '../main'; 10 | import { PBIDesktopReport, PBIDesktopReportConnectionMode } from '../model/pbi-report'; 11 | 12 | export class PBIDesktop extends Dispatchable { 13 | 14 | static CheckSeconds = 10 15 | checkTimeout: number; 16 | 17 | reports: PBIDesktopReport[] = []; 18 | 19 | constructor() { 20 | super(); 21 | 22 | this.checkTimeout = window.setInterval(() => { 23 | this.poll(); 24 | }, PBIDesktop.CheckSeconds * 1000); 25 | 26 | } 27 | 28 | poll() { 29 | 30 | if (this.solo) { 31 | this.reports = []; 32 | return; 33 | } 34 | 35 | const processReponse = (reports: PBIDesktopReport[]) => { 36 | 37 | // Sort reports - order is casually changed 38 | reports.sort((a, b) => a.id.toString().localeCompare(b.id.toString())); 39 | const changed = (!deepEqual(reports, this.reports)); 40 | 41 | this.reports = reports; 42 | this.trigger("poll", changed); 43 | }; 44 | 45 | return host.listReports() 46 | .then((reports: PBIDesktopReport[]) => { 47 | processReponse(reports.filter(report => report.connectionMode != PBIDesktopReportConnectionMode.UnsupportedProcessNotReady)); 48 | }) 49 | .catch(ignore => { 50 | processReponse([]); 51 | }); 52 | } 53 | 54 | destroy() { 55 | window.clearInterval(this.checkTimeout); 56 | super.destroy(); 57 | } 58 | } -------------------------------------------------------------------------------- /src/Scripts/css/analyze-model.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | .analyze-model { 3 | h1 { 4 | .icon-analyze-model; 5 | } 6 | .scene-content { 7 | 8 | .fcols { 9 | height: calc(100% - 100px); 10 | 11 | .coll { 12 | width: 70%; 13 | height: 100%; 14 | //max-width: 700px; 15 | float: left; 16 | position: relative; 17 | 18 | .flexbox(flex-start, flex-start, column); 19 | } 20 | .colr { 21 | width: calc(30% - 50px); 22 | padding: 0 0 0 50px; 23 | height: 100%; 24 | float: left; 25 | 26 | .treemap { 27 | position: relative; 28 | width: 100%; 29 | //max-height: 450px; 30 | height: calc(100% - 100px); 31 | } 32 | } 33 | } 34 | 35 | .table { 36 | width:100%; 37 | 38 | .icon-broken-link { 39 | &:before { 40 | font-size:22px !important; 41 | } 42 | } 43 | } 44 | 45 | .toolbar { 46 | width: 100%; 47 | .flexbox(center); 48 | .flex-wrap(wrap); 49 | 50 | margin: 0 0 30px 0; 51 | 52 | .search { 53 | margin: 5px 15px 5px 5px; 54 | min-width:150px; 55 | width: 30%; 56 | } 57 | 58 | .show-if-group { 59 | transition: opacity .3s; 60 | } 61 | 62 | .save-vpax { 63 | margin-left: auto; 64 | } 65 | 66 | } 67 | 68 | .warning-explanation { 69 | margin: 20px 0 0 0; 70 | padding: 0 20px 0 0; 71 | font-size: 15px; 72 | line-height: 1.4; 73 | color: @notice-color; 74 | 75 | .flexbox(); 76 | .icon{ 77 | 78 | &:before { 79 | font-size: 40px; 80 | margin-right: 10px; 81 | } 82 | } 83 | p { 84 | margin:0 85 | } 86 | 87 | .dark & { 88 | color: @notice-color-dark; 89 | } 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /src/Scripts/css/best-practices.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | .best-practices { 3 | h1 { 4 | .icon-best-practices; 5 | } 6 | } -------------------------------------------------------------------------------- /src/Scripts/css/dax-editor.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | .dax-editor { 3 | height: calc(100% - 12px); 4 | width: calc(100% - 2px); 5 | padding: 5px 0; 6 | font-size: 16px; 7 | overflow: hidden; 8 | position: relative; 9 | 10 | .border-radius; 11 | border: 1px solid @border-color; 12 | background: @input-back-color; 13 | 14 | .dark & { 15 | border-color: @border-color-dark; 16 | background-color: @input-back-color-dark; 17 | } 18 | 19 | .toolbar { 20 | margin: auto 0 0 0; 21 | padding: 2px 5px 0 5px; 22 | height: 34px; 23 | width: calc(100% - 10px); 24 | .flexbox(center); 25 | 26 | .toggle-whitespaces { 27 | margin-left: auto; 28 | } 29 | .zoom { 30 | font-size: 14px; 31 | } 32 | 33 | .ctrl:before { 34 | font-size: 22px; 35 | } 36 | } 37 | 38 | .cm { 39 | 40 | overflow: auto; 41 | height: calc(100% - 35px); 42 | width: calc(100% - 2px); 43 | 44 | .notice { 45 | width: 100%; 46 | height: 100%; 47 | text-align: center; 48 | .flexbox(center, center); 49 | 50 | .button { 51 | 52 | margin: 10px; 53 | } 54 | 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/Scripts/css/export-data.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | .export-data { 3 | h1 { 4 | .icon-export-data; 5 | } 6 | .scene-content { 7 | .flexbox(flex-start, stretch, column); 8 | 9 | .cols { 10 | height: 100%; 11 | width: 100%; 12 | .flex(0 1 auto); 13 | overflow: auto; 14 | 15 | .coll { 16 | width: 50%; 17 | margin-right: 30px; 18 | .flex(1 1 auto); 19 | .flexbox(flex-start, flex-start, column); 20 | } 21 | .colr { 22 | width: calc(50% - 60px); 23 | margin: 0 0 0 20px; 24 | 25 | } 26 | } 27 | 28 | .table { 29 | width:100%; 30 | } 31 | 32 | .toolbar { 33 | width: 100%; 34 | .flexbox(center); 35 | .flex-wrap(wrap); 36 | 37 | margin: 0 0 30px 0; 38 | 39 | .search { 40 | margin: 5px 15px 5px 5px; 41 | min-width:150px; 42 | width: 50%; 43 | } 44 | } 45 | 46 | .export-options { 47 | margin-top: 30px; 48 | 49 | .menu { 50 | margin-bottom: 20px; 51 | cursor: default !important; 52 | } 53 | } 54 | 55 | .scene-action { 56 | .flexbox(center, flex-end); 57 | width: 100%; 58 | padding-top: 20px; 59 | 60 | .do-export { 61 | //margin-top: 10px; 62 | margin-left: auto; 63 | } 64 | } 65 | } 66 | } 67 | 68 | .exported-data { 69 | 70 | .success-message { 71 | width: 100%; 72 | max-width: 100% !important; 73 | .flex-direction(row) !important; 74 | } 75 | 76 | .job-message { 77 | max-width: 300px; 78 | padding: 20px 60px; 79 | 80 | .open-path { 81 | font-size: 16px; 82 | } 83 | } 84 | .table { 85 | margin-top: 20px; 86 | 87 | .job-status { 88 | .icon-completed { 89 | //opacity: 0.1; 90 | color: @good-color; 91 | } 92 | } 93 | .row-error { 94 | .table-rows { 95 | color: @error-color; 96 | } 97 | 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/Scripts/css/fonts.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | 3 | @system-font:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif; 4 | @text-font: "Segoe UI Variable",@system-font; 5 | @code-font: Consolas,Menlo,Monaco,monospace; -------------------------------------------------------------------------------- /src/Scripts/css/json-formatter.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | 3 | .json-formatter-toggler { 4 | opacity: 1 !important; 5 | font-size: 1em !important; 6 | line-height: 1 !important; 7 | .icon; 8 | &:after { 9 | 10 | content: "\e908" !important // .icon-collapsed 11 | } 12 | } 13 | 14 | .dark { 15 | .json-formatter-row, 16 | .json-formatter-row a, 17 | .json-formatter-row a:hover { 18 | color: white; 19 | } 20 | 21 | .json-formatter-row .json-formatter-string, 22 | .json-formatter-row .json-formatter-stringifiable { 23 | color: #31F031; 24 | } 25 | .json-formatter-row .json-formatter-number { 26 | color: #66C2FF; 27 | } 28 | .json-formatter-row .json-formatter-boolean { 29 | color: #EC4242; 30 | } 31 | .json-formatter-row .json-formatter-null { 32 | color: #EEC97D; 33 | } 34 | .json-formatter-row .json-formatter-undefined { 35 | color: #ef8fbe; 36 | } 37 | .json-formatter-row .json-formatter-function { 38 | color: #FD48CB; 39 | } 40 | .json-formatter-row .json-formatter-date { 41 | background-color: rgba(255, 255, 255, 0.05); 42 | } 43 | .json-formatter-row .json-formatter-url { 44 | color: #027BFF; 45 | } 46 | .json-formatter-row .json-formatter-bracket { 47 | color: #9494FF; 48 | } 49 | .json-formatter-row .json-formatter-key { 50 | color: #23A0DB; 51 | } 52 | } -------------------------------------------------------------------------------- /src/Scripts/css/main.less: -------------------------------------------------------------------------------- 1 | // out: ../../css/main.css, sourceMap: false 2 | /*! 3 | * Bravo for Power BI 4 | * Copyright (c) SQLBI corp. - All rights reserved. 5 | * https://www.sqlbi.com 6 | */ 7 | 8 | // Normalize 9 | @import "normalize.less"; 10 | 11 | // Paths 12 | @images: "../images"; 13 | 14 | // Fonts 15 | @import "fonts.less"; 16 | 17 | // Variables 18 | @import "colors.less"; 19 | @import "media.less"; 20 | @import "images.less"; 21 | 22 | // Mixins 23 | @import "flex.less"; 24 | @import "mixins.less"; 25 | 26 | // Common 27 | @import "common.less"; 28 | @import "menu.less"; 29 | @import "multiview-pane.less"; 30 | @import "tabulator.less"; 31 | @import "codemirror.less"; 32 | @import "contextmenu.less"; 33 | @import "tabular-browser.less"; 34 | @import "json-formatter.less"; 35 | @import "dax-editor.less"; 36 | 37 | // UI 38 | @import "root.less"; 39 | @import "sidebar.less"; 40 | @import "tabs.less"; 41 | @import "notification-sidebar.less"; 42 | @import "dialog.less"; 43 | @import "diagnostic-pane.less"; 44 | 45 | // Scenes 46 | @import "sheet.less"; 47 | @import "welcome.less"; 48 | @import "analyze-model.less"; 49 | @import "dax-formatter.less"; 50 | @import "manage-dates.less"; 51 | @import "export-data.less"; 52 | @import "best-practices.less"; 53 | @import "unsupported.less"; -------------------------------------------------------------------------------- /src/Scripts/css/media.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | 3 | // Example: 4 | // .screen(@tablet, { width: 40px; }); 5 | // .hide-if(880px); 6 | 7 | .screen(@max-width, @rules) { 8 | @media only screen and (max-width: @max-width){ 9 | & { @rules(); } 10 | } 11 | } 12 | .hide-if(@max-width) { 13 | @media only screen and (max-width: @max-width){ 14 | & { display: none !important; } 15 | } 16 | } 17 | .show-if(@max-width) { 18 | @media only screen and (max-width: @max-width){ 19 | & { display: inherit !important; } 20 | } 21 | } 22 | 23 | // Predefined sizes 24 | @min-width: 375px; 25 | @max-width: 1140px; 26 | 27 | @desktop: 1440px; 28 | @hd: 1920px; 29 | @4k: 2560px; -------------------------------------------------------------------------------- /src/Scripts/css/menu.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | .menu { 3 | .flexbox(center); 4 | 5 | .item { 6 | .flexbox(center, flex-end, column); 7 | margin: 0 0 0 30px; 8 | opacity: .7; 9 | font-size: 14px; 10 | cursor: pointer !important; 11 | white-space: nowrap; 12 | 13 | &:first-child { 14 | margin-left: 0; 15 | } 16 | 17 | &.disabled { 18 | cursor: not-allowed !important; 19 | } 20 | 21 | .selector { 22 | .flex(0 0 auto); 23 | background: transparent; 24 | .border-radius; 25 | width: 95%; 26 | height: 4px; 27 | margin-top: 2px; 28 | transition: background .2s ease-in-out; 29 | } 30 | &.selected { 31 | opacity: 1; 32 | cursor: default !important; 33 | .selector { 34 | background: @accent-back-color; 35 | .dark & { 36 | background-color: @accent-back-color-dark; 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Scripts/css/multiview-pane.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | .multiview-pane { 3 | 4 | #multiview-menu { 5 | width: 100%; 6 | height: 100%; 7 | .flexbox(flex-start, stretch, column); 8 | 9 | .menu { 10 | width: 100%; 11 | 12 | .toolbar { 13 | width: auto; 14 | margin: 0 0 0 auto; 15 | } 16 | } 17 | 18 | .menu-body { 19 | position: relative; 20 | height: calc(100% - 65px); 21 | width: 100%; 22 | margin-top: 20px; 23 | .flexbox(flex-start, stretch, column); 24 | overflow: hidden; 25 | 26 | .item-body { 27 | height: 100%; 28 | width: 100%; 29 | font-size: 16px; 30 | overflow: hidden; 31 | position: relative; 32 | } 33 | 34 | } 35 | 36 | .gutter { 37 | background-color: @back-color; 38 | .dark & { 39 | background-color: @back-color-dark; 40 | } 41 | 42 | &.gutter-vertical { 43 | width: 100%; 44 | } 45 | &.gutter-horizontal { 46 | height: 100%; 47 | } 48 | } 49 | } 50 | 51 | /*&.layout-VerticalSplit, &.layout-HorizontalSplit { 52 | .menu-body { 53 | justify-content: space-between !important; 54 | } 55 | }*/ 56 | 57 | &.layout-VerticalSplit { 58 | .menu-body { 59 | flex-direction: row !important; 60 | } 61 | } 62 | 63 | &.layout-HorizontalSplit, &.layout-VerticalSplit { 64 | .menu { 65 | .item:nth-child(2) { 66 | margin-left: auto 67 | } 68 | .toolbar { 69 | margin-left: 20px !important; 70 | } 71 | } 72 | } 73 | 74 | /*&:not(.dragging) { 75 | .menu { 76 | .item, .toolbar { 77 | transition: all .2s ease-in-out; 78 | } 79 | } 80 | .menu-body { 81 | .item-body, .gutter { 82 | transition: all .2s ease-in-out; 83 | } 84 | 85 | } 86 | }*/ 87 | } -------------------------------------------------------------------------------- /src/Scripts/css/root.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | 3 | .root { 4 | .flexbox(stretch, flex-start, column); 5 | height: 100%; 6 | background: @window-back-color; 7 | 8 | .dark & { 9 | background-color: @window-back-color-dark; 10 | } 11 | } 12 | 13 | #main-pane { 14 | .flexbox(); 15 | height: 100%; 16 | } 17 | 18 | #bottom-pane { 19 | 20 | } 21 | 22 | .gutter { 23 | background-color: @gutter-back-color; 24 | background-repeat: no-repeat; 25 | background-position: center; 26 | 27 | .dark & { 28 | background-color: @gutter-back-color-dark; 29 | } 30 | 31 | &:hover { 32 | background-color: darken(@gutter-back-color, 5%); 33 | 34 | .dark & { 35 | background-color: darken(@gutter-back-color-dark, 3%); 36 | } 37 | } 38 | &.gutter-vertical { 39 | cursor: ns-resize; 40 | } 41 | 42 | &.gutter-horizontal { 43 | cursor: ew-resize; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Scripts/css/tabular-browser.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | .tabular-browser { 3 | height: 100%; 4 | width: 100%; 5 | 6 | .toolbar { 7 | margin-bottom: 20px; 8 | 9 | .search { 10 | margin-right: 15px; 11 | } 12 | } 13 | 14 | .tabulator { 15 | width: 100%; 16 | //overflow: auto; 17 | 18 | .tabulator-row { 19 | cursor: pointer; 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/Scripts/css/unsupported.less: -------------------------------------------------------------------------------- 1 | // main: main.less 2 | 3 | .unsupported-scene { 4 | .error { 5 | height: calc(100% - 40px); 6 | } 7 | } -------------------------------------------------------------------------------- /src/Scripts/helpers/dispatchable.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { Dic, Utils } from './utils'; 8 | 9 | interface QueueItem { 10 | event: string 11 | callback: any 12 | } 13 | export class Dispatchable { 14 | queue: Dic = {}; 15 | 16 | get solo(): boolean { 17 | return (Utils.Obj.isEmpty(this.queue)); 18 | } 19 | 20 | on(event: string | string[], callback: any, id?: string) { 21 | if (!id) id = Utils.Text.uuid(); 22 | 23 | let events = (Utils.Obj.isArray(event) ? event : [event]); 24 | events.forEach(_event => { 25 | this.queue[`${id}_${_event}`] = { event: _event, callback: callback }; 26 | }); 27 | } 28 | off(event: string, id: string) { 29 | delete this.queue[`${id}_${event}`]; 30 | } 31 | trigger(event: string, args = {}) { 32 | for (let id in this.queue) { 33 | if (this.queue[id].event == event) 34 | this.queue[id].callback(args); 35 | }; 36 | } 37 | destroy() { 38 | this.queue = {}; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Scripts/helpers/idle.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | export class Idle { 8 | 9 | static AwayTimeout = 15000; 10 | static CatchingEvents = ["click", "mousemove", "mousedown", "keydown", "scroll", "mousewheel", "touchmove", "touchstart"]; 11 | 12 | awayTimer: number; 13 | isAway: boolean; 14 | startTime: number; 15 | 16 | _time: number; 17 | get time(): number { 18 | this.updateIdleTime(); 19 | return this._time; 20 | } 21 | 22 | constructor() { 23 | this._time = 0; 24 | this.isAway = true; 25 | 26 | Idle.CatchingEvents.forEach(event => { 27 | document.addEventListener(event, ()=>this.mouseListener()); 28 | }); 29 | document.addEventListener("visibilitychange", ()=>this.visibilityListener()); 30 | } 31 | 32 | reset() { 33 | this._time = 0; 34 | this.updateStartTime(); 35 | } 36 | 37 | destroy() { 38 | Idle.CatchingEvents.forEach(event => { 39 | document.removeEventListener(event, ()=>this.mouseListener()); 40 | }); 41 | document.removeEventListener("visibilitychange", ()=>this.visibilityListener()); 42 | } 43 | 44 | mouseListener() { 45 | 46 | if (this.isAway) 47 | this.active(); 48 | 49 | window.clearTimeout(this.awayTimer); 50 | this.awayTimer = window.setTimeout(()=> { 51 | this.away(); 52 | }, Idle.AwayTimeout); 53 | } 54 | 55 | visibilityListener() { 56 | if (document.visibilityState === 'hidden') { 57 | window.clearTimeout(this.awayTimer); 58 | this.away(); 59 | } else if (document.visibilityState === 'visible') { 60 | this.mouseListener(); 61 | } 62 | } 63 | 64 | active() { 65 | this.isAway = false; 66 | this.updateStartTime(); 67 | } 68 | 69 | away() { 70 | if (this.isAway) return; 71 | this.isAway = true; 72 | this.updateIdleTime(); 73 | } 74 | 75 | updateIdleTime() { 76 | const now = new Date().getTime(); 77 | this._time += (now - this.startTime); 78 | } 79 | 80 | updateStartTime() { 81 | this.startTime = new Date().getTime(); 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/Scripts/helpers/loader.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { _ } from './utils'; 8 | 9 | export class Loader { 10 | 11 | container: Element; 12 | 13 | constructor(container: Element, append = true, solo = true, manual = false) { 14 | this.container = container; 15 | this.add(append, solo, manual); 16 | } 17 | 18 | add(append = true, solo = true, manual = false) { 19 | (append ? this.container.insertAdjacentHTML("beforeend", Loader.html(solo, manual)) : this.container.innerHTML = Loader.html(solo, manual)); 20 | } 21 | 22 | remove() { 23 | let element = _(".loader", this.container); 24 | if (!element.empty) 25 | element.remove(); 26 | } 27 | 28 | setProgress(progress: number) { 29 | 30 | let element = _(".loader", this.container); 31 | const progresses = [ 32 | 0, 5, 12, 25, 37, 50, 62, 75, 87, 100 33 | ]; 34 | let matchProgress = 0; 35 | let percProgress = (progress * 100); 36 | for (let i = 0; i < progresses.length; i++) { 37 | if (progresses[i] > percProgress) break; 38 | matchProgress = progresses[i]; 39 | element.classList.remove(`p${matchProgress}`); 40 | } 41 | 42 | element.classList.add(`p${matchProgress}`); 43 | } 44 | 45 | static html(solo = true, manual = false, speed = 0|1|2|3|5|20) { 46 | return `
`; 47 | } 48 | } -------------------------------------------------------------------------------- /src/Scripts/main.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | import { App, AppVersion } from './controllers/app'; 7 | import { Auth } from './controllers/auth'; 8 | import { Host } from './controllers/host'; 9 | import { DiagnosticLevelType, OptionsController } from './controllers/options'; 10 | import { ThemeController } from './controllers/theme'; 11 | import { Telemetry } from './controllers/telemetry'; 12 | import { PBIDesktop } from './controllers/pbi-desktop'; 13 | import { NotifyCenter } from './controllers/notifications'; 14 | import { Logger } from './controllers/logger'; 15 | import { Debug } from './controllers/debug'; 16 | 17 | // Load Tabulator modules 18 | import { Tabulator, ColumnCalcsModule, DataTreeModule, FilterModule, FormatModule, InteractionModule, ResizeColumnsModule, ResizeTableModule, SelectRowModule, SortModule, TooltipModule } from 'tabulator-tables'; 19 | 20 | Tabulator.registerModule([ColumnCalcsModule, DataTreeModule, FilterModule, FormatModule, InteractionModule, ResizeColumnsModule, ResizeTableModule, SelectRowModule, SortModule, TooltipModule]); 21 | 22 | // Init the app 23 | let debug = new Debug(!!CONFIG.debug); 24 | let host = new Host(CONFIG.address, CONFIG.token); 25 | let optionsController = new OptionsController(CONFIG.options, CONFIG.policies); 26 | let themeController = new ThemeController(); 27 | let logger = new Logger(CONFIG.options.diagnosticLevel !== DiagnosticLevelType.None); 28 | let telemetry = new Telemetry(CONFIG.telemetry); 29 | let auth = new Auth(); 30 | let pbiDesktop = new PBIDesktop(); 31 | let notificationCenter = new NotifyCenter(); 32 | 33 | let app = new App(new AppVersion({ 34 | version: CONFIG.version, 35 | build: CONFIG.build 36 | })); 37 | 38 | export { debug, host, optionsController, themeController, auth, telemetry, pbiDesktop, notificationCenter, logger, app }; -------------------------------------------------------------------------------- /src/Scripts/model/extend-tabulator.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { Tabulator } from 'tabulator-tables'; 8 | import { Utils } from '../helpers/utils'; 9 | 10 | export interface TabulatorTreeChildrenFilterParams { 11 | column: string 12 | comparison: "like" | "=" 13 | value: string 14 | } 15 | 16 | export function tabulatorTreeChildrenFilter(data: any, params: TabulatorTreeChildrenFilterParams): boolean { 17 | 18 | const match = (fieldValue: string, comparison: string, value: string): boolean => { 19 | if (comparison == "=") { 20 | return (fieldValue.toLowerCase() == value.toLowerCase()); 21 | } else if (comparison == "like") { 22 | return (fieldValue.toLowerCase().includes(value.toLowerCase())); 23 | } else { 24 | return false; 25 | } 26 | }; 27 | 28 | const matchNode = (node: any): boolean => { 29 | if ((params.column in node) && match(node[params.column], params.comparison, params.value)) { 30 | return true; 31 | } else { 32 | if (("_children" in node) && Utils.Obj.isArray(node._children)) { 33 | for (let i = 0; i < node._children.length; i++) { 34 | if (matchNode(node._children[i])) 35 | return true; 36 | } 37 | } 38 | return false; 39 | } 40 | }; 41 | 42 | return matchNode(data); 43 | } 44 | 45 | export function tabulatorGetAllRows(table: Tabulator) { 46 | 47 | let rows: Tabulator.RowComponent[] = []; 48 | 49 | const traverseRow = (row: Tabulator.RowComponent) => { 50 | rows.push(row); 51 | 52 | let children = row.getTreeChildren(); 53 | if (children && children.length){ 54 | for (let childrenRow of children) 55 | traverseRow(childrenRow); 56 | } 57 | } 58 | 59 | for (let row of table.getRows()) 60 | traverseRow(row); 61 | 62 | return rows; 63 | } -------------------------------------------------------------------------------- /src/Scripts/model/help.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { PageType } from '../controllers/page'; 8 | import { Dic } from '../helpers/utils'; 9 | import { strings } from './strings'; 10 | 11 | export interface HelpRes { 12 | title: strings; 13 | link: string; 14 | videoId: string; 15 | } 16 | 17 | export const help: Dic = { 18 | ui: { 19 | title: strings.helpUserInterface, 20 | videoId: "763673584", 21 | link: "https://docs.sqlbi.com/bravo/user-interface" 22 | }, 23 | connect: { 24 | title: strings.helpConnectVideo, 25 | videoId: "763673603", 26 | link: "https://docs.sqlbi.com/bravo/connect" 27 | }, 28 | AnalyzeModel: { 29 | title: strings.AnalyzeModel, 30 | videoId: "763673832", 31 | link: "https://docs.sqlbi.com/bravo/features/analyze-model" 32 | }, 33 | DaxFormatter: { 34 | title: strings.DaxFormatter, 35 | videoId: "763677100", 36 | link: "https://docs.sqlbi.com/bravo/features/format-dax" 37 | }, 38 | ManageDates: { 39 | title: strings.ManageDates, 40 | videoId: "763679068", 41 | link: "https://docs.sqlbi.com/bravo/features/manage-dates" 42 | }, 43 | templates: { 44 | title: strings.helpTemplates, 45 | videoId: "763684375", 46 | link: "https://docs.sqlbi.com/bravo/features/manage-dates/customize-date-template" 47 | }, 48 | ExportData: { 49 | title: strings.ExportData, 50 | videoId: "763681111", 51 | link: "https://docs.sqlbi.com/bravo/features/export-data" 52 | }, 53 | options: { 54 | title: strings.helpOptions, 55 | videoId: "763683383", 56 | link: "https://docs.sqlbi.com/bravo/configuration/options" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Scripts/model/i18n/locales.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | import en from './en'; 7 | import cz from './cz'; 8 | import da from './da'; 9 | import de from './de'; 10 | import es from './es'; 11 | import fa from './fa'; 12 | import fr from './fr'; 13 | import gr from './gr'; 14 | import it from './it'; 15 | import nl from './nl'; 16 | import pl from './pl'; 17 | import pt from './pt'; 18 | import ru from './ru'; 19 | import zh from './zh'; 20 | import tr from './tr'; 21 | import uk from './uk'; 22 | const locales = { 23 | [en.locale]: en, 24 | [cz.locale]: cz, 25 | [da.locale]: da, 26 | [de.locale]: de, 27 | [es.locale]: es, 28 | [fa.locale]: fa, 29 | [fr.locale]: fr, 30 | [gr.locale]: gr, 31 | [it.locale]: it, 32 | [nl.locale]: nl, 33 | [pl.locale]: pl, 34 | [pt.locale]: pt, 35 | [ru.locale]: ru, 36 | [zh.locale]: zh, 37 | [tr.locale]: tr, 38 | [uk.locale]: uk, 39 | }; 40 | 41 | export default locales; 42 | -------------------------------------------------------------------------------- /src/Scripts/model/message.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { PBICloudDataset } from '../model/pbi-dataset'; 8 | import { PBIDesktopReport } from '../model/pbi-report'; 9 | 10 | export enum WebMessageType { 11 | Unknown = 0, 12 | ReportOpen = 1, 13 | DatasetOpen = 2, 14 | VpaxOpen = 3 15 | } 16 | 17 | export interface WebMessage { 18 | type: WebMessageType 19 | } 20 | 21 | export interface UnknownWebMessage extends WebMessage { 22 | message?: string 23 | exception?: string 24 | } 25 | 26 | export interface PBIDesktopReportOpenWebMessage extends WebMessage { 27 | report?: PBIDesktopReport 28 | } 29 | 30 | export interface PBICloudDatasetOpenWebMessage extends WebMessage { 31 | dataset?: PBICloudDataset 32 | } 33 | 34 | export interface VpaxFileOpenWebMessage extends WebMessage { 35 | blob?: string[] 36 | name?: string 37 | lastModified?: number 38 | } 39 | 40 | /*export interface TokenUpdateWebMessage extends WebMessage { 41 | token?: string 42 | }*/ 43 | -------------------------------------------------------------------------------- /src/Scripts/model/model-changes.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | export interface ModelChanges { 7 | removedObjects: TableChanges[] 8 | modifiedObjects: TableChanges[] 9 | } 10 | 11 | export interface TableChanges { 12 | name?: string 13 | isHidden: boolean 14 | expression?: string 15 | preview?: any[] 16 | columns?: ColumnChanges[] 17 | measures?: MeasureChanges[] 18 | hierarchies?: HierarchyChanges[] 19 | } 20 | export interface ColumnChanges { 21 | name?: string 22 | isHidden: boolean 23 | dataType?: string 24 | } 25 | 26 | export interface MeasureChanges { 27 | name?: string 28 | isHidden: boolean 29 | expression?: string 30 | displayFolder?: string 31 | } 32 | 33 | export interface HierarchyChanges { 34 | name?: string 35 | isHidden: boolean 36 | levels?: string[] 37 | } 38 | 39 | export enum ChangeType { 40 | Added, 41 | Modified, 42 | Deleted 43 | } -------------------------------------------------------------------------------- /src/Scripts/model/pbi-cloud.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | export interface PBICloudEnvironment { 8 | type: PBICloudEnvironmentType 9 | name?: string 10 | description?: string 11 | aadAuthority?: string 12 | aadClientId?: string 13 | aadRedirectAddress?: string 14 | aadResource?: string 15 | aadScopes?: string 16 | serviceEndpoint?: string 17 | clusterEndpoint?: string 18 | } 19 | 20 | export enum PBICloudEnvironmentType { 21 | Unknown = 0, 22 | Custom = 1, 23 | Public = 2, 24 | Germany = 3, 25 | China = 4, 26 | USGov = 5, 27 | USGovHigh = 6, 28 | USGovMil = 7 29 | } -------------------------------------------------------------------------------- /src/Scripts/model/pbi-dataset.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | export enum PBICloudDatasetEndorsement { 7 | None = "None", 8 | Promoted = "Promoted", 9 | Certified = "Certified" 10 | } 11 | 12 | export enum PBICloudDatasetConnectionMode { 13 | Unknown = 0, 14 | Supported = 1, 15 | } 16 | 17 | export interface PBICloudDataset { 18 | workspaceId?: string 19 | workspaceName?: string 20 | workspaceObjectId?: string 21 | id: number 22 | name?: string 23 | serverName?: string 24 | databaseName?: string 25 | description?: string 26 | owner?: string 27 | refreshed?: string 28 | endorsement: PBICloudDatasetEndorsement 29 | connectionMode: PBICloudDatasetConnectionMode 30 | diagnostic?: any 31 | } -------------------------------------------------------------------------------- /src/Scripts/model/pbi-report.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { ASCompatibilityMode } from './tabular' 8 | 9 | export enum PBIDesktopReportConnectionMode { 10 | Unknown = 0, 11 | Supported = 1, 12 | UnsupportedProcessNotReady = 2, 13 | UnsupportedAnalysisServicesProcessNotFound = 3, 14 | UnsupportedAnalysisServicesConnectionNotFound = 4, 15 | UnsupportedAnalysisServicesCompatibilityMode = 5, 16 | UnsupportedDatabaseCollectionEmpty = 6, 17 | UnsupportedDatabaseCollectionUnexpectedCount = 7, 18 | UnsupportedConnectionException = 8 19 | } 20 | 21 | export interface PBIDesktopReport { 22 | id: number 23 | reportName?: string 24 | serverName?: string 25 | databaseName?: string 26 | compatibilityMode?: ASCompatibilityMode 27 | connectionMode: PBIDesktopReportConnectionMode 28 | } -------------------------------------------------------------------------------- /src/Scripts/model/pii.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | // Personal identifiable information - Used by anonymization 8 | 9 | const userPii = [ 10 | "userPrincipalName", 11 | "username", 12 | "emailAddress" 13 | ]; 14 | 15 | const databasePii = [ 16 | "workspaceId", 17 | "workspaceName", 18 | "workspaceObjectId", 19 | "serverName", 20 | "databaseName", 21 | "dbName", 22 | "owner", 23 | "creatorUser" 24 | ]; 25 | 26 | export const pii = [...userPii, ...databasePii]; -------------------------------------------------------------------------------- /src/Scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bravo", 3 | "version": "1.0.0", 4 | "author": "SQLBI", 5 | "license": "MIT", 6 | "private": true, 7 | "scripts": { 8 | "build": "npm run build:Release", 9 | "build:Release": "webpack --mode production --env production --env configuration=Release", 10 | "build:Debug": "webpack --mode development --env configuration=Debug", 11 | "build:UI": "webpack --mode development --output-path ../wwwroot", 12 | "watch:UI": "webpack --watch --mode development --output-path ../wwwroot", 13 | "update:UI": "npm update" 14 | }, 15 | "devDependencies": { 16 | "@types/codemirror": "^5.60.5", 17 | "@types/draggabilly": "^2.1.3", 18 | "@types/sanitize-html": "^2.6.1", 19 | "css-loader": "^6.5.1", 20 | "css-minimizer-webpack-plugin": "^3.3.1", 21 | "less": "^4.1.2", 22 | "less-loader": "^10.2.0", 23 | "mini-css-extract-plugin": "^2.4.5", 24 | "source-map-loader": "^3.0.1", 25 | "style-loader": "^3.3.1", 26 | "ts-loader": "^9.2.6", 27 | "typescript": "^4.5.4", 28 | "webpack": "^5.76.0", 29 | "webpack-cli": "^4.9.1" 30 | }, 31 | "dependencies": { 32 | "@microsoft/applicationinsights-web": "^2.7.2", 33 | "chart.js": "^3.6.2", 34 | "chartjs-chart-treemap": "^2.0.1", 35 | "codemirror": "^5.65.0", 36 | "draggabilly": "^2.4.1", 37 | "fast-equals": "^2.0.4", 38 | "json-formatter-js": "^2.5.23", 39 | "sanitize-html": "^2.6.1", 40 | "split.js": "^1.6.5", 41 | "tabulator-tables": "^5.4.2", 42 | "ts-md5": "1.2.11" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "module": "es2020", 5 | "target": "es2020", 6 | "jsx": "react", 7 | "allowJs": true, 8 | "moduleResolution": "node", 9 | "outDir": "./dist" 10 | }, 11 | "exclude": [ 12 | "node_modules", 13 | "dist" 14 | ] 15 | } -------------------------------------------------------------------------------- /src/Scripts/view/alert.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { i18n } from '../model/i18n'; 8 | import { strings } from '../model/strings'; 9 | import { Dialog } from './dialog'; 10 | 11 | 12 | export class Alert extends Dialog { 13 | 14 | constructor(id: string, title = "", buttonTitle = i18n(strings.dialogOK), neverShowAgain = false) { 15 | super(id, document.body, title, [ 16 | { name: buttonTitle, action: "ok", className: "button-alt" }, 17 | ], "", neverShowAgain); 18 | 19 | this.element.classList.add("dialog-alert"); 20 | } 21 | 22 | show(message?: string) { 23 | const html = `${message}`; 24 | this.body.insertAdjacentHTML("beforeend", html); 25 | 26 | return super.show(); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/Scripts/view/confirm.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | import { i18n } from '../model/i18n'; 7 | import { strings } from '../model/strings'; 8 | import { Dialog } from './dialog'; 9 | 10 | export class Confirm extends Dialog { 11 | 12 | constructor(id: string, neverShowAgain = true) { 13 | super(`confirm-${id}`, document.body, "", [ 14 | { name: i18n(strings.dialogOK), action: "ok" }, 15 | { name: i18n(strings.dialogCancel), action: "cancel", className: "button-alt" }, 16 | ], null, neverShowAgain); 17 | 18 | this.element.classList.add("dialog-confirm"); 19 | } 20 | 21 | show(message?: string) { 22 | 23 | let html = ` 24 | ${message} 25 | `; 26 | this.body.insertAdjacentHTML("beforeend", html); 27 | 28 | return super.show(); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/Scripts/view/connect-item.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | import { Utils, _ } from '../helpers/utils'; 7 | import { i18n } from '../model/i18n'; 8 | import { strings } from '../model/strings'; 9 | import { Connect } from './connect'; 10 | 11 | export class ConnectMenuItem { 12 | 13 | element: HTMLElement; 14 | dialog: Connect; 15 | 16 | constructor(dialog: Connect) { 17 | this.dialog = dialog; 18 | } 19 | 20 | render(element: HTMLElement) { 21 | this.element = element; 22 | } 23 | 24 | renderError(element: HTMLElement, message: string, copy = false, retry?: () => void) { 25 | 26 | if (!this.element) return; 27 | 28 | const retryId = Utils.DOM.uniqueId(); 29 | 30 | element.innerHTML = ` 31 |
32 |
33 |

${message}

34 | ${ copy ? ` 35 |

${i18n(strings.copyErrorDetails)}

36 | ` : ""} 37 | ${ retry ? ` 38 |
${i18n(strings.errorRetry)}
39 | ` : ""} 40 |
41 |
42 | `; 43 | 44 | if (retry) { 45 | _(`#${retryId}`, element).addEventListener("click", e => { 46 | e.preventDefault(); 47 | retry(); 48 | }); 49 | } 50 | if (copy) { 51 | _(".copy-error", element).addEventListener("click", e =>{ 52 | e.preventDefault(); 53 | navigator.clipboard.writeText(message); 54 | 55 | let ctrl = e.currentTarget; 56 | ctrl.innerText = i18n(strings.copiedErrorDetails); 57 | window.setTimeout(() => { 58 | ctrl.innerText = i18n(strings.copyErrorDetails); 59 | }, 1500); 60 | }); 61 | } 62 | this.dialog.okButton.toggleAttr("disabled", true); 63 | } 64 | 65 | destroy() { 66 | this.dialog = null; 67 | this.element = null; 68 | } 69 | } -------------------------------------------------------------------------------- /src/Scripts/view/control.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { Utils } from '../helpers/utils'; 8 | import { View } from '../view/view'; 9 | 10 | export interface ControlConfig { 11 | className?: string | string[] 12 | title?: string 13 | activable?: boolean 14 | disable?: boolean 15 | icon?: string 16 | label?: string 17 | onClick?: (e: Event) => void 18 | } 19 | 20 | export class Control extends View { 21 | 22 | constructor(container: HTMLElement, config: ControlConfig) { 23 | super(null, container); 24 | 25 | this.element.classList.add("ctrl"); 26 | if (config.className) { 27 | let classNames = (!Utils.Obj.isArray(config.className) ? [config.className] : config.className); 28 | classNames.forEach(cn => this.element.classList.add(cn)); 29 | } 30 | if (config.disable) 31 | this.element.classList.add("disable"); 32 | if (config.activable) 33 | this.element.classList.add("toggle"); 34 | if (config.icon) 35 | this.element.classList.add("icon", `icon-${config.icon}`); 36 | if (config.label) 37 | this.element.innerHTML = config.label; 38 | else if (config.icon) 39 | this.element.classList.add("solo"); 40 | 41 | if (config.title) 42 | this.element.setAttribute("title", config.title); 43 | 44 | if (config.onClick || config.activable) { 45 | this.element.addEventListener("click", e => { 46 | e.preventDefault(); 47 | 48 | if (this.element.classList.contains("disable")) return; 49 | 50 | if (config.activable) 51 | this.element.toggleClass("active"); 52 | 53 | if (config.onClick) 54 | config.onClick(e); 55 | }); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/Scripts/view/error-alert.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { Utils, _ } from '../helpers/utils'; 8 | import { app } from '../main'; 9 | import { AppError, AppErrorType } from '../model/exceptions'; 10 | import { i18n } from '../model/i18n'; 11 | import { strings } from '../model/strings'; 12 | import { Dialog } from './dialog'; 13 | 14 | 15 | export class ErrorAlert extends Dialog { 16 | 17 | error: AppError; 18 | 19 | constructor(error: AppError, title?: string) { 20 | super("error", document.body, title ? title : `${i18n(strings.errorTitle)}${error.code && error.type == AppErrorType.Managed ? ` (${error.code})` : "" }`, [ 21 | { name: i18n(strings.dialogOK), action: "cancel", className: "button-alt" }, 22 | ], "icon-alert"); 23 | 24 | this.error = error; 25 | } 26 | 27 | show() { 28 | let html = ` 29 |

30 | ${this.error.message} 31 | ${Utils.Obj.isString(this.error.details) ? `
${this.error.details}` : ""} 32 |

33 | 34 |

35 | ${i18n(strings.version)}: ${app.currentVersion.toString()} 36 | ${this.error.traceId ? `
${i18n(strings.traceId)}: ${this.error.traceId}` : ""} 37 |

38 | 39 |

${i18n(strings.copyErrorDetails)}

40 | `; 41 | this.body.insertAdjacentHTML("beforeend", html); 42 | 43 | _(".copy-error", this.element).addEventListener("click", e =>{ 44 | e.preventDefault(); 45 | navigator.clipboard.writeText(this.error.toString(true)); 46 | 47 | let ctrl = e.currentTarget; 48 | ctrl.innerText = i18n(strings.copiedErrorDetails); 49 | window.setTimeout(() => { 50 | ctrl.innerText = i18n(strings.copyErrorDetails); 51 | }, 1500); 52 | }); 53 | 54 | return super.show(); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/Scripts/view/help-dialog.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | import { _ } from '../helpers/utils'; 7 | import { host } from '../main'; 8 | import { HelpRes } from '../model/help'; 9 | import { i18n } from '../model/i18n'; 10 | import { strings } from '../model/strings'; 11 | import { Dialog } from './dialog'; 12 | 13 | export class HelpDialog extends Dialog { 14 | help: HelpRes; 15 | 16 | constructor(help: HelpRes) { 17 | super("help", document.body, "", [], "", false, true); 18 | 19 | this.help = help; 20 | this.show(); 21 | } 22 | 23 | show() { 24 | 25 | if (this.help.link) { 26 | _("header", this.element).insertAdjacentHTML("afterbegin", ` 27 |
28 | `); 29 | 30 | _(".open-docs", this.element).addEventListener("click", e => { 31 | e.preventDefault(); 32 | host.navigateTo(this.help.link); 33 | }); 34 | } 35 | 36 | let html = ` 37 |
38 | 39 |
40 | `; 41 | this.body.insertAdjacentHTML("beforeend", html); 42 | 43 | return super.show(); 44 | } 45 | 46 | 47 | } -------------------------------------------------------------------------------- /src/Scripts/view/options-dialog-telemetry.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { DiagnosticLevelType } from '../controllers/options'; 8 | import { OptionStruct, OptionType, Renderer } from '../helpers/renderer'; 9 | import { _, __ } from '../helpers/utils'; 10 | import { optionsController } from '../main'; 11 | import { i18n } from '../model/i18n'; 12 | import { strings } from '../model/strings'; 13 | 14 | export class OptionsDialogTelemetry { 15 | 16 | render(element: HTMLElement) { 17 | 18 | let optionsStruct: OptionStruct[] = [ 19 | { 20 | option: "telemetryEnabled", 21 | lockedByPolicy: optionsController.optionIsPolicyLocked("telemetryEnabled"), 22 | icon: "telemetry", 23 | name: i18n(strings.optionTelemetry), 24 | description: i18n(strings.optionTelemetryDescription), 25 | additionalNotes: i18n(strings.optionTelemetryMore), 26 | type: OptionType.switch, 27 | }, 28 | { 29 | option: "diagnosticLevel", 30 | lockedByPolicy: optionsController.optionIsPolicyLocked("diagnosticLevel"), 31 | icon: "bug", 32 | name: i18n(strings.optionDiagnostic), 33 | description: i18n(strings.optionDiagnosticDescription), 34 | additionalNotes: `${i18n(strings.optionDiagnosticMore)} github.com/sql-bi/bravo/issues`, 35 | type: OptionType.select, 36 | values: [ 37 | [DiagnosticLevelType.None, i18n(strings.optionDiagnosticLevelNone)], 38 | [DiagnosticLevelType.Basic, i18n(strings.optionDiagnosticLevelBasic)], 39 | [DiagnosticLevelType.Verbose, i18n(strings.optionDiagnosticLevelVerbose)], 40 | ] 41 | } 42 | ]; 43 | 44 | optionsStruct.forEach(struct => { 45 | Renderer.Options.render(struct, element, optionsController); 46 | }); 47 | } 48 | } -------------------------------------------------------------------------------- /src/Scripts/view/scene-back.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | import { _ } from '../helpers/utils'; 7 | import { i18n } from '../model/i18n'; 8 | import { strings } from '../model/strings'; 9 | import { Scene } from './scene'; 10 | 11 | export class BackableScene extends Scene { 12 | 13 | onBack: (()=>void) | boolean; 14 | 15 | constructor(id: string, container: HTMLElement, title?: string, onBack?: (()=>void) | boolean) { 16 | super(id, container, title); 17 | this.onBack = onBack; 18 | } 19 | 20 | render() { 21 | super.render(); 22 | 23 | if (this.onBack) { 24 | let html = ` 25 |
26 | `; 27 | 28 | this.element.insertAdjacentHTML("beforeend", html); 29 | 30 | _(".go-back", this.element).addEventListener("click", e => { 31 | e.preventDefault(); 32 | this.back(); 33 | }); 34 | this.element.addLiveEventListener("click", ".link-back", (e, element) => { 35 | e.preventDefault(); 36 | this.back(); 37 | }); 38 | } 39 | } 40 | 41 | back() { 42 | if (typeof this.onBack === "function") 43 | this.onBack(); 44 | 45 | this.pop(); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/Scripts/view/scene-best-practices.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | import { Page, PageType } from '../controllers/page'; 7 | import { Doc } from '../model/doc'; 8 | import { i18n } from '../model/i18n'; 9 | import { strings } from '../model/strings'; 10 | import { DocScene } from './scene-doc'; 11 | 12 | export class BestPracticesScene extends DocScene { 13 | 14 | constructor(id: string, container: HTMLElement, doc: Doc, type: PageType) { 15 | super(id, container, [doc.name, i18n(strings.BestPractices)], doc, type, true); 16 | 17 | this.element.classList.add("best-practices"); 18 | } 19 | 20 | render() { 21 | if (!super.render()) return false; 22 | 23 | let html = ` 24 | 25 | `; 26 | this.body.insertAdjacentHTML("beforeend", html); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Scripts/view/scene-loader.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { Loader } from '../helpers/loader'; 8 | import { _ } from '../helpers/utils'; 9 | import { BackableScene } from './scene-back'; 10 | 11 | export class LoaderScene extends BackableScene { 12 | 13 | manual: boolean; 14 | loader: Loader; 15 | 16 | constructor(id: string, container: HTMLElement, title?: string, onBack?: (()=>void) | boolean, manual = false) { 17 | super(id, container, title, onBack); 18 | this.manual = manual; 19 | this.render(); 20 | } 21 | 22 | render() { 23 | super.render(); 24 | 25 | let html = ` 26 |
27 | ${ this.title ? ` 28 |

${this.title}

29 | ` : "" } 30 |
31 | `; 32 | 33 | this.element.insertAdjacentHTML("beforeend", html); 34 | 35 | this.loader = new Loader(_(".loader-container", this.element), true, false, this.manual); 36 | } 37 | 38 | removeLoader() { 39 | _(".loader-container", this.element).remove(); 40 | } 41 | 42 | update(title?: string, progress?: number) { 43 | super.update(); 44 | 45 | if (title) { 46 | this.title = title; 47 | _(".job", this.element).innerText = title; 48 | } 49 | 50 | if (progress) 51 | this.loader.setProgress(progress); 52 | } 53 | } -------------------------------------------------------------------------------- /src/Scripts/view/scene-success.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { _ } from '../helpers/utils'; 8 | import { i18n } from '../model/i18n'; 9 | import { strings } from '../model/strings'; 10 | import { Scene } from './scene'; 11 | 12 | export class SuccessScene extends Scene { 13 | 14 | onDismiss: ()=>void; 15 | message: string; 16 | 17 | constructor(id: string, container: HTMLElement, message: string, onDismiss: ()=>void) { 18 | super(id, container, ""); 19 | this.message = message; 20 | this.onDismiss = onDismiss; 21 | this.render(); 22 | } 23 | 24 | render() { 25 | super.render(); 26 | 27 | let html = ` 28 |
29 | 30 |
31 |
32 |

${this.message}

33 |
34 |
35 |
${i18n(strings.doneCtrlTitle)}
36 |
37 |
38 | `; 39 | 40 | this.element.insertAdjacentHTML("beforeend", html); 41 | 42 | _(".dismiss", this.element).addEventListener("click", e => { 43 | e.preventDefault(); 44 | this.onDismiss(); 45 | }); 46 | } 47 | } -------------------------------------------------------------------------------- /src/Scripts/view/scene-unsupported.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { _ } from '../helpers/utils'; 8 | import { i18n } from '../model/i18n'; 9 | import { strings } from '../model/strings'; 10 | import { Scene } from './scene'; 11 | import { DocScene } from './scene-doc'; 12 | 13 | export class UnsupportedScene extends Scene { 14 | 15 | parent: DocScene 16 | 17 | constructor(id: string, container: HTMLElement, parent: DocScene, reason?: string) { 18 | super(id, container, ""); 19 | 20 | this.element.classList.add("unsupported-scene"); 21 | this.parent = parent; 22 | 23 | this.render(reason); 24 | } 25 | 26 | render(reason?: string) { 27 | super.render(); 28 | 29 | let html = ` 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |

${i18n(strings.sceneUnsupportedTitle)}

38 |

${reason ? reason : i18n(strings.sceneUnsupportedReason)}

39 |
40 | `; 41 | 42 | this.element.insertAdjacentHTML("beforeend", html); 43 | 44 | _(".ctrl-sync", this.element).addEventListener("click", e => { 45 | e.preventDefault(); 46 | if ((e.currentTarget).hasAttribute("disabled")) return; 47 | if (this.element.classList.contains("syncing")) return; 48 | 49 | this.parent.sync(); 50 | }); 51 | } 52 | 53 | update() { 54 | if (this.parent.supported[0]) { 55 | this.parent.reload(); 56 | this.pop(); 57 | } 58 | } 59 | 60 | destroy(){ 61 | this.parent = null; 62 | super.destroy(); 63 | } 64 | } -------------------------------------------------------------------------------- /src/Scripts/view/scene.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | import { View } from './view'; 7 | 8 | export class Scene extends View { 9 | rendered: boolean; 10 | title: string; 11 | 12 | constructor(id: string, container: HTMLElement, title?: string) { 13 | super(id, container); 14 | this.element.classList.add("scene"); 15 | this.hide(); 16 | 17 | this.title = title; 18 | this.rendered = false; 19 | } 20 | 21 | render() { 22 | this.rendered = true; 23 | this.show(); 24 | } 25 | 26 | update() { 27 | 28 | } 29 | 30 | push(scene: Scene) { 31 | this.trigger("push", scene); 32 | } 33 | 34 | pop() { 35 | this.trigger("pop"); 36 | } 37 | 38 | splice(scene: Scene) { 39 | this.trigger("splice", scene); 40 | } 41 | 42 | reload() { 43 | this.element.innerHTML = ""; 44 | this.rendered = false; 45 | this.render(); 46 | } 47 | } -------------------------------------------------------------------------------- /src/Scripts/view/toast.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { _, __ } from '../helpers/utils'; 8 | import { View } from './view'; 9 | 10 | export class Toast extends View { 11 | 12 | constructor(id: string, message: string, timeout = 10000) { 13 | super(`toast-${id}`, document.body); 14 | 15 | this.element.classList.add("toast"); 16 | 17 | let html = ` 18 |
19 |
20 | ${message} 21 |
22 | `; 23 | 24 | this.element.insertAdjacentHTML("beforeend", html); 25 | 26 | this.element.addEventListener("click", e => { 27 | e.preventDefault(); 28 | this.trigger("click", id); 29 | this.destroy(); 30 | }); 31 | 32 | if (timeout) 33 | window.setTimeout(()=>this.destroy(), timeout); 34 | } 35 | 36 | destroy() { 37 | this.element.classList.add("out"); 38 | 39 | window.setTimeout(() => { 40 | this.element.parentElement.removeChild(this.element); 41 | super.destroy(); 42 | }, 500); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Scripts/view/view.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bravo for Power BI 3 | * Copyright (c) SQLBI corp. - All rights reserved. 4 | * https://www.sqlbi.com 5 | */ 6 | 7 | import { Utils, _, __ } from "../helpers/utils"; 8 | import { Dispatchable } from "../helpers/dispatchable"; 9 | 10 | export class View extends Dispatchable { 11 | 12 | id: string; 13 | element: HTMLElement; 14 | body: HTMLElement; 15 | destroyed: boolean = false; 16 | 17 | constructor(id: string, container: HTMLElement) { 18 | super(); 19 | 20 | if (!id) { 21 | id = Utils.DOM.uniqueId(); 22 | } 23 | this.id = id; 24 | 25 | let element = document.createElement("div"); 26 | element.id = this.id; 27 | this.element = element; 28 | this.body = element; 29 | container.appendChild(element); 30 | 31 | } 32 | 33 | show() { 34 | this.element.toggle(true); 35 | } 36 | 37 | hide() { 38 | this.element.toggle(false); 39 | } 40 | 41 | destroy() { 42 | super.destroy(); 43 | this.element.remove(); 44 | this.destroyed = true; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Scripts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const path = require("path"); 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); 5 | 6 | module.exports = (env, argv) => { 7 | const mode = argv.mode || "development"; 8 | 9 | return { 10 | entry: [ 11 | "./main.ts", 12 | "./css/main.less" 13 | ], 14 | mode: mode, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.tsx?$/, 19 | use: 'ts-loader' 20 | }, 21 | { 22 | test: /\.less$/i, 23 | use: [ 24 | MiniCssExtractPlugin.loader, 25 | { 26 | loader: 'css-loader', 27 | options: { 28 | url: false, 29 | }, 30 | }, 31 | { 32 | loader: "less-loader", 33 | options: { 34 | } 35 | } 36 | ], 37 | }, 38 | { 39 | test: /\.js$/, 40 | enforce: 'pre', 41 | use: ['source-map-loader'], 42 | }, 43 | { 44 | test: /\.m?js$/, 45 | resolve: { 46 | fullySpecified: false, 47 | }, 48 | }, 49 | 50 | ], 51 | }, 52 | ignoreWarnings: [/Failed to parse source map/], 53 | resolve: { 54 | extensions: ['.tsx', '.ts', '.js'], 55 | }, 56 | output: { 57 | filename: "./js/main.js" 58 | }, 59 | watchOptions: { 60 | poll: true 61 | }, 62 | plugins:[ 63 | new webpack.DefinePlugin({ 64 | 'process.env.MODE': JSON.stringify(mode) 65 | }), 66 | new MiniCssExtractPlugin({ 67 | filename: "./css/[name].css" 68 | }), 69 | ], 70 | optimization: { 71 | minimizer: [ 72 | '...', 73 | new CssMinimizerPlugin(), 74 | ], 75 | minimize: true, 76 | } 77 | } 78 | }; -------------------------------------------------------------------------------- /src/Services/BestPracticeAnalyzerService.cs: -------------------------------------------------------------------------------- 1 | namespace Sqlbi.Bravo.Services 2 | { 3 | using System; 4 | 5 | public interface IBestPracticeAnalyzerService 6 | { 7 | 8 | } 9 | 10 | internal class BestPracticeAnalyzerService : IBestPracticeAnalyzerService 11 | { 12 | 13 | } 14 | } -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-120.png -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-128.png -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-152.png -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-16.png -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-167.png -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-180.png -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-196.png -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-228.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-228.png -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-32.png -------------------------------------------------------------------------------- /src/wwwroot/favicons/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/favicons/icon-96.png -------------------------------------------------------------------------------- /src/wwwroot/fonts/bravo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/fonts/bravo.eot -------------------------------------------------------------------------------- /src/wwwroot/fonts/bravo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/fonts/bravo.ttf -------------------------------------------------------------------------------- /src/wwwroot/fonts/bravo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sql-bi/Bravo/a95203a253f77e79e49d03da7901558e545d3eed/src/wwwroot/fonts/bravo.woff -------------------------------------------------------------------------------- /src/wwwroot/images/bravo-shadows.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/bravo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/dax-formatter-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/notifications-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/notifications-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/preview-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/preview-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/sqlbi.svg: -------------------------------------------------------------------------------- 1 | sqlbi -------------------------------------------------------------------------------- /src/wwwroot/images/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/vertipaq.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 12 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Bravo for Power BI 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/Bravo.Tests/Bravo.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0-windows;net8.0-windows 5 | 7.0 6 | enable 7 | false 8 | false 9 | $(Configurations);Debug_wwwroot 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ..\..\src\Assets\lib\Antlr4.Runtime.dll 36 | 37 | 38 | ..\..\src\Assets\lib\TOMWrapper.dll 39 | 40 | 41 | 42 | 43 | 44 | Always 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /test/Bravo.Tests/ClassFixture.cs: -------------------------------------------------------------------------------- 1 | namespace Bravo.Tests 2 | { 3 | using System; 4 | using TabularEditor.TOMWrapper; 5 | 6 | public class TabularModelFixture : IDisposable 7 | { 8 | private const string ModelFilePath = @"_data\Models\AdventureWorks.bim"; 9 | 10 | private readonly TabularModelHandlerSettings _handlerSettings = new() 11 | { 12 | AutoFixup = false, 13 | PBIFeaturesOnly = false, 14 | ChangeDetectionLocalServers = false, 15 | UsePowerQueryPartitionsByDefault = false, 16 | }; 17 | 18 | private readonly TabularModelHandler _handler; 19 | 20 | public TabularModelFixture() 21 | { 22 | _handler = new TabularModelHandler(ModelFilePath, _handlerSettings); 23 | } 24 | 25 | public TabularModelHandler Handler => _handler; 26 | 27 | public void Dispose() 28 | { 29 | _handler.Dispose(); 30 | GC.SuppressFinalize(this); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/Bravo.Tests/Infrastructure/Extensions/CommonExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | namespace Bravo.Tests.Infrastructure.Extensions 2 | { 3 | using Sqlbi.Bravo.Infrastructure.Configuration.Settings; 4 | using Sqlbi.Bravo.Infrastructure.Extensions; 5 | using Xunit; 6 | 7 | public class CommonExtensionsTests 8 | { 9 | [Fact] 10 | public void TryParseTo_IntTest() 11 | { 12 | var expected = UpdateChannelType.Dev; 13 | var actual = EnumExtensions.TryParseTo((int)expected); 14 | 15 | Assert.Equal(expected, actual); 16 | } 17 | 18 | [Fact] 19 | public void TryParseTo_IntNullTest1() 20 | { 21 | var @enum = EnumExtensions.TryParseTo((int?)null); 22 | Assert.Null(@enum); 23 | } 24 | 25 | [Fact] 26 | public void TryParseTo_IntNullTest2() 27 | { 28 | var @enum = EnumExtensions.TryParseTo(int.MaxValue); 29 | Assert.Null(@enum); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Bravo.Tests/Infrastructure/Helpers/CommonHelperTests.cs: -------------------------------------------------------------------------------- 1 | namespace Bravo.Tests.Infrastructure.Helpers 2 | { 3 | using Sqlbi.Bravo.Infrastructure.Helpers; 4 | using System; 5 | using Xunit; 6 | 7 | public class CommonHelperTests 8 | { 9 | [Theory] 10 | [InlineData("https://www.contoso.com", "https://www.contoso.com/")] 11 | [InlineData("https://www.CONTOSO.com", "https://www.contoso.com/")] 12 | public void NormalizeUriString_SimpleTest(string uriString, string expectedUriString) 13 | { 14 | var actualUriString = CommonHelper.NormalizeUriString(uriString); 15 | Assert.Equal(expectedUriString, actualUriString); 16 | } 17 | 18 | [Fact] 19 | public void NormalizeUriString_RelativeUriTest() 20 | { 21 | var relativeUri = "path/index.htm?key=value"; 22 | Assert.Throws(() => CommonHelper.NormalizeUriString(relativeUri)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/Bravo.Tests/Infrastructure/Security/CryptographyExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | namespace Bravo.Tests.Infrastructure.Security 2 | { 3 | using Sqlbi.Bravo.Infrastructure.Security; 4 | using System.Net; 5 | using Xunit; 6 | 7 | public class CryptographyExtensionsTests 8 | { 9 | [Theory] 10 | [InlineData("MyValue123456789$", "5f45bfb8ab5c9ea6fe0762974f7bbbe3602155c66d1139496fc2246c360a874b")] 11 | [InlineData("1234567890??=", "cb0f4399a2850ee589414b82de85b736ced269035c48e4275be850a3162ba284")] 12 | [InlineData("abcdefghiABCDEFGHI??=", "1bca6736f96f84e35fa921938f45ba981a6e3f6aa02bcf46763009d3614cf89d")] 13 | [InlineData("", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")] 14 | [InlineData(null, null)] 15 | public void ToSHA256Hash_SimpleTest(string value, string expected) 16 | { 17 | var actual = value.ToSHA256Hash(); 18 | Assert.Equal(expected, actual); 19 | } 20 | 21 | [Theory] 22 | [InlineData("MySecret^ìfd56486-+{6 ♠ ⌂¿EFE==")] 23 | [InlineData("AsDfJkIl123456")] 24 | [InlineData("123==")] 25 | [InlineData("")] 26 | public void ToProtectedStringToSecureString_SimpleTest(string expected) 27 | { 28 | var secureString = new NetworkCredential(string.Empty, expected).SecurePassword; 29 | var protectedString = secureString.ToProtectedString(); 30 | var secureStringActual = protectedString.ToSecureString(); 31 | var actual = new NetworkCredential(string.Empty, secureStringActual).Password; 32 | 33 | Assert.Equal(expected, actual); 34 | } 35 | 36 | [Theory] 37 | [InlineData("MySecret^ìfd56486-+{6 ♠ ⌂¿EFE==")] 38 | [InlineData("AsDfJkIl123456")] 39 | [InlineData("123==")] 40 | [InlineData("")] 41 | public void ToProtectedStringToUnprotectedString_SimpleTest(string expected) 42 | { 43 | var protectedString = expected.ToProtectedString(); 44 | var actual = protectedString.ToUnprotectedString(); 45 | 46 | Assert.Equal(expected, actual); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/Bravo.Tests/Infrastructure/Services/BestPracticeAnalyzer/BestPracticeCollectionTests.cs: -------------------------------------------------------------------------------- 1 | namespace Bravo.Tests.Infrastructure.Services.BestPracticeAnalyzer 2 | { 3 | using Sqlbi.Bravo.Infrastructure.Extensions; 4 | using Sqlbi.Bravo.Infrastructure.Services.BestPracticeAnalyzer; 5 | using Xunit; 6 | 7 | public class BestPracticeCollectionTests 8 | { 9 | private const string BPARulesStandardPath = @"_data\BestPracticeAnalyzer\TabularEditor_BPARules-standard.json"; 10 | 11 | [Fact] 12 | public void CreateFromFile_SimpleTest() 13 | { 14 | var collection = BestPracticeCollection.CreateFromFile(BPARulesStandardPath); 15 | 16 | Assert.NotEmpty(collection); 17 | Assert.Equal(29, collection.Count); 18 | } 19 | 20 | [Fact] 21 | public void Contains_SimpleTest() 22 | { 23 | var collection = BestPracticeCollection.CreateFromFile(BPARulesStandardPath); 24 | var actual = collection.Contains("DAX_TODO"); 25 | 26 | Assert.True(actual); 27 | } 28 | 29 | [Fact] 30 | public void Indexer_SimpleTest() 31 | { 32 | var existsingRule = new BestPracticeRule() { ID = "DAX_TODO" }; 33 | var nonExistingRule = new BestPracticeRule() { ID = "NON EXISTING RULE 123456" }; 34 | 35 | var collection = BestPracticeCollection.CreateFromFile(BPARulesStandardPath); 36 | 37 | Assert.Null(collection[nonExistingRule.ID]); 38 | Assert.NotNull(collection[existsingRule.ID]); 39 | } 40 | 41 | [Fact] 42 | public void SerializeToJson_SimpleTest() 43 | { 44 | var collection = BestPracticeCollection.CreateFromFile(BPARulesStandardPath); 45 | var collectionJson = collection.SerializeToJson(); 46 | 47 | Assert.NotNull(collectionJson); 48 | Assert.False(collectionJson.IsNullOrEmpty()); 49 | } 50 | } 51 | } 52 | --------------------------------------------------------------------------------