diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..2b3b4c4a7 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,33 @@ +# Add 'Documentation' label to any changes within 'docs' folder or any subfolders +documentation: +- changed-files: + - any-glob-to-any-file: docs/** + +# Add 'source' label to any change to src files within the source dir EXCEPT for the docs sub-folder +build-action: +- changed-files: + - any-glob-to-any-file: ['build/**', '.github/workflows/**'] +- head-branch: ['^build', 'build'] + +enhancement: +- changed-files: + - any-glob-to-any-file: ['src/**'] + +enhancement-classic: +- changed-files: + - any-glob-to-any-file: ['src/MigrationTools/_EngineV1/**', 'src/VstsSyncMigrator*/**'] + +enhancement-modern: +- changed-files: + - any-glob-to-any-file: ['src/**'] + - all-globs-to-all-files: ['!src/MigrationTools/_EngineV1/**', '!src/VstsSyncMigrator*/**'] + +# Add 'feature' label to any PR where the head branch name starts with `feature` or has a `feature` section in the name +feature: +- head-branch: ['^feature', 'feature', '^topic', 'topic'] + +# Add 'bug' label to any PR +bug: +- head-branch: ['^fix', 'fix','^bug', 'bug'] + + diff --git a/.github/release.yml b/.github/release.yml index dfc7dacf3..9d74c78da 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -7,7 +7,7 @@ changelog: - '*' exclude: labels: - - dependencies - - title: 👒 Dependencies + - architecture + - title: 👒 Architecture Improvements labels: - - dependencies + - architecture diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml new file mode 100644 index 000000000..57b2be78e --- /dev/null +++ b/.github/workflows/code-review.yml @@ -0,0 +1,26 @@ +name: OpenAI - Code Review + +permissions: + contents: read + pull-requests: write + +on: + pull_request: + types: [opened, reopened] + +jobs: + code-review: + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.NKDAGILITY_BOT_APP_ID }} + private-key: ${{ secrets.NKDAGILITY_BOT_CLIENTSECRET }} + - uses: fitomad/github-chatgpt-integration@main + with: + openai-api-key: ${{ secrets.OPENAI_API_KEY }} + github-token: ${{ steps.app-token.outputs.token }} + github-pr-id: ${{ github.event.number }} + dev-lang: c# + openai-max-tokens: 4096 \ No newline at end of file diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 000000000..f25102135 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,14 @@ +name: "Pull Request Labeler" +on: +- pull_request_target + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + with: + sync-labels: true \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4de76dc0..353e82c5a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,27 +1,30 @@ name: Build & Release (Azure DevOps Migration Tools) -on: +permissions: + contents: read + pull-requests: write + +on: push: - tags-ignore: - - 'v*-*' + branches: ["main"] + tags-ignore: ["v*-*"] pull_request: - branches: - - main + branches: ["main"] workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - APP_ID: "Iv23li9aYvt0VW9x4Jhh" - PRIVATE_KEY: ${{ secrets.NKDAGILITY_BOT_CLIENTSECRET }} +defaults: + run: + shell: pwsh jobs: # Setup & Configuration Setup: - name: "Setup & Configuration" + name: "Setup & Configuration " runs-on: ubuntu-latest outputs: GitVersion_BranchName: ${{ steps.gitversion.outputs.GitVersion_BranchName }} @@ -30,6 +33,14 @@ jobs: GitVersion_AssemblySemVer: ${{ steps.gitversion.outputs.GitVersion_AssemblySemVer }} GitVersion_InformationalVersion: ${{ steps.gitversion.outputs.GitVersion_InformationalVersion }} GitVersion_NuGetVersion: ${{ steps.gitversion.outputs.GitVersion_NuGetVersion }} + GitVersion_PreReleaseNumber: ${{ steps.gitversion.outputs.GitVersion_PreReleaseNumber }} + GitVersion_MajorMinorPatch: ${{ steps.gitversion.outputs.GitVersion_MajorMinorPatch }} + HasChanged_src: ${{ steps.filter.outputs.src }} + HasChanged_docs: ${{ steps.filter.outputs.docs }} + HasChanged_automation: ${{ steps.filter.outputs.automation }} + nkdAgility_Ring: ${{ steps.nkdagility.outputs.Ring }} + nkdAgility_WingetApplicationId: ${{ steps.nkdagility.outputs.WingetApplicationId }} + nkdAgility_IsBuildEditBranch: ${{ steps.nkdagility.outputs.IsBuildEditBranch }} steps: - name: Checkout uses: actions/checkout@v2 @@ -45,43 +56,99 @@ jobs: uses: gittools/actions/gitversion/execute@v1.1.1 with: useConfigFile: true + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + src: + - 'src/**' + docs: + - 'docs/**' + automation: + - 'build/**' + - '.github/workflows/**' + - name: "Build NKDAgility Outputs" + shell: pwsh + id: nkdagility + run: | + $Ring = "Canary" + $WingetApplicationId = "nkdagility.azure-devops-migration-tools" + switch ($Env:GitVersion_PreReleaseLabel) { + "" { + $Ring = "Release"; + $WingetApplicationId = "nkdagility.azure-devops-migration-tools"; + } + "Preview" { + $Ring = "Preview"; + $WingetApplicationId = "nkdagility.azure-devops-migration-tools.Preview"; + } + default { + $Ring = "Canary"; + $WingetApplicationId = "nkdagility.azure-devops-migration-tools.Canary"; + } + } + Write-Output "We are running for the $Ring Ring!" + Write-Output "We are focused on Winget ID $WingetApplicationId!" + echo "Ring=$Ring" >> $env:GITHUB_OUTPUT + echo "WingetApplicationId=$WingetApplicationId" >> $env:GITHUB_OUTPUT + + $IsBuildEditBranch = $false; + if (("${{ github.ref_name }}").contains("build/")) { + $IsBuildEditBranch = $true; + } + Write-Output "IsBuildEditBranch=$IsBuildEditBranch" + echo "IsBuildEditBranch=$IsBuildEditBranch" >> $env:GITHUB_OUTPUT # Setup Validator - SetupValidatorStage: - name: "Setup Validator" + SetupSummeryStage: + name: "Build Run Data" runs-on: ubuntu-latest needs: Setup steps: - - name: "Release WorkItemClone" - shell: pwsh - run: | - Write-Output "GitVersion_BranchName: ${{needs.Setup.outputs.GitVersion_BranchName}}" - Write-Output "GitVersion_SemVer: ${{needs.Setup.outputs.GitVersion_SemVer}}" - Write-Output "GitVersion_PreReleaseLabel: ${{needs.Setup.outputs.GitVersion_PreReleaseLabel}}" - Write-Output "GitVersion_AssemblySemVer: ${{needs.Setup.outputs.GitVersion_AssemblySemVer}}" - Write-Output "GitVersion_InformationalVersion: ${{needs.Setup.outputs.GitVersion_InformationalVersion}}" + - name: "Create Summery" + if: always() + shell: pwsh + id: nkdagility-summery + run: | + $markdown = @" + ## ${{needs.Setup.outputs.GitVersion_SemVer}} (${{needs.Setup.outputs.nkdAgility_Ring}}) + ### NKDAgility + - nkdAgility_Ring: ${{needs.Setup.outputs.nkdAgility_Ring}} + - nkdAgility_IsBuildEditBranch: ${{needs.Setup.outputs.nkdAgility_IsBuildEditBranch}} + - nkdAgility_WingetApplicationId: ${{needs.Setup.outputs.nkdAgility_WingetApplicationId}} + ### GitVersion + - GitVersion_BranchName: ${{needs.Setup.outputs.GitVersion_BranchName}} + - GitVersion_SemVer: ${{needs.Setup.outputs.GitVersion_SemVer}} + - GitVersion_PreReleaseLabel: ${{needs.Setup.outputs.GitVersion_PreReleaseLabel}} + - GitVersion_AssemblySemVer: ${{needs.Setup.outputs.GitVersion_AssemblySemVer}} + - GitVersion_InformationalVersion: ${{needs.Setup.outputs.GitVersion_InformationalVersion}} + - GitVersion_NuGetVersion: ${{needs.Setup.outputs.GitVersion_NuGetVersion}} + ### Has Changed + - HasChanged_src: ${{needs.Setup.outputs.HasChanged_src}} + - HasChanged_docs: ${{needs.Setup.outputs.HasChanged_docs}} + - HasChanged_automation: ${{needs.Setup.outputs.HasChanged_automation}} + "@ + echo $markdown >> $Env:GITHUB_STEP_SUMMARY # Build, Test, Sonar Cloud Analysis, & Package build: name: "Build, Test, Sonar Cloud Analysis, & Package" runs-on: windows-latest needs: Setup + if: ${{ needs.Setup.outputs.HasChanged_src }} env: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' - GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + nkdAgility_Ring: ${{ needs.Setup.outputs.nkdAgility_Ring }} + GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} GitVersion_AssemblySemVer: ${{ needs.Setup.outputs.GitVersion_AssemblySemVer }} GitVersion_InformationalVersion: ${{ needs.Setup.outputs.GitVersion_InformationalVersion }} GitVersion_NuGetVersion: ${{ needs.Setup.outputs.GitVersion_NuGetVersion }} GitVersion_PreReleaseLabel: ${{ needs.Setup.outputs.GitVersion_PreReleaseLabel }} + GitVersion_PreReleaseNumber: ${{ needs.Setup.outputs.GitVersion_PreReleaseNumber }} + GitVersion_MajorMinorPatch: ${{ needs.Setup.outputs.GitVersion_MajorMinorPatch }} steps: - # - name: Setup NuGet - # uses: NuGet/setup-nuget@v1.0.2 - # - name: Restore NuGet Packages - # run: nuget restore MigrationTools.sln - # - name: Build and Publish Web App - # run: msbuild MigrationTools.sln - name: Set up JDK 17 uses: actions/setup-java@v3 with: @@ -94,6 +161,10 @@ jobs: files: '["**/StaticVariables.cs"]' tokenPrefix: "${" tokenSuffix: "}" + - uses: actions/upload-artifact@v4 + with: + name: AzureDevOpsMigrationTools-BuildScripts + path: ./build/** - name: Setup .NET uses: actions/setup-dotnet@v1 with: @@ -170,20 +241,24 @@ jobs: with: name: AzureDevOpsMigrationTools-Site path: ./_site/**/* - + # GitHubRelease GitHubRelease: - name: "Release to GitHub Releases (Not Migrated)" + name: "Release to GitHub Releases" runs-on: ubuntu-latest env: + nkdAgility_Ring: ${{ needs.Setup.outputs.nkdAgility_Ring }} GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} GitVersion_AssemblySemVer: ${{ needs.Setup.outputs.GitVersion_AssemblySemVer }} GitVersion_InformationalVersion: ${{ needs.Setup.outputs.GitVersion_InformationalVersion }} GitVersion_NuGetVersion: ${{ needs.Setup.outputs.GitVersion_NuGetVersion }} GitVersion_PreReleaseLabel: ${{ needs.Setup.outputs.GitVersion_PreReleaseLabel }} + HasChanged_src: ${{ needs.Setup.outputs.HasChanged_src }} + HasChanged_docs: ${{ needs.Setup.outputs.HasChanged_docs }} + HasChanged_automation: ${{ needs.Setup.outputs.HasChanged_automation }} needs: [build, Setup] - #if: ${{ success() && ( needs.Setup.outputs.GitVersion_PreReleaseLabel == 'Preview' || needs.Setup.outputs.GitVersion_PreReleaseLabel == '' ) }} + if: ${{ success() && ( needs.Setup.outputs.nkdAgility_Ring != 'Canary' ) }} steps: - uses: actions/download-artifact@v4 with: @@ -191,14 +266,21 @@ jobs: - uses: actions/create-github-app-token@v1 id: app-token with: - app-id: ${{ env.APP_ID }} - private-key: ${{ env.PRIVATE_KEY }} + app-id: ${{ secrets.NKDAGILITY_BOT_APP_ID }} + private-key: ${{ secrets.NKDAGILITY_BOT_CLIENTSECRET }} - name: "Package Files" shell: pwsh run: | Get-ChildItem -Path .\ -Recurse | ForEach-Object { $_.FullName } + - name: "Release options" + id: release-options + shell: pwsh + run: | + if ($Env:nkdAgility_Ring -ne 'Canary' && ($Env:HasChanged_src || $Env:HasChanged_docs )) { + echo "discussion_category_name=Anouncement" >> $env:GITHUB_OUTPUT + } + - name: Release - if: ${{ false }} uses: softprops/action-gh-release@v2 with: files: | @@ -207,72 +289,124 @@ jobs: vsts-sync-migrator.*.nupkg generate_release_notes: true tag_name: v${{ needs.Setup.outputs.GitVersion_SemVer }} - name: Azure DevOps Migration Tools v${{ needs.Setup.outputs.GitVersion_SemVer }} + name: v${{ needs.Setup.outputs.GitVersion_SemVer }} (${{ needs.Setup.outputs.nkdAgility_Ring }}) token: ${{ steps.app-token.outputs.token }} - prerelease: ${{ needs.Setup.outputs.GitVersion_PreReleaseLabel == 'Preview' }} - discussion_category_name: "Anouncement" + prerelease: ${{ needs.Setup.outputs.nkdAgility_Ring != 'Release' }} + discussion_category_name: ${{ steps.release-options.outputs.discussion_category_name }} + draft: ${{ needs.Setup.outputs.nkdAgility_Ring == 'Canary' }} + body: | + ## Azure DevOps Migration Tools v${{ needs.Setup.outputs.GitVersion_SemVer }} + Version: ${{ needs.Setup.outputs.GitVersion_SemVer }} + Ring: (${{ needs.Setup.outputs.nkdAgility_Ring }}) + + ## Get the tools + + - Download the [MigrationTools-${{ needs.Setup.outputs.GitVersion_SemVer }}.zip](https://github.com/nkdAgility/azure-devops-migration-tools/releases/download/v${{ needs.Setup.outputs.GitVersion_SemVer }}/MigrationTools-${{ needs.Setup.outputs.GitVersion_SemVer }}.zip) file below + - Install with Winget with `winget install ${{needs.Setup.outputs.nkdAgility_WingetApplicationId}} --version ${{ needs.Setup.outputs.GitVersion_SemVer }}` . There is a delay for aprovals on the winget store, so you may need to wait a few days before this is available. + - Install with Chocolatey with `choco install nkdagility.azure-devops-migration-tools --version ${{ needs.Setup.outputs.GitVersion_NuGetVersion }}`. There is a delay for aprovals on the chocolatey store, so you may need to wait a few hours before this is available. + + append_body: true +# ElmahDeployment + ElmahDeployemnt: + name: "Create Elmah.io Deployment" + runs-on: ubuntu-latest + env: + GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + needs: [GitHubRelease, Setup] + if: ${{ success() && ( needs.Setup.outputs.nkdAgility_Ring != 'Canary' ) }} + steps: + - name: Create Deployment on elmah.io + uses: elmahio/github-create-deployment-action@v1 + with: + apiKey: ${{ secrets.ELMAH_IO_API_KEY }} + version: ${{ needs.Setup.outputs.GitVersion_SemVer }} + logId: ${{ secrets.ELMAH_IO_LOG_ID }} + + # Release to Marketplace MarketplaceRelease: - name: "Release to Marketplace (Not Migrated)" + name: "Release to Marketplace" runs-on: ubuntu-latest needs: [Setup, GitHubRelease] - if: ${{ false }} - #if: ${{ success() && ( needs.Setup.outputs.GitVersion_PreReleaseLabel == 'Preview' || needs.Setup.outputs.GitVersion_PreReleaseLabel == '' ) }} + if: ${{ success() && ( needs.Setup.outputs.nkdAgility_Ring != 'Canary' ) && (needs.Setup.outputs.HasChanged_src) }} steps: + - name: Checkout + uses: actions/checkout@v2 - uses: actions/download-artifact@v4 - with: - name: AzureDevOpsMigrationTools-Packages - - name: "Find solution files" + - name: "Find files" shell: pwsh run: | - Get-Item -Path .\ - Get-ChildItem -Path .\ -Recurse -Filter '*.sln' | ForEach-Object { $_.FullName } + Get-Item -Path .\ + Write-Output "Build Files" + Get-ChildItem -Path .\ -Recurse -Filter '*.ps1' | ForEach-Object { $_.FullName } + - name: "Marketplace" + shell: pwsh + run: | + $vsixFile = Get-ChildItem -Path .\ -Recurse -Filter '*.vsix' + if ($vsixFile -eq $null) { + Write-Output "No VSIX file found" + exit 1 + } else { + Write-Output $"Running with {$vsixFile}" + } + .\build\releaseExtension.ps1 -vsixFile $vsixFile.FullName -marketplaceToken ${{ secrets.VS_MARKET_TOKEN }} + # Release to Chocolatey ChocolateyRelease: - name: "Release to Chocolatey (Not Migrated)" - runs-on: ubuntu-latest + name: "Release to Chocolatey" + runs-on: windows-latest needs: [Setup, GitHubRelease] - if: ${{ false }} - #if: ${{ success() && ( needs.Setup.outputs.GitVersion_PreReleaseLabel == 'Preview' || needs.Setup.outputs.GitVersion_PreReleaseLabel == '' ) }} + if: ${{ success() && ( needs.Setup.outputs.nkdAgility_Ring != 'Canary' ) && (needs.Setup.outputs.HasChanged_src) }} steps: - uses: actions/download-artifact@v4 with: name: AzureDevOpsMigrationTools-Packages - - name: "Find solution files" + - name: "Choco" shell: pwsh run: | - Get-Item -Path .\ - Get-ChildItem -Path .\ -Recurse -Filter '*.sln' | ForEach-Object { $_.FullName } + $chocoFile = Get-ChildItem -Path .\ -Recurse -Filter 'vsts-sync-migrator.${{ needs.Setup.outputs.GitVersion_NuGetVersion }}.nupkg' + if ($chocoFile -eq $null) { + Write-Output "No Choco file found" + exit 1 + } else { + Write-Output $"Running with {$chocoFile}" + } + Write-Output 'choco push "$chocoFile" --key "${{ secrets.CHOCO_APIKEY }}" --source "https://push.chocolatey.org/"' + choco push "$chocoFile" --key "${{ secrets.CHOCO_APIKEY }}" --source "https://push.chocolatey.org/" + # Release to Winget WingetRelease: - name: "Release to Winget (Not Migrated)" - runs-on: ubuntu-latest + name: "Release to Winget" + runs-on: windows-latest needs: [Setup, GitHubRelease] - if: ${{ false }} - #if: ${{ success() && ( needs.Setup.outputs.GitVersion_PreReleaseLabel == 'Preview' || needs.Setup.outputs.GitVersion_PreReleaseLabel == '' ) }} + if: ${{ success() && ( needs.Setup.outputs.nkdAgility_Ring != 'Canary' ) && (needs.Setup.outputs.HasChanged_src) }} steps: + - name: Checkout + uses: actions/checkout@v2 - uses: actions/download-artifact@v4 - with: - name: AzureDevOpsMigrationTools-Packages - - name: "Find solution files" + - name: "Find files" shell: pwsh run: | - Get-Item -Path .\ - Get-ChildItem -Path .\ -Recurse -Filter '*.sln' | ForEach-Object { $_.FullName } + Get-Item -Path .\ + Write-Output "Build Files" + Get-ChildItem -Path .\ -Recurse -Filter '*.ps1' | ForEach-Object { $_.FullName } + - name: "Winget Release" + shell: pwsh + run: | + .\build\releaseWingetPackage.ps1 -version ${{ needs.Setup.outputs.GitVersion_SemVer }} -ring ${{needs.Setup.outputs.nkdAgility_Ring}} -GH_TOKEN ${{ secrets.NKD_MRHINSH_TOKEN }} # Release to Docs DocsRelease: name: "Release to Docs" runs-on: ubuntu-latest - needs: [BuildDocs, GitHubRelease] - if: ${{ false }} - #if: ${{ success() && ( needs.Setup.outputs.GitVersion_PreReleaseLabel == 'Preview' || needs.Setup.outputs.GitVersion_PreReleaseLabel == '' ) }} + needs: [Setup, BuildDocs, GitHubRelease] + if: ${{ success() && ( needs.Setup.outputs.nkdAgility_Ring != 'Canary' ) }} steps: - name: Download a single artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: AzureDevOpsMigrationTools-Site path: ./_site @@ -280,19 +414,27 @@ jobs: shell: pwsh run: | Get-Item -Path .\ - - name: SFTP Upload - # You may pin to the exact commit or the version. - # uses: Dylan700/sftp-upload-action@f90db607d9fe1acdc0eefccee84d37c119b268fe + - name: "Folder Desider" + id: Folder-Decision + shell: pwsh + run: | + if ($Env:nkdAgility_Ring -ne 'Release') { + echo "subfolder=$Env:nkdAgility_Ring" >> $env:GITHUB_OUTPUT + Write-Output "subfolder=$Env:nkdAgility_Ring" + } else { + Write-Output "subfolder=" + } + - name: SFTP Upload ${{ (needs.Setup.outputs.nkdAgility_Ring) }} uses: Dylan700/sftp-upload-action@v1.1.4 with: server: nakedalmweb.sftp.wpengine.com username: nakedalmweb-learn - password: ${{ secrets.SFTP_PASSWORD }} + password: ${{ secrets.NKDAGILITY_LEARN_SFTP }} port: 2222 # If true, outputs the results of the upload, without actually uploading. - dry-run: false + dry-run: ${{ (needs.Setup.outputs.nkdAgility_Ring) == 'Canary' }} # A list of folders to upload in the format of `folder/ => upload_folder/` uploads: | - ./_site/ => ./azure-devops-migration-tools + ./_site/ => ./azure-devops-migration-tools/${{ steps.Folder-Decision.outputs.subfolder }} # If true, any existing files in the remote upload directories are deleted. delete: false \ No newline at end of file diff --git a/.github/workflows/open-pr-describer.yml b/.github/workflows/open-pr-describer.yml new file mode 100644 index 000000000..48c2fbc27 --- /dev/null +++ b/.github/workflows/open-pr-describer.yml @@ -0,0 +1,22 @@ +name: "OpenAI - PR Description Generator" + +on: + pull_request: + types: + - opened + - synchronize + +permissions: + pull-requests: write + contents: read + +jobs: + pull-request: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Ant0wan/openai-pr@v1 + with: + api-key: ${{ secrets.OPENAI_API_KEY }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/opencommit.yml b/.github/workflows/opencommit.yml new file mode 100644 index 000000000..b16dd7535 --- /dev/null +++ b/.github/workflows/opencommit.yml @@ -0,0 +1,46 @@ +name: 'OpenAI - OpenCommit AI' + +permissions: + contents: write + pull-requests: write + +on: + push: + # this list of branches is often enough, + # but you may still ignore other public branches + branches-ignore: [main master dev development release] + workflow_dispatch: + +jobs: + opencommit: + timeout-minutes: 10 + name: OpenCommit + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: Setup Node.js Environment + uses: actions/setup-node@v2 + with: + node-version: '16' + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: di-sukharev/opencommit@github-action-v1.0.4 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + env: + # set openAI api key in repo actions secrets, + # for openAI keys go to: https://platform.openai.com/account/api-keys + # for repo secret go to: /settings/secrets/actions + OCO_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + + # customization + OCO_TOKENS_MAX_INPUT: 1100 + OCO_TOKENS_MAX_OUTPUT: 500 + OCO_OPENAI_BASE_PATH: '' + OCO_DESCRIPTION: false + OCO_EMOJI: false + OCO_MODEL: gpt-4 + OCO_LANGUAGE: en + OCO_PROMPT_MODULE: conventional-commit \ No newline at end of file diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index ff50fbee3..0d901f27f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,6 +17,6 @@ jobs: stale-pr-label: 'no-pr-activity' stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 10 days' stale-pr-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 10 days' - days-before-stale: 30 - days-before-close: 10 + days-before-stale: 90 + days-before-close: 30 exempt-issue-label: 'enhancement' diff --git a/Directory.Build.props b/Directory.Build.props index f3fe61328..2d94db4f3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,10 +1,12 @@ - 0.0.0.1 + 0.0.0.0 + 0.0.0.0 + 0.0.0-local Martin Hinshelwood naked Agility with Martin Hinshelwood - 9.0 MigrationTools.CommandLine + default 1701;1702;1591 diff --git a/GitVersion.yml b/GitVersion.yml index b2456bec1..a02d9e2c4 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,6 +1,6 @@ assembly-versioning-scheme: MajorMinorPatch mode: ContinuousDelivery -continuous-delivery-fallback-tag: '' +continuous-delivery-fallback-tag: 'Canary' next-version: 15.0.1 branches: main: diff --git a/MigrationTools.sln b/MigrationTools.sln index 378b54de0..1d248ae92 100644 --- a/MigrationTools.sln +++ b/MigrationTools.sln @@ -90,7 +90,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MigrationTools.TestExtensio EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".workflows", ".workflows", "{8A70932A-F6C7-45D1-8E72-608B6BF0F9FD}" ProjectSection(SolutionItems) = preProject + .github\workflows\code-review.yml = .github\workflows\code-review.yml .github\workflows\main.yml = .github\workflows\main.yml + .github\workflows\open-pr-describer.yml = .github\workflows\open-pr-describer.yml + .github\workflows\opencommit.yml = .github\workflows\opencommit.yml + .github\workflows\stale.yml = .github\workflows\stale.yml + triggertest.yml = triggertest.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MigrationTools.ConsoleDataGenerator", "src\MigrationTools.ConsoleDataGenerator\MigrationTools.ConsoleDataGenerator.csproj", "{6A259EA6-860B-448A-8943-594DC1A15105}" @@ -113,6 +118,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{88C358 build\packageExtension.ps1 = build\packageExtension.ps1 build\packageNuget.ps1 = build\packageNuget.ps1 build\packageWinget.ps1 = build\packageWinget.ps1 + build\releaseExtension.ps1 = build\releaseExtension.ps1 build\releaseGitHubRelease.ps1 = build\releaseGitHubRelease.ps1 build\releaseWingetPackage.ps1 = build\releaseWingetPackage.ps1 build\versioning.ps1 = build\versioning.ps1 @@ -120,6 +126,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{88C358 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{BB497233-248C-49DF-AE12-F7A76F775E74}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NKDAgility.AzureDevOps.Tools.CommandHost", "src\NKDAgility.AzureDevOps.Tools.CommandHost\NKDAgility.AzureDevOps.Tools.CommandHost.csproj", "{60EF98A1-5AA4-4589-8B6F-A77B3940025D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MigrationTools.Helpers.Tests", "src\MigrationTools.Fakes\MigrationTools.Helpers.Tests.csproj", "{EB20ED85-8876-4585-BC90-E6976C11DEE3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -202,6 +212,14 @@ Global {6A259EA6-860B-448A-8943-594DC1A15105}.Debug|Any CPU.Build.0 = Debug|Any CPU {6A259EA6-860B-448A-8943-594DC1A15105}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A259EA6-860B-448A-8943-594DC1A15105}.Release|Any CPU.Build.0 = Release|Any CPU + {60EF98A1-5AA4-4589-8B6F-A77B3940025D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60EF98A1-5AA4-4589-8B6F-A77B3940025D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60EF98A1-5AA4-4589-8B6F-A77B3940025D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60EF98A1-5AA4-4589-8B6F-A77B3940025D}.Release|Any CPU.Build.0 = Release|Any CPU + {EB20ED85-8876-4585-BC90-E6976C11DEE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB20ED85-8876-4585-BC90-E6976C11DEE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB20ED85-8876-4585-BC90-E6976C11DEE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB20ED85-8876-4585-BC90-E6976C11DEE3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -235,6 +253,8 @@ Global {6A259EA6-860B-448A-8943-594DC1A15105} = {83F36820-E9BC-4F48-8202-5EAF9530405E} {AC3B5101-83F5-4C28-976C-C325425D1988} = {1F5E9C8C-AD05-4C4F-B370-FF3D080A6541} {BB497233-248C-49DF-AE12-F7A76F775E74} = {83F36820-E9BC-4F48-8202-5EAF9530405E} + {60EF98A1-5AA4-4589-8B6F-A77B3940025D} = {BB497233-248C-49DF-AE12-F7A76F775E74} + {EB20ED85-8876-4585-BC90-E6976C11DEE3} = {BB497233-248C-49DF-AE12-F7A76F775E74} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {62EE0B27-C55A-46EE-8D17-1691DE9BBD50} diff --git a/build/azure-pipeline.yml b/build/azure-pipeline.yml index 8580372cd..199bb49d6 100644 --- a/build/azure-pipeline.yml +++ b/build/azure-pipeline.yml @@ -96,12 +96,30 @@ stages: } Write-Host "##vso[build.addbuildtag]$releaseTag" + $Ring = "Canary" + switch ($Env:GitVersion_PreReleaseLabel) { + "" { + $Ring = "Release"; + } + "Preview" { + $Ring = "Preview"; + } + default { + $Ring = "Canary"; + } + } + Write-Output "We are running for the $Ring Ring!" + Write-Host "##vso[task.setvariable variable=nkdAgility_Ring;isoutput=true]true" + # Output the variables for debugging Write-Host "GITVERSION.PreReleaseLabel: $(GITVERSION.PreReleaseLabel)" Write-Host "GITVERSION.SemVer: $(GITVERSION.SemVer)" Write-Host "GitVersion.SemVer: $(GitVersion.SemVer)" Write-Host "GitVersion.AssemblySemVer: $(GitVersion.AssemblySemVer)" Write-Host "GitVersion.InformationalVersion: $(GitVersion.InformationalVersion)" + Write-Host "GitVersion.NuGetVersion: $(GitVersion.NuGetVersion)" + Write-Host "GitVersion.MajorMinorPatch: $(GitVersion.MajorMinorPatch)" + Write-Host "GitVersion.PreReleaseNumber: $(GitVersion.PreReleaseNumber)" # Debug Value Check @@ -122,6 +140,7 @@ stages: GitVersion.SemVer: $[ stageDependencies.SetupStage.ConfigJob.outputs['GitVersion.SemVer'] ] GitVersion.PreReleaseTag: $[ stageDependencies.SetupStage.ConfigJob.outputs['GitVersion.PreReleaseTag'] ] isPreRelease: $[ stageDependencies.SetupStage.ConfigJob.outputs['VersionOut.isPreRelease'] ] + nkdAgility_Ring: $[ stageDependencies.SetupStage.ConfigJob.outputs['VersionOut.nkdAgility_Ring'] ] steps: - task: PowerShell@2 displayName: Read Parameters @@ -144,6 +163,7 @@ stages: Write-Host "GitVersion.PreReleaseTag: $(GitVersion.PreReleaseTag)" Write-Host "isPreRelease: $(isPreRelease)" Write-Host "skipCompileTest: $(skipCompileTest)" + Write-Host "nkdAgility_Ring: $(nkdAgility_Ring)" # Build, Test and Package @@ -466,6 +486,7 @@ stages: version: v0.7.x - task: ms-devlabs.vsts-developer-tools-build-tasks.publish-extension-build-task.PublishAzureDevOpsExtension@4 displayName: 'Publish Extension' + condition: and(succeeded(), ne(variables['nkdAgility_Ring'], "Canary")) inputs: connectedServiceName: 'nkdAgility-Marketplace' fileType: vsix @@ -498,6 +519,7 @@ stages: downloadPath: '$(System.ArtifactsDirectory)' - task: NuGetCommand@2 displayName: 'NuGet push' + condition: and(succeeded(), ne(variables['nkdAgility_Ring'], "Canary")) inputs: command: push packagesToPush: '$(System.ArtifactsDirectory)\**\*vsts-sync-migrator.*.nupkg' @@ -530,9 +552,10 @@ stages: downloadPath: '$(System.ArtifactsDirectory)' - task: PowerShell@2 displayName: Create Winget Release + condition: and(succeeded(), ne(variables['nkdAgility_Ring'], "Canary")) inputs: filePath: 'build/releaseWingetPackage.ps1' - arguments: '-version $(GitVersion.SemVer) -releaseTag $(releaseTag) -GH_TOKEN $(GH_TOKEN)' + arguments: '-version $(GitVersion.SemVer) -ring $(nkdAgility_Ring) -GH_TOKEN $(GH_TOKEN)' # Documentation release Stage - stage: DocsReleaseStage @@ -553,6 +576,7 @@ stages: GitVersion.SemVer: $[ stageDependencies.SetupStage.ConfigJob.outputs['GitVersion.SemVer'] ] GitVersion.PreReleaseTag: $[ stageDependencies.SetupStage.ConfigJob.outputs['GitVersion.PreReleaseTag'] ] isPreRelease: $[ stageDependencies.SetupStage.ConfigJob.outputs['VersionOut.isPreRelease'] ] + nkdAgility_Ring: $[ stageDependencies.SetupStage.ConfigJob.outputs['VersionOut.nkdAgility_Ring'] ] steps: - task: DownloadBuildArtifacts@1 inputs: @@ -582,6 +606,7 @@ stages: Write-Host "Remote Path: $remoteDirectory" Write-Host "##vso[task.setvariable variable=remoteDirectory;isOutput=true]$remoteDirectory" - task: CopyFilesOverSSH@0 + condition: and(succeeded(), ne(variables['nkdAgility_Ring'], "Canary")) inputs: sshEndpoint: 'nakedalmweb-learn' sourceFolder: '$(System.ArtifactsDirectory)/site' diff --git a/build/releaseExtension.ps1 b/build/releaseExtension.ps1 new file mode 100644 index 000000000..02902b4c0 --- /dev/null +++ b/build/releaseExtension.ps1 @@ -0,0 +1,30 @@ +<# + Script description. + + Some notes. +#> +param ( + # name of the output folder + [Parameter(Mandatory=$true)] + [string]$vsixFile, + [Parameter(Mandatory=$true)] + [string]$marketplaceToken +) +Write-Output "Azure DevOps Migration Tools (Extension) Release" +Write-Output "----------------------------------------" +Write-Output "Extension file: $extensionFile" +Write-Output "----------------------------------------" +if (((npm list -g tfx-cli) -join "," ).Contains("empty")) { + Write-Output "Installing tfx-cli" + npm i -g tfx-cli +} else { Write-Output "Detected tfx-cli"} +Write-Output "----------------------------------------" +# Login +Write-Output ">>>>> Login" +tfx login --service-url https://marketplace.visualstudio.com --token $marketplaceToken +Write-Output "----------------------------------------" +Write-Output "----------------------------------------" +# Build TFS Extension +Write-Output ">>>>> Send TFS Extension" +tfx extension publish --vsix "$vsixFile" --token $marketplaceToken +Write-Output "----------------------------------------" \ No newline at end of file diff --git a/build/releaseWingetPackage.ps1 b/build/releaseWingetPackage.ps1 index d81b26d6e..543598824 100644 --- a/build/releaseWingetPackage.ps1 +++ b/build/releaseWingetPackage.ps1 @@ -8,7 +8,7 @@ param ( # name of the releaseTag [Parameter(Mandatory=$true)] - [string]$releaseTag, + [string]$ring, # GH_TOKEN [Parameter(Mandatory=$true)] @@ -33,13 +33,21 @@ Write-Host $"##[warning] $installURL" $wigetPackageId = "nkdAgility.AzureDevOpsMigrationTools" -if ($releaseTag -eq "Preview") -{ - $wigetPackageId = "$wigetPackageId.Preview" -} Write-Host "Winget Create with $wigetPackageId" - -./wingetcreate.exe update --submit --token $GH_TOKEN --urls $installURL --version $version $wigetPackageId +switch ($ring) { + "Preview" { + $wigetPackageId = "$wigetPackageId.Preview" + ./wingetcreate.exe update --submit --token $GH_TOKEN --urls $installURL --version $version $wigetPackageId + } + "Release" { + $wigetPackageId = "nkdAgility.AzureDevOpsMigrationTools" + ./wingetcreate.exe update --submit --token $GH_TOKEN --urls $installURL --version $version $wigetPackageId + } + default { + $wigetPackageId = "nkdAgility.AzureDevOpsMigrationTools.Canary" + Write-Host "We dont ship canary builds!" + } +} Write-Host "Deployed : $wigetPackageId" diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index 9a3480cc5..a0b92ea79 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -344,6 +344,41 @@ + + + A work item query based on WIQL to select only important work items. To migrate all leave this empty. See [WIQL Query Bits](#wiql-query-bits) + + AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') + + + + A list of work items to import + + [] + + + + This loads all of the work items already saved to the Target and removes them from the Source work item list prior to commencing the run. + While this may take some time in large data sets it reduces the time of the overall migration significantly if you need to restart. + + true + + + + Pause after each work item is migrated + + false + + + + **beta** If set to a number greater than 0 work items that fail to save will retry after a number of seconds equal to the retry count. + This allows for periodic network glitches not to end the process. + + 5 + + + + @@ -541,51 +576,111 @@ - + + from https://gist.github.com/pietergheysens/792ed505f09557e77ddfc1b83531e4fb + + - A work item query based on WIQL to select only important work items. To migrate all leave this empty. See [WIQL Query Bits](#wiql-query-bits) + Retrieve Image Format for a given byte array - AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') + Image to check + From https://stackoverflow.com/a/9446045/1317161 + Image format - + - A list of work items to import + Gets whether the current repository is dirty. - [] - + - This loads all of the work items already saved to the Target and removes them from the Source work item list prior to commencing the run. - While this may take some time in large data sets it reduces the time of the overall migration significantly if you need to restart. + => @"true" - true - + - Pause after each work item is migrated + => @"https://github.com/nkdAgility/azure-devops-migration-tools.git" - false - + - **beta** If set to a number greater than 0 work items that fail to save will retry after a number of seconds equal to the retry count. - This allows for periodic network glitches not to end the process. + => @"feature/small-changes-group-1" - 5 - - + + + => @"0e5f65f" + - - from https://gist.github.com/pietergheysens/792ed505f09557e77ddfc1b83531e4fb + + + => @"0e5f65ff4368b8da72e7e64991c4d0a095637a02" + - + - Retrieve Image Format for a given byte array + => @"2024-07-26T14:20:01+01:00" + + + + + => @"26" + + + + + => @"v15.1.5-Preview.2-26-g0e5f65f" + + + + + => @"v15.1.5-Preview.2" + + + + + => @"15" + + + + + => @"1" + + + + + => @"5" + + + + + => @"15" + + + + + => @"1" + + + + + => @"31" + + + + + => @"Preview.2" + + + + + => @"-Preview.2" + + + + + => @"Tag" - Image to check - From https://stackoverflow.com/a/9446045/1317161 - Image format diff --git a/docs/Reference/Generated/VstsSyncMigrator.Core.xml b/docs/Reference/Generated/VstsSyncMigrator.Core.xml index 63c78e83e..c415a50c4 100644 --- a/docs/Reference/Generated/VstsSyncMigrator.Core.xml +++ b/docs/Reference/Generated/VstsSyncMigrator.Core.xml @@ -123,18 +123,18 @@ alpha Profiles - + - The `WorkItemDelete` processor allows you to delete any amount of work items that meet the query. - **DANGER:** This is not a recoverable action and should be use with extream caution. + This processor allows you to make changes in place where we load from teh Target and update the Target. This is used for bulk updates with the most common reason being a process template change. - ready WorkItem - + - This processor allows you to make changes in place where we load from teh Target and update the Target. This is used for bulk updates with the most common reason being a process template change. + The `WorkItemDelete` processor allows you to delete any amount of work items that meet the query. + **DANGER:** This is not a recoverable action and should be use with extream caution. + ready WorkItem diff --git a/docs/_data/reference.v1.processors.workitemupdate.yaml b/docs/_data/reference.v1.processors.workitembulkeditprocessor.yaml similarity index 90% rename from docs/_data/reference.v1.processors.workitemupdate.yaml rename to docs/_data/reference.v1.processors.workitembulkeditprocessor.yaml index 8b64b0fd6..6d4f96c92 100644 --- a/docs/_data/reference.v1.processors.workitemupdate.yaml +++ b/docs/_data/reference.v1.processors.workitembulkeditprocessor.yaml @@ -1,11 +1,11 @@ -optionsClassName: WorkItemUpdateConfig -optionsClassFullName: MigrationTools._EngineV1.Configuration.Processing.WorkItemUpdateConfig +optionsClassName: WorkItemBulkEditProcessorConfig +optionsClassFullName: MigrationTools._EngineV1.Configuration.Processing.WorkItemBulkEditProcessorConfig configurationSamples: - name: default description: code: >- { - "$type": "WorkItemUpdateConfig", + "$type": "WorkItemBulkEditProcessorConfig", "Enabled": false, "WhatIf": false, "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdFieldName] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", @@ -14,9 +14,9 @@ configurationSamples: "PauseAfterEachWorkItem": false, "WorkItemCreateRetryLimit": 0 } - sampleFor: MigrationTools._EngineV1.Configuration.Processing.WorkItemUpdateConfig + sampleFor: MigrationTools._EngineV1.Configuration.Processing.WorkItemBulkEditProcessorConfig description: This processor allows you to make changes in place where we load from teh Target and update the Target. This is used for bulk updates with the most common reason being a process template change. -className: WorkItemUpdate +className: WorkItemBulkEditProcessor typeName: Processors architecture: v1 options: @@ -50,5 +50,5 @@ options: defaultValue: '[]' status: missng XML code comments processingTarget: WorkItem -classFile: /src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemUpdate.cs -optionsClassFile: /src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemUpdateConfig.cs +classFile: /src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemBulkEditProcessor.cs +optionsClassFile: /src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemBulkEditProcessorConfig.cs diff --git a/docs/collections/_reference/reference.v1.processors.workitemupdate.md b/docs/collections/_reference/reference.v1.processors.workitembulkeditprocessor.md similarity index 84% rename from docs/collections/_reference/reference.v1.processors.workitemupdate.md rename to docs/collections/_reference/reference.v1.processors.workitembulkeditprocessor.md index 6dda7c76b..a1be1281c 100644 --- a/docs/collections/_reference/reference.v1.processors.workitemupdate.md +++ b/docs/collections/_reference/reference.v1.processors.workitembulkeditprocessor.md @@ -1,12 +1,12 @@ --- -optionsClassName: WorkItemUpdateConfig -optionsClassFullName: MigrationTools._EngineV1.Configuration.Processing.WorkItemUpdateConfig +optionsClassName: WorkItemBulkEditProcessorConfig +optionsClassFullName: MigrationTools._EngineV1.Configuration.Processing.WorkItemBulkEditProcessorConfig configurationSamples: - name: default description: code: >- { - "$type": "WorkItemUpdateConfig", + "$type": "WorkItemBulkEditProcessorConfig", "Enabled": false, "WhatIf": false, "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdFieldName] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", @@ -15,9 +15,9 @@ configurationSamples: "PauseAfterEachWorkItem": false, "WorkItemCreateRetryLimit": 0 } - sampleFor: MigrationTools._EngineV1.Configuration.Processing.WorkItemUpdateConfig + sampleFor: MigrationTools._EngineV1.Configuration.Processing.WorkItemBulkEditProcessorConfig description: This processor allows you to make changes in place where we load from teh Target and update the Target. This is used for bulk updates with the most common reason being a process template change. -className: WorkItemUpdate +className: WorkItemBulkEditProcessor typeName: Processors architecture: v1 options: @@ -51,24 +51,23 @@ options: defaultValue: '[]' status: missng XML code comments processingTarget: WorkItem -classFile: /src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemUpdate.cs -optionsClassFile: /src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemUpdateConfig.cs - +classFile: /src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemBulkEditProcessor.cs +optionsClassFile: /src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemBulkEditProcessorConfig.cs redirectFrom: [] layout: reference toc: true -permalink: /Reference/v1/Processors/WorkItemUpdate/ -title: WorkItemUpdate +permalink: /Reference/v1/Processors/WorkItemBulkEditProcessor/ +title: WorkItemBulkEditProcessor categories: - Processors - v1 topics: - topic: notes - path: /docs/Reference/v1/Processors/WorkItemUpdate-notes.md + path: /docs/Reference/v1/Processors/WorkItemBulkEditProcessor-notes.md exists: false markdown: '' - topic: introduction - path: /docs/Reference/v1/Processors/WorkItemUpdate-introduction.md + path: /docs/Reference/v1/Processors/WorkItemBulkEditProcessor-introduction.md exists: false markdown: '' diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Endpoints/TfsWorkItemEndPointTests.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Endpoints/TfsWorkItemEndPointTests.cs index 63b4712bd..4561ea89c 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Endpoints/TfsWorkItemEndPointTests.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Endpoints/TfsWorkItemEndPointTests.cs @@ -19,7 +19,7 @@ public void Setup() Services = ServiceProviderHelper.GetServices(); } - [TestMethod(), TestCategory("L3"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L3")] public void TfsWorkItemEndPointTest() { var endpoint = Services.GetRequiredService(); @@ -28,7 +28,7 @@ public void TfsWorkItemEndPointTest() Assert.IsNotNull(endpoint); } - [TestMethod(), TestCategory("L3"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L3")] public void TfsWorkItemEndPointConfigureTest() { var endpoint = Services.GetRequiredService(); @@ -36,7 +36,7 @@ public void TfsWorkItemEndPointConfigureTest() Assert.IsNotNull(endpoint); } - [TestMethod(), TestCategory("L3"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L3")] public void TfsWorkItemEndPointGetWorkItemsTest() { var endpoint = Services.GetRequiredService(); @@ -45,7 +45,7 @@ public void TfsWorkItemEndPointGetWorkItemsTest() Assert.AreEqual(13, result.Count()); } - [TestMethod(), TestCategory("L3"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L3")] public void TfsWorkItemEndPointGetWorkItemsQueryTest() { TfsWorkItemEndpoint endpoint = Services.GetRequiredService(); diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/MigrationTools.Clients.AzureDevops.ObjectModel.Tests.csproj b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/MigrationTools.Clients.AzureDevops.ObjectModel.Tests.csproj index 5e080088e..de4d64e22 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/MigrationTools.Clients.AzureDevops.ObjectModel.Tests.csproj +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/MigrationTools.Clients.AzureDevops.ObjectModel.Tests.csproj @@ -13,8 +13,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -27,6 +27,7 @@ + diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ProcessorEnrichers/TfsNodeStructureTests.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ProcessorEnrichers/TfsNodeStructureTests.cs index 8ef065246..80fb971c4 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ProcessorEnrichers/TfsNodeStructureTests.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ProcessorEnrichers/TfsNodeStructureTests.cs @@ -37,7 +37,7 @@ public void Setup() }); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void GetTfsNodeStructure_WithDifferentAreaPath() { var nodeStructure = _services.GetRequiredService(); diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ProcessorEnrichers/TfsRevisionManagerTests.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ProcessorEnrichers/TfsRevisionManagerTests.cs index a31a86569..83ebad318 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ProcessorEnrichers/TfsRevisionManagerTests.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ProcessorEnrichers/TfsRevisionManagerTests.cs @@ -45,7 +45,7 @@ private static List GetWorkItemWithRevisions(DateTime currentDateT } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsRevisionManagerInSync1() { var peOptions = GetTfsRevisionManagerOptions(); @@ -62,7 +62,7 @@ public void TfsRevisionManagerInSync1() } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsRevisionManagerInSync10() { var peOptions = GetTfsRevisionManagerOptions(); @@ -79,7 +79,7 @@ public void TfsRevisionManagerInSync10() } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsRevisionManagerSync1() { var peOptions = GetTfsRevisionManagerOptions(); @@ -95,7 +95,7 @@ public void TfsRevisionManagerSync1() Assert.AreEqual(1, revs.Count); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsRevisionManagerSync10() { var peOptions = GetTfsRevisionManagerOptions(); @@ -111,7 +111,7 @@ public void TfsRevisionManagerSync10() Assert.AreEqual(10, revs.Count); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsRevisionManagerReplayRevisionsOff() { var peOptions = GetTfsRevisionManagerOptions(); @@ -129,7 +129,7 @@ public void TfsRevisionManagerReplayRevisionsOff() } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsRevisionManagerMaxRevision51() { var peOptions = GetTfsRevisionManagerOptions(); @@ -146,7 +146,7 @@ public void TfsRevisionManagerMaxRevision51() Assert.AreEqual(0, revs.Count); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsRevisionManagerMaxRevision56() { var peOptions = GetTfsRevisionManagerOptions(); @@ -163,7 +163,7 @@ public void TfsRevisionManagerMaxRevision56() Assert.AreEqual(5, revs.Count); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsRevisionManagerMaxRevision59() { var peOptions = GetTfsRevisionManagerOptions(); @@ -179,7 +179,7 @@ public void TfsRevisionManagerMaxRevision59() Assert.AreEqual(5, revs.Count); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsRevisionManagerDatesMustBeIncreasing() { var peOptions = GetTfsRevisionManagerOptions(); diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Processors/TfsSharedQueryProcessorTests.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Processors/TfsSharedQueryProcessorTests.cs index 176dc3f9b..2ce4cbd15 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Processors/TfsSharedQueryProcessorTests.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Processors/TfsSharedQueryProcessorTests.cs @@ -9,14 +9,14 @@ namespace MigrationTools.Processors.Tests [TestClass()] public class TfsSharedQueryProcessorTests : TfsProcessorTests { - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsSharedQueryProcessorTest() { var x = Services.GetRequiredService(); Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsSharedQueryProcessorConfigureTest() { var y = new TfsSharedQueryProcessorOptions @@ -31,7 +31,7 @@ public void TfsSharedQueryProcessorConfigureTest() Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsSharedQueryProcessorRunTest() { var y = new TfsSharedQueryProcessorOptions @@ -46,7 +46,7 @@ public void TfsSharedQueryProcessorRunTest() Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L3"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L3")] public void TfsSharedQueryProcessorNoEnrichersTest() { // Senario 1 Migration from Tfs to Tfs with no Enrichers. diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Processors/TfsTeamSettingsProcessorTests.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Processors/TfsTeamSettingsProcessorTests.cs index d9a86b241..3a8ddb8e8 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Processors/TfsTeamSettingsProcessorTests.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/Processors/TfsTeamSettingsProcessorTests.cs @@ -6,14 +6,14 @@ namespace MigrationTools.Processors.Tests [TestClass()] public class TfsTeamSettingsProcessorTests : TfsProcessorTests { - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsTeamSettingsProcessorTest() { var x = Services.GetRequiredService(); Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsTeamSettingsProcessorConfigureTest() { var y = new TfsTeamSettingsProcessorOptions @@ -30,7 +30,7 @@ public void TfsTeamSettingsProcessorConfigureTest() Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L0")] public void TfsTeamSettingsProcessorRunTest() { var y = new TfsTeamSettingsProcessorOptions @@ -47,7 +47,7 @@ public void TfsTeamSettingsProcessorRunTest() Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L3"), TestCategory("AzureDevOps.ObjectModel")] + [TestMethod(), TestCategory("L3")] public void TfsTeamSettingsProcessorNoEnrichersTest() { // Senario 1 Migration from Tfs to Tfs with no Enrichers. diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ServiceProviderHelper.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ServiceProviderHelper.cs index 19ab26968..2375fa7a2 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ServiceProviderHelper.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/ServiceProviderHelper.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MigrationTools.Endpoints; +using MigrationTools.Fakes; +using MigrationTools.Services; using MigrationTools.TestExtensions; namespace MigrationTools.Tests @@ -23,6 +25,9 @@ public static ServiceProvider GetServices() AddTfsTeamEndpoint(services, "TfsTeamSettingsSource", "migrationSource1"); AddTfsTeamEndpoint(services, "TfsTeamSettingsTarget", "migrationTarget1"); + services.AddSingleton(); + services.AddSingleton(); + return services.BuildServiceProvider(); } diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsMigrationClient.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsMigrationClient.cs index 24efc558e..1199b65a5 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsMigrationClient.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsMigrationClient.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net; using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.TeamFoundation; using Microsoft.TeamFoundation.Client; using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.WebApi; @@ -155,6 +156,13 @@ private TfsTeamProjectCollection GetDependantTfsCollection(NetworkCredential cre Log.Information("Access granted to {CollectionUrl} for {Name} ({Account})", TfsConfig.Collection, y.AuthorizedIdentity.DisplayName, y.AuthorizedIdentity.UniqueName); _Telemetry.TrackDependency(new DependencyTelemetry("TfsObjectModel", TfsConfig.Collection.ToString(), "GetWorkItem", null, startTime, timer.Elapsed, "200", true)); } + catch (TeamFoundationServerUnauthorizedException ex) + { + timer.Stop(); + _Telemetry.TrackDependency(new DependencyTelemetry("TfsObjectModel", TfsConfig.Collection.ToString(), "GetWorkItem", null, startTime, timer.Elapsed, "401", false)); + Log.Error(ex, "Unable to configure store: Check persmissions and credentials for {AuthenticationMode}!", _config.AuthenticationMode); + Environment.Exit(-1); + } catch (Exception ex) { timer.Stop(); @@ -167,7 +175,7 @@ private TfsTeamProjectCollection GetDependantTfsCollection(NetworkCredential cre new Dictionary { { "Time",timer.ElapsedMilliseconds } }); - Log.Error(ex, "Unable to configure store: Check persmissions and credentials for {AuthenticationMode}!", _config.AuthenticationMode); + Log.Error("Unable to configure store: Check persmissions and credentials for {AuthenticationMode}: " + ex.Message, _config.AuthenticationMode); switch (_config.AuthenticationMode) { case AuthenticationMode.AccessToken: diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemMigrationClient.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemMigrationClient.cs index 8c84a40aa..64201db69 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemMigrationClient.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemMigrationClient.cs @@ -155,7 +155,7 @@ public override WorkItemData GetWorkItem(int id) } var startTime = DateTime.UtcNow; var timer = System.Diagnostics.Stopwatch.StartNew(); - WorkItem y; + WorkItem y = null ; try { Log.Debug("TfsWorkItemMigrationClient::GetWorkItem({id})", id); @@ -167,13 +167,15 @@ public override WorkItemData GetWorkItem(int id) { Telemetry.TrackException(ex, new Dictionary { - { "CollectionUrl", MigrationClient.Config.AsTeamProjectConfig().Collection.ToString() } + { "CollectionUrl", MigrationClient.Config.AsTeamProjectConfig().Collection.ToString() }, + { "Project", MigrationClient.Config.AsTeamProjectConfig().Project.ToString() }, + { "WorkItem", id.ToString() } }, new Dictionary { { "Time",timer.ElapsedMilliseconds } }); Log.Error(ex, "Unable to GetWorkItem with id[{id}]", id); - throw; + Environment.Exit(-1); } finally { timer.Stop(); diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemQuery.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemQuery.cs index cd4188993..6e82f5ef2 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemQuery.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemQuery.cs @@ -82,6 +82,13 @@ private IList GetWorkItemsFromQuery(TfsWorkItemMigrationClient wiClien timer.Stop(); Telemetry.TrackDependency(new DependencyTelemetry("TfsObjectModel", MigrationClient.Config.AsTeamProjectConfig().Collection.ToString(), "GetWorkItemsFromQuery", null, startTime, timer.Elapsed, "200", true)); } + catch (ValidationException ex) + { + timer.Stop(); + Telemetry.TrackDependency(new DependencyTelemetry("TfsObjectModel", MigrationClient.Config.AsTeamProjectConfig().Collection.ToString(), "GetWorkItemsFromQuery", null, startTime, timer.Elapsed, "500", false)); + Log.Error(ex, " Error running query"); + Environment.Exit(-1); + } catch (Exception ex) { timer.Stop(); diff --git a/src/MigrationTools.Clients.AzureDevops.Rest.Tests/MigrationTools.Clients.AzureDevops.Rest.Tests.csproj b/src/MigrationTools.Clients.AzureDevops.Rest.Tests/MigrationTools.Clients.AzureDevops.Rest.Tests.csproj index 87b4321b5..d6e5a72e3 100644 --- a/src/MigrationTools.Clients.AzureDevops.Rest.Tests/MigrationTools.Clients.AzureDevops.Rest.Tests.csproj +++ b/src/MigrationTools.Clients.AzureDevops.Rest.Tests/MigrationTools.Clients.AzureDevops.Rest.Tests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -19,6 +19,8 @@ + + diff --git a/src/MigrationTools.Clients.AzureDevops.Rest.Tests/Processors/AzureDevOpsPipelineProcessorTests.cs b/src/MigrationTools.Clients.AzureDevops.Rest.Tests/Processors/AzureDevOpsPipelineProcessorTests.cs index 9a26eb8f1..b9c28cd75 100644 --- a/src/MigrationTools.Clients.AzureDevops.Rest.Tests/Processors/AzureDevOpsPipelineProcessorTests.cs +++ b/src/MigrationTools.Clients.AzureDevops.Rest.Tests/Processors/AzureDevOpsPipelineProcessorTests.cs @@ -11,14 +11,14 @@ namespace MigrationTools.Processors.Tests [TestClass()] public class AzureDevOpsPipelineProcessorTests : AzureDevOpsProcessorTests { - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.REST")] + [TestMethod(), TestCategory("L0")] public void AzureDevOpsPipelineProcessorTest() { var x = Services.GetRequiredService(); Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.REST")] + [TestMethod(), TestCategory("L0")] public void AzureDevOpsPipelineProcessorConfigureTest() { var y = new AzureDevOpsPipelineProcessorOptions @@ -32,7 +32,7 @@ public void AzureDevOpsPipelineProcessorConfigureTest() Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L0"), TestCategory("AzureDevOps.REST")] + [TestMethod(), TestCategory("L0")] public void AzureDevOpsPipelineProcessorRunTest() { var y = new AzureDevOpsPipelineProcessorOptions @@ -46,7 +46,7 @@ public void AzureDevOpsPipelineProcessorRunTest() Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L3"), TestCategory("AzureDevOps.REST")] + [TestMethod(), TestCategory("L3")] public void AzureDevOpsPipelineProcessorNoEnrichersTest() { // Senario 1 Migration from Tfs to Tfs with no Enrichers. @@ -57,7 +57,7 @@ public void AzureDevOpsPipelineProcessorNoEnrichersTest() Assert.AreEqual(ProcessingStatus.Complete, processor.Status); } - [TestMethod, TestCategory("L3"), TestCategory("AzureDevOps.REST")] + [TestMethod, TestCategory("L3")] public void AzureDevOpsPipelineProcessorSelectedBuildDefinitionsTest() { var config = new AzureDevOpsPipelineProcessorOptions @@ -89,7 +89,7 @@ public void AzureDevOpsPipelineProcessorSelectedBuildDefinitionsTest() Assert.AreEqual(2, InMemorySink.Instance.LogEvents.Count(configuredMessageFilter)); } - [TestMethod, TestCategory("L3"), TestCategory("AzureDevOps.REST")] + [TestMethod, TestCategory("L3")] public void AzureDevOpsPipelineProcessorSelectedReleaseDefinitionsTest() { var config = new AzureDevOpsPipelineProcessorOptions diff --git a/src/MigrationTools.Clients.AzureDevops.Rest.Tests/ServiceProviderHelper.cs b/src/MigrationTools.Clients.AzureDevops.Rest.Tests/ServiceProviderHelper.cs index 12fc32133..0ffb0b124 100644 --- a/src/MigrationTools.Clients.AzureDevops.Rest.Tests/ServiceProviderHelper.cs +++ b/src/MigrationTools.Clients.AzureDevops.Rest.Tests/ServiceProviderHelper.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MigrationTools.Endpoints; +using MigrationTools.Fakes; +using MigrationTools.Services; using MigrationTools.TestExtensions; namespace MigrationTools.Tests @@ -15,9 +17,13 @@ internal static ServiceProvider GetServices() services.AddMigrationToolServices(); services.AddMigrationToolServicesForClientAzureDevopsRest(configuration); + AddEndpoint(services, "Source", "migrationSource1"); AddEndpoint(services, "Target", "migrationTarget1"); + services.AddSingleton(); + services.AddSingleton(); + return services.BuildServiceProvider(); } diff --git a/src/MigrationTools.Clients.FileSystem.Tests/MigrationTools.Clients.FileSystem.Tests.csproj b/src/MigrationTools.Clients.FileSystem.Tests/MigrationTools.Clients.FileSystem.Tests.csproj index d98ce49cb..79ad15394 100644 --- a/src/MigrationTools.Clients.FileSystem.Tests/MigrationTools.Clients.FileSystem.Tests.csproj +++ b/src/MigrationTools.Clients.FileSystem.Tests/MigrationTools.Clients.FileSystem.Tests.csproj @@ -12,8 +12,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -22,6 +22,7 @@ + diff --git a/src/MigrationTools.Clients.FileSystem.Tests/ServiceProviderHelper.cs b/src/MigrationTools.Clients.FileSystem.Tests/ServiceProviderHelper.cs index 58b89be60..c9a23d53f 100644 --- a/src/MigrationTools.Clients.FileSystem.Tests/ServiceProviderHelper.cs +++ b/src/MigrationTools.Clients.FileSystem.Tests/ServiceProviderHelper.cs @@ -1,4 +1,6 @@ using Microsoft.Extensions.DependencyInjection; +using MigrationTools.Fakes; +using MigrationTools.Services; using MigrationTools.TestExtensions; namespace MigrationTools.Tests @@ -13,6 +15,9 @@ internal static ServiceProvider GetServices() services.AddMigrationToolServices(); services.AddMigrationToolServicesForClientFileSystem(); + services.AddSingleton(); + services.AddSingleton(); + return services.BuildServiceProvider(); } } diff --git a/src/MigrationTools.Clients.InMemory.Tests/MigrationTools.Clients.InMemory.Tests.csproj b/src/MigrationTools.Clients.InMemory.Tests/MigrationTools.Clients.InMemory.Tests.csproj index eef5132cf..dd0a554e0 100644 --- a/src/MigrationTools.Clients.InMemory.Tests/MigrationTools.Clients.InMemory.Tests.csproj +++ b/src/MigrationTools.Clients.InMemory.Tests/MigrationTools.Clients.InMemory.Tests.csproj @@ -12,8 +12,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -22,6 +22,8 @@ + + diff --git a/src/MigrationTools.Clients.InMemory.Tests/ServiceProviderHelper.cs b/src/MigrationTools.Clients.InMemory.Tests/ServiceProviderHelper.cs index 651ce61ab..418b09d02 100644 --- a/src/MigrationTools.Clients.InMemory.Tests/ServiceProviderHelper.cs +++ b/src/MigrationTools.Clients.InMemory.Tests/ServiceProviderHelper.cs @@ -1,4 +1,6 @@ using Microsoft.Extensions.DependencyInjection; +using MigrationTools.Fakes; +using MigrationTools.Services; using MigrationTools.TestExtensions; namespace MigrationTools.Tests @@ -13,6 +15,9 @@ internal static ServiceProvider GetServices() services.AddMigrationToolServices(); services.AddMigrationToolServicesForClientInMemory(); + services.AddSingleton(); + services.AddSingleton(); + return services.BuildServiceProvider(); } } diff --git a/src/MigrationTools.ConsoleCore/Properties/launchSettings.json b/src/MigrationTools.ConsoleCore/Properties/launchSettings.json index 0ad86d657..cb8b11e02 100644 --- a/src/MigrationTools.ConsoleCore/Properties/launchSettings.json +++ b/src/MigrationTools.ConsoleCore/Properties/launchSettings.json @@ -1,6 +1,9 @@ { "profiles": { - "MigrationTools.ConsoleUI": { + "empty": { + "commandName": "Project" + }, + "execute ": { "commandName": "Project", "commandLineArgs": "execute --config \"configuration.json\"" } diff --git a/src/MigrationTools.ConsoleFull/Properties/launchSettings.json b/src/MigrationTools.ConsoleFull/Properties/launchSettings.json index 1e63b28b4..338a832c3 100644 --- a/src/MigrationTools.ConsoleFull/Properties/launchSettings.json +++ b/src/MigrationTools.ConsoleFull/Properties/launchSettings.json @@ -19,6 +19,9 @@ "init2": { "commandName": "Project", "commandLineArgs": "init -c configuration2.json --options Fullv2" + }, + "empty": { + "commandName": "Project" } } } \ No newline at end of file diff --git a/src/MigrationTools.Fakes/FakeMigrationToolVersion.cs b/src/MigrationTools.Fakes/FakeMigrationToolVersion.cs new file mode 100644 index 000000000..dabe9cd68 --- /dev/null +++ b/src/MigrationTools.Fakes/FakeMigrationToolVersion.cs @@ -0,0 +1,12 @@ +using MigrationTools.Services; + +namespace MigrationTools.Fakes +{ + public class FakeMigrationToolVersion : IMigrationToolVersion + { + public (Version version, string PreReleaseLabel, string versionString) GetRunningVersion() + { + return (new System.Version("0.0.0"), "test", "0.0.0-test"); + } + } +} \ No newline at end of file diff --git a/src/MigrationTools.Fakes/FakeMigrationToolVersionInfo.cs b/src/MigrationTools.Fakes/FakeMigrationToolVersionInfo.cs new file mode 100644 index 000000000..f20f8cdfb --- /dev/null +++ b/src/MigrationTools.Fakes/FakeMigrationToolVersionInfo.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using MigrationTools.Services; + +namespace MigrationTools.Fakes +{ + public class FakeMigrationToolVersionInfo : MigrationToolVersionInfo + { + public FakeMigrationToolVersionInfo() + { + ProductVersion = "0.0.0-test+7acec2e6266f5f05b2807264ee8f1db7b94b1949"; + FileVersion = "0.0.0.0"; + GitTag = "v0.0.0-test.0-0-g7acec2e"; + } + public FakeMigrationToolVersionInfo(string productVersion, string fileVersion, string gitTag) + { + ProductVersion = productVersion; + FileVersion = fileVersion; + GitTag = gitTag; + } + } +} diff --git a/src/MigrationTools.Fakes/MigrationTools.Helpers.Tests.csproj b/src/MigrationTools.Fakes/MigrationTools.Helpers.Tests.csproj new file mode 100644 index 000000000..4bb328964 --- /dev/null +++ b/src/MigrationTools.Fakes/MigrationTools.Helpers.Tests.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0;net8.0 + enable + enable + + + + + + + + + + + diff --git a/src/MigrationTools.Host.Tests/MigrationHostTests.cs b/src/MigrationTools.Host.Tests/MigrationHostTests.cs index bb97d24d6..541513ccd 100644 --- a/src/MigrationTools.Host.Tests/MigrationHostTests.cs +++ b/src/MigrationTools.Host.Tests/MigrationHostTests.cs @@ -16,14 +16,14 @@ public void Setup() host = MigrationToolHost.CreateDefaultBuilder(new string[] { "execute", "-c", "configuration.json" }).Build(); } - [TestMethod, TestCategory("L2"), TestCategory("Host")] + [TestMethod, TestCategory("L2")] [Ignore("need to ignore for now, missing a good config file for non-objectmodel")] public void MigrationHostTest() { IMigrationEngine mh = host.Services.GetRequiredService(); } - [TestMethod, TestCategory("L1"), TestCategory("Host")] + [TestMethod, TestCategory("L1")] [Ignore("need to ignore for now, untill we get some generic field maps")] public void TestEngineExecuteEmptyProcessors() { @@ -33,7 +33,7 @@ public void TestEngineExecuteEmptyProcessors() me.Run(); } - [TestMethod, TestCategory("L1"), TestCategory("Host")] + [TestMethod, TestCategory("L1")] [Ignore("need to ignore for now, missing a good config file for non-objectmodel")] public void TestEngineExecuteEmptyFieldMaps() { @@ -44,7 +44,7 @@ public void TestEngineExecuteEmptyFieldMaps() me.Run(); } - [TestMethod, TestCategory("L3"), TestCategory("L2"), TestCategory("Host")] + [TestMethod, TestCategory("L3"), TestCategory("L2")] [Ignore("need to ignore for now, missing a good config file for non-objectmodel")] public void TestEngineExecuteProcessors() { diff --git a/src/MigrationTools.Host.Tests/MigrationTools.Host.Tests.csproj b/src/MigrationTools.Host.Tests/MigrationTools.Host.Tests.csproj index ba9e76aae..c70cc31c0 100644 --- a/src/MigrationTools.Host.Tests/MigrationTools.Host.Tests.csproj +++ b/src/MigrationTools.Host.Tests/MigrationTools.Host.Tests.csproj @@ -17,8 +17,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -26,6 +26,7 @@ + diff --git a/src/MigrationTools.Host.Tests/Services/DetectVersionService2Tests.cs b/src/MigrationTools.Host.Tests/Services/DetectVersionService2Tests.cs index 25338dfed..dacdd87dc 100644 --- a/src/MigrationTools.Host.Tests/Services/DetectVersionService2Tests.cs +++ b/src/MigrationTools.Host.Tests/Services/DetectVersionService2Tests.cs @@ -1,9 +1,13 @@ -using Microsoft.Extensions.DependencyInjection; +using MigrationTools.Host.Services; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using MigrationTools.Services; using Serilog; using Serilog.Events; +using MigrationTools.Host.Tests; +using MigrationTools.Tests; +using MigrationTools.Fakes; namespace MigrationTools.Host.Services.Tests { @@ -26,11 +30,10 @@ public void Setup() public void DetectVersionServiceTest_Initialise() { var loggerFactory = new LoggerFactory().AddSerilog(); - IDetectVersionService2 dos = new DetectVersionService2(new TelemetryLoggerMock(), new Logger(loggerFactory)); + IDetectVersionService2 dos = new DetectVersionService2(new TelemetryLoggerMock(), new Logger(loggerFactory), new FakeMigrationToolVersion()); Assert.IsNotNull(dos); } - } } \ No newline at end of file diff --git a/src/MigrationTools.Host/CommandLine/ExecuteOptions.cs b/src/MigrationTools.Host/CommandLine/ExecuteOptions.cs deleted file mode 100644 index 1a327176e..000000000 --- a/src/MigrationTools.Host/CommandLine/ExecuteOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CommandLine; - -namespace MigrationTools.Host.CommandLine -{ - [Verb("execute", HelpText = "Record changes to the repository.")] - public class ExecuteOptions - { - [Option('c', "config", Required = true, HelpText = "Configuration file to be processed.")] - public string ConfigFile { get; set; } - - [Option("sourceDomain", Required = false, HelpText = "Domain used to connect to the source TFS instance.")] - public string SourceDomain { get; set; } - - [Option("sourceUserName", Required = false, HelpText = "User Name used to connect to the source TFS instance.")] - public string SourceUserName { get; set; } - - [Option("sourcePassword", Required = false, HelpText = "Password used to connect to source TFS instance.")] - public string SourcePassword { get; set; } - - [Option("targetDomain", Required = false, HelpText = "Domain used to connect to the target TFS instance.")] - public string TargetDomain { get; set; } - - [Option("targetUserName", Required = false, HelpText = "User Name used to connect to the target TFS instance.")] - public string TargetUserName { get; set; } - - [Option("targetPassword", Required = false, HelpText = "Password used to connect to target TFS instance.")] - public string TargetPassword { get; set; } - - [Option("disableTelemetry", Required = false, HelpText = "Pass 'true' to turn temimetery off. No data will be colected.")] - public string DisableTelemetry { get; set; } - } -} \ No newline at end of file diff --git a/src/MigrationTools.Host/CommandLine/InitOptions.cs b/src/MigrationTools.Host/CommandLine/InitOptions.cs deleted file mode 100644 index 41ed4921d..000000000 --- a/src/MigrationTools.Host/CommandLine/InitOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using CommandLine; - -namespace MigrationTools.Host.CommandLine -{ - [Verb("init", HelpText = "Creates initial config file")] - public class InitOptions - { - [Option('c', "config", Required = false, HelpText = "Configuration file to be processed.")] - public string ConfigFile { get; set; } - - [Option('o', "options", Required = false, Default = OptionsMode.Basic, HelpText = "Configuration file to be generated: Basic, Reference, WorkItemTracking, Fullv2, WorkItemTrackingv2")] - public OptionsMode Options { get; set; } - } -} \ No newline at end of file diff --git a/src/MigrationTools.Host/CommandLine/OptionsMode.cs b/src/MigrationTools.Host/CommandLine/OptionsMode.cs deleted file mode 100644 index 5e2feb3e4..000000000 --- a/src/MigrationTools.Host/CommandLine/OptionsMode.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MigrationTools.Host.CommandLine -{ - public enum OptionsMode - { - Reference = 0, - WorkItemTracking = 1, - Fullv2 = 2, - WorkItemTrackingv2 = 3, - Basic = 4 - } -} \ No newline at end of file diff --git a/src/MigrationTools.Host/Commands/CommandBase.cs b/src/MigrationTools.Host/Commands/CommandBase.cs new file mode 100644 index 000000000..0a45657a0 --- /dev/null +++ b/src/MigrationTools.Host/Commands/CommandBase.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using MigrationTools.Host.Services; +using MigrationTools.Services; +using Serilog; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace MigrationTools.Host.Commands +{ + internal abstract class CommandBase : AsyncCommand where TSettings : CommandSettingsBase + { + private IMigrationToolVersion _MigrationToolVersion; + private readonly IHostApplicationLifetime _LifeTime; + private readonly IDetectOnlineService _detectOnlineService; + private readonly IDetectVersionService2 _detectVersionService; + private readonly ILogger> _logger; + private readonly ITelemetryLogger _telemetryLogger; + private static Stopwatch _mainTimer = new Stopwatch(); + + public CommandBase(IHostApplicationLifetime appLifetime, IDetectOnlineService detectOnlineService, IDetectVersionService2 detectVersionService, ILogger> logger, ITelemetryLogger telemetryLogger, IMigrationToolVersion migrationToolVersion) + { + _MigrationToolVersion = migrationToolVersion; + _LifeTime = appLifetime; + _detectOnlineService = detectOnlineService; + _detectVersionService = detectVersionService; + _logger = logger; + _telemetryLogger = telemetryLogger; + } + + public override async Task ExecuteAsync(CommandContext context, TSettings settings) + { + _mainTimer.Start(); + _logger.LogTrace("Starting {CommandName}", this.GetType().Name); + _telemetryLogger.TrackEvent(this.GetType().Name); + RunStartupLogic(settings); + try + { + return await ExecuteInternalAsync(context, settings); + } + catch (Exception ex) + { + _telemetryLogger.TrackException(ex, null, null); + _logger.LogError(ex, "Unhandled exception!"); + return 1; + } + finally + { + _LifeTime.StopApplication(); + _mainTimer.Stop(); + _logger.LogInformation("Command {CommandName} completed in {Elapsed}", this.GetType().Name, _mainTimer.Elapsed); + } + } + + internal virtual async Task ExecuteInternalAsync(CommandContext context, TSettings settings) + { + // no-op + return 0; + } + + public void RunStartupLogic(TSettings settings) + { + ApplicationStartup(settings); + if (!settings.skipVersionCheck && _detectOnlineService.IsOnline()) + { + _logger.LogTrace("Package Management Info:"); + Log.Debug(" IsPackageManagerInstalled: {IsPackageManagerInstalled}", _detectVersionService.IsPackageManagerInstalled); + Log.Debug(" IsPackageInstalled: {IsPackageInstalled}", _detectVersionService.IsPackageInstalled); + Log.Debug(" IsUpdateAvailable: {IsUpdateAvailable}", _detectVersionService.IsUpdateAvailable); + Log.Debug(" IsNewLocalVersionAvailable: {IsNewLocalVersionAvailable}", _detectVersionService.IsNewLocalVersionAvailable); + Log.Debug(" IsRunningInDebug: {IsRunningInDebug}", _detectVersionService.IsRunningInDebug); + Log.Verbose("Full version data: ${_detectVersionService}", _detectVersionService); + + Log.Information("Verion Info:"); + Log.Information(" Running: {RunningVersion}", _detectVersionService.RunningVersion); + Log.Information(" Installed: {InstalledVersion}", _detectVersionService.InstalledVersion); + Log.Information(" Available: {AvailableVersion}", _detectVersionService.AvailableVersion); + + if (_detectVersionService.RunningVersion.Major == 0) + { + Log.Information("Git Info:"); + Log.Information(" Repo: {GitRepositoryUrl}", ThisAssembly.Git.RepositoryUrl); + Log.Information(" Tag: {GitTag}", ThisAssembly.Git.Tag); + Log.Information(" Branch: {GitBranch}", ThisAssembly.Git.Branch); + Log.Information(" Commits: {GitCommits}", ThisAssembly.Git.Commits); + + } + + if (!_detectVersionService.IsPackageManagerInstalled) + { + Log.Warning("Windows Client: The Windows Package Manager is not installed, we use it to determine if you have the latest version, and to make sure that this application is up to date. You can download and install it from https://aka.ms/getwinget. After which you can call `winget install {PackageId}` from the Windows Terminal to get a manged version of this program.", _detectVersionService.PackageId); + Log.Warning("Windows Server: If you are running on Windows Server you can use the experimental version of Winget, or you can still use Chocolatey to manage the install. Install chocolatey from https://chocolatey.org/install and then use `choco install vsts-sync-migrator` to install, and `choco upgrade vsts-sync-migrator` to upgrade to newer versions.", _detectVersionService.PackageId); + } + else + { + if (!_detectVersionService.IsRunningInDebug) + { + if (!_detectVersionService.IsPackageInstalled) + { + Log.Information("It looks like this application has been installed from a zip, would you like to use the managed version?"); + Console.WriteLine("Do you want exit and install the managed version? (y/n)"); + if (Console.ReadKey().Key == ConsoleKey.Y) + { + Thread.Sleep(2000); + Environment.Exit(0); + } + } + if (_detectVersionService.IsUpdateAvailable && _detectVersionService.IsPackageInstalled) + { + Log.Information("It looks like an updated version is available from Winget, would you like to exit and update?"); + Console.WriteLine("Do you want to exit and update? (y/n)"); + if (Console.ReadKey().Key == ConsoleKey.Y) + { + Thread.Sleep(2000); + Environment.Exit(0); + } + } + } + else + { + Log.Information("Running in Debug! No further version checkes....."); + } + } + } + else + { + /// not online or you have specified not to + Log.Warning("You are either not online or have chosen `skipVersionCheck`. We will not check for a newer version of the tools.", _detectVersionService.PackageId); + } + } + + + private void ApplicationStartup( TSettings settings) + { + _mainTimer.Start(); + AsciiLogo(_MigrationToolVersion.GetRunningVersion().versionString); + TelemetryNote(); + _logger.LogInformation("Start Time: {StartTime}", DateTime.Now.ToUniversalTime().ToLocalTime()); + _logger.LogInformation("Running with settings: {@settings}", settings); + _logger.LogInformation("OSVersion: {OSVersion}", Environment.OSVersion.ToString()); + _logger.LogInformation("Version (Assembly): {Version}", _MigrationToolVersion.GetRunningVersion().versionString); + } + + private void TelemetryNote() + { + _logger.LogInformation("Telemetry Note:"); + _logger.LogInformation(" We use Application Insights to collect usage and error information in order to improve the quality of the tools."); + _logger.LogInformation(" Currently we collect the following anonymous data:"); + _logger.LogInformation(" -Event data: application version, client city/country, hosting type, item count, error count, warning count, elapsed time."); + _logger.LogInformation(" -Exceptions: application errors and warnings."); + _logger.LogInformation(" -Dependencies: REST/ObjectModel calls to Azure DevOps to help us understand performance issues."); + _logger.LogInformation(" This data is tied to a session ID that is generated on each run of the application and shown in the logs. This can help with debugging. If you want to disable telemetry you can run the tool with '--disableTelemetry' on the command prompt."); + _logger.LogInformation(" Note: Exception data cannot be 100% guaranteed to not leak production data"); + _logger.LogInformation("--------------------------------------"); + } + + private void AsciiLogo(string thisVersion) + { + AnsiConsole.Write(new FigletText("Azure DevOps").LeftJustified().Color(Color.Purple)); + AnsiConsole.Write(new FigletText("Migration Tools").LeftJustified().Color(Color.Purple)); + var productName = ((AssemblyProductAttribute)Assembly.GetEntryAssembly() + .GetCustomAttributes(typeof(AssemblyProductAttribute), true)[0]).Product; + _logger.LogInformation("{productName} ", productName); + _logger.LogInformation("{thisVersion}", thisVersion); + var companyName = ((AssemblyCompanyAttribute)Assembly.GetEntryAssembly() + .GetCustomAttributes(typeof(AssemblyCompanyAttribute), true)[0]).Company; + _logger.LogInformation("{companyName} ", companyName); + _logger.LogInformation("==============================================================================="); + } + } +} diff --git a/src/MigrationTools.Host/Commands/CommandSettingsBase.cs b/src/MigrationTools.Host/Commands/CommandSettingsBase.cs new file mode 100644 index 000000000..fe6071e9b --- /dev/null +++ b/src/MigrationTools.Host/Commands/CommandSettingsBase.cs @@ -0,0 +1,40 @@ +using System.ComponentModel; +using Newtonsoft.Json; +using Spectre.Console.Cli; +using YamlDotNet.Serialization; +using System.CommandLine; + +namespace MigrationTools.Host.Commands +{ + internal class CommandSettingsBase : CommandSettings + { + [Description("Pre configure paramiters using this config file. Run `Init` to create it.")] + [CommandOption("--config|--configFile|-c")] + [DefaultValue("configuration.json")] + [JsonIgnore, YamlIgnore] + public string ConfigFile { get; set; } + + [Description("Add this paramiter to turn Telemetry off")] + [CommandOption("--disableTelemetry")] + public bool DisableTelemetry { get; set; } + + [Description("Add this paramiter to turn version check off")] + [CommandOption("--skipVersionCheck")] + public bool skipVersionCheck { get; set; } + + public static string ForceGetConfigFile(string[] args) + { + var fileOption = new Option("--config"); + fileOption.AddAlias("-c"); + fileOption.AddAlias("--configFile"); + + var rootCommand = new RootCommand(); + rootCommand.AddOption(fileOption); + + var file = rootCommand.Parse(args); + return file.GetValueForOption(fileOption); + + } + } + +} diff --git a/src/MigrationTools.Host/Commands/ExecuteMigrationCommand.cs b/src/MigrationTools.Host/Commands/ExecuteMigrationCommand.cs new file mode 100644 index 000000000..2f1fa0c64 --- /dev/null +++ b/src/MigrationTools.Host/Commands/ExecuteMigrationCommand.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using MigrationTools.Host.Commands; +using MigrationTools.Host.Services; +using MigrationTools.Services; +using Spectre.Console.Cli; + +namespace MigrationTools.Host.Commands +{ + internal class ExecuteMigrationCommand : CommandBase + { + private readonly IServiceProvider _services; + private readonly ILogger _logger; + private readonly IHostApplicationLifetime _appLifetime; + private readonly ITelemetryLogger Telemetery; + + public ExecuteMigrationCommand(IServiceProvider services, + ILogger logger, + IHostApplicationLifetime appLifetime, ITelemetryLogger telemetryLogger, IDetectOnlineService detectOnlineService, IDetectVersionService2 detectVersionService, IMigrationToolVersion migrationToolVersion) : base(appLifetime, detectOnlineService, detectVersionService, logger, telemetryLogger, migrationToolVersion) + { + Telemetery = telemetryLogger; + _services = services; + _logger = logger; + _appLifetime = appLifetime; + } + + internal override async Task ExecuteInternalAsync(CommandContext context, ExecuteMigrationCommandSettings settings) + { + int _exitCode; + try + { + var migrationEngine = _services.GetRequiredService(); + migrationEngine.Run(); + _exitCode = 0; + } + catch (Exception ex) + { + Telemetery.TrackException(ex, null, null); + _logger.LogError(ex, "Unhandled exception!"); + _exitCode = 1; + } + finally + { + // Stop the application once the work is done + _appLifetime.StopApplication(); + } + return _exitCode; + } + } +} + diff --git a/src/MigrationTools.Host/Commands/ExecuteMigrationCommandSettings.cs b/src/MigrationTools.Host/Commands/ExecuteMigrationCommandSettings.cs new file mode 100644 index 000000000..54f275418 --- /dev/null +++ b/src/MigrationTools.Host/Commands/ExecuteMigrationCommandSettings.cs @@ -0,0 +1,33 @@ +using System.ComponentModel; +using Microsoft.VisualStudio.Services.Common.CommandLine; +using Spectre.Console.Cli; + +namespace MigrationTools.Host.Commands +{ + internal class ExecuteMigrationCommandSettings : CommandSettingsBase + { + [Description("Domain used to connect to the source TFS instance.")] + [CommandOption("--sourceDomain")] + public string SourceDomain { get; set; } + + [Description("User Name used to connect to the source TFS instance.")] + [CommandOption("--sourceUserName")] + public string SourceUserName { get; set; } + + [Description("Password used to connect to source TFS instance.")] + [CommandOption("--sourcePassword")] + public string SourcePassword { get; set; } + + [Description("Domain used to connect to the target TFS instance.")] + [CommandOption("--targetDomain")] + public string TargetDomain { get; set; } + + [Description("User Name used to connect to the target TFS instance.")] + [CommandOption("--targetUserName")] + public string TargetUserName { get; set; } + + [Description("Password used to connect to target TFS instance.")] + [CommandOption("--targetPassword")] + public string TargetPassword { get; set; } + } +} \ No newline at end of file diff --git a/src/MigrationTools.Host/Commands/InitMigrationCommand.cs b/src/MigrationTools.Host/Commands/InitMigrationCommand.cs new file mode 100644 index 000000000..4beb5730d --- /dev/null +++ b/src/MigrationTools.Host/Commands/InitMigrationCommand.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MigrationTools._EngineV1.Configuration; +using Spectre.Console.Cli; + +namespace MigrationTools.Host.Commands +{ + internal class InitMigrationCommand : AsyncCommand + { + private readonly IEngineConfigurationBuilder _configurationBuilder; + private readonly ISettingsWriter _settingWriter; + private readonly ILogger _logger; + private readonly ITelemetryLogger Telemetery; + private readonly IHostApplicationLifetime _appLifetime; + + public InitMigrationCommand( + IEngineConfigurationBuilder configurationBuilder, + ISettingsWriter settingsWriter, + ILogger logger, + ITelemetryLogger telemetryLogger, + IHostApplicationLifetime appLifetime) + { + _configurationBuilder = configurationBuilder; + _settingWriter = settingsWriter; + _logger = logger; + Telemetery = telemetryLogger; + _appLifetime = appLifetime; + } + + + public override async Task ExecuteAsync(CommandContext context, InitMigrationCommandSettings settings) + { + int _exitCode; + try + { + Telemetery.TrackEvent(new EventTelemetry("InitCommand")); + string configFile = settings.ConfigFile; + if (string.IsNullOrEmpty(configFile)) + { + configFile = "configuration.json"; + } + _logger.LogInformation("ConfigFile: {configFile}", configFile); + if (File.Exists(configFile)) + { + _logger.LogInformation("Deleting old configuration.json reference file"); + File.Delete(configFile); + } + if (!File.Exists(configFile)) + { + _logger.LogInformation("Populating config with {Options}", settings.Options.ToString()); + EngineConfiguration config; + switch (settings.Options) + { + case OptionsMode.Reference: + config = _configurationBuilder.BuildReference(); + break; + case OptionsMode.Basic: + config = _configurationBuilder.BuildGettingStarted(); + break; + + case OptionsMode.WorkItemTracking: + config = _configurationBuilder.BuildWorkItemMigration(); + break; + + case OptionsMode.Fullv2: + config = _configurationBuilder.BuildDefault2(); + break; + + case OptionsMode.WorkItemTrackingv2: + config = _configurationBuilder.BuildWorkItemMigration2(); + break; + + default: + config = _configurationBuilder.BuildGettingStarted(); + break; + } + _settingWriter.WriteSettings(config, configFile); + _logger.LogInformation($"New {configFile} file has been created"); + } + _exitCode = 0; + } + catch (Exception ex) + { + Telemetery.TrackException(ex, null, null); + _logger.LogError(ex, "Unhandled exception!"); + _exitCode = 1; + } + finally + { + // Stop the application once the work is done + _appLifetime.StopApplication(); + } + return _exitCode; + } + } +} diff --git a/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs b/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs new file mode 100644 index 000000000..19245b65a --- /dev/null +++ b/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; +using Spectre.Console.Cli; + +namespace MigrationTools.Host.Commands +{ + internal class InitMigrationCommandSettings : CommandSettingsBase + { + [Description("What type of config do you want to output? WorkItemTracking is the default.")] + [CommandOption("--outputMode|--options")] + [DefaultValue(OptionsMode.WorkItemTracking)] + public OptionsMode Options { get; set; } + } + + public enum OptionsMode + { + Reference = 0, + WorkItemTracking = 1, + Fullv2 = 2, + WorkItemTrackingv2 = 3, + Basic = 4 + } +} \ No newline at end of file diff --git a/src/MigrationTools.Host/ExecuteHostedService.cs b/src/MigrationTools.Host/ExecuteHostedService.cs deleted file mode 100644 index ef7e59137..000000000 --- a/src/MigrationTools.Host/ExecuteHostedService.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.Channel; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace MigrationTools.Host -{ - public class ExecuteHostedService : IHostedService - { - private readonly IServiceProvider _services; - private readonly ILogger _logger; - private readonly IHostApplicationLifetime _appLifetime; - private readonly ITelemetryLogger Telemetery; - - private int? _exitCode; - - public ExecuteHostedService( - IServiceProvider services, - ILogger logger, - IHostApplicationLifetime appLifetime, ITelemetryLogger telemetryLogger) - { - Telemetery = telemetryLogger; - _services = services; - _logger = logger; - _appLifetime = appLifetime; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _appLifetime.ApplicationStarted.Register(() => - { - Task.Run(() => - { - try - { - var migrationEngine = _services.GetRequiredService(); - migrationEngine.Run(); - _exitCode = 0; - } - catch (Exception ex) - { - Telemetery.TrackException(ex, null, null); - _logger.LogError(ex, "Unhandled exception!"); - _exitCode = 1; - } - finally - { - // Stop the application once the work is done - _appLifetime.StopApplication(); - } - }); - }); - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - _logger.LogDebug($"Exiting with return code: {_exitCode}"); - - if (_exitCode.HasValue) - { - Environment.ExitCode = _exitCode.Value; - } - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/src/MigrationTools.Host/HostExtensions.cs b/src/MigrationTools.Host/HostExtensions.cs index ac26aace5..9e07b8294 100644 --- a/src/MigrationTools.Host/HostExtensions.cs +++ b/src/MigrationTools.Host/HostExtensions.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using MigrationTools.Host.CommandLine; namespace MigrationTools.Host { diff --git a/src/MigrationTools.Host/InitHostedService.cs b/src/MigrationTools.Host/InitHostedService.cs deleted file mode 100644 index 0140ff544..000000000 --- a/src/MigrationTools.Host/InitHostedService.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.DataContracts; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using MigrationTools._EngineV1.Configuration; -using MigrationTools.Host.CommandLine; - -namespace MigrationTools.Host -{ - public class InitHostedService : IHostedService - { - private readonly IEngineConfigurationBuilder _configurationBuilder; - private readonly ISettingsWriter _settingWriter; - private readonly InitOptions _initOptions; - private readonly ILogger _logger; - private readonly ITelemetryLogger _telemetryLogger; - private readonly IHostApplicationLifetime _appLifetime; - private int? _exitCode; - - public InitHostedService( - IEngineConfigurationBuilder configurationBuilder, - ISettingsWriter settingsWriter, - IOptions initOptions, - ILogger logger, - ITelemetryLogger telemetryLogger, - IHostApplicationLifetime appLifetime) - { - _configurationBuilder = configurationBuilder; - _settingWriter = settingsWriter; - _initOptions = initOptions.Value; - _logger = logger; - _telemetryLogger = telemetryLogger; - _appLifetime = appLifetime; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _appLifetime.ApplicationStarted.Register(() => - { - Task.Run(() => - { - try - { - _telemetryLogger.TrackEvent(new EventTelemetry("InitCommand")); - string configFile = _initOptions.ConfigFile; - if (string.IsNullOrEmpty(configFile)) - { - configFile = "configuration.json"; - } - _logger.LogInformation("ConfigFile: {configFile}", configFile); - if (File.Exists(configFile)) - { - _logger.LogInformation("Deleting old configuration.json reference file"); - File.Delete(configFile); - } - if (!File.Exists(configFile)) - { - _logger.LogInformation("Populating config with {Options}", _initOptions.Options.ToString()); - EngineConfiguration config; - switch (_initOptions.Options) - { - case OptionsMode.Reference: - config = _configurationBuilder.BuildReference(); - break; - case OptionsMode.Basic: - config = _configurationBuilder.BuildGettingStarted(); - break; - - case OptionsMode.WorkItemTracking: - config = _configurationBuilder.BuildWorkItemMigration(); - break; - - case OptionsMode.Fullv2: - config = _configurationBuilder.BuildDefault2(); - break; - - case OptionsMode.WorkItemTrackingv2: - config = _configurationBuilder.BuildWorkItemMigration2(); - break; - - default: - config = _configurationBuilder.BuildGettingStarted(); - break; - } - _settingWriter.WriteSettings(config, configFile); - _logger.LogInformation($"New {configFile} file has been created"); - } - _exitCode = 0; - } - catch (Exception ex) - { - _telemetryLogger.TrackException(ex, null, null); - _logger.LogError(ex, "Unhandled exception!"); - _exitCode = 1; - } - finally - { - // Stop the application once the work is done - _appLifetime.StopApplication(); - } - }); - }); - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - _logger.LogDebug($"Exiting with return code: {_exitCode}"); - if (_exitCode.HasValue) - { - Environment.ExitCode = _exitCode.Value; - } - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/src/MigrationTools.Host/MigrationService.cs b/src/MigrationTools.Host/MigrationService.cs new file mode 100644 index 000000000..d4b2f69ff --- /dev/null +++ b/src/MigrationTools.Host/MigrationService.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog.Core; +using Spectre.Console.Cli; + +namespace MigrationTools.Host +{ + internal class MigrationService : BackgroundService + { + private ICommandApp AppCommand { get; } + private IHostApplicationLifetime AppLifetime { get; } + private ILogger Logger { get; } + + public MigrationService(ICommandApp appCommand, IHostApplicationLifetime appLifetime, ILogger logger) + { + AppCommand = appCommand; + AppLifetime = appLifetime; + Logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await AppCommand.RunAsync(Environment.GetCommandLineArgs().Skip(1)); + AppLifetime.StopApplication(); + } + } +} diff --git a/src/MigrationTools.Host/MigrationToolHost.cs b/src/MigrationTools.Host/MigrationToolHost.cs index acbc9869f..422cbdc8c 100644 --- a/src/MigrationTools.Host/MigrationToolHost.cs +++ b/src/MigrationTools.Host/MigrationToolHost.cs @@ -2,7 +2,6 @@ using System.IO; using System.Reflection; using System.Threading.Tasks; -using CommandLine; using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.WorkerService; @@ -12,7 +11,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MigrationTools._EngineV1.Configuration; -using MigrationTools.Host.CommandLine; using MigrationTools.Host.CustomDiagnostics; using MigrationTools.Host.Services; using MigrationTools.Options; @@ -20,70 +18,83 @@ using Serilog.Core; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; +using Spectre.Console.Cli.Extensions.DependencyInjection; +using Spectre.Console.Cli; +using Serilog.Filters; +using MigrationTools.Host.Commands; +using System.Diagnostics; +using System.Text.RegularExpressions; +using MigrationTools.Services; namespace MigrationTools.Host { + + + public static class MigrationToolHost { + static int logs = 1; + private static bool LoggerHasBeenBuilt = false; + public static IHostBuilder CreateDefaultBuilder(string[] args) { - (var initOptions, var executeOptions) = ParseOptions(args); - - if (initOptions is null && executeOptions is null) - { - return null; - } + var configFile = CommandSettingsBase.ForceGetConfigFile(args); + var mtv = new MigrationToolVersion(); + var hostBuilder = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args); - - var hostBuilder = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .UseSerilog((hostingContext, services, loggerConfiguration) => - { - string outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [" + GetVersionTextForLog() + "] {Message:lj}{NewLine}{Exception}"; - string logsPath = CreateLogsPath(); - var logPath = Path.Combine(logsPath, "migration.log"); - var logLevel = hostingContext.Configuration.GetValue("LogLevel"); - var levelSwitch = new LoggingLevelSwitch(logLevel); - loggerConfiguration - .MinimumLevel.ControlledBy(levelSwitch) - .ReadFrom.Configuration(hostingContext.Configuration) - .Enrich.FromLogContext() - .Enrich.WithMachineName() - .Enrich.WithProcessId() - .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Debug, theme: AnsiConsoleTheme.Code, outputTemplate: outputTemplate) - .WriteTo.ApplicationInsights(services.GetService(), new CustomConverter(), LogEventLevel.Error) - .WriteTo.File(logPath, LogEventLevel.Verbose, outputTemplate: outputTemplate); - }) - .ConfigureLogging((context, logBuilder) => + hostBuilder.UseSerilog((hostingContext, services, loggerConfiguration) => + { + string outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [" + mtv.GetRunningVersion().versionString + "] {Message:lj}{NewLine}{Exception}"; // {SourceContext} + string logsPath = CreateLogsPath(); + var logPath = Path.Combine(logsPath, $"migration-{logs}.log"); + + var logLevel = hostingContext.Configuration.GetValue("LogLevel"); + var levelSwitch = new LoggingLevelSwitch(logLevel); + loggerConfiguration + .MinimumLevel.ControlledBy(levelSwitch) + .ReadFrom.Configuration(hostingContext.Configuration) + .Enrich.FromLogContext() + .Enrich.WithMachineName() + .Enrich.WithProcessId() + .WriteTo.File(logPath, LogEventLevel.Verbose, outputTemplate) + .WriteTo.Logger(lc => lc + .Filter.ByExcluding(Matching.FromSource("Microsoft")) + .Filter.ByExcluding(Matching.FromSource("MigrationTools.Host.StartupService")) + .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Debug, theme: AnsiConsoleTheme.Code, outputTemplate: outputTemplate)) + .WriteTo.Logger(lc => lc + .Filter.ByExcluding(Matching.FromSource("Microsoft")) + .WriteTo.ApplicationInsights(services.GetService (), new CustomConverter(), LogEventLevel.Error)); + logs++; + LoggerHasBeenBuilt = true; + }); + + hostBuilder.ConfigureLogging((context, logBuilder) => { }) - .ConfigureAppConfiguration(builder => - { - if (executeOptions is not null) - { - builder.AddJsonFile(executeOptions.ConfigFile); - } - }) - .ConfigureServices((context, services) => + .ConfigureAppConfiguration(builder => + { + if (!string.IsNullOrEmpty(configFile) && File.Exists(configFile)) + { + builder.AddJsonFile(configFile); + } + }); + + hostBuilder.ConfigureServices((context, services) => { services.AddOptions(); services.Configure((config) => { - if(executeOptions is null) - { - return; - } - var sp = services.BuildServiceProvider(); var logger = sp.GetService().CreateLogger(); - if (!File.Exists(executeOptions.ConfigFile)) + if (!File.Exists(configFile)) { - logger.LogInformation("The config file {ConfigFile} does not exist, nor does the default 'configuration.json'. Use '{ExecutableName}.exe init' to create a configuration file first", executeOptions.ConfigFile, Assembly.GetEntryAssembly().GetName().Name); + logger.LogInformation("The config file {ConfigFile} does not exist, nor does the default 'configuration.json'. Use '{ExecutableName}.exe init' to create a configuration file first", configFile, Assembly.GetEntryAssembly().GetName().Name); throw new ArgumentException("missing configfile"); } logger.LogInformation("Config Found, creating engine host"); var reader = sp.GetRequiredService(); - var parsed = reader.BuildFromFile(executeOptions.ConfigFile); + var parsed = reader.BuildFromFile(configFile); config.ChangeSetMappingFile = parsed.ChangeSetMappingFile; config.FieldMaps = parsed.FieldMaps; config.GitRepoMapping = parsed.GitRepoMapping; @@ -96,20 +107,25 @@ public static IHostBuilder CreateDefaultBuilder(string[] args) config.WorkItemTypeDefinition = parsed.WorkItemTypeDefinition; }); + // Application Insights ApplicationInsightsServiceOptions aiso = new ApplicationInsightsServiceOptions(); aiso.ApplicationVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - aiso.ConnectionString = "InstrumentationKey=2d666f84-b3fb-4dcf-9aad-65de038d2772"; + aiso.ConnectionString = "InstrumentationKey=2d666f84-b3fb-4dcf-9aad-65de038d2772;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/;ApplicationId=9146fe72-5c18-48d7-a0f2-8fb891ef1277"; //# if DEBUG //aiso.DeveloperMode = true; //#endif services.AddApplicationInsightsTelemetryWorkerService(aiso); - + // Services services.AddTransient(); //services.AddTransient(); services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + + // Config services.AddSingleton(); services.AddTransient((provider) => provider.GetRequiredService() as IEngineConfigurationReader); @@ -122,46 +138,33 @@ public static IHostBuilder CreateDefaultBuilder(string[] args) // Host Services services.AddTransient(); - if (initOptions is not null) - { - services.Configure((opts) => - { - opts.ConfigFile = initOptions.ConfigFile; - opts.Options = initOptions.Options; - }); - services.AddHostedService(); - } - if (executeOptions is not null) - { - services.Configure(cred => - { - cred.Source = new Credentials - { - Domain = executeOptions.SourceDomain, - UserName = executeOptions.SourceUserName, - Password = executeOptions.SourcePassword - }; - cred.Target = new Credentials - { - Domain = executeOptions.TargetDomain, - UserName = executeOptions.TargetUserName, - Password = executeOptions.TargetPassword - }; - }); - services.AddHostedService(); - } - }) - .UseConsoleLifetime(); + }); - return hostBuilder; - } + hostBuilder.ConfigureServices((context, services) => + { + using var registrar = new DependencyInjectionRegistrar(services); + var app = new CommandApp(registrar); + app.Configure(config => + { + config.PropagateExceptions(); + config.AddCommand("execute"); + config.AddCommand("init"); + + }); + services.AddSingleton(app); + }); + + hostBuilder.ConfigureServices((context, services) => + { + services.AddHostedService(); + }); - private static string GetVersionTextForLog() - { - Version runningVersion = DetectVersionService2.GetRunningVersion().version; - string textVersion = "v" + DetectVersionService2.GetRunningVersion().version + "-" + DetectVersionService2.GetRunningVersion().PreReleaseLabel; - return textVersion; + hostBuilder.UseConsoleLifetime(); + + + + return hostBuilder; } public static async Task RunMigrationTools(this IHostBuilder hostBuilder, string[] args) @@ -175,28 +178,25 @@ public static async Task RunMigrationTools(this IHostBuilder hostBuilder, string // Disanle telemitery from options - (var initOptions, var executeOptions) = ParseOptions(args); - if (initOptions is null && executeOptions is null) - { - return; - } - bool DisableTelemetry = false; - Serilog.ILogger logger = host.Services.GetService(); - if (executeOptions is not null && bool.TryParse(executeOptions.DisableTelemetry, out DisableTelemetry)) - { - TelemetryConfiguration ai = host.Services.GetService(); - ai.DisableTelemetry = DisableTelemetry; - } - logger.Information("Telemetry: {status}", !DisableTelemetry); + //bool DisableTelemetry = false; + //Serilog.ILogger logger = host.Services.GetService(); + //if (executeOptions is not null && bool.TryParse(executeOptions.DisableTelemetry, out DisableTelemetry)) + //{ + // TelemetryConfiguration ai = host.Services.GetService(); + // ai.DisableTelemetry = DisableTelemetry; + //} + //logger.Information("Telemetry: {status}", !DisableTelemetry); await host.RunAsync(); } + static string logDate = DateTime.Now.ToString("yyyyMMddHHmmss"); + private static string CreateLogsPath() { string exportPath; string assPath = Assembly.GetEntryAssembly().Location; - exportPath = Path.Combine(Path.GetDirectoryName(assPath), "logs", DateTime.Now.ToString("yyyyMMddHHmmss")); + exportPath = Path.Combine(Path.GetDirectoryName(assPath), "logs", logDate); if (!Directory.Exists(exportPath)) { Directory.CreateDirectory(exportPath); @@ -204,21 +204,5 @@ private static string CreateLogsPath() return exportPath; } - - private static (InitOptions init, ExecuteOptions execute) ParseOptions(string[] args) - { - InitOptions initOptions = null; - ExecuteOptions executeOptions = null; - Parser.Default.ParseArguments(args) - .WithParsed(opts => - { - initOptions = opts; - }) - .WithParsed(opts => - { - executeOptions = opts; - }); - return (initOptions, executeOptions); - } } } \ No newline at end of file diff --git a/src/MigrationTools.Host/MigrationTools.Host.csproj b/src/MigrationTools.Host/MigrationTools.Host.csproj index 3174b6c22..966f7654b 100644 --- a/src/MigrationTools.Host/MigrationTools.Host.csproj +++ b/src/MigrationTools.Host/MigrationTools.Host.csproj @@ -13,7 +13,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -32,7 +31,11 @@ + + + + diff --git a/src/MigrationTools.Host/Services/DetectVersionService2.cs b/src/MigrationTools.Host/Services/DetectVersionService2.cs index e2d1937c4..0479ba637 100644 --- a/src/MigrationTools.Host/Services/DetectVersionService2.cs +++ b/src/MigrationTools.Host/Services/DetectVersionService2.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; using MigrationTools.DataContracts.Pipelines; using MigrationTools.EndpointEnrichers; +using MigrationTools.Services; using NuGet.Common; using NuGet.Configuration; using NuGet.Protocol; @@ -22,6 +23,7 @@ namespace MigrationTools.Host.Services { public class DetectVersionService2 : IDetectVersionService2 { + private IMigrationToolVersion _VerionsInfo; private readonly ITelemetryLogger _Telemetry; private ILogger _logger; @@ -45,7 +47,7 @@ public Version RunningVersion { get { - return GetRunningVersion().version; + return _VerionsInfo.GetRunningVersion().version; } } public Version AvailableVersion @@ -62,7 +64,7 @@ private Version GetAvailableVersion() { return Package.AvailableVersion; } - return new Version("0.0.0"); + return new Version("0.0.0"); } public Version InstalledVersion @@ -95,12 +97,13 @@ private bool GetIsPackageInstalled() return Package != null; } - public bool IsPackageManagerInstalled { + public bool IsPackageManagerInstalled + { get { return GetIsPackageManagerInstalled(); } - } + } private bool GetIsPackageManagerInstalled() { @@ -112,7 +115,7 @@ public bool IsPreviewVersion { get { - return !string.IsNullOrEmpty( GetRunningVersion().PreReleaseLabel); + return !string.IsNullOrEmpty(_VerionsInfo.GetRunningVersion().PreReleaseLabel); } } @@ -128,7 +131,7 @@ public bool IsRunningInDebug { get { - return GetRunningVersion().PreReleaseLabel.ToLower() == "local"; + return _VerionsInfo.GetRunningVersion().PreReleaseLabel.ToLower() == "local"; } } @@ -140,18 +143,20 @@ public bool IsNewLocalVersionAvailable } } - public DetectVersionService2(ITelemetryLogger telemetry, ILogger logger) + public DetectVersionService2(ITelemetryLogger telemetry, ILogger logger, IMigrationToolVersion verionsInfo) { + _VerionsInfo = verionsInfo; _Telemetry = telemetry; _logger = logger; if (IsPreviewVersion) { PackageId = "nkdAgility.AzureDevOpsMigrationTools.Preview"; - } else + } + else { PackageId = "nkdAgility.AzureDevOpsMigrationTools"; } - + } private WinGetPackage GetPackage() @@ -170,41 +175,32 @@ private WinGetPackage GetPackage() return _package; } - public static (Version version, string PreReleaseLabel, string versionString) GetRunningVersion() + public class Benchmark : IDisposable { - FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly()?.Location); - var matches = Regex.Matches(myFileVersionInfo.ProductVersion, @"^(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:-((? diff --git a/src/MigrationTools.Integration.Tests/ServiceProviderHelper.cs b/src/MigrationTools.Integration.Tests/ServiceProviderHelper.cs index ee10e7136..7befa5ede 100644 --- a/src/MigrationTools.Integration.Tests/ServiceProviderHelper.cs +++ b/src/MigrationTools.Integration.Tests/ServiceProviderHelper.cs @@ -2,6 +2,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MigrationTools.Endpoints; +using MigrationTools.Fakes; +using MigrationTools.Services; using MigrationTools.TestExtensions; namespace MigrationTools.Tests @@ -24,6 +26,9 @@ internal static ServiceProvider GetServicesV2() AddEndpoint(services, "Source", "migrationSource1"); AddEndpoint(services, "Target", "migrationTarget1"); + services.AddSingleton(); + services.AddSingleton(); + return services.BuildServiceProvider(); } diff --git a/src/MigrationTools.Tests/MigrationTools.Tests.csproj b/src/MigrationTools.Tests/MigrationTools.Tests.csproj index 3b5aed8fa..bc59cf9bf 100644 --- a/src/MigrationTools.Tests/MigrationTools.Tests.csproj +++ b/src/MigrationTools.Tests/MigrationTools.Tests.csproj @@ -11,8 +11,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -21,6 +21,8 @@ + + diff --git a/src/MigrationTools.Tests/ProcessorEnrichers/StringManipulatorEnricherTests.cs b/src/MigrationTools.Tests/ProcessorEnrichers/StringManipulatorEnricherTests.cs index 244c0c18d..fb89ac796 100644 --- a/src/MigrationTools.Tests/ProcessorEnrichers/StringManipulatorEnricherTests.cs +++ b/src/MigrationTools.Tests/ProcessorEnrichers/StringManipulatorEnricherTests.cs @@ -21,7 +21,7 @@ public void Setup() Services = ServiceProviderHelper.GetWorkItemMigrationProcessor(); } - [TestMethod(), TestCategory("L0"), TestCategory("Generic.Processor")] + [TestMethod(), TestCategory("L0")] public void StringManipulatorEnricher_ConfigureTest() { var y = new StringManipulatorEnricherOptions @@ -45,7 +45,7 @@ public void StringManipulatorEnricher_ConfigureTest() Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L1"), TestCategory("ProcessorEnrichers")] + [TestMethod(), TestCategory("L1")] public void StringManipulatorEnricher_RegexTest() { var y = new StringManipulatorEnricherOptions @@ -82,7 +82,7 @@ public void StringManipulatorEnricher_RegexTest() Assert.AreEqual("Test 2", fieldItem.Value); } - [TestMethod(), TestCategory("L1"), TestCategory("ProcessorEnrichers")] + [TestMethod(), TestCategory("L1")] public void StringManipulatorEnricher_LengthShorterThanMaxTest() { var y = new StringManipulatorEnricherOptions @@ -108,7 +108,7 @@ public void StringManipulatorEnricher_LengthShorterThanMaxTest() Assert.AreEqual(4, fieldItem.Value.ToString().Length); } - [TestMethod(), TestCategory("L1"), TestCategory("ProcessorEnrichers")] + [TestMethod(), TestCategory("L1")] public void StringManipulatorEnricher_LengthLongerThanMaxTest() { var y = new StringManipulatorEnricherOptions diff --git a/src/MigrationTools.Tests/Processors/WorkItemMigrationProcessorTests.cs b/src/MigrationTools.Tests/Processors/WorkItemMigrationProcessorTests.cs index 3d5d4a397..b6f805924 100644 --- a/src/MigrationTools.Tests/Processors/WorkItemMigrationProcessorTests.cs +++ b/src/MigrationTools.Tests/Processors/WorkItemMigrationProcessorTests.cs @@ -16,7 +16,7 @@ public void Setup() Services = ServiceProviderHelper.GetWorkItemMigrationProcessor(); } - [TestMethod(), TestCategory("L0"), TestCategory("Generic.Processor")] + [TestMethod(), TestCategory("L0")] public void WorkItemMigrationProcessorConfigureTest() { var y = new WorkItemTrackingProcessorOptions @@ -33,7 +33,7 @@ public void WorkItemMigrationProcessorConfigureTest() Assert.IsNotNull(x); } - [TestMethod(), TestCategory("L1"), TestCategory("Generic.Processor")] + [TestMethod(), TestCategory("L1")] public void WorkItemMigrationProcessorRunTest() { var y = new WorkItemTrackingProcessorOptions diff --git a/src/MigrationTools.Tests/ServiceProviderHelper.cs b/src/MigrationTools.Tests/ServiceProviderHelper.cs index c211260bf..f68cccba7 100644 --- a/src/MigrationTools.Tests/ServiceProviderHelper.cs +++ b/src/MigrationTools.Tests/ServiceProviderHelper.cs @@ -3,8 +3,10 @@ using MigrationTools.EndpointEnrichers; using MigrationTools.Endpoints; using MigrationTools.Enrichers; +using MigrationTools.Fakes; using MigrationTools.ProcessorEnrichers.WorkItemProcessorEnrichers; using MigrationTools.Processors; +using MigrationTools.Services; using MigrationTools.TestExtensions; namespace MigrationTools.Tests @@ -39,6 +41,9 @@ internal static ServiceProvider GetWorkItemMigrationProcessor() AddEndpoint(services, "Source"); AddEndpoint(services, "Target"); + services.AddSingleton(); + services.AddSingleton(); + return services.BuildServiceProvider(); } diff --git a/src/MigrationTools.Tests/Services/MigrationToolVersionInfoTests.cs b/src/MigrationTools.Tests/Services/MigrationToolVersionInfoTests.cs new file mode 100644 index 000000000..f14d58c12 --- /dev/null +++ b/src/MigrationTools.Tests/Services/MigrationToolVersionInfoTests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MigrationTools.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MigrationTools.Fakes; + +namespace MigrationTools.Tests +{ + [TestClass()] + public class MigrationToolVersionInfoTests + { + + [TestMethod(), TestCategory("L0")] + public void GetRunningVersionTest_Release() + { + IMigrationToolVersionInfo mtvi = new FakeMigrationToolVersionInfo("15.4.4", "15.4.4.0", "v15.4.4"); + IMigrationToolVersion mtv = new MigrationToolVersion(mtvi); + var result = mtv.GetRunningVersion(); + Assert.AreEqual("15.4.4", result.versionString); + } + + [TestMethod(), TestCategory("L0")] + public void GetRunningVersionTest_Preview() + { + IMigrationToolVersionInfo mtvi = new FakeMigrationToolVersionInfo("15.1.5-Preview.3", "15.1.5.3", "v15.1.5-Preview.3"); + IMigrationToolVersion mtv = new MigrationToolVersion(mtvi); + var result = mtv.GetRunningVersion(); + Assert.AreEqual("15.1.5-Preview.3", result.versionString); + } + + [TestMethod(), TestCategory("L0")] + public void GetRunningVersionTest_Canary() + { + IMigrationToolVersionInfo mtvi = new FakeMigrationToolVersionInfo("0.0.0-local+7acec2e6266f5f05b2807264ee8f1db7b94b1949", "0.0.0.0", "v15.1.5-Preview.2-18-g7acec2e"); + IMigrationToolVersion mtv = new MigrationToolVersion(mtvi); + var result = mtv.GetRunningVersion(); + Assert.AreEqual("15.1.5-Local.2-18-g7acec2e", result.versionString); + } + } +} \ No newline at end of file diff --git a/src/MigrationTools.Tests/Services/TelemetryClientAdapterTests.cs b/src/MigrationTools.Tests/Services/TelemetryClientAdapterTests.cs new file mode 100644 index 000000000..49f6b432b --- /dev/null +++ b/src/MigrationTools.Tests/Services/TelemetryClientAdapterTests.cs @@ -0,0 +1,18 @@ +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MigrationTools; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MigrationTools.Tests +{ + [TestClass()] + public class TelemetryClientAdapterTests + { + + } +} \ No newline at end of file diff --git a/src/MigrationTools/MigrationTools.csproj b/src/MigrationTools/MigrationTools.csproj index c24412983..1b575ecbb 100644 --- a/src/MigrationTools/MigrationTools.csproj +++ b/src/MigrationTools/MigrationTools.csproj @@ -15,12 +15,20 @@ true + + false + + ..\..\docs\Reference\Generated\MigrationTools.xml + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/MigrationTools/ServiceCollectionExtensions.cs b/src/MigrationTools/ServiceCollectionExtensions.cs index fc74439bf..e650d174f 100644 --- a/src/MigrationTools/ServiceCollectionExtensions.cs +++ b/src/MigrationTools/ServiceCollectionExtensions.cs @@ -6,6 +6,7 @@ using MigrationTools.Enrichers; using MigrationTools.ProcessorEnrichers.WorkItemProcessorEnrichers; using MigrationTools.Processors; +using MigrationTools.Services; namespace MigrationTools { @@ -32,6 +33,8 @@ public static void AddMigrationToolServices(this IServiceCollection context) //context.AddTransient(); // processor Enrichers context.AddTransient(); + + } [Obsolete("This is the v1 Archtiecture, we are movign to V2", false)] diff --git a/src/MigrationTools/Services/MigrationToolVersionInfo.cs b/src/MigrationTools/Services/MigrationToolVersionInfo.cs new file mode 100644 index 000000000..b33197e99 --- /dev/null +++ b/src/MigrationTools/Services/MigrationToolVersionInfo.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; + + +namespace MigrationTools.Services +{ + public interface IMigrationToolVersionInfo + { + string ProductVersion { get; } + string FileVersion { get;} + string GitTag { get; } + } + public class MigrationToolVersionInfo : IMigrationToolVersionInfo + { + public string ProductVersion { get; set; } + public string FileVersion { get; set; } + public string GitTag { get; set; } + + public MigrationToolVersionInfo() + { + FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly()?.Location); + ProductVersion = myFileVersionInfo.ProductVersion; + FileVersion = myFileVersionInfo.FileVersion; + try + { + GitTag = ThisAssembly.Git.Tag; + } + catch (Exception) + { + // Do nothing + } + + } + } + + public interface IMigrationToolVersion + { + (Version version, string PreReleaseLabel, string versionString) GetRunningVersion(); + } + + public class MigrationToolVersion : IMigrationToolVersion + { + private IMigrationToolVersionInfo _MigrationToolVersionInfo; + + public MigrationToolVersion() + { + _MigrationToolVersionInfo = new MigrationToolVersionInfo(); + } + + public MigrationToolVersion(IMigrationToolVersionInfo migrationToolVersionInfo) + { + _MigrationToolVersionInfo = migrationToolVersionInfo; + } + + public (Version version, string PreReleaseLabel, string versionString) GetRunningVersion() + { + try + { + var matches = Regex.Matches(_MigrationToolVersionInfo.ProductVersion, @"^(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:-((?