diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 806161f8..556f4ae3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,10 @@ updates: open-pull-requests-limit: 10 assignees: - "samsmithnz" + groups: + tests: + patterns: ["*"] + update-types: ["minor", "patch"] - package-ecosystem: nuget directory: "/src/DevOpsMetrics.Web" schedule: @@ -18,6 +22,10 @@ updates: open-pull-requests-limit: 10 assignees: - "samsmithnz" + groups: + web: + patterns: ["*"] + update-types: ["minor", "patch"] - package-ecosystem: nuget directory: "/src/DevOpsMetrics.FunctionalTests" schedule: @@ -25,15 +33,10 @@ updates: time: "06:00" timezone: America/New_York open-pull-requests-limit: 10 - assignees: - - "samsmithnz" -- package-ecosystem: npm - directory: /src/ProbotMetrics - schedule: - interval: daily - time: "06:00" - timezone: America/New_York - open-pull-requests-limit: 10 + groups: + functionalTests: + patterns: ["*"] + update-types: ["minor", "patch"] assignees: - "samsmithnz" # Maintain dependencies for GitHub Actions @@ -41,5 +44,12 @@ updates: directory: "/" schedule: interval: "daily" + time: "06:00" + timezone: America/New_York + open-pull-requests-limit: 10 assignees: - "samsmithnz" + groups: + actions: + patterns: ["*"] + update-types: ["minor", "patch"] diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 53ff6b38..15bd26ea 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,11 +38,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -51,14 +51,14 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4.0.1 with: - dotnet-version: 7.0.x + dotnet-version: 10.0.x # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -72,4 +72,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 17909567..ef732f66 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -11,12 +11,29 @@ jobs: build: runs-on: windows-latest + outputs: # https://stackoverflow.com/questions/59175332/using-output-from-a-previous-job-in-a-new-one-in-a-github-action + Version: ${{ steps.gitversion.outputs.SemVer }} + CommitsSinceVersionSource: ${{ steps.gitversion.outputs.CommitsSinceVersionSource }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 #fetch-depth is needed for GitVersion + #Install and calculate the new version with GitVersion + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v1.2.0 + with: + versionSpec: 5.x + - name: Determine Version + uses: gittools/actions/gitversion/execute@v1.2.0 + id: gitversion # step id used as reference for output values + - name: Display GitVersion outputs + run: | + echo "Version: ${{ steps.gitversion.outputs.SemVer }}" + echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.CommitsSinceVersionSource }}" - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4.0.1 with: - dotnet-version: 7.0.x + dotnet-version: 10.0.x #Publish dotnet objects - name: .NET Publish Web Service @@ -27,33 +44,33 @@ jobs: - name: .NET Publish functional tests run: dotnet publish src/DevOpsMetrics.FunctionalTests/DevOpsMetrics.FunctionalTests.csproj --configuration Release --output ${{ github.workspace }}/functionalTests - name: Copy chromedriver for functional test - run: copy "src/DevOpsMetrics.FunctionalTests/bin/Release/net7.0/chromedriver.exe" "${{ github.workspace }}/functionalTests" + run: copy "src/DevOpsMetrics.FunctionalTests/bin/Release/net10.0/chromedriver.exe" "${{ github.workspace }}/functionalTests" shell: powershell - name: DotNet restore functional tests to get correct NewtonSoft version run: dotnet restore src/DevOpsMetrics.FunctionalTests/DevOpsMetrics.FunctionalTests.csproj - name: Copy new NewtonSoft version for functional test - run: copy "src/DevOpsMetrics.FunctionalTests/bin/Release/net7.0/Newtonsoft.Json.dll" "${{ github.workspace }}/functionalTests" + run: copy "src/DevOpsMetrics.FunctionalTests/bin/Release/net10.0/Newtonsoft.Json.dll" "${{ github.workspace }}/functionalTests" shell: powershell #Publish build artifacts to GitHub - name: Upload web service build artifacts back to GitHub - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: serviceapp path: ${{ github.workspace }}/webservice - name: Upload website build artifacts back to GitHub - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: webapp path: ${{ github.workspace }}/web - name: Upload functional test build artifacts back to GitHub - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: functionaltests path: ${{ github.workspace }}/functionalTests - name: Upload probot build artifacts back to GitHub - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: probot path: src/ProbotMetrics @@ -64,11 +81,11 @@ jobs: permissions: actions: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4.0.1 with: - dotnet-version: 7.0.x + dotnet-version: 10.0.x #Run automated .NET tests - name: Variable Substitution appsettings file for tests uses: microsoft/variable-substitution@v1 @@ -84,31 +101,30 @@ jobs: - name: build test project run: dotnet build src/DevOpsMetrics.Tests/DevOpsMetrics.Tests.csproj --configuration Debug - name: Run automated unit and integration tests - run: dotnet test src/DevOpsMetrics.Tests/DevOpsMetrics.Tests.csproj --no-build --configuration Debug --logger trx /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov --settings:./src/DevOpsMetrics.Tests/CodeCoverage.runsettings + run: dotnet test src/DevOpsMetrics.Tests/DevOpsMetrics.Tests.csproj --no-build --configuration Debug --logger trx -e:CollectCoverage=true -e:CoverletOutput=TestResults/ -e:CoverletOutputFormat=lcov --settings:./src/DevOpsMetrics.Tests/CodeCoverage.runsettings - name: Publish coverage report to coveralls.io uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: src/DevOpsMetrics.Tests/TestResults/coverage.info - continue-on-error: true buildFunction: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4.0.1 with: - dotnet-version: 7.0.x + dotnet-version: 10.0.x #Not a typo, the function is locked to a LTS version of .NET - name: .NET Publish Function run: dotnet publish src/DevOpsMetrics.Function/DevOpsMetrics.Function.csproj --configuration Debug --output ${{ github.workspace }}/function - name: Upload function build artifacts back to GitHub - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: functionapp path: ${{ github.workspace }}/function - + #Disabling PROBOT for 2021 reboot to focus on website #Deploy the probot artifacts to Azure @@ -120,7 +136,7 @@ jobs: # steps: # # Login with the secret SP details # - name: Log into Azure -# uses: azure/login@v1 +# uses: azure/login@v2 # with: # creds: ${{ secrets.AZURE_SP_PROBOT }} # - name: Download webapp artifact @@ -128,7 +144,7 @@ jobs: # with: # name: probot # - name: Deploy probot to Azure WebApp -# uses: Azure/webapps-deploy@v2.2.3 +# uses: Azure/webapps-deploy@v2.2.11 # with: # app-name: devops-prod-eu-probot # package: probot @@ -143,38 +159,39 @@ jobs: - build - test - buildFunction + if: github.ref == 'refs/heads/main' steps: # Login with the secret SP details - name: Log into Azure - uses: azure/login@v1 + uses: azure/login@v2 with: creds: ${{ secrets.AZURE_SP }} #Download the artifacts from GitHub - name: Download serviceapp artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: serviceapp path: serviceapp - name: Download webapp artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: webapp path: webapp - name: Download webapp artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: functionapp path: functionapp - name: Download functionaltests artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: functionaltests path: functionaltests #Deploy service, website, and function to Azure - name: Deploy web service to Azure WebApp - uses: Azure/webapps-deploy@v2.2.3 + uses: Azure/webapps-deploy@v3 with: app-name: devops-prod-eu-service package: serviceapp @@ -183,7 +200,7 @@ jobs: run: az webapp config appsettings set --name "devops-prod-eu-service" --resource-group "devopsmetrics" --settings "AppSettings:AzureDevOpsPatToken=${{ secrets.AzureDevOpsPATToken }}" "AppSettings:GitHubClientId=${{ secrets.GitHubClientId }}" "AppSettings:GitHubClientSecret=${{ secrets.GitHubClientSecret }}" "AppSettings:AzureStorageAccountConfigurationString=${{ secrets.AzureStorageConnectionString }}" "AppSettings:KeyVaultClientId=${{ secrets.KeyVaultClientId }}" "AppSettings:KeyVaultClientSecret=${{ secrets.KeyVaultClientSecret }}" #--slot "staging" - name: Deploy website to Azure WebApp - uses: Azure/webapps-deploy@v2.2.3 + uses: Azure/webapps-deploy@v3 with: app-name: devops-prod-eu-web package: webapp @@ -207,14 +224,27 @@ jobs: # deploy function and settings - name: Deploy function to Azure WebApp - uses: Azure/webapps-deploy@v2.2.3 + uses: Azure/webapps-deploy@v3 with: app-name: devops-prod-eu-function package: functionapp - name: Deploy function app settings run: az webapp config appsettings set --name "devops-prod-eu-function" --resource-group "devopsmetrics" --settings "AppSettings:AzureDevOpsPatToken=${{ secrets.AzureDevOpsPATToken }}" "AppSettings:GitHubClientId=${{ secrets.GitHubClientId }}" "AppSettings:GitHubClientSecret=${{ secrets.GitHubClientSecret }}" "AppSettings:AzureStorageAccountConfigurationString=${{ secrets.AzureStorageConnectionString }}" "AppSettings:WebServiceURL=https://devops-prod-eu-service.azurewebsites.net/" "AppSettings:KeyVaultClientId=${{ secrets.KeyVaultClientId }}" "AppSettings:KeyVaultClientSecret=${{ secrets.KeyVaultClientSecret }}" "AppSettings:KeyVaultURL=https://devops-prod-eu-vault.vault.azure.net/" "AzureStorageAccountContainerAzureDevOpsBuilds=DevOpsAzureDevOpsBuilds" "AppSettings:AzureStorageAccountContainerAzureDevOpsPRs=DevOpsAzureDevOpsPRs" "AppSettings:AzureStorageAccountContainerAzureDevOpsPRCommits=DevOpsAzureDevOpsPRCommits" "AppSettings:AzureStorageAccountContainerAzureDevOpsSettings=DevOpsAzureDevOpsSettings" "AppSettings:AzureStorageAccountContainerGitHubRuns=DevOpsGitHubRuns" "AppSettings:AzureStorageAccountContainerGitHubPRs=DevOpsGitHubPRs" "AppSettings:AzureStorageAccountContainerGitHubPRCommits=DevOpsGitHubPRCommits" "AppSettings:AzureStorageAccountContainerGitHubSettings=DevOpsGitHubSettings" "AppSettings:AzureStorageAccountContainerMTTR=DevOpsMTTR" "AppSettings:AzureStorageAccountContainerChangeFailureRate=DevOpsChangeFailureRate" "AppSettings:AzureStorageAccountContainerTableLog=DevOpsLog" + - name: Display GitVersion outputs + run: | + echo "Version: ${{ needs.build.outputs.Version }}" + echo "CommitsSinceVersionSource: ${{ needs.build.outputs.CommitsSinceVersionSource }}" + - name: Create Release + id: create_release + uses: actions/create-release@v1 + if: github.ref == 'refs/heads/main' && needs.build.outputs.CommitsSinceVersionSource > 0 #Only create a release if there has been a commit/version change + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: "v${{ needs.build.outputs.Version }}" + release_name: "v${{ needs.build.outputs.Version }}" - + swapDeploymentSlots: runs-on: ubuntu-latest # Note, Azure CLI requires a Linux runner... needs: [build, deployToStagingSlots] @@ -223,18 +253,18 @@ jobs: steps: # Login with the secret SP details - name: Log into Azure - uses: azure/login@v1 + uses: azure/login@v2 with: creds: ${{ secrets.AZURE_SP }} #Swap staging slots with prod - name: Swap web service staging slot to production - uses: Azure/cli@v1.0.7 + uses: Azure/cli@v2.0.0 with: inlineScript: az webapp deployment slot swap --name "devops-prod-eu-service" --resource-group "devopsmetrics" --slot staging --target-slot production - name: Deploy web service app settings run: az webapp config appsettings set --name "devops-prod-eu-service" --resource-group "devopsmetrics" --settings "AppSettings:AzureDevOpsPatToken=${{ secrets.AzureDevOpsPATToken }}" "AppSettings:GitHubClientId=${{ secrets.GitHubClientId }}" "AppSettings:GitHubClientSecret=${{ secrets.GitHubClientSecret }}" "AppSettings:AzureStorageAccountConfigurationString=${{ secrets.AzureStorageConnectionString }}" - name: Swap web site staging slot to production - uses: Azure/cli@v1.0.7 + uses: Azure/cli@v2.0.0 with: inlineScript: az webapp deployment slot swap --name "devops-prod-eu-web" --resource-group "devopsmetrics" --slot staging --target-slot production - name: Deploy website app settings @@ -255,9 +285,9 @@ jobs: # extensionVersion: "0.0.0" # #if: github.ref == 'refs/heads/main' # steps: -# - uses: actions/checkout@v3 +# - uses: actions/checkout@v4 # - name: Setup .NET Core -# uses: actions/setup-dotnet@v3 +# uses: actions/setup-dotnet@v4.0.1 # with: # dotnet-version: 3.1.101 # - name: .NET core Publish Azure DevOps Extension @@ -274,7 +304,7 @@ jobs: # dir # Write-Host "New env variable " ${{ env.extensionVersion }} # - name: Upload Azure DevOps extension build artifacts back to GitHub -# uses: actions/upload-artifact@v3 +# uses: actions/upload-artifact@v4 # with: # name: azuredevopsextensionpackage # path: src\DevOpsMetrics.AzureDevOpsExtension\bin\Release\netcoreapp3.1\publish\wwwroot\SamSmithNZ.high-performing-devops-metrics-${{ env.extensionVersion }}.vsix diff --git a/GitVersion.yml b/GitVersion.yml index 74d295e6..a87cc81a 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1 +1 @@ -next-version: 1.3.0 +next-version: 1.11.0 diff --git a/README.md b/README.md index 91fc1f76..346ad195 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,12 @@ [![Build](https://GitHub.com/samsmithnz/DevOpsMetrics/workflows/CI/CD/badge.svg)](https://GitHub.com/samsmithnz/DevOpsMetrics/actions?query=workflow%3ACI%2FCD) [![Coverage Status](https://coveralls.io/repos/github/samsmithnz/DevOpsMetrics/badge.svg?branch=main)](https://coveralls.io/github/samsmithnz/DevOpsMetrics?branch=main) +[![Current Release](https://img.shields.io/github/release/samsmithnz/DevOpsMetrics/all.svg)](https://github.com/samsmithnz/DevOpsMetrics/releases) -**Why should we care about DevOps Metrics and what are they?** All engineering, including software, needs metrics to track performance, but many metrics when measured individually, can be 'gamed', or don't encourage the right behaviors or incentives. This has been an issue with metrics for many years. The [DORA metrics](https://services.google.com/fh/files/misc/state-of-devops-2019.pdf) are a step in the right direction, combining several metrics that encourage the behaviors and incentives - and hence that encourage DevOps teams to perform at a high level of performance. + +**Why should we care about DevOps Metrics and what are they?** All engineering, including software, needs metrics to track performance, but many metrics when measured individually, can be 'gamed', or don't encourage the right behaviors or incentives. This has been an issue with metrics for many years. The [DORA metrics](https://services.google.com/fh/files/misc/state-of-devops-2019.pdf) are a step in the right direction, combining several metrics that encourage the behaviors and incentives - and hence that encourage DevOps teams to perform at a high level of performance. DORA metrics aren't perfect, but are still the best we have available today. +- A [demo website displaying these metrics can be viewed here](https://devops-prod-eu-web.azurewebsites.net/). +- Insights I've noted about implementing DORA DevOps metrics can be found in a [blog post here](https://samlearnsazure.blog/2020/04/30/high-performing-devops-metrics/) This project is focused on helping you collect and analyze four key high performing DevOps metrics from GitHub and Azure DevOps. [DORA's "State of DevOps" research](https://cloud.google.com/blog/products/devops-sre/announcing-dora-2021-accelerate-state-of-devops-report) and [Accelerate](https://www.amazon.com/Accelerate-Software-Performing-Technology-Organizations/dp/1942788339) highlighted four driving indicators of high performing DevOps teams. While these four metrics are widely used in DevOps discussion, it's challenging to implement and capture all of the metrics. @@ -12,13 +16,11 @@ This project is focused on helping you collect and analyze four key high perform - **Mean time to restore (MTTR): How quickly restoration of production occurs in an outage or degradation.** When there is a degradation, how quickly can the system auto-heal itself, scale to handle increased load, and/or This one is contraversal, as it's challenging to compare different events that cause degradation. - **Change failure rate: After a production deployment, was it successful? Or was a fix or rollback required after the fact?** How often is a change we made 'successful'? This ties in well with deployment frequency and lead time for changes, but is challenging to measure - as it requires a signoff off of success. Not just that the code deployed correctly, but that there weren't adverse effects or degradation of the deployment to the system -![High performing metrics](https://user-images.githubusercontent.com/8389039/140629477-27849eb7-0550-4ccd-ae43-dee43e48f3ec.png) -(Chart from [page 9 of state of DevOps 2021 report](https://services.google.com/fh/files/misc/state-of-devops-2021.pdf)) -A [demo website displaying the metrics can be viewed here](https://devops-prod-eu-web.azurewebsites.net/). -More information about high performing DevOps metrics can be found in a [blog post here](https://samlearnsazure.blog/2020/04/30/high-performing-devops-metrics/) +![High performing metrics](https://user-images.githubusercontent.com/8389039/212061370-6984b2c3-bc13-4d92-8afc-0068be4cdde1.png) +[^1] ## The current solution: -**We currently have all four of the metrics implemented and undergoing a pilot. There is a Probot for GitHub. (The Azure DevOps widget is on hold to focus on GitHub).** +**We currently have all four of the metrics implemented and undergoing a pilot. (The Azure DevOps widget is currently not planned - but is possible if someone wants to build it!).** - **Deployment Frequency**, in both Azure DevOps and GitHub: - How does it work? We look at the number of successful pipeline runs. @@ -60,12 +62,11 @@ More information about high performing DevOps metrics can be found in a [blog po ![Change failure rate](https://github.com/samsmithnz/DevOpsMetrics/blob/main/ReadmeImages/ChangeFailureRate.png) # Architecture -Developed in .NET 7. A GitHub action runs the CI/CD process. +Developed in .NET 10. A GitHub action runs the CI/CD process. Currently the CI/CD process: 1. Builds the code 2. Runs the unit tests -3. ~~Deploys the Probot code to a Azure web app (http://devops-prod-eu-probot.azurewebsites.net/)~~ (Currently disabled) 3. Deploys the webservice to a Azure web app (https://devops-prod-eu-service.azurewebsites.net) 4. Deploys the demo website to a Azure web app (https://devops-prod-eu-web.azurewebsites.net) 4. Deploys the function website to a Azure function @@ -75,8 +76,8 @@ Dependabot runs daily to check for dependency upgrades. ![Architecture diagram](https://github.com/samsmithnz/DevOpsMetrics/blob/main/ReadmeImages/Architecture.png) ## Badges -The API can generate a URL for static badges, but more work is needed. Some current samples are shown below: -[![Build](https://img.shields.io/badge/Deployment%20frequency-Elite-brightgreen)](https://img.shields.io/badge/Deployment%20frequency-Elite-brightgreen) [![Build](https://img.shields.io/badge/Lead%20time%20for%20changes-High-green)](https://img.shields.io/badge/Lead%20time%20for%20changes-High-green) [![Build](https://img.shields.io/badge/Time%20to%20restore%20service-Medium-orange)](https://img.shields.io/badge/Time%20to%20restore%20service-Medium-orange) [![Build](https://img.shields.io/badge/Change%20failure%20rate-Low-red)](https://img.shields.io/badge/Change%20failure%20rate-Low-red) +The API can generate a URL for static badges, some samples are shown below: +[![Build](https://img.shields.io/badge/Lead%20time%20for%20changes-High-green)](https://img.shields.io/badge/Lead%20time%20for%20changes-High-green) [![Build](https://img.shields.io/badge/Time%20to%20restore%20service-Medium-orange)](https://img.shields.io/badge/Time%20to%20restore%20service-Medium-orange) [![Build](https://img.shields.io/badge/Change%20failure%20rate-Low-red)](https://img.shields.io/badge/Change%20failure%20rate-Low-red) # Setup @@ -91,7 +92,6 @@ The API can generate a URL for static badges, but more work is needed. Some curr # What's next? - Upgrades to packaging and setup (in progress) - Upgrades to store data in CosmosDB (currently in Azure storage) -- Reviewing the current GitHub probot approach, to find a better target than issues (perhaps a metrics readme.md file?) - Support for more scenarios, releases, etc - ~~Azure DevOps marketplace integrations, so you can see the changes real time on your project/repo.~~ (lower priority to focus on GitHub) @@ -99,3 +99,5 @@ The API can generate a URL for static badges, but more work is needed. Some curr - GitHub API: https://developer.GitHub.com/v3/actions/workflow-runs/ - Azure DevOps API: https://docs.microsoft.com/en-us/rest/api/azure/devops/build/builds/list?view=azure-devops-rest-5.1 + +[^1]: Chart from [page 11 of state of DevOps 2022 report](https://cloud.google.com/devops/state-of-devops) diff --git a/src/DevOpsMetrics.Cmd/DevOpsMetrics.Cmd.csproj b/src/DevOpsMetrics.Cmd/DevOpsMetrics.Cmd.csproj new file mode 100644 index 00000000..71e32af7 --- /dev/null +++ b/src/DevOpsMetrics.Cmd/DevOpsMetrics.Cmd.csproj @@ -0,0 +1,34 @@ + + + + Exe + net10.0 + enable + enable + dc96f8ff-2de5-4ed3-b124-89a134688b66 + + + + + + + + + PreserveNewest + + + + + + + + + + + + + + + + + diff --git a/src/DevOpsMetrics.Cmd/Program.cs b/src/DevOpsMetrics.Cmd/Program.cs new file mode 100644 index 00000000..924aebc7 --- /dev/null +++ b/src/DevOpsMetrics.Cmd/Program.cs @@ -0,0 +1,99 @@ +using Azure.Identity; +using DevOpsMetrics.Core.DataAccess.TableStorage; +using DevOpsMetrics.Core.Models.AzureDevOps; +using DevOpsMetrics.Core.Models.Common; +using DevOpsMetrics.Core.Models.GitHub; +using DevOpsMetrics.Service; +using DevOpsMetrics.Service.Controllers; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Azure.Core; +using Azure.Identity; + +namespace DevOpsMetrics.Cmd +{ + internal class Program + { + private static string ProjectId = "DeveloperMetrics_DevOpsMetrics"; + + static async Task Main() + { + DateTime startTime = DateTime.Now; + Console.WriteLine($"C# Timer trigger function UpdateStorageTables started at: {startTime}"); + + //Load settings + IConfigurationBuilder? builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false) + .AddUserSecrets(true); + IConfigurationRoot Configuration = builder.Build(); + ILogger log = new Logger(new LoggerFactory()); + + string? keyVaultURL = Configuration["AppSettings:KeyVaultURL"]; + string? keyVaultId = Configuration["AppSettings:KeyVaultClientId"]; + string? keyVaultSecret = Configuration["AppSettings:KeyVaultClientSecret"]; + string? tenantId = Configuration["AppSettings:TenantId"]; + //AzureServiceTokenProvider azureServiceTokenProvider = new(); + //KeyVaultClient keyVaultClient = new(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); + //builder.AddAzureKeyVault(keyVaultURL, keyVaultId, keyVaultSecret); + if (keyVaultURL != null && keyVaultId != null && keyVaultSecret != null && tenantId != null) + { + TokenCredential tokenCredential = new ClientSecretCredential(tenantId, keyVaultId, keyVaultSecret); + builder.AddAzureKeyVault(new(keyVaultURL), tokenCredential); + } + else + { + throw new System.Exception("Missing configuration for Azure Key Vault"); + } + Configuration = builder.Build(); + ServiceApiClient serviceApiClient = new(Configuration); + + //Get settings + string clientId = Configuration["AppSettings:GitHubClientId"]; + string clientSecret = Configuration["AppSettings:GitHubClientSecret"]; + AzureTableStorageDA azureTableStorageDA = new(); + SettingsController settingsController = new(Configuration, new AzureTableStorageDA()); + DORASummaryController doraSummaryController = new(Configuration); + List azSettings = await settingsController.GetAzureDevOpsSettings(); + List ghSettings = await settingsController.GetGitHubSettings(); + TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + + //Loop through each setting to update the runs, pull requests and pull request commits + int numberOfDays = 30; + int maxNumberOfItems = 20; + int totalResults = 0; + foreach (AzureDevOpsSettings azSetting in azSettings) + { + if (ProjectId == azSetting.RowKey) + { + log.LogInformation($"Processing Azure DevOps organization {azSetting.Organization}, project {azSetting.Project}"); + ProcessingResult ghResult = await doraSummaryController.UpdateDORASummaryItem( + azSetting.Organization, azSetting.Project, azSetting.Repository, + azSetting.Branch, azSetting.BuildName, azSetting.BuildId, + azSetting.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, + null, true, false); + totalResults = ghResult.TotalResults; + } + } + + foreach (GitHubSettings ghSetting in ghSettings) + { + if (ProjectId == ghSetting.RowKey) + { + log.LogInformation($"Processing GitHub owner {ghSetting.Owner}, repo {ghSetting.Repo}"); + ProcessingResult ghResult = await doraSummaryController.UpdateDORASummaryItem( + ghSetting.Owner, "", ghSetting.Repo, ghSetting.Branch, + ghSetting.WorkflowName, ghSetting.WorkflowId, + ghSetting.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, + null, true, true, true); + totalResults = ghResult.TotalResults; + } + } + DateTime endTime = DateTime.Now; + Console.WriteLine($"C# Timer trigger function complete at: {endTime} after updating {totalResults} records"); + Console.WriteLine($"Processing finished in {(endTime - startTime).TotalSeconds}"); + } + } +} \ No newline at end of file diff --git a/src/DevOpsMetrics.Cmd/ServiceApiClient.cs b/src/DevOpsMetrics.Cmd/ServiceApiClient.cs new file mode 100644 index 00000000..f847f08f --- /dev/null +++ b/src/DevOpsMetrics.Cmd/ServiceApiClient.cs @@ -0,0 +1,98 @@ +using System.Diagnostics; +using System.Text; +using DevOpsMetrics.Core.Models.AzureDevOps; +using DevOpsMetrics.Core.Models.Common; +using DevOpsMetrics.Core.Models.GitHub; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; + +namespace DevOpsMetrics.Cmd +{ + public class ServiceApiClient + { + private readonly IConfiguration Configuration; + private readonly HttpClient Client; + + public ServiceApiClient(IConfiguration configuration) + { + Configuration = configuration; + Client = new HttpClient + { + BaseAddress = new Uri(Configuration["AppSettings:WebServiceURL"]) + }; + } + + public async Task> GetAzureDevOpsSettings() + { + string url = $"/api/Settings/GetAzureDevOpsSettings"; + return await GetResponse>(Client, url); + } + + public async Task> GetGitHubSettings() + { + string url = $"/api/Settings/GetGitHubSettings"; + return await GetResponse>(Client, url); + } + + public async Task UpdateDORASummaryItem( + string owner, string project, string repository, + string branch, string workflowName, string workflowId, + string resourceGroup, int numberOfDays, int maxNumberOfItems, + bool isGitHub = true) + { + string url = $"/api/DORASummary/UpdateDORASummaryItem?owner={owner}&project={project}&repository={repository}&branch={branch}&workflowName={workflowName}&workflowId={workflowId}&resourceGroup={resourceGroup}&numberOfDays={numberOfDays}&maxNumberOfItems={maxNumberOfItems}&log=&useCache=true&isGitHub={isGitHub}"; + return await GetResponse(Client, url); + } + + public async Task UpdateDevOpsMonitoringEvent(MonitoringEvent monitoringEvent) + { + string url = $"/api/Settings/UpdateDevOpsMonitoringEvent"; + return await PostResponse(Client, url, monitoringEvent); + } + + private static async Task GetResponse(HttpClient client, string url) + { + T obj = default; + if (client != null && url != null) + { + Debug.WriteLine("Running url: " + client.BaseAddress.ToString() + url); + using (HttpResponseMessage response = await client.GetAsync(url)) + { + //TODO: Add a flag in the service to turn this on and off. + if (response.IsSuccessStatusCode == false) + { + //Log the URL to help with debugging + } + response.EnsureSuccessStatusCode(); + string responseBody = await response.Content.ReadAsStringAsync(); + if (string.IsNullOrEmpty(responseBody) == false) + { + obj = JsonConvert.DeserializeObject(responseBody); + } + } + } + return obj; + } + + private static async Task PostResponse(HttpClient client, string url, MonitoringEvent monitoringEvent) + { + + if (client != null && url != null) + { + StringContent content = new(JsonConvert.SerializeObject(monitoringEvent), Encoding.UTF8, "application/json"); + + Debug.WriteLine("Running url: " + client.BaseAddress.ToString() + url); + using (HttpResponseMessage response = await client.PostAsync(url, content)) + { + response.EnsureSuccessStatusCode(); + //string responseBody = await response.Content.ReadAsStringAsync(); + //if (string.IsNullOrEmpty(responseBody) == false) + //{ + // obj = JsonConvert.DeserializeObject(responseBody); + //} + } + } + return true; + } + } +} diff --git a/src/DevOpsMetrics.Cmd/appsettings.json b/src/DevOpsMetrics.Cmd/appsettings.json new file mode 100644 index 00000000..b734d7e0 --- /dev/null +++ b/src/DevOpsMetrics.Cmd/appsettings.json @@ -0,0 +1,28 @@ +{ + "AppSettings": { + "GitHubClientId": "", //Set in key vault + "GitHubClientSecret": "", //Set in key vault + "AzureDevOpsPatToken": "", //Set in key vault + "AzureStorageAccountConfigurationString": "", + "AzureStorageAccountContainerAzureDevOpsBuilds": "DevOpsAzureDevOpsBuilds", + "AzureStorageAccountContainerAzureDevOpsPRs": "DevOpsAzureDevOpsPRs", + "AzureStorageAccountContainerAzureDevOpsPRCommits": "DevOpsAzureDevOpsPRCommits", + "AzureStorageAccountContainerAzureDevOpsSettings": "DevOpsAzureDevOpsSettings", + "AzureStorageAccountContainerGitHubRuns": "DevOpsGitHubRuns", + "AzureStorageAccountContainerGitHubPRs": "DevOpsGitHubPRs", + "AzureStorageAccountContainerGitHubPRCommits": "DevOpsGitHubPRCommits", + "AzureStorageAccountContainerGitHubSettings": "DevOpsGitHubSettings", + "AzureStorageAccountContainerMTTR": "DevOpsMTTR", + "AzureStorageAccountContainerChangeFailureRate": "DevOpsChangeFailureRate", + "AzureStorageAccountDORASummaryItem": "DevOpsDORASummaryItem", + "AzureStorageAccountContainerTableLog": "DevOpsLog", + "WebServiceURL": "https://devops-prod-eu-service.azurewebsites.net/", + "KeyVaultURL": "https://devops-prod-eu-vault.vault.azure.net/", + "KeyVaultClientId": "", + "KeyVaultClientSecret": "", + "TenantId": "40244aae-8aee-4af5-b221-b480db24698b" + }, + "ApplicationInsights": { + "InstrumentationKey": "dc3fb590-3612-4f09-8c95-e9378cba8654" + } +} diff --git a/src/DevOpsMetrics.Core/ChangeFailureRate.cs b/src/DevOpsMetrics.Core/ChangeFailureRate.cs index 54171a19..0193c455 100644 --- a/src/DevOpsMetrics.Core/ChangeFailureRate.cs +++ b/src/DevOpsMetrics.Core/ChangeFailureRate.cs @@ -74,27 +74,27 @@ private List> GetChangeFailureRate(int numberOfDays public static string GetChangeFailureRateRating(float changeFailureRate) { - string rating = ""; + string rating; if (changeFailureRate < 0) { rating = "None"; } else if (changeFailureRate <= 0.15f) //0-15% - { - rating = "Elite"; - } - else if (changeFailureRate <= 0.30f) //16-30% (not a typo, overriding table to <= 30% to create a range) { rating = "High"; } - else if (changeFailureRate < 0.46f) //16-30% (not a typo, overriding table to < 46% to create a range) + else if (changeFailureRate <= 0.30f) //16-30% { rating = "Medium"; } - else if (changeFailureRate >= 0.46f)// 16-30% (not a typo, overriding table to > 46% to create a range) + else if (changeFailureRate <= 1.00f) //46-60% (not a typo, overriding table from 46-60 to < 100% to create a range and capture all possible values) { rating = "Low"; } + else //no rating + { + rating = "None"; + } return rating; } } diff --git a/src/DevOpsMetrics.Core/DataAccess/APIAccess/AzureDevOpsAPIAccess.cs b/src/DevOpsMetrics.Core/DataAccess/APIAccess/AzureDevOpsAPIAccess.cs index d077c2f7..f8495586 100644 --- a/src/DevOpsMetrics.Core/DataAccess/APIAccess/AzureDevOpsAPIAccess.cs +++ b/src/DevOpsMetrics.Core/DataAccess/APIAccess/AzureDevOpsAPIAccess.cs @@ -71,7 +71,7 @@ private async static Task GetAzureDevOpsMessage(string url, string patTo using (HttpResponseMessage response = await client.GetAsync(url)) { //Throw a response exception - response.EnsureSuccessStatusCode(); + //response.EnsureSuccessStatusCode(); if (response.IsSuccessStatusCode) { responseBody = await response.Content.ReadAsStringAsync(); diff --git a/src/DevOpsMetrics.Core/DataAccess/APIAccess/GitHubAPIAccess.cs b/src/DevOpsMetrics.Core/DataAccess/APIAccess/GitHubAPIAccess.cs index 2f0afcfc..4d92c6a8 100644 --- a/src/DevOpsMetrics.Core/DataAccess/APIAccess/GitHubAPIAccess.cs +++ b/src/DevOpsMetrics.Core/DataAccess/APIAccess/GitHubAPIAccess.cs @@ -58,7 +58,7 @@ public static async Task GetGitHubPullRequestCommitsJArray(string client public async static Task GetGitHubMessage(string url, string clientId, string clientSecret) { - Console.WriteLine($"Running GitHub url: {url}"); + //Console.WriteLine($"Running GitHub url: {url}"); string responseBody = ""; if (url.Contains("api.github.com") == false) { @@ -77,7 +77,7 @@ public async static Task GetGitHubMessage(string url, string clientId, s using (HttpResponseMessage response = await client.GetAsync(url)) { //Throw a response exception - response.EnsureSuccessStatusCode(); + //response.EnsureSuccessStatusCode(); if (response.IsSuccessStatusCode) { responseBody = await response.Content.ReadAsStringAsync(); diff --git a/src/DevOpsMetrics.Core/DataAccess/BuildsDA.cs b/src/DevOpsMetrics.Core/DataAccess/BuildsDA.cs index bf77c9dd..2c32b7c0 100644 --- a/src/DevOpsMetrics.Core/DataAccess/BuildsDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/BuildsDA.cs @@ -18,11 +18,11 @@ public static async Task> GetAzureDevOpsBuilds(string pat { List builds = new(); JArray list = null; - if (useCache == true) + if (useCache) { //Get the builds from Azure storage AzureTableStorageDA daTableStorage = new(); - list = daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsBuilds, PartitionKeys.CreateBuildWorkflowPartitionKey(organization, project, buildName)); + list = await daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsBuilds, PartitionKeys.CreateBuildWorkflowPartitionKey(organization, project, buildName)); } else { @@ -51,11 +51,11 @@ public static async Task> GetGitHubActionRuns(string clie { List runs = new(); JArray list = null; - if (useCache == true) + if (useCache) { //Get the builds from Azure storage AzureTableStorageDA daTableStorage = new(); - list = daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubRuns, PartitionKeys.CreateBuildWorkflowPartitionKey(owner, repo, workflowName)); + list = await daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubRuns, PartitionKeys.CreateBuildWorkflowPartitionKey(owner, repo, workflowName)); } else { diff --git a/src/DevOpsMetrics.Core/DataAccess/ChangeFailureRateDA.cs b/src/DevOpsMetrics.Core/DataAccess/ChangeFailureRateDA.cs index ecb293d5..c6864141 100644 --- a/src/DevOpsMetrics.Core/DataAccess/ChangeFailureRateDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/ChangeFailureRateDA.cs @@ -12,7 +12,7 @@ namespace DevOpsMetrics.Core.DataAccess { public class ChangeFailureRateDA { - public static ChangeFailureRateModel GetChangeFailureRate(bool getSampleData, TableStorageConfiguration tableStorageConfig, + public static async Task GetChangeFailureRate(bool getSampleData, TableStorageConfiguration tableStorageConfig, DevOpsPlatform targetDevOpsPlatform, string organization_owner, string project_repo, string branch, string buildName_workflowName, int numberOfDays, int maxNumberOfItems) { @@ -22,7 +22,7 @@ public static ChangeFailureRateModel GetChangeFailureRate(bool getSampleData, Ta { //Gets a list of change failure rate builds from Azure storage AzureTableStorageDA daTableStorage = new(); - JArray list = daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableChangeFailureRate, PartitionKeys.CreateBuildWorkflowPartitionKey(organization_owner, project_repo, buildName_workflowName)); + JArray list = await daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableChangeFailureRate, PartitionKeys.CreateBuildWorkflowPartitionKey(organization_owner, project_repo, buildName_workflowName)); List initialBuilds = JsonConvert.DeserializeObject>(list.ToString()); //Build the date list and then generate the change failure rate metric @@ -106,7 +106,7 @@ public static async Task UpdateChangeFailureRate(TableStorageConfiguration //Gets a list of change failure rate builds AzureTableStorageDA daTableStorage = new(); string partitionKey = PartitionKeys.CreateBuildWorkflowPartitionKey(organization_owner, project_repo, buildName_workflowName); - JArray list = daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableChangeFailureRate, partitionKey); + JArray list = await daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableChangeFailureRate, partitionKey); List initialBuilds = JsonConvert.DeserializeObject>(list.ToString()); //Get the list of items we are going to process, within the date/day range diff --git a/src/DevOpsMetrics.Core/DataAccess/DORASummaryDA.cs b/src/DevOpsMetrics.Core/DataAccess/DORASummaryDA.cs index 38a827e1..a5e9a02e 100644 --- a/src/DevOpsMetrics.Core/DataAccess/DORASummaryDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/DORASummaryDA.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using DevOpsMetrics.Core.DataAccess.TableStorage; using DevOpsMetrics.Core.Models.Common; using Newtonsoft.Json; @@ -8,22 +9,32 @@ namespace DevOpsMetrics.Core.DataAccess { public class DORASummaryDA { - public static DORASummaryItem GetDORASummaryItem(TableStorageConfiguration tableStorageConfig, - string owner, string repo) + public static async Task> GetDORASummaryItems(TableStorageConfiguration tableStorageConfig, + string owner) { - DORASummaryItem model = null; AzureTableStorageDA da = new(); - JArray list = da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableDORASummaryItem, owner); + JArray list = await da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableDORASummaryItem, owner); List doraItems = JsonConvert.DeserializeObject>(list.ToString()); - foreach (DORASummaryItem item in doraItems) + return doraItems; + } + + public static async Task GetDORASummaryItem(TableStorageConfiguration tableStorageConfig, + string owner, string project, string repo) + { + DORASummaryItem result = null; + List doraItems = await GetDORASummaryItems(tableStorageConfig, owner); + if (doraItems != null) { - if (item.Repo.ToLower() == repo.ToLower()) + foreach (DORASummaryItem item in doraItems) { - model = item; - break; + if ((project != null && item.Project == project) || (item.Repo?.ToLower() == repo.ToLower())) + { + result = item; + break; + } } } - return model; + return result; } } diff --git a/src/DevOpsMetrics.Core/DataAccess/DeploymentFrequencyDA.cs b/src/DevOpsMetrics.Core/DataAccess/DeploymentFrequencyDA.cs index 2020840a..68b7d562 100644 --- a/src/DevOpsMetrics.Core/DataAccess/DeploymentFrequencyDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/DeploymentFrequencyDA.cs @@ -107,7 +107,7 @@ public static async Task GetAzureDevOpsDeploymentFrequ DeploymentName = buildName, BuildList = builds, DeploymentsPerDayMetric = 10f, - DeploymentsPerDayMetricDescription = "Elite", + DeploymentsPerDayMetricDescription = "High", NumberOfDays = numberOfDays, MaxNumberOfItems = builds.Count, TotalItems = builds.Count @@ -212,7 +212,7 @@ public static async Task GetGitHubDeploymentFrequency( DeploymentName = workflowName, BuildList = builds, DeploymentsPerDayMetric = 10f, - DeploymentsPerDayMetricDescription = "Elite", + DeploymentsPerDayMetricDescription = "High", NumberOfDays = numberOfDays, MaxNumberOfItems = builds.Count, TotalItems = builds.Count diff --git a/src/DevOpsMetrics.Core/DataAccess/LeadTimeForChangesDA.cs b/src/DevOpsMetrics.Core/DataAccess/LeadTimeForChangesDA.cs index 9faa0e8d..5134a370 100644 --- a/src/DevOpsMetrics.Core/DataAccess/LeadTimeForChangesDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/LeadTimeForChangesDA.cs @@ -166,7 +166,7 @@ public static async Task GetAzureDevOpsLeadTimesForChan AverageBuildHours = 1f, AveragePullRequestHours = 12f, LeadTimeForChangesMetric = 12f + 1f, - LeadTimeForChangesMetricDescription = "Elite", + LeadTimeForChangesMetricDescription = "High", PullRequests = samplePullRequests, NumberOfDays = numberOfDays, MaxNumberOfItems = samplePullRequests.Count, @@ -282,10 +282,6 @@ public static async Task GetGitHubLeadTimesForChanges(b { pullRequest.Status = "inProgress"; } - else - { - pullRequest.Status = pullRequest.Status; - } leadTimeForChangesList.Add(new KeyValuePair(minTime, pullRequest.Duration)); pullRequests.Add(pullRequest); @@ -327,7 +323,7 @@ public static async Task GetGitHubLeadTimesForChanges(b AverageBuildHours = averageBuildHours, AveragePullRequestHours = leadTime, LeadTimeForChangesMetric = leadTime + averageBuildHours, - LeadTimeForChangesMetricDescription = LeadTimeForChanges.GetLeadTimeForChangesRating(leadTime), + LeadTimeForChangesMetricDescription = LeadTimeForChanges.GetLeadTimeForChangesRating(leadTime + averageBuildHours), PullRequests = utility.GetLastNItems(pullRequests, maxNumberOfItems), NumberOfDays = numberOfDays, MaxNumberOfItems = uiPullRequests.Count, @@ -346,7 +342,7 @@ public static async Task GetGitHubLeadTimesForChanges(b AverageBuildHours = 1f, AveragePullRequestHours = 20.33f, LeadTimeForChangesMetric = 20.33f + 1f, - LeadTimeForChangesMetricDescription = "Elite", + LeadTimeForChangesMetricDescription = "High", PullRequests = samplePullRequests, NumberOfDays = numberOfDays, MaxNumberOfItems = samplePullRequests.Count, diff --git a/src/DevOpsMetrics.Core/DataAccess/MeanTimeToRestoreDA.cs b/src/DevOpsMetrics.Core/DataAccess/MeanTimeToRestoreDA.cs index 957ccc41..40b18377 100644 --- a/src/DevOpsMetrics.Core/DataAccess/MeanTimeToRestoreDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/MeanTimeToRestoreDA.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using DevOpsMetrics.Core.DataAccess.Common; using DevOpsMetrics.Core.DataAccess.TableStorage; using DevOpsMetrics.Core.Models.Azure; @@ -11,7 +12,7 @@ namespace DevOpsMetrics.Core.DataAccess { public class MeanTimeToRestoreDA { - public static MeanTimeToRestoreModel GetAzureMeanTimeToRestore(bool getSampleData, + public static async Task GetAzureMeanTimeToRestore(bool getSampleData, TableStorageConfiguration tableStorageConfig, DevOpsPlatform targetDevOpsPlatform, string resourceGroup, int numberOfDays, int maxNumberOfItems) @@ -27,7 +28,7 @@ public static MeanTimeToRestoreModel GetAzureMeanTimeToRestore(bool getSampleDat //Pull the events from the table storage AzureTableStorageDA daTableStorage = new(); - JArray list = daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableMTTR, resourceGroup); + JArray list = await daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableMTTR, resourceGroup); List alerts = new(); foreach (JToken item in list) { diff --git a/src/DevOpsMetrics.Core/DataAccess/PullRequestsDA.cs b/src/DevOpsMetrics.Core/DataAccess/PullRequestsDA.cs index 06af6def..e0f8cefe 100644 --- a/src/DevOpsMetrics.Core/DataAccess/PullRequestsDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/PullRequestsDA.cs @@ -17,11 +17,11 @@ public static async Task GetAzureDevOpsPullRequest(string patToke { List prs = new(); JArray list; - if (useCache == true) + if (useCache) { //Get the pull requests from Azure storage AzureTableStorageDA daTableStorage = new(); - list = daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsPRs, PartitionKeys.CreateGitHubPRPartitionKey(organization, project)); + list = await daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsPRs, PartitionKeys.CreateGitHubPRPartitionKey(organization, project)); } else { @@ -49,11 +49,11 @@ public static async Task GetAzureDevOpsPullRequest(string patToke public static async Task> GetAzureDevOpsPullRequestCommits(string patToken, TableStorageConfiguration tableStorageConfig, string organization, string project, string repository, string pullRequestId, bool useCache) { JArray list; - if (useCache == true) + if (useCache) { //Get the commits from Azure storage AzureTableStorageDA daTableStorage = new(); - list = daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsPRCommits, PartitionKeys.CreateAzureDevOpsPRCommitPartitionKey(organization, project, pullRequestId)); + list = await daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsPRCommits, PartitionKeys.CreateAzureDevOpsPRCommitPartitionKey(organization, project, pullRequestId)); } else { @@ -69,11 +69,11 @@ public static async Task GetGitHubPullRequest(string clientId, string { List prs = new(); JArray list; - if (useCache == true) + if (useCache) { //Get the pull requests from Azure storage AzureTableStorageDA daTableStorage = new(); - list = daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubPRs, PartitionKeys.CreateGitHubPRPartitionKey(owner, repo)); + list = await daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubPRs, PartitionKeys.CreateGitHubPRPartitionKey(owner, repo)); } else { @@ -101,11 +101,11 @@ public static async Task GetGitHubPullRequest(string clientId, string public static async Task> GetGitHubPullRequestCommits(string clientId, string clientSecret, TableStorageConfiguration tableStorageConfig, string owner, string repo, string pull_number, bool useCache) { JArray list; - if (useCache == true) + if (useCache) { //Get the commits from Azure storage AzureTableStorageDA daTableStorage = new(); - list = daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubPRCommits, PartitionKeys.CreateGitHubPRCommitPartitionKey(owner, repo, pull_number)); + list = await daTableStorage.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubPRCommits, PartitionKeys.CreateGitHubPRCommitPartitionKey(owner, repo, pull_number)); } else { diff --git a/src/DevOpsMetrics.Core/DataAccess/TableStorage/AzureTableStorageDA.cs b/src/DevOpsMetrics.Core/DataAccess/TableStorage/AzureTableStorageDA.cs index a8ec7196..7013e9d4 100644 --- a/src/DevOpsMetrics.Core/DataAccess/TableStorage/AzureTableStorageDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/TableStorage/AzureTableStorageDA.cs @@ -23,14 +23,18 @@ public class AzureTableStorageDA : IAzureTableStorageDA /// Partition key to filter by /// Include Partition and row key metadata (for debugging) /// - public JArray GetTableStorageItemsFromStorage(TableStorageConfiguration tableStorageConfig, string tableName, string partitionKey, bool includePartitionAndRowKeys = false) + public async Task GetTableStorageItemsFromStorage(TableStorageConfiguration tableStorageConfig, string tableName, string partitionKey, bool includePartitionAndRowKeys = false) { TableStorageCommonDA tableDA = new(tableStorageConfig.StorageAccountConnectionString, tableName); - List items = tableDA.GetItems(partitionKey); + List items = await tableDA.GetItems(partitionKey); + if (items == null) + { + items = new(); + } JArray list = new(); foreach (AzureStorageTableModel item in items) { - if (includePartitionAndRowKeys == true) + if (includePartitionAndRowKeys) { string data = item.Data?.ToString(); list.Add( @@ -48,11 +52,11 @@ public JArray GetTableStorageItemsFromStorage(TableStorageConfiguration tableSto return list; } - public List GetAzureDevOpsSettingsFromStorage(TableStorageConfiguration tableStorageConfig, string settingsTable, string rowKey) + public async Task> GetAzureDevOpsSettingsFromStorage(TableStorageConfiguration tableStorageConfig, string settingsTable, string rowKey) { List settings = null; string partitionKey = "AzureDevOpsSettings"; - JArray list = GetTableStorageItemsFromStorage(tableStorageConfig, settingsTable, partitionKey); + JArray list = await GetTableStorageItemsFromStorage(tableStorageConfig, settingsTable, partitionKey); if (list != null) { settings = JsonConvert.DeserializeObject>(list.ToString()); @@ -70,11 +74,11 @@ public List GetAzureDevOpsSettingsFromStorage(TableStorageC } } - public List GetGitHubSettingsFromStorage(TableStorageConfiguration tableStorageConfig, string settingsTable, string rowKey) + public async Task> GetGitHubSettingsFromStorage(TableStorageConfiguration tableStorageConfig, string settingsTable, string rowKey) { List settings = null; string partitionKey = "GitHubSettings"; - JArray list = GetTableStorageItemsFromStorage(tableStorageConfig, settingsTable, partitionKey); + JArray list = await GetTableStorageItemsFromStorage(tableStorageConfig, settingsTable, partitionKey); if (list != null) { settings = JsonConvert.DeserializeObject>(list.ToString()); @@ -92,10 +96,10 @@ public List GetGitHubSettingsFromStorage(TableStorageConfigurati } } - public List GetProjectLogsFromStorage(TableStorageConfiguration tableStorageConfig, string partitionKey) + public async Task> GetProjectLogsFromStorage(TableStorageConfiguration tableStorageConfig, string partitionKey) { List logs = null; - JArray list = GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableLog, partitionKey, true); + JArray list = await GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableLog, partitionKey, true); if (list != null) { logs = JsonConvert.DeserializeObject>(list.ToString()); @@ -109,7 +113,10 @@ public async Task UpdateAzureDevOpsBuildsInStorage(string patToken, TableSt int numberOfDays, int maxNumberOfItems) { JArray items = await AzureDevOpsAPIAccess.GetAzureDevOpsBuildsJArray(patToken, organization, project); - + if (items == null) + { + items = new(); + } int itemsAdded = 0; TableStorageCommonDA tableBuildsDA = new(tableStorageConfig.StorageAccountConnectionString, tableStorageConfig.TableAzureDevOpsBuilds); TableStorageCommonDA tableChangeFailureRateDA = new(tableStorageConfig.StorageAccountConnectionString, tableStorageConfig.TableChangeFailureRate); @@ -124,7 +131,7 @@ public async Task UpdateAzureDevOpsBuildsInStorage(string patToken, TableSt string partitionKey = PartitionKeys.CreateBuildWorkflowPartitionKey(organization, project, buildName); string rowKey = build.buildNumber; AzureStorageTableModel newItem = new(partitionKey, rowKey, item.ToString()); - if (await tableBuildsDA.AddItem(newItem) == true) + if (await tableBuildsDA.AddItem(newItem)) { itemsAdded++; } @@ -154,7 +161,10 @@ public async Task UpdateAzureDevOpsPullRequestsInStorage(string patToken, T int numberOfDays, int maxNumberOfItems) { JArray items = await AzureDevOpsAPIAccess.GetAzureDevOpsPullRequestsJArray(patToken, organization, project, repository); - + if (items == null) + { + items = new(); + } int itemsAdded = 0; TableStorageCommonDA tableDA = new(tableStorageConfig.StorageAccountConnectionString, tableStorageConfig.TableAzureDevOpsPRs); //Check each build to see if it's in storage, adding the items not in storage @@ -165,7 +175,7 @@ public async Task UpdateAzureDevOpsPullRequestsInStorage(string patToken, T string partitionKey = PartitionKeys.CreateAzureDevOpsPRPartitionKey(organization, project); string rowKey = pullRequest.PullRequestId; AzureStorageTableModel newItem = new(partitionKey, rowKey, item.ToString()); - if (await tableDA.AddItem(newItem) == true) + if (await tableDA.AddItem(newItem)) { itemsAdded++; } @@ -183,7 +193,10 @@ public async Task UpdateAzureDevOpsPullRequestCommitsInStorage(string patTo int numberOfDays, int maxNumberOfItems) { JArray items = await AzureDevOpsAPIAccess.GetAzureDevOpsPullRequestCommitsJArray(patToken, organization, project, repository, pullRequestId); - + if (items == null) + { + items = new(); + } int itemsAdded = 0; TableStorageCommonDA tableDA = new(tableStorageConfig.StorageAccountConnectionString, tableStorageConfig.TableAzureDevOpsPRCommits); //Check each build to see if it's in storage, adding the items not in storage @@ -194,7 +207,7 @@ public async Task UpdateAzureDevOpsPullRequestCommitsInStorage(string patTo string partitionKey = PartitionKeys.CreateAzureDevOpsPRCommitPartitionKey(organization, project, pullRequestId); string rowKey = pullRequestCommit.commitId; AzureStorageTableModel newItem = new(partitionKey, rowKey, item.ToString()); - if (await tableDA.AddItem(newItem) == true) + if (await tableDA.AddItem(newItem)) { itemsAdded++; } @@ -208,6 +221,10 @@ public async Task UpdateGitHubActionRunsInStorage(string clientId, string c int numberOfDays, int maxNumberOfItems) { JArray items = await GitHubAPIAccess.GetGitHubActionRunsJArray(clientId, clientSecret, owner, repo, workflowId); + if (items == null) + { + items = new(); + } Debug.WriteLine($"{items.Count} builds found for {owner}/{repo}/{workflowName}"); int itemsAdded = 0; @@ -224,7 +241,7 @@ public async Task UpdateGitHubActionRunsInStorage(string clientId, string c string partitionKey = PartitionKeys.CreateBuildWorkflowPartitionKey(owner, repo, workflowName); string rowKey = build.run_number; AzureStorageTableModel newItem = new(partitionKey, rowKey, item.ToString()); - if (await tableBuildDA.AddItem(newItem) == true) + if (await tableBuildDA.AddItem(newItem)) { itemsAdded++; } @@ -257,7 +274,7 @@ public async Task UpdateChangeFailureRate(TableStorageCommonDA tableChangeF string rowKey = newBuild.Id; string json = JsonConvert.SerializeObject(newBuild); AzureStorageTableModel newItem = new(partitionKey, rowKey, json); - if (await tableChangeFailureRateDA.AddItem(newItem, forceUpdate) == true) + if (await tableChangeFailureRateDA.AddItem(newItem, forceUpdate)) { itemsAdded++; } @@ -269,7 +286,10 @@ public async Task UpdateGitHubActionPullRequestsInStorage(string clientId, int numberOfDays, int maxNumberOfItems) { JArray items = await GitHubAPIAccess.GetGitHubPullRequestsJArray(clientId, clientSecret, owner, repo, branch); - + if (items == null) + { + items = new(); + } int itemsAdded = 0; TableStorageCommonDA tableDA = new(tableStorageConfig.StorageAccountConnectionString, tableStorageConfig.TableGitHubPRs); //Check each build to see if it's in storage, adding the items not in storage @@ -277,12 +297,12 @@ public async Task UpdateGitHubActionPullRequestsInStorage(string clientId, { GitHubPR pr = JsonConvert.DeserializeObject(item.ToString()); - if (pr.state == "closed" & pr.merged_at != null) + if (pr.state == "closed" && pr.merged_at != null) { string partitionKey = PartitionKeys.CreateGitHubPRPartitionKey(owner, repo); string rowKey = pr.number; string json = item.ToString(); - Debug.WriteLine($"PartitionKey: {partitionKey}, RowKey: {rowKey}, Length: {json.Length}"); + //Debug.WriteLine($"PartitionKey: {partitionKey}, RowKey: {rowKey}, Length: {json.Length}"); if (item.ToString().Length > (1024 * 32)) //1024 x 32 is the column limit { @@ -292,7 +312,7 @@ public async Task UpdateGitHubActionPullRequestsInStorage(string clientId, json = o.ToString(); } AzureStorageTableModel newItem = new(partitionKey, rowKey, json); - if (await tableDA.AddItem(newItem) == true) + if (await tableDA.AddItem(newItem)) { itemsAdded++; } @@ -307,7 +327,10 @@ public async Task UpdateGitHubActionPullRequestCommitsInStorage(string clie string owner, string repo, string pull_number) { JArray items = await GitHubAPIAccess.GetGitHubPullRequestCommitsJArray(clientId, clientSecret, owner, repo, pull_number); - + if (items == null) + { + items = new(); + } int itemsAdded = 0; TableStorageCommonDA tableDA = new(tableStorageConfig.StorageAccountConnectionString, tableStorageConfig.TableGitHubPRCommits); //Check each build to see if it's in storage, adding the items not in storage @@ -318,7 +341,7 @@ public async Task UpdateGitHubActionPullRequestCommitsInStorage(string clie string partitionKey = PartitionKeys.CreateGitHubPRCommitPartitionKey(owner, repo, pull_number); string rowKey = commit.sha; AzureStorageTableModel newItem = new(partitionKey, rowKey, item.ToString()); - if (await tableDA.AddItem(newItem) == true) + if (await tableDA.AddItem(newItem)) { itemsAdded++; } @@ -397,10 +420,18 @@ public async Task UpdateProjectLogInStorage(TableStorageConfiguration tabl } public static async Task UpdateDORASummaryItem(TableStorageConfiguration tableStorageConfig, - string owner, string repo, DORASummaryItem DORASummaryItem) + string owner, string project, string repo, DORASummaryItem DORASummaryItem) { string partitionKey = owner; - string rowKey = repo; + string rowKey; + if (string.IsNullOrEmpty(project)) + { + rowKey = repo; + } + else + { + rowKey = project; + } string json = JsonConvert.SerializeObject(DORASummaryItem); AzureStorageTableModel newItem = new(partitionKey, rowKey, json); TableStorageCommonDA tableDA = new(tableStorageConfig.StorageAccountConnectionString, tableStorageConfig.TableDORASummaryItem); diff --git a/src/DevOpsMetrics.Core/DataAccess/TableStorage/IAzureTableStorageDA.cs b/src/DevOpsMetrics.Core/DataAccess/TableStorage/IAzureTableStorageDA.cs index c287ac58..3c150d57 100644 --- a/src/DevOpsMetrics.Core/DataAccess/TableStorage/IAzureTableStorageDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/TableStorage/IAzureTableStorageDA.cs @@ -9,9 +9,9 @@ namespace DevOpsMetrics.Core.DataAccess.TableStorage { public interface IAzureTableStorageDA { - JArray GetTableStorageItemsFromStorage(TableStorageConfiguration tableStorageConfig, string tableName, string partitionKey, bool includePartitionAndRowKeys = false); - List GetAzureDevOpsSettingsFromStorage(TableStorageConfiguration tableStorageConfig, string settingsTable, string rowKey); - List GetGitHubSettingsFromStorage(TableStorageConfiguration tableStorageConfig, string settingsTable, string rowKey); + Task GetTableStorageItemsFromStorage(TableStorageConfiguration tableStorageConfig, string tableName, string partitionKey, bool includePartitionAndRowKeys = false); + Task> GetAzureDevOpsSettingsFromStorage(TableStorageConfiguration tableStorageConfig, string settingsTable, string rowKey); + Task> GetGitHubSettingsFromStorage(TableStorageConfiguration tableStorageConfig, string settingsTable, string rowKey); Task UpdateAzureDevOpsBuildsInStorage(string patToken, TableStorageConfiguration tableStorageConfig, string organization, string project, string branch, string buildName, string buildId, int numberOfDays, int maxNumberOfItems); Task UpdateAzureDevOpsPullRequestCommitsInStorage(string patToken, TableStorageConfiguration tableStorageConfig, string organization, string project, string repository, string pullRequestId, int numberOfDays, int maxNumberOfItems); Task UpdateAzureDevOpsPullRequestsInStorage(string patToken, TableStorageConfiguration tableStorageConfig, string organization, string project, string repository, int numberOfDays, int maxNumberOfItems); @@ -22,7 +22,7 @@ public interface IAzureTableStorageDA Task UpdateGitHubActionPullRequestsInStorage(string clientId, string clientSecret, TableStorageConfiguration tableStorageConfig, string owner, string repo, string branch, int numberOfDays, int maxNumberOfItems); Task UpdateGitHubActionRunsInStorage(string clientId, string clientSecret, TableStorageConfiguration tableStorageConfig, string owner, string repo, string branch, string workflowName, string workflowId, int numberOfDays, int maxNumberOfItems); Task UpdateGitHubSettingInStorage(TableStorageConfiguration tableStorageConfig, string settingsTable, string owner, string repo, string branch, string workflowName, string workflowId, string resourceGroupName, int itemOrder, bool showSetting); - List GetProjectLogsFromStorage(TableStorageConfiguration tableStorageConfig, string partitionKey); + Task> GetProjectLogsFromStorage(TableStorageConfiguration tableStorageConfig, string partitionKey); Task UpdateProjectLogInStorage(TableStorageConfiguration tableStorageConfig, ProjectLog log); } } \ No newline at end of file diff --git a/src/DevOpsMetrics.Core/DataAccess/TableStorage/PartitionKeys.cs b/src/DevOpsMetrics.Core/DataAccess/TableStorage/PartitionKeys.cs index c827ef98..af0559df 100644 --- a/src/DevOpsMetrics.Core/DataAccess/TableStorage/PartitionKeys.cs +++ b/src/DevOpsMetrics.Core/DataAccess/TableStorage/PartitionKeys.cs @@ -1,6 +1,8 @@ -namespace DevOpsMetrics.Core.DataAccess.TableStorage +using System.Text.RegularExpressions; + +namespace DevOpsMetrics.Core.DataAccess.TableStorage { - public static class PartitionKeys + public static partial class PartitionKeys { public static string CreateAzureDevOpsSettingsPartitionKey(string organization, string project, string repository) { @@ -11,7 +13,7 @@ public static string CreateAzureDevOpsSettingsPartitionKeyPatToken(string organi { //add a azdo/Azure DevOps prefix, remove _/underscores and append a suffix for the pat token string result = "azdo" + CreateAzureDevOpsSettingsPartitionKey(organization, project, repository) + "patToken"; - return result.Replace("_", ""); + return CleanKey(result.Replace("_", "")); } public static string CreateBuildWorkflowPartitionKey(string organization_owner, string project_repo, string buildName_workflowName) @@ -38,14 +40,14 @@ public static string CreateGitHubSettingsPartitionKeyClientId(string owner, stri { //add a gh/github prefix, remove _/underscores and append a suffix for the client id string result = "gh" + CreateGitHubSettingsPartitionKey(owner, repo) + "clientId"; - return result.Replace("_", ""); + return CleanKey(result.Replace("_", "")); } public static string CreateGitHubSettingsPartitionKeyClientSecret(string owner, string repo) { //add a gh/github prefix, remove _/underscores and append a suffix for the client secret string result = "gh" + CreateGitHubSettingsPartitionKey(owner, repo) + "clientSecret"; - return result.Replace("_", ""); + return CleanKey(result.Replace("_", "")); } public static string CreateGitHubPRPartitionKey(string owner, string repo) @@ -57,5 +59,11 @@ public static string CreateGitHubPRCommitPartitionKey(string owner, string repo, { return owner + "_" + repo + "_" + pullRequestId; } + + //Only Alphanumerics and hyphens allowed https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftkeyvault + public static string CleanKey(string name) + { + return Regex.Replace(name, @"[^a-zA-Z0-9]+", "-").Trim('-'); + } } } diff --git a/src/DevOpsMetrics.Core/DataAccess/TableStorage/TableStorageCommonDA.cs b/src/DevOpsMetrics.Core/DataAccess/TableStorage/TableStorageCommonDA.cs index 9e411fd7..8cb2051a 100644 --- a/src/DevOpsMetrics.Core/DataAccess/TableStorage/TableStorageCommonDA.cs +++ b/src/DevOpsMetrics.Core/DataAccess/TableStorage/TableStorageCommonDA.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Azure; +using Azure.Data.Tables; using DevOpsMetrics.Core.Models.Azure; -using Microsoft.Azure.Cosmos.Table; namespace DevOpsMetrics.Core.DataAccess.TableStorage { @@ -21,32 +22,25 @@ public TableStorageCommonDA() { } - private CloudTable CreateConnection() + private TableClient CreateConnection() { - //CloudStorageAccount storageAccount = new CloudStorageAccount(new StorageCredentials(AccountName, AccessKey), true); - CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationString); - - // Create the table client. - CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); - - // Get a reference to a table named "items" - CloudTable table = tableClient.GetTableReference(TableName); + // Create a TableServiceClient for service level operations + TableServiceClient serviceClient = new(ConfigurationString); // Create the table if it doesn't exist - //table.CreateIfNotExists(); // DON"T use this, it throws an internal 409 in App insights: https://stackoverflow.com/questions/48893519/azure-table-storage-exception-409-conflict-unexpected - if (!table.Exists()) - { - table.Create(); - } + TableClient tableClient = serviceClient.GetTableClient(TableName); - return table; + // Create the table if it doesn't exist. + tableClient.CreateIfNotExists(); + + return tableClient; } public async Task AddItem(AzureStorageTableModel data, bool forceUpdate = false) { //Check if the item exists in storage AzureStorageTableModel item = await GetItem(data.PartitionKey, data.RowKey); - if (item == null || forceUpdate == true) + if (item == null || forceUpdate) { await SaveItem(data); return true; //data saved to table! @@ -62,39 +56,37 @@ public async Task GetItem(string partitionKey, string ro //prepare the partition key partitionKey = EncodePartitionKey(partitionKey); - CloudTable table = CreateConnection(); - - // Create a retrieve operation that takes a customer entity. - TableOperation retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey); + TableClient tableClient = CreateConnection(); - // Execute the retrieve operation. - TableResult retrievedResult = await table.ExecuteAsync(retrieveOperation); - - return (AzureStorageTableModel)retrievedResult.Result; + // Get the entity. + NullableResponse temp = await tableClient.GetEntityIfExistsAsync(partitionKey, rowKey); + if (temp.HasValue) + { + return await tableClient.GetEntityAsync(partitionKey, rowKey); + } + else + { + return null; + } } //This can't be async, because of how it queries the underlying data - public List GetItems(string partitionKey) + public async Task> GetItems(string partitionKey) { partitionKey = EncodePartitionKey(partitionKey); - CloudTable table = CreateConnection(); - - // execute the query on the table - List list = table.CreateQuery() - .Where(ent => ent.PartitionKey == partitionKey) - .ToList(); + TableClient tableClient = CreateConnection(); + AsyncPageable results = tableClient.QueryAsync(e => e.PartitionKey == partitionKey); + List list = await results.ToListAsync(); return list; } public async Task SaveItem(AzureStorageTableModel data) { - CloudTable table = CreateConnection(); + TableClient tableClient = CreateConnection(); + await tableClient.UpsertEntityAsync(data); - // Create the TableOperation that inserts/merges the entity. - TableOperation operation = TableOperation.InsertOrMerge(data); - await table.ExecuteAsync(operation); return true; } @@ -124,23 +116,26 @@ public async Task SaveItem(AzureStorageTableModel data) public static string EncodePartitionKey(string text) { - //The forward slash(/) character - //The backslash(\) character - //The number sign(#) character - //The question mark (?) character - text = text.Replace("/", "_"); - //text = text.Replace("\\", "_"); - //text = text.Replace("#", "_"); - //text = text.Replace("?", "_"); - - ////Control characters from U+0000 to U+001F, including: - ////The horizontal tab(\t) character - //text = text.Replace("\t", "_"); - ////The linefeed(\n) character - //text = text.Replace("\n", "_"); - ////The carriage return (\r) character - //text = text.Replace("\r", "_"); - ////Control characters from U + 007F to U+009F + if (string.IsNullOrEmpty(text) == false) + { + //The forward slash(/) character + //The backslash(\) character + //The number sign(#) character + //The question mark (?) character + text = text.Replace("/", "_"); + //text = text.Replace("\\", "_"); + //text = text.Replace("#", "_"); + //text = text.Replace("?", "_"); + + ////Control characters from U+0000 to U+001F, including: + ////The horizontal tab(\t) character + //text = text.Replace("\t", "_"); + ////The linefeed(\n) character + //text = text.Replace("\n", "_"); + ////The carriage return (\r) character + //text = text.Replace("\r", "_"); + ////Control characters from U + 007F to U+009F + } return text; } diff --git a/src/DevOpsMetrics.Core/DeploymentFrequency.cs b/src/DevOpsMetrics.Core/DeploymentFrequency.cs index 948b9fe7..4edac705 100644 --- a/src/DevOpsMetrics.Core/DeploymentFrequency.cs +++ b/src/DevOpsMetrics.Core/DeploymentFrequency.cs @@ -70,26 +70,22 @@ public static string GetDeploymentFrequencyRating(float deploymentsPerDay) float dailyDeployment = 1f; //float weeklyDeployment = 1f / 7f; float monthlyDeployment = 1f / 30f; - float everySixMonthsDeployment = 1f / (6f * 30f); //Every 6 months + //float everySixMonthsDeployment = 1f / (6f * 30f); //Every 6 months string rating = ""; if (deploymentsPerDay <= 0f) { rating = "None"; } - else if (deploymentsPerDay >= dailyDeployment) //NOTE: Does not capture on-demand deployments - { - rating = "Elite"; - } - else if (deploymentsPerDay < dailyDeployment && deploymentsPerDay >= monthlyDeployment) + else if (deploymentsPerDay >= dailyDeployment) //NOTE: Assumes on-demand deployments are <1 day { rating = "High"; } - else if (deploymentsPerDay < monthlyDeployment && deploymentsPerDay >= everySixMonthsDeployment) + else if (deploymentsPerDay < dailyDeployment && deploymentsPerDay >= monthlyDeployment) //Captures days to months { rating = "Medium"; } - else if (deploymentsPerDay < everySixMonthsDeployment) + else if (deploymentsPerDay < monthlyDeployment) { rating = "Low"; } diff --git a/src/DevOpsMetrics.Core/DevOpsMetrics.Core.csproj b/src/DevOpsMetrics.Core/DevOpsMetrics.Core.csproj index a4b9414c..cb4079c6 100644 --- a/src/DevOpsMetrics.Core/DevOpsMetrics.Core.csproj +++ b/src/DevOpsMetrics.Core/DevOpsMetrics.Core.csproj @@ -1,13 +1,14 @@ - + - net7.0 + net10.0 - + - + + diff --git a/src/DevOpsMetrics.Core/GlobalSuppressions.cs b/src/DevOpsMetrics.Core/GlobalSuppressions.cs index 20197f8e..cabd9003 100644 --- a/src/DevOpsMetrics.Core/GlobalSuppressions.cs +++ b/src/DevOpsMetrics.Core/GlobalSuppressions.cs @@ -11,3 +11,9 @@ //[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "This is a 'silly' rule. The switch is clearer.", Scope = "member", Target = "~M:DevOpsMetrics.Core.Models.Common.Badges.BadgeColor(System.String)~System.String")] [assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "This is a 'silly' rule. The switch is clearer.", Scope = "member", Target = "~M:DevOpsMetrics.Core.Models.Common.Badges.BadgeURL(System.String,System.String)~System.String")] [assembly: SuppressMessage("CodeQuality", "IDE0077:Avoid legacy format target in 'SuppressMessageAttribute'", Justification = "")] +[assembly: SuppressMessage("Style", "IDE0270:Use coalesce expression", Justification = "Does not improve readability", Scope = "member", Target = "~M:DevOpsMetrics.Core.DataAccess.TableStorage.AzureTableStorageDA.UpdateGitHubActionPullRequestCommitsInStorage(System.String,System.String,DevOpsMetrics.Core.Models.Common.TableStorageConfiguration,System.String,System.String,System.String)~System.Threading.Tasks.Task{System.Int32}")] +[assembly: SuppressMessage("Style", "IDE0074:Use compound assignment", Justification = "Does not improve readability", Scope = "member", Target = "~M:DevOpsMetrics.Core.DataAccess.TableStorage.AzureTableStorageDA.UpdateGitHubActionPullRequestCommitsInStorage(System.String,System.String,DevOpsMetrics.Core.Models.Common.TableStorageConfiguration,System.String,System.String,System.String)~System.Threading.Tasks.Task{System.Int32}")] +[assembly: SuppressMessage("Style", "IDE0270:Use coalesce expression", Justification = "Does not improve readability", Scope = "member", Target = "~M:DevOpsMetrics.Core.DataAccess.TableStorage.AzureTableStorageDA.UpdateGitHubActionPullRequestsInStorage(System.String,System.String,DevOpsMetrics.Core.Models.Common.TableStorageConfiguration,System.String,System.String,System.String,System.Int32,System.Int32)~System.Threading.Tasks.Task{System.Int32}")] +[assembly: SuppressMessage("Style", "IDE0074:Use compound assignment", Justification = "Does not improve readability", Scope = "member", Target = "~M:DevOpsMetrics.Core.DataAccess.TableStorage.AzureTableStorageDA.UpdateGitHubActionPullRequestsInStorage(System.String,System.String,DevOpsMetrics.Core.Models.Common.TableStorageConfiguration,System.String,System.String,System.String,System.Int32,System.Int32)~System.Threading.Tasks.Task{System.Int32}")] +[assembly: SuppressMessage("Style", "IDE0270:Use coalesce expression", Justification = "Does not improve readability", Scope = "member", Target = "~M:DevOpsMetrics.Core.DataAccess.TableStorage.AzureTableStorageDA.UpdateGitHubActionRunsInStorage(System.String,System.String,DevOpsMetrics.Core.Models.Common.TableStorageConfiguration,System.String,System.String,System.String,System.String,System.String,System.Int32,System.Int32)~System.Threading.Tasks.Task{System.Int32}")] +[assembly: SuppressMessage("Style", "IDE0074:Use compound assignment", Justification = "Does not improve readability", Scope = "member", Target = "~M:DevOpsMetrics.Core.DataAccess.TableStorage.AzureTableStorageDA.UpdateGitHubActionRunsInStorage(System.String,System.String,DevOpsMetrics.Core.Models.Common.TableStorageConfiguration,System.String,System.String,System.String,System.String,System.String,System.Int32,System.Int32)~System.Threading.Tasks.Task{System.Int32}")] diff --git a/src/DevOpsMetrics.Core/LeadTimeForChanges.cs b/src/DevOpsMetrics.Core/LeadTimeForChanges.cs index 1e2df776..a5461f57 100644 --- a/src/DevOpsMetrics.Core/LeadTimeForChanges.cs +++ b/src/DevOpsMetrics.Core/LeadTimeForChanges.cs @@ -64,32 +64,30 @@ private List> GetLeadTimeForChanges(int numberO public static string GetLeadTimeForChangesRating(float leadTimeForChangesInHours) { - float dailyDeployment = 24f; + //float dailyDeployment = 24f; float weeklyDeployment = 24f * 7f; - //float monthlyDeployment = 24f * 30f; - float sixMonthDeployment = 24f * 30f * 6f; + float monthlyDeployment = 24f * 30f; + //float sixMonthDeployment = 24f * 30f * 6f; string rating = ""; if (leadTimeForChangesInHours <= 0f) //no rating { rating = "None"; } - else if (leadTimeForChangesInHours < dailyDeployment) //less than one day - { - rating = "Elite"; - } - else if (leadTimeForChangesInHours >= dailyDeployment && leadTimeForChangesInHours <= weeklyDeployment) //between one day and one week + else if (leadTimeForChangesInHours <= weeklyDeployment) //between one day and one week/ or once a week or faster { rating = "High"; } - else if (leadTimeForChangesInHours > weeklyDeployment && leadTimeForChangesInHours <= sixMonthDeployment) //between one week and six monthes + else if (leadTimeForChangesInHours > weeklyDeployment && leadTimeForChangesInHours <= monthlyDeployment) //between one week and one month { rating = "Medium"; } - else if (leadTimeForChangesInHours > sixMonthDeployment) //more than once every 6 months month + else if (leadTimeForChangesInHours > monthlyDeployment) //more than once every month { rating = "Low"; } + //no rating else statement not required here, as all scenarios are covered above with < and > + return rating; } } diff --git a/src/DevOpsMetrics.Core/MeanTimeToRestore.cs b/src/DevOpsMetrics.Core/MeanTimeToRestore.cs index 3695cc38..69c05b00 100644 --- a/src/DevOpsMetrics.Core/MeanTimeToRestore.cs +++ b/src/DevOpsMetrics.Core/MeanTimeToRestore.cs @@ -65,35 +65,29 @@ private List> GetMeanTimeToRestore(int numberOf public static string GetMeanTimeToRestoreRating(float meanTimeToRestoreInHours) { - float hourlyRestoration = 1f; - float dailyDeployment = 24f; - float weeklyDeployment = 24f * 7f; + //float hourlyRestoration = 1f; + float dailyRestoration = 24f; + float weeklyRestoration = 24f * 7f; - string rating; + string rating = ""; if (meanTimeToRestoreInHours <= 0) { rating = "None"; } - else if (meanTimeToRestoreInHours < hourlyRestoration) //less than one hour - { - rating = "Elite"; - } - else if (meanTimeToRestoreInHours < dailyDeployment) //less than one day + else if (meanTimeToRestoreInHours < dailyRestoration) //less than one day { rating = "High"; } - else if (meanTimeToRestoreInHours < weeklyDeployment) //less than one day and one week + else if (meanTimeToRestoreInHours < weeklyRestoration) //less than one day and one week { rating = "Medium"; } - else if (meanTimeToRestoreInHours > weeklyDeployment) //more than one week + else if (meanTimeToRestoreInHours > weeklyRestoration) //more than one week (originally 1 week - 1 month) { rating = "Low"; } - else //no rating - { - rating = "None"; - } + //no rating else statement not required here, as all scenarios are covered above with < and > + return rating; } } diff --git a/src/DevOpsMetrics.Core/Models/Azure/AzureStorageTableModel.cs b/src/DevOpsMetrics.Core/Models/Azure/AzureStorageTableModel.cs index 2acbb5a2..5505f37f 100644 --- a/src/DevOpsMetrics.Core/Models/Azure/AzureStorageTableModel.cs +++ b/src/DevOpsMetrics.Core/Models/Azure/AzureStorageTableModel.cs @@ -1,9 +1,11 @@ -using DevOpsMetrics.Core.DataAccess.TableStorage; -using Microsoft.Azure.Cosmos.Table; +using System; +using Azure; +using Azure.Data.Tables; +using DevOpsMetrics.Core.DataAccess.TableStorage; namespace DevOpsMetrics.Core.Models.Azure { - public class AzureStorageTableModel : TableEntity + public class AzureStorageTableModel : ITableEntity { public AzureStorageTableModel(string partitionKey, string rowKey, string data) { @@ -21,5 +23,21 @@ public string Data { get; set; } + public string PartitionKey + { + get; set; + } + public string RowKey + { + get; set; + } + public DateTimeOffset? Timestamp + { + get; set; + } + public ETag ETag + { + get; set; + } } } diff --git a/src/DevOpsMetrics.Core/Models/Common/Badges.cs b/src/DevOpsMetrics.Core/Models/Common/Badges.cs index b717f353..35770f58 100644 --- a/src/DevOpsMetrics.Core/Models/Common/Badges.cs +++ b/src/DevOpsMetrics.Core/Models/Common/Badges.cs @@ -24,7 +24,7 @@ public static string BadgeURL(string description, string rating) break; } - //Example: https://img.shields.io/badge/Deployment%20frequency-Elite-brightgreen + //Example: https://img.shields.io/badge/Deployment%20frequency-High-green return $"https://img.shields.io/badge/{description}-{rating}-{color}"; } } diff --git a/src/DevOpsMetrics.Core/Models/Common/ChangeFailureRateModel.cs b/src/DevOpsMetrics.Core/Models/Common/ChangeFailureRateModel.cs index e8b17a1f..4f47dbef 100644 --- a/src/DevOpsMetrics.Core/Models/Common/ChangeFailureRateModel.cs +++ b/src/DevOpsMetrics.Core/Models/Common/ChangeFailureRateModel.cs @@ -51,7 +51,7 @@ public string BadgeURL { get { - //Example: https://img.shields.io/badge/Change%20failure%20rate-Elite-brightgreen + //Example: https://img.shields.io/badge/Change%20failure%20rate-High-green string title = Uri.EscapeDataString("Change failure rate"); return Badges.BadgeURL(title, ChangeFailureRateMetricDescription); } @@ -62,7 +62,7 @@ public string BadgeWithMetricURL get { string changeFailureRate = ChangeFailureRateMetric.ToString("0.00%").Replace("-", ""); - //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-Elite-brightgreen + //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-High-green string title = Uri.EscapeDataString("Change failure rate (" + changeFailureRate + ")"); return Badges.BadgeURL(title, ChangeFailureRateMetricDescription); } diff --git a/src/DevOpsMetrics.Core/Models/Common/DORASummaryItem.cs b/src/DevOpsMetrics.Core/Models/Common/DORASummaryItem.cs index 4a0422e1..c67fd2af 100644 --- a/src/DevOpsMetrics.Core/Models/Common/DORASummaryItem.cs +++ b/src/DevOpsMetrics.Core/Models/Common/DORASummaryItem.cs @@ -1,15 +1,54 @@ -namespace DevOpsMetrics.Core.Models.Common +using System; + +namespace DevOpsMetrics.Core.Models.Common { public class DORASummaryItem { + public DORASummaryItem() + { + } + + public DORASummaryItem(string owner, string project, string repo) + { + Owner = owner; + Project = project; + Repo = repo; + //Set the properties to defaults (none) + DeploymentFrequency = 0; + DeploymentFrequencyBadgeURL = "https://img.shields.io/badge/Deployment%20frequency-None-lightgrey"; + DeploymentFrequencyBadgeWithMetricURL = "https://img.shields.io/badge/Deployment%20frequency%20-None-lightgrey"; + LeadTimeForChanges = 0; + LeadTimeForChangesBadgeURL = "https://img.shields.io/badge/Lead%20time%20for%20changes-None-lightgrey"; + LeadTimeForChangesBadgeWithMetricURL = "https://img.shields.io/badge/Lead%20time%20for%20changes%20-None-lightgrey"; + MeanTimeToRestore = 0; + MeanTimeToRestoreBadgeURL = "https://img.shields.io/badge/Time%20to%20restore%20service-None-lightgrey"; + MeanTimeToRestoreBadgeWithMetricURL = "https://img.shields.io/badge/Time%20to%20restore%20service%20-None-lightgrey"; + ChangeFailureRate = 0; + ChangeFailureRateBadgeURL = "https://img.shields.io/badge/Change%20failure%20rate-None-lightgrey"; + ChangeFailureRateBadgeWithMetricURL = "https://img.shields.io/badge/Change%20failure%20rate%20-None-lightgrey"; + LastUpdatedMessage = "No data available"; + } + public string Owner { get; set; } + public string Project + { + get; set; + } public string Repo { get; set; } + public int NumberOfDays + { + get; set; + } + public float DeploymentFrequency + { + get; set; + } public string DeploymentFrequencyBadgeURL { get; set; @@ -18,11 +57,19 @@ public string DeploymentFrequencyBadgeWithMetricURL { get; set; } + public float LeadTimeForChanges + { + get; set; + } public string LeadTimeForChangesBadgeURL { get; set; } - public string LeadTimeForChangesWithMetricURL + public string LeadTimeForChangesBadgeWithMetricURL + { + get; set; + } + public float MeanTimeToRestore { get; set; } @@ -34,6 +81,10 @@ public string MeanTimeToRestoreBadgeWithMetricURL { get; set; } + public float ChangeFailureRate + { + get; set; + } public string ChangeFailureRateBadgeURL { get; set; @@ -42,5 +93,13 @@ public string ChangeFailureRateBadgeWithMetricURL { get; set; } + public string LastUpdatedMessage + { + get; set; + } + public DateTime LastUpdated + { + get; set; + } } } diff --git a/src/DevOpsMetrics.Core/Models/Common/DeploymentFrequencyModel.cs b/src/DevOpsMetrics.Core/Models/Common/DeploymentFrequencyModel.cs index a2cf4b26..62320a1c 100644 --- a/src/DevOpsMetrics.Core/Models/Common/DeploymentFrequencyModel.cs +++ b/src/DevOpsMetrics.Core/Models/Common/DeploymentFrequencyModel.cs @@ -105,7 +105,7 @@ public string BadgeURL { get { - //Example: https://img.shields.io/badge/Deployment%20frequency-Elite-brightgreen + //Example: https://img.shields.io/badge/Deployment%20frequency-High-green string title = Uri.EscapeDataString("Deployment frequency"); return Badges.BadgeURL(title, DeploymentsPerDayMetricDescription); } @@ -115,7 +115,7 @@ public string BadgeWithMetricURL { get { - //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-Elite-brightgreen + //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-High-green string title = Uri.EscapeDataString("Deployment frequency (" + DeploymentsToDisplayMetric.ToString("0.00") + " " + DeploymentsToDisplayUnit + ")"); return Badges.BadgeURL(title, DeploymentsPerDayMetricDescription); } diff --git a/src/DevOpsMetrics.Core/Models/Common/LeadTimeForChangesModel.cs b/src/DevOpsMetrics.Core/Models/Common/LeadTimeForChangesModel.cs index befe8f57..93541455 100644 --- a/src/DevOpsMetrics.Core/Models/Common/LeadTimeForChangesModel.cs +++ b/src/DevOpsMetrics.Core/Models/Common/LeadTimeForChangesModel.cs @@ -85,7 +85,7 @@ public string BadgeURL { get { - //Example: https://img.shields.io/badge/Lead%20time%20for%20changes-Elite-brightgreen + //Example: https://img.shields.io/badge/Lead%20time%20for%20changes-High-green string title = Uri.EscapeDataString("Lead time for changes"); return Badges.BadgeURL(title, LeadTimeForChangesMetricDescription); } @@ -95,7 +95,7 @@ public string BadgeWithMetricURL { get { - //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-Elite-brightgreen + //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-High-green string title = Uri.EscapeDataString("Lead time for changes (" + LeadTimeForChangesMetricDisplayMetric.ToString("0.0") + " " + LeadTimeForChangesMetricDisplayUnit + ")"); return Badges.BadgeURL(title, LeadTimeForChangesMetricDescription); } diff --git a/src/DevOpsMetrics.Core/Models/Common/MeanTimeToRestoreModel.cs b/src/DevOpsMetrics.Core/Models/Common/MeanTimeToRestoreModel.cs index 4ee94af7..62e7d7ba 100644 --- a/src/DevOpsMetrics.Core/Models/Common/MeanTimeToRestoreModel.cs +++ b/src/DevOpsMetrics.Core/Models/Common/MeanTimeToRestoreModel.cs @@ -62,7 +62,7 @@ public string BadgeURL { get { - //Example: https://img.shields.io/badge/Time%20to%20restore%20service-Elite-brightgreen + //Example: https://img.shields.io/badge/Time%20to%20restore%20service-High-green string title = Uri.EscapeDataString("Time to restore service"); return Badges.BadgeURL(title, MTTRAverageDurationDescription); } @@ -72,7 +72,7 @@ public string BadgeWithMetricURL { get { - //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-Elite-brightgreen + //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-High-green string title = Uri.EscapeDataString("Time to restore service (" + MTTRAverageDurationInHours.ToString("0.00") + " hours)"); return Badges.BadgeURL(title, MTTRAverageDurationDescription); } diff --git a/src/DevOpsMetrics.Function/ProcessingResult.cs b/src/DevOpsMetrics.Core/Models/Common/ProcessingResult.cs similarity index 85% rename from src/DevOpsMetrics.Function/ProcessingResult.cs rename to src/DevOpsMetrics.Core/Models/Common/ProcessingResult.cs index aa7c90dd..a7f929b9 100644 --- a/src/DevOpsMetrics.Function/ProcessingResult.cs +++ b/src/DevOpsMetrics.Core/Models/Common/ProcessingResult.cs @@ -1,4 +1,4 @@ -namespace DevOpsMetrics.Function +namespace DevOpsMetrics.Core.Models.Common { public class ProcessingResult { diff --git a/src/DevOpsMetrics.Core/Models/Common/SLAModel.cs b/src/DevOpsMetrics.Core/Models/Common/SLAModel.cs index 6ae17986..3b2717d2 100644 --- a/src/DevOpsMetrics.Core/Models/Common/SLAModel.cs +++ b/src/DevOpsMetrics.Core/Models/Common/SLAModel.cs @@ -48,7 +48,7 @@ public int ItemOrder //public string BadgeURL { // get // { - // //Example: https://img.shields.io/badge/Time%20to%20restore%20service-Elite-brightgreen + // //Example: https://img.shields.io/badge/Time%20to%20restore%20service-High-green // string title = Uri.EscapeDataString("SLA"); // return Badges.BadgeURL(title, MTTRAverageDurationDescription); // } @@ -58,7 +58,7 @@ public int ItemOrder //{ // get // { - // //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-Elite-brightgreen + // //Example: https://img.shields.io/badge/Change%20failure%20rate%20(83.33%25)-High-green // string title = Uri.EscapeDataString("Time to restore service (" + MTTRAverageDurationInHours.ToString("0.00") + " hours)"); // return Badges.BadgeURL(title, MTTRAverageDurationDescription); // } diff --git a/src/DevOpsMetrics.Function/AzureAlertProcessor.cs b/src/DevOpsMetrics.Function/AzureAlertProcessor.cs index 926accf4..c0fdd849 100644 --- a/src/DevOpsMetrics.Function/AzureAlertProcessor.cs +++ b/src/DevOpsMetrics.Function/AzureAlertProcessor.cs @@ -1,9 +1,7 @@ using System.IO; using System.Reflection; using System.Threading.Tasks; -using DevOpsMetrics.Core.DataAccess.TableStorage; using DevOpsMetrics.Core.Models.Common; -using DevOpsMetrics.Service.Controllers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; @@ -29,6 +27,7 @@ public static async Task Run( .AddUserSecrets(Assembly.GetExecutingAssembly(), true) .AddEnvironmentVariables() .Build(); + ServiceApiClient serviceApiClient = new(configuration); //Process response string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); @@ -36,9 +35,9 @@ public static async Task Run( //save response to table //ServiceApiClient api = new ServiceApiClient(configuration); - SettingsController settingsController = new(configuration, new AzureTableStorageDA()); + //SettingsController settingsController = new(configuration, new AzureTableStorageDA()); MonitoringEvent monitoringEvent = new(requestBody); - await settingsController.UpdateDevOpsMonitoringEvent(monitoringEvent); + await serviceApiClient.UpdateDevOpsMonitoringEvent(monitoringEvent); string responseMessage = "monitoring event processed successfully"; return new OkObjectResult(responseMessage); diff --git a/src/DevOpsMetrics.Function/DevOpsMetrics.Function.csproj b/src/DevOpsMetrics.Function/DevOpsMetrics.Function.csproj index 2f331ede..7d7c976c 100644 --- a/src/DevOpsMetrics.Function/DevOpsMetrics.Function.csproj +++ b/src/DevOpsMetrics.Function/DevOpsMetrics.Function.csproj @@ -1,24 +1,25 @@  - - net7.0 - v4 - ed162474-1392-455c-b088-8b07e5438062 - - - - - - - - - - - - - PreserveNewest - - - Always - - + + + net10.0 + v4 + ed162474-1392-455c-b088-8b07e5438062 + + + + + + + + + + + + + PreserveNewest + + + Always + + \ No newline at end of file diff --git a/src/DevOpsMetrics.Function/NightlyProcessor.cs b/src/DevOpsMetrics.Function/NightlyProcessor.cs index eab9427a..ff2f5b21 100644 --- a/src/DevOpsMetrics.Function/NightlyProcessor.cs +++ b/src/DevOpsMetrics.Function/NightlyProcessor.cs @@ -2,17 +2,16 @@ using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using Azure.Core; using DevOpsMetrics.Core.DataAccess.TableStorage; using DevOpsMetrics.Core.Models.AzureDevOps; using DevOpsMetrics.Core.Models.Common; using DevOpsMetrics.Core.Models.GitHub; -using DevOpsMetrics.Service; -using DevOpsMetrics.Service.Controllers; -using Microsoft.Azure.KeyVault; -using Microsoft.Azure.Services.AppAuthentication; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Azure.Core; +using Azure.Identity; namespace DevOpsMetrics.Function { @@ -38,54 +37,53 @@ public static async Task Run( string keyVaultURL = Configuration["AppSettings:KeyVaultURL"]; string keyVaultId = Configuration["AppSettings:KeyVaultClientId"]; string keyVaultSecret = Configuration["AppSettings:KeyVaultClientSecret"]; - AzureServiceTokenProvider azureServiceTokenProvider = new(); - KeyVaultClient keyVaultClient = new(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); - builder.AddAzureKeyVault(keyVaultURL, keyVaultId, keyVaultSecret); + string tenantId = Configuration["AppSettings:TenantId"]; + //AzureServiceTokenProvider azureServiceTokenProvider = new(); + //KeyVaultClient keyVaultClient = new(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); + //builder.AddAzureKeyVault(keyVaultURL, keyVaultId, keyVaultSecret); + + if (keyVaultURL != null && keyVaultId != null && keyVaultSecret != null && tenantId != null) + { + TokenCredential tokenCredential = new ClientSecretCredential(tenantId, keyVaultId, keyVaultSecret); + builder.AddAzureKeyVault(new(keyVaultURL), tokenCredential); + } + else + { + throw new System.Exception("Missing configuration for Azure Key Vault"); + } Configuration = builder.Build(); + ServiceApiClient serviceApiClient = new(Configuration); //Get settings string clientId = Configuration["AppSettings:GitHubClientId"]; string clientSecret = Configuration["AppSettings:GitHubClientSecret"]; AzureTableStorageDA azureTableStorageDA = new(); - BuildsController buildsController = new(Configuration, azureTableStorageDA); - PullRequestsController pullRequestsController = new(Configuration, azureTableStorageDA); - SettingsController settingsController = new(Configuration, azureTableStorageDA); - List azSettings = settingsController.GetAzureDevOpsSettings(); - List ghSettings = settingsController.GetGitHubSettings(); - TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + List azSettings = await serviceApiClient.GetAzureDevOpsSettings(); + List ghSettings = await serviceApiClient.GetGitHubSettings(); //Loop through each setting to update the runs, pull requests and pull request commits int numberOfDays = 30; int maxNumberOfItems = 20; int totalResults = 0; - foreach (AzureDevOpsSettings item in azSettings) + foreach (AzureDevOpsSettings azSetting in azSettings) { - // (int, string) buildsUpdated = (0, null); - // (int, string) prsUpdated = (0, null); - // try - // { - log.LogInformation($"Processing Azure DevOps organization {item.Organization}, project {item.Project}"); - // buildsUpdated = await api.UpdateAzureDevOpsBuilds(item.Organization, item.Project, item.Repository, item.Branch, item.BuildName, item.BuildId, numberOfDays, maxNumberOfItems); - // prsUpdated = await api.UpdateAzureDevOpsPullRequests(item.Organization, item.Project, item.Repository, numberOfDays, maxNumberOfItems); - // log.LogInformation($"Processed Azure DevOps organization {item.Organization}, project {item.Project}. {buildsUpdated.Item1} builds and {prsUpdated.Item1} prs/commits updated"); - // totalResults += buildsUpdated.Item1 + prsUpdated.Item1; - // await api.UpdateAzureDevOpsProjectLog(item.Organization, item.Project, item.Repository, buildsUpdated.Item1, prsUpdated.Item1, buildsUpdated.Item2, prsUpdated.Item2, null, null); - // } - // catch (Exception ex) - // { - // string error = $"Exception while processing Azure DevOps organization {item.Organization}, project {item.Project}. {buildsUpdated.Item1} builds and {prsUpdated.Item1} prs/commits updated"; - // log.LogInformation(error); - // await api.UpdateAzureDevOpsProjectLog(item.Organization, item.Project, item.Repository, buildsUpdated.Item1, prsUpdated.Item1, buildsUpdated.Item2, prsUpdated.Item2, ex.Message, error); - // } + log.LogInformation($"Processing Azure DevOps organization {azSetting.Organization}, project {azSetting.Project}"); + ProcessingResult ghResult = await serviceApiClient.UpdateDORASummaryItem( + azSetting.Organization, azSetting.Project, azSetting.Repository, + azSetting.Branch, azSetting.BuildName, azSetting.BuildId, + azSetting.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, false); + totalResults = ghResult.TotalResults; } foreach (GitHubSettings ghSetting in ghSettings) { - ProcessingResult ghResult = await Processing.ProcessGitHubItem(ghSetting, - clientId, clientSecret, tableStorageConfig, - numberOfDays, maxNumberOfItems, - buildsController, pullRequestsController, settingsController, - log, totalResults); + log.LogInformation($"Processing GitHub owner {ghSetting.Owner}, repo {ghSetting.Repo}"); + ProcessingResult ghResult = await serviceApiClient.UpdateDORASummaryItem( + ghSetting.Owner, "", ghSetting.Repo, ghSetting.Branch, + ghSetting.WorkflowName, ghSetting.WorkflowId, + ghSetting.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, true); totalResults = ghResult.TotalResults; } log.LogInformation($"C# Timer trigger function complete at: {DateTime.Now} after updating {totalResults} records"); diff --git a/src/DevOpsMetrics.Function/Processing.cs b/src/DevOpsMetrics.Function/Processing.cs deleted file mode 100644 index d06c3cbc..00000000 --- a/src/DevOpsMetrics.Function/Processing.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Threading.Tasks; -using DevOpsMetrics.Core; -using DevOpsMetrics.Core.DataAccess; -using DevOpsMetrics.Core.DataAccess.TableStorage; -using DevOpsMetrics.Core.Models.Common; -using DevOpsMetrics.Core.Models.GitHub; -using DevOpsMetrics.Service.Controllers; -using Microsoft.Extensions.Logging; - -namespace DevOpsMetrics.Function -{ - public static class Processing - { - - public async static Task ProcessGitHubItem(GitHubSettings ghSetting, - string clientId, - string clientSecret, - TableStorageConfiguration tableStorageConfig, - int numberOfDays, - int maxNumberOfItems, - BuildsController buildsController, - PullRequestsController pullRequestsController, - SettingsController settingsController, - ILogger log, - int totalResults) - { - ProcessingResult result = new() - { - TotalResults = totalResults - }; - try - { - log.LogInformation($"Processing GitHub owner {ghSetting.Owner}, repo {ghSetting.Repo}"); - result.BuildsUpdated = await buildsController.UpdateGitHubActionRuns(ghSetting.Owner, ghSetting.Repo, ghSetting.Branch, ghSetting.WorkflowName, ghSetting.WorkflowId, numberOfDays, maxNumberOfItems); - //log.LogInformation($"Processing GitHub owner {item.Owner}, repo {item.Repo}: {buildsUpdated} builds updated"); - result.PRsUpdated = await pullRequestsController.UpdateGitHubActionPullRequests(ghSetting.Owner, ghSetting.Repo, ghSetting.Branch, numberOfDays, maxNumberOfItems); - //log.LogInformation($"Processing GitHub owner {item.Owner}, repo {item.Repo}: {prsUpdated} pull requests updated"); - log.LogInformation($"Processed GitHub owner {ghSetting.Owner}, repo {ghSetting.Repo}. {result.BuildsUpdated} builds and {result.PRsUpdated} prs/commits updated"); - result.TotalResults += result.BuildsUpdated + result.PRsUpdated; - - //Process summary results for last 90 days, creating badges for the four metrics - await UpdateSummaryMetrics(clientId, clientSecret, tableStorageConfig, - ghSetting.Owner, ghSetting.Repo, ghSetting.Branch, - ghSetting.WorkflowName, ghSetting.WorkflowId, - ghSetting.ProductionResourceGroup, - numberOfDays, maxNumberOfItems); - - await settingsController.UpdateGitHubProjectLog(ghSetting.Owner, ghSetting.Repo, result.BuildsUpdated, result.PRsUpdated, "", "", null, null); - } - catch (Exception ex) - { - string error = $"Exception while processing GitHub owner {ghSetting.Owner}, repo {ghSetting.Repo}. {result.BuildsUpdated} builds and {result.PRsUpdated} prs/commits updated"; - log.LogInformation(error); - await settingsController.UpdateGitHubProjectLog(ghSetting.Owner, ghSetting.Repo, result.BuildsUpdated, result.PRsUpdated, - ghSetting.Owner + "_" + ghSetting.Repo + "_" + ghSetting.Branch + "_" + ghSetting.WorkflowName + "_" + ghSetting.WorkflowId + "_" + numberOfDays + "_" + maxNumberOfItems, - ghSetting.Owner + "_" + ghSetting.Repo + "_" + ghSetting.Branch + "_" + numberOfDays + "_" + maxNumberOfItems, - ex.ToString(), error); - } - - return result; - } - - private static async Task UpdateSummaryMetrics(string clientId, string clientSecret, - TableStorageConfiguration tableStorageConfig, - string owner, string repo, - string branch, string workflowName, string workflowId, - string resourceGroup, - int numberOfDays, int maxNumberOfItems, - bool useCache = true) - { - //Get the DORA metrics for the last 90 days - DeploymentFrequencyModel deploymentFrequencyModel = await DeploymentFrequencyDA.GetGitHubDeploymentFrequency(false, clientId, clientSecret, tableStorageConfig, - owner, repo, branch, workflowName, workflowId, - numberOfDays, maxNumberOfItems, useCache); - - LeadTimeForChangesModel leadTimeForChangesModel = await LeadTimeForChangesDA.GetGitHubLeadTimesForChanges(false, clientId, clientSecret, tableStorageConfig, - owner, repo, branch, workflowName, workflowId, - numberOfDays, maxNumberOfItems, useCache); - - MeanTimeToRestoreModel meanTimeToRestoreModel = new(); - if (resourceGroup != null) - { - meanTimeToRestoreModel = MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(false, tableStorageConfig, - DevOpsPlatform.GitHub, - resourceGroup, - numberOfDays, maxNumberOfItems); - } - else - { - meanTimeToRestoreModel.MTTRAverageDurationInHours = 0; - meanTimeToRestoreModel.MTTRAverageDurationDescription = MeanTimeToRestore.GetMeanTimeToRestoreRating(0); - } - - ChangeFailureRateModel changeFailureRateModel = ChangeFailureRateDA.GetChangeFailureRate(false, tableStorageConfig, - DevOpsPlatform.GitHub, - owner, repo, branch, workflowName, - numberOfDays, maxNumberOfItems); - - //Summarize the results into a new object - DORASummaryItem DORASummary = new() - { - Owner = owner, - Repo = repo, - DeploymentFrequencyBadgeURL = deploymentFrequencyModel.BadgeURL, - DeploymentFrequencyBadgeWithMetricURL = deploymentFrequencyModel.BadgeWithMetricURL, - LeadTimeForChangesBadgeURL = leadTimeForChangesModel.BadgeURL, - LeadTimeForChangesWithMetricURL = leadTimeForChangesModel.BadgeWithMetricURL, - MeanTimeToRestoreBadgeURL = meanTimeToRestoreModel.BadgeURL, - MeanTimeToRestoreBadgeWithMetricURL = meanTimeToRestoreModel.BadgeWithMetricURL, - ChangeFailureRateBadgeURL = changeFailureRateModel.BadgeURL, - ChangeFailureRateBadgeWithMetricURL = changeFailureRateModel.BadgeWithMetricURL - }; - - //Serialize the summary into an Azure storage table - await AzureTableStorageDA.UpdateDORASummaryItem(tableStorageConfig, owner, repo, DORASummary); - - return true; - } - } -} diff --git a/src/DevOpsMetrics.Function/ServiceApiClient.cs b/src/DevOpsMetrics.Function/ServiceApiClient.cs index acc9ad72..aaac5fbe 100644 --- a/src/DevOpsMetrics.Function/ServiceApiClient.cs +++ b/src/DevOpsMetrics.Function/ServiceApiClient.cs @@ -1,130 +1,102 @@ -//using System; -//using System.Collections.Generic; -//using System.Diagnostics; -//using System.Net.Http; -//using System.Text; -//using System.Threading.Tasks; -//using System.Web; -//using DevOpsMetrics.Core.Models.AzureDevOps; -//using DevOpsMetrics.Core.Models.Common; -//using DevOpsMetrics.Core.Models.GitHub; -//using Microsoft.Extensions.Configuration; -//using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using DevOpsMetrics.Core.Models.AzureDevOps; +using DevOpsMetrics.Core.Models.Common; +using DevOpsMetrics.Core.Models.GitHub; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; -//namespace DevOpsMetrics.Function -//{ -// public class ServiceApiClient -// { -// private readonly IConfiguration Configuration; -// private readonly HttpClient Client; +namespace DevOpsMetrics.Function +{ + public class ServiceApiClient + { + private readonly IConfiguration Configuration; + private readonly HttpClient Client; -// public ServiceApiClient(IConfiguration configuration) -// { -// Configuration = configuration; -// Client = new HttpClient -// { -// BaseAddress = new Uri(Configuration["AppSettings:WebServiceURL"]) -// }; -// } + public ServiceApiClient(IConfiguration configuration) + { + Configuration = configuration; + Client = new HttpClient + { + BaseAddress = new Uri(Configuration["AppSettings:WebServiceURL"]) + }; + } -// public async Task> GetAzureDevOpsSettings() -// { -// string url = $"/api/Settings/GetAzureDevOpsSettings"; -// return await GetResponse>(Client, url); -// } + public async Task> GetAzureDevOpsSettings() + { + string url = $"/api/Settings/GetAzureDevOpsSettings"; + return await GetResponse>(Client, url); + } -// public async Task> GetGitHubSettings() -// { -// string url = $"/api/Settings/GetGitHubSettings"; -// return await GetResponse>(Client, url); -// } + public async Task> GetGitHubSettings() + { + string url = $"/api/Settings/GetGitHubSettings"; + return await GetResponse>(Client, url); + } -// public async Task<(int, string)> UpdateAzureDevOpsBuilds(string organization, string project, string repository, string branch, string buildName, string buildId, int numberOfDays, int maxNumberOfItems) -// { -// string url = $"/api/Builds/UpdateAzureDevOpsBuilds?organization={organization}&project={project}&repository={repository}&branch={branch}&buildName={buildName}&buildId={buildId}&numberOfDays={numberOfDays}&maxNumberOfItems={maxNumberOfItems}"; -// return (await GetResponse(Client, url), url); -// } + public async Task UpdateDORASummaryItem( + string owner, string project, string repository, + string branch, string workflowName, string workflowId, + string resourceGroup, int numberOfDays, int maxNumberOfItems, + bool isGitHub = true) + { + string url = $"/api/DORASummary/UpdateDORASummaryItem?owner={owner}&project={project}&repository={repository}&branch={branch}&workflowName={workflowName}&workflowId={workflowId}&resourceGroup={resourceGroup}&numberOfDays={numberOfDays}&maxNumberOfItems={maxNumberOfItems}&log=&useCache=true&isGitHub={isGitHub}"; + return await GetResponse(Client, url); + } -// public async Task<(int, string)> UpdateGitHubActionRuns(string owner, string repo, string branch, string workflowName, string workflowId, int numberOfDays, int maxNumberOfItems) -// { -// string url = $"/api/Builds/UpdateGitHubActionRuns?owner={owner}&repo={repo}&branch={branch}&workflowName={workflowName}&workflowId={workflowId}&numberOfDays={numberOfDays}&maxNumberOfItems={maxNumberOfItems}"; -// return (await GetResponse(Client, url), url); -// } + public async Task UpdateDevOpsMonitoringEvent(MonitoringEvent monitoringEvent) + { + string url = $"/api/Settings/UpdateDevOpsMonitoringEvent"; + return await PostResponse(Client, url, monitoringEvent); + } -// public async Task<(int, string)> UpdateAzureDevOpsPullRequests(string organization, string project, string repository, int numberOfDays, int maxNumberOfItems) -// { -// string url = $"/api/PullRequests/UpdateAzureDevOpsPullRequests?organization={organization}&project={project}&repository={repository}&numberOfDays={numberOfDays}&maxNumberOfItems={maxNumberOfItems}"; -// return (await GetResponse(Client, url), url); -// } + private async Task GetResponse(HttpClient client, string url) + { + T obj = default; + if (client != null && url != null) + { + Debug.WriteLine("Running url: " + client.BaseAddress.ToString() + url); + using (HttpResponseMessage response = await client.GetAsync(url)) + { + //TODO: Add a flag in the service to turn this on and off. + if (response.IsSuccessStatusCode == false) + { + //Log the URL to help with debugging + } + response.EnsureSuccessStatusCode(); + string responseBody = await response.Content.ReadAsStringAsync(); + if (string.IsNullOrEmpty(responseBody) == false) + { + obj = JsonConvert.DeserializeObject(responseBody); + } + } + } + return obj; + } -// public async Task<(int, string)> UpdateGitHubActionPullRequests(string owner, string repo, string branch, int numberOfDays, int maxNumberOfItems) -// { -// string url = $"/api/PullRequests/UpdateGitHubActionPullRequests?owner={owner}&repo={repo}&branch={branch}&numberOfDays={numberOfDays}&maxNumberOfItems={maxNumberOfItems}"; -// return (await GetResponse(Client, url), url); -// } + private async Task PostResponse(HttpClient client, string url, MonitoringEvent monitoringEvent) + { -// public async Task UpdateDevOpsMonitoringEvent(string requestBody) -// { -// MonitoringEvent monitoringEvent = new MonitoringEvent(requestBody); -// string url = $"/api/Settings/UpdateDevOpsMonitoringEvent"; -// return await PostResponse(Client, url, monitoringEvent); -// } + if (client != null && url != null) + { + StringContent content = new StringContent(JsonConvert.SerializeObject(monitoringEvent), Encoding.UTF8, "application/json"); -// public async Task UpdateAzureDevOpsProjectLog(string organization, string project, string repository, int buildsUpdated, int prsUpdated, string buildUrl, string prUrl, string exceptionMessage, string exceptionStackTrace) -// { -// string url = $"/api/Settings/UpdateAzureDevOpsProjectLog?organization={organization}&project={project}&repository={repository}&buildsUpdated={buildsUpdated}&prsUpdated={prsUpdated}&buildsUpdated={buildsUpdated}&prsUpdated={prsUpdated}&buildUrl={HttpUtility.UrlEncode(buildUrl)}&prUrl={HttpUtility.UrlEncode(prUrl)}&exceptionMessage={exceptionMessage}&exceptionStackTrace={exceptionStackTrace}"; -// return await GetResponse(Client, url); -// } - -// public async Task UpdateGitHubProjectLog(string owner, string repo, int buildsUpdated, int prsUpdated, string buildUrl, string prUrl, string exceptionMessage, string exceptionStackTrace) -// { -// string url = $"/api/Settings/UpdateGitHubProjectLog?owner={owner}&repo={repo}&buildsUpdated={buildsUpdated}&prsUpdated={prsUpdated}&buildUrl={HttpUtility.UrlEncode(buildUrl)}&prUrl={HttpUtility.UrlEncode(prUrl)}&exceptionMessage={exceptionMessage}&exceptionStackTrace={exceptionStackTrace}"; -// return await GetResponse(Client, url); -// } - -// private async Task GetResponse(HttpClient client, string url) -// { -// T obj = default; -// if (client != null && url != null) -// { -// Debug.WriteLine("Running url: " + client.BaseAddress.ToString() + url); -// using (HttpResponseMessage response = await client.GetAsync(url)) -// { -// //TODO: Add a flag in the service to turn this on and off. -// if (response.IsSuccessStatusCode == false) -// { -// //Log the URL to help with debugging -// } -// response.EnsureSuccessStatusCode(); -// string responseBody = await response.Content.ReadAsStringAsync(); -// if (string.IsNullOrEmpty(responseBody) == false) -// { -// obj = JsonConvert.DeserializeObject(responseBody); -// } -// } -// } -// return obj; -// } - -// private async Task PostResponse(HttpClient client, string url, MonitoringEvent monitoringEvent) -// { - -// if (client != null && url != null) -// { -// StringContent content = new StringContent(JsonConvert.SerializeObject(monitoringEvent), Encoding.UTF8, "application/json"); - -// Debug.WriteLine("Running url: " + client.BaseAddress.ToString() + url); -// using (HttpResponseMessage response = await client.PostAsync(url, content)) -// { -// response.EnsureSuccessStatusCode(); -// //string responseBody = await response.Content.ReadAsStringAsync(); -// //if (string.IsNullOrEmpty(responseBody) == false) -// //{ -// // obj = JsonConvert.DeserializeObject(responseBody); -// //} -// } -// } -// return true; -// } -// } -//} + Debug.WriteLine("Running url: " + client.BaseAddress.ToString() + url); + using (HttpResponseMessage response = await client.PostAsync(url, content)) + { + response.EnsureSuccessStatusCode(); + //string responseBody = await response.Content.ReadAsStringAsync(); + //if (string.IsNullOrEmpty(responseBody) == false) + //{ + // obj = JsonConvert.DeserializeObject(responseBody); + //} + } + } + return true; + } + } +} diff --git a/src/DevOpsMetrics.FunctionalTests/DevOpsMetrics.FunctionalTests.csproj b/src/DevOpsMetrics.FunctionalTests/DevOpsMetrics.FunctionalTests.csproj index f1f56f2a..e32e0670 100644 --- a/src/DevOpsMetrics.FunctionalTests/DevOpsMetrics.FunctionalTests.csproj +++ b/src/DevOpsMetrics.FunctionalTests/DevOpsMetrics.FunctionalTests.csproj @@ -1,21 +1,21 @@  - net7.0 + net10.0 false - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/DevOpsMetrics.FunctionalTests/WebsiteSmokeTest.cs b/src/DevOpsMetrics.FunctionalTests/WebsiteSmokeTest.cs index 7c9a81ea..3b2dd769 100644 --- a/src/DevOpsMetrics.FunctionalTests/WebsiteSmokeTest.cs +++ b/src/DevOpsMetrics.FunctionalTests/WebsiteSmokeTest.cs @@ -145,7 +145,7 @@ public void GotoWebHomeProjectDevOpsMetricsPageTest() bool webLoaded; //Act - string webURL = _webUrl + "Home//Project?projectId=samsmithnz_DevOpsMetrics"; + string webURL = _webUrl + "Home//Project?projectId=DeveloperMetrics_DevOpsMetrics"; Console.WriteLine("webURL:" + webURL); _driver.Navigate().GoToUrl(webURL); webLoaded = (_driver.Url == webURL); diff --git a/src/DevOpsMetrics.Service/Controllers/BuildsController.cs b/src/DevOpsMetrics.Service/Controllers/BuildsController.cs index 6ea8bf85..d1984439 100644 --- a/src/DevOpsMetrics.Service/Controllers/BuildsController.cs +++ b/src/DevOpsMetrics.Service/Controllers/BuildsController.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using DevOpsMetrics.Core.DataAccess.TableStorage; using DevOpsMetrics.Core.Models.Common; -using DevOpsMetrics.Service.Utility; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -35,12 +34,11 @@ public async Task UpdateAzureDevOpsBuilds( //Get the PAT token from the key vault string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); - patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; - if (string.IsNullOrEmpty(patToken) == true) - { - throw new Exception($"patToken '{patTokenName}' not found in key vault"); - } + //if (string.IsNullOrEmpty(patToken)) + //{ + // throw new Exception($"patToken '{patTokenName}' not found in key vault"); + //} numberOfRecordsSaved = await AzureTableStorageDA.UpdateAzureDevOpsBuildsInStorage(patToken, tableStorageConfig, organization, project, branch, buildName, buildId, numberOfDays, maxNumberOfItems); } @@ -71,15 +69,13 @@ public async Task UpdateGitHubActionRuns( //Get the client id and secret from the settings string clientIdName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientId(owner, repo); - clientIdName = SecretsProcessing.CleanKey(clientIdName); string clientSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientSecret(owner, repo); - clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; - if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) - { - throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); - } + //if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) + //{ + // throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); + //} numberOfRecordsSaved = await AzureTableStorageDA.UpdateGitHubActionRunsInStorage(clientId, clientSecret, tableStorageConfig, owner, repo, branch, workflowName, workflowId, numberOfDays, maxNumberOfItems); diff --git a/src/DevOpsMetrics.Service/Controllers/ChangeFailureRateController.cs b/src/DevOpsMetrics.Service/Controllers/ChangeFailureRateController.cs index 6c59e316..39bf2e9a 100644 --- a/src/DevOpsMetrics.Service/Controllers/ChangeFailureRateController.cs +++ b/src/DevOpsMetrics.Service/Controllers/ChangeFailureRateController.cs @@ -18,12 +18,12 @@ public ChangeFailureRateController(IConfiguration configuration) } [HttpGet("GetChangeFailureRate")] - public ChangeFailureRateModel GetChangeFailureRate(bool getSampleData, + public async Task GetChangeFailureRate(bool getSampleData, DevOpsPlatform targetDevOpsPlatform, string organization_owner, string project_repo, string branch, string buildName_workflowName, int numberOfDays, int maxNumberOfItems) { TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - ChangeFailureRateModel model = ChangeFailureRateDA.GetChangeFailureRate(getSampleData, tableStorageConfig, targetDevOpsPlatform, + ChangeFailureRateModel model = await ChangeFailureRateDA.GetChangeFailureRate(getSampleData, tableStorageConfig, targetDevOpsPlatform, organization_owner, project_repo, branch, buildName_workflowName, numberOfDays, maxNumberOfItems); return model; diff --git a/src/DevOpsMetrics.Service/Controllers/DORASummaryController.cs b/src/DevOpsMetrics.Service/Controllers/DORASummaryController.cs index 09aa8c8d..f677b69d 100644 --- a/src/DevOpsMetrics.Service/Controllers/DORASummaryController.cs +++ b/src/DevOpsMetrics.Service/Controllers/DORASummaryController.cs @@ -1,7 +1,13 @@ -using DevOpsMetrics.Core.DataAccess; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DevOpsMetrics.Core; +using DevOpsMetrics.Core.DataAccess; +using DevOpsMetrics.Core.DataAccess.TableStorage; using DevOpsMetrics.Core.Models.Common; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; namespace DevOpsMetrics.Service.Controllers { @@ -18,13 +24,402 @@ public DORASummaryController(IConfiguration configuration) // Get DORA Summary Items [HttpGet("GetDORASummaryItems")] - public DORASummaryItem GetDORASummaryItems(string owner, string repository) + public async Task> GetDORASummaryItems(string owner) { TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - DORASummaryItem model = DORASummaryDA.GetDORASummaryItem(tableStorageConfig, owner, repository); + List model = await DORASummaryDA.GetDORASummaryItems(tableStorageConfig, owner); + return model; + } + // Get DORA Summary Item + [HttpGet("GetDORASummaryItem")] + public async Task GetDORASummaryItem(string owner, string project, string repo) + { + TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + DORASummaryItem model = await DORASummaryDA.GetDORASummaryItem(tableStorageConfig, owner, project, repo); return model; } + [HttpGet("UpdateDORASummaryItem")] + public async Task UpdateDORASummaryItem( + string owner, + string project, + string repo, + string branch, + string workflowName, + string workflowId, + string resourceGroup, + int numberOfDays, + int maxNumberOfItems, + Microsoft.Extensions.Logging.ILogger log = null, + bool useCache = true, + bool isGitHub = true, + bool useParallelProcessing = true) + { + //Start timer + DateTime startTime = DateTime.Now; + AzureTableStorageDA azureTableStorageDA = new(); + TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + string clientId = null; + string clientSecret = null; + string patToken = null; + //Instead of throwing exceptions when there are no secrets, add the error message to the overall processing message + string errorMessage = null; + if (isGitHub == true) + { + //Get the client id and secret from the settings + string clientIdName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientId(owner, repo); + string clientSecretName = PartitionKeys.CreateGitHubSettingsPartitionKeyClientSecret(owner, repo); + clientId = Configuration[clientIdName]; + clientSecret = Configuration[clientSecretName]; + if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) + { + errorMessage = $"clientId '{clientIdName}' or clientSecret '{clientSecretName}' not found in key vault"; + } + } + else + { + string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(owner, project, repo); + patToken = Configuration[patTokenName]; + if (string.IsNullOrEmpty(patToken)) + { + errorMessage = $"patToken '{patTokenName}' not found in key vault"; + } + } + + ProcessingResult result = new(); + //If the settings are missing, skip the processing and return an empty result + if (string.IsNullOrEmpty(errorMessage) == false) + { + if (log == null) + { + Console.WriteLine(errorMessage); + } + else + { + log.LogInformation(errorMessage); + } + return result; + } + DeploymentFrequencyModel deploymentFrequencyModel = new(); + LeadTimeForChangesModel leadTimeForChangesModel = new(); + MeanTimeToRestoreModel meanTimeToRestoreModel = new(); + ChangeFailureRateModel changeFailureRateModel = new(); + try + { + string message = ""; + if (isGitHub == true) + { + message = $"Processing GitHub owner {owner}, repo {repo}"; + } + else + { + message = $"Processing Azure DevOps organization {owner}, project {project}, repo {repo}"; + } + //TODO: fix this - should be using a common interface, not this null hack + if (log == null) + { + Console.WriteLine(message); + } + else + { + log.LogInformation(message); + } + if (useParallelProcessing) + { + //Call the builds and prs in parallel + Task buildTask; + Task prTask; + if (isGitHub == true) + { + //result.BuildsUpdated = await + buildTask = azureTableStorageDA.UpdateGitHubActionRunsInStorage(clientId, clientSecret, tableStorageConfig, + owner, repo, branch, workflowName, workflowId, numberOfDays, maxNumberOfItems); + //result.PRsUpdated = await + prTask = azureTableStorageDA.UpdateGitHubActionPullRequestsInStorage(clientId, clientSecret, tableStorageConfig, + owner, repo, branch, numberOfDays, maxNumberOfItems); + message = $"Processed GitHub owner {owner}, repo {repo}. {result.BuildsUpdated} builds and {result.PRsUpdated} prs/commits updated"; + } + else + { + //result.BuildsUpdated = await + buildTask = azureTableStorageDA.UpdateAzureDevOpsBuildsInStorage(patToken, tableStorageConfig, + owner, repo, branch, workflowName, workflowId, numberOfDays, maxNumberOfItems); + //result.PRsUpdated = await + prTask = azureTableStorageDA.UpdateAzureDevOpsPullRequestsInStorage(patToken, tableStorageConfig, + owner, repo, branch, numberOfDays, maxNumberOfItems); + message = $"Processed Azure DevOps organization {owner}, project {project}, repo {repo}. {result.BuildsUpdated} builds and {result.PRsUpdated} prs/commits updated"; + } + await Task.WhenAll(buildTask, prTask); + result.BuildsUpdated = await buildTask; + result.PRsUpdated = await prTask; + } + else + { + if (isGitHub == true) + { + result.BuildsUpdated = await azureTableStorageDA.UpdateGitHubActionRunsInStorage(clientId, clientSecret, tableStorageConfig, + owner, repo, branch, workflowName, workflowId, numberOfDays, maxNumberOfItems); + result.PRsUpdated = await azureTableStorageDA.UpdateGitHubActionPullRequestsInStorage(clientId, clientSecret, tableStorageConfig, + owner, repo, branch, numberOfDays, maxNumberOfItems); + message = $"Processed GitHub owner {owner}, repo {repo}. {result.BuildsUpdated} builds and {result.PRsUpdated} prs/commits updated"; + } + else + { + result.BuildsUpdated = await azureTableStorageDA.UpdateAzureDevOpsBuildsInStorage(patToken, tableStorageConfig, + owner, repo, branch, workflowName, workflowId, numberOfDays, maxNumberOfItems); + result.PRsUpdated = await azureTableStorageDA.UpdateAzureDevOpsPullRequestsInStorage(patToken, tableStorageConfig, + owner, repo, branch, numberOfDays, maxNumberOfItems); + message = $"Processed Azure DevOps organization {owner}, project {project}, repo {repo}. {result.BuildsUpdated} builds and {result.PRsUpdated} prs/commits updated"; + } + } + if (log == null) + { + Console.WriteLine(message); + } + else + { + log.LogInformation(message); + } + result.TotalResults += result.BuildsUpdated + result.PRsUpdated; + + //Process summary results for last 90 days, creating badges for the four metrics + //Get the DORA metrics for the last 90 days + if (useParallelProcessing) + { + Task deploymentFrequencyTask; + Task leadTimeForChangesTask; + Task meanTimeToRestoreTask; + Task changeFailureRateTask; + + //Get the deployment frequency and lead time for changes in parallel + if (isGitHub == true) + { + deploymentFrequencyTask = DeploymentFrequencyDA.GetGitHubDeploymentFrequency(false, clientId, clientSecret, tableStorageConfig, + owner, repo, branch, workflowName, workflowId, + numberOfDays, maxNumberOfItems, useCache); + + leadTimeForChangesTask = LeadTimeForChangesDA.GetGitHubLeadTimesForChanges(false, clientId, clientSecret, tableStorageConfig, + owner, repo, branch, workflowName, workflowId, + numberOfDays, maxNumberOfItems, useCache); + } + else + { + deploymentFrequencyTask = DeploymentFrequencyDA.GetAzureDevOpsDeploymentFrequency(false, patToken, tableStorageConfig, + owner, project, branch, workflowName, + numberOfDays, maxNumberOfItems, useCache); + + leadTimeForChangesTask = LeadTimeForChangesDA.GetAzureDevOpsLeadTimesForChanges(false, patToken, tableStorageConfig, + owner, project, repo, branch, workflowName, + numberOfDays, maxNumberOfItems, useCache); + } + //Get the mean time to restore and change failure rate in parallel + if (resourceGroup != null) + { + meanTimeToRestoreTask = MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(false, tableStorageConfig, + DevOpsPlatform.GitHub, + resourceGroup, + numberOfDays, maxNumberOfItems); + } + else + { + meanTimeToRestoreTask = null; + meanTimeToRestoreModel.MTTRAverageDurationInHours = 0; + meanTimeToRestoreModel.MTTRAverageDurationDescription = MeanTimeToRestore.GetMeanTimeToRestoreRating(0); + } + changeFailureRateTask = ChangeFailureRateDA.GetChangeFailureRate(false, tableStorageConfig, + DevOpsPlatform.GitHub, + owner, repo, branch, workflowName, + numberOfDays, maxNumberOfItems); + + //Process the tasks in parallel + if (meanTimeToRestoreTask != null) + { + await Task.WhenAll(deploymentFrequencyTask, leadTimeForChangesTask, meanTimeToRestoreTask, changeFailureRateTask); + } + else + { + await Task.WhenAll(deploymentFrequencyTask, leadTimeForChangesTask, changeFailureRateTask); + } + deploymentFrequencyModel = await deploymentFrequencyTask; + leadTimeForChangesModel = await leadTimeForChangesTask; + if (meanTimeToRestoreTask != null) + { + meanTimeToRestoreModel = await meanTimeToRestoreTask; + } + changeFailureRateModel = await changeFailureRateTask; + } + else + { + if (isGitHub == true) + { + //Get the deployment frequency and lead time for changes + deploymentFrequencyModel = await DeploymentFrequencyDA.GetGitHubDeploymentFrequency(false, clientId, clientSecret, tableStorageConfig, + owner, repo, branch, workflowName, workflowId, + numberOfDays, maxNumberOfItems, useCache); + + leadTimeForChangesModel = await LeadTimeForChangesDA.GetGitHubLeadTimesForChanges(false, clientId, clientSecret, tableStorageConfig, + owner, repo, branch, workflowName, workflowId, + numberOfDays, maxNumberOfItems, useCache); + } + else + { + deploymentFrequencyModel = await DeploymentFrequencyDA.GetAzureDevOpsDeploymentFrequency(false, patToken, tableStorageConfig, + owner, project, branch, workflowName, + numberOfDays, maxNumberOfItems, useCache); + + leadTimeForChangesModel = await LeadTimeForChangesDA.GetAzureDevOpsLeadTimesForChanges(false, patToken, tableStorageConfig, + owner, project, repo, branch, workflowName, + numberOfDays, maxNumberOfItems, useCache); + } + //Get the mean time to restore and change failure rate + if (resourceGroup != null) + { + meanTimeToRestoreModel = await MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(false, tableStorageConfig, + DevOpsPlatform.GitHub, + resourceGroup, + numberOfDays, maxNumberOfItems); + } + else + { + meanTimeToRestoreModel.MTTRAverageDurationInHours = 0; + meanTimeToRestoreModel.MTTRAverageDurationDescription = MeanTimeToRestore.GetMeanTimeToRestoreRating(0); + } + changeFailureRateModel = await ChangeFailureRateDA.GetChangeFailureRate(false, tableStorageConfig, + DevOpsPlatform.GitHub, + owner, repo, branch, workflowName, + numberOfDays, maxNumberOfItems); + } + + //Get the total time since startTime + string processingLogMessage = $"Processed summary for {owner}, repo {repo} in {(DateTime.Now - startTime).TotalSeconds} seconds"; + if (errorMessage != null) + { + processingLogMessage += $", error: {errorMessage}"; + } + + //Summarize the results into a new object + DORASummaryItem DORASummary = new() + { + Owner = owner, + Project = project, + Repo = repo, + NumberOfDays = numberOfDays, + DeploymentFrequency = deploymentFrequencyModel.DeploymentsPerDayMetric, + DeploymentFrequencyBadgeURL = deploymentFrequencyModel.BadgeURL, + DeploymentFrequencyBadgeWithMetricURL = deploymentFrequencyModel.BadgeWithMetricURL, + LeadTimeForChanges = leadTimeForChangesModel.LeadTimeForChangesMetric, + LeadTimeForChangesBadgeURL = leadTimeForChangesModel.BadgeURL, + LeadTimeForChangesBadgeWithMetricURL = leadTimeForChangesModel.BadgeWithMetricURL, + MeanTimeToRestore = meanTimeToRestoreModel.MTTRAverageDurationInHours, + MeanTimeToRestoreBadgeURL = meanTimeToRestoreModel.BadgeURL, + MeanTimeToRestoreBadgeWithMetricURL = meanTimeToRestoreModel.BadgeWithMetricURL, + ChangeFailureRate = changeFailureRateModel.ChangeFailureRateMetric, + ChangeFailureRateBadgeURL = changeFailureRateModel.BadgeURL, + ChangeFailureRateBadgeWithMetricURL = changeFailureRateModel.BadgeWithMetricURL, + LastUpdatedMessage = processingLogMessage, + LastUpdated = DateTime.Now + }; + + //Serialize the summary into an Azure storage table + await AzureTableStorageDA.UpdateDORASummaryItem(tableStorageConfig, owner, project, repo, DORASummary); + + //await settingsController.UpdateGitHubProjectLog(ghSetting.Owner, ghSetting.Repo, result.BuildsUpdated, result.PRsUpdated, "", "", null, null); + ProjectLog projectLog = null; + if (isGitHub == true) + { + projectLog = new( + PartitionKeys.CreateGitHubSettingsPartitionKey(owner, repo), + result.BuildsUpdated, result.PRsUpdated, + "", "", null, null); + } + else + { + projectLog = new( + PartitionKeys.CreateAzureDevOpsSettingsPartitionKey(owner, project, repo), + result.BuildsUpdated, result.PRsUpdated, + "", "", null, null); + } + if (projectLog != null) + { + await azureTableStorageDA.UpdateProjectLogInStorage(tableStorageConfig, projectLog); + } + } + catch (Exception ex) + { + string error = $"Exception while processing GitHub owner {owner}, repo {repo}. {result.BuildsUpdated} builds and {result.PRsUpdated} prs/commits updated " + ex.ToString(); + if (log == null) + { + Console.WriteLine(error); + } + else + { + log.LogInformation(error); + } + ProjectLog projectLog; + if (isGitHub == true) + { + projectLog = new( + PartitionKeys.CreateGitHubSettingsPartitionKey(owner, repo), + result.BuildsUpdated, result.PRsUpdated, + owner + "_" + repo + "_" + branch + "_" + workflowName + "_" + workflowId + "_" + numberOfDays + "_" + maxNumberOfItems, + owner + "_" + repo + "_" + branch + "_" + numberOfDays + "_" + maxNumberOfItems, + ex.ToString(), error); + } + else + { + projectLog = new( + PartitionKeys.CreateAzureDevOpsSettingsPartitionKey(owner, project, repo), + result.BuildsUpdated, result.PRsUpdated, + owner + "_" + project + "_" + repo + "_" + branch + "_" + workflowName + "_" + workflowId + "_" + numberOfDays + "_" + maxNumberOfItems, + owner + "_" + project + "_" + repo + "_" + branch + "_" + numberOfDays + "_" + maxNumberOfItems, + ex.ToString(), error); + } + //Update the DORA object with the error message + try + { + //Get the total time since startTime + string processingLogMessage = $"Error processing summary for {owner}, repo {repo} in {(DateTime.Now - startTime).TotalSeconds} seconds"; + + //Summarize the results into a new object + DORASummaryItem DORASummary = new() + { + Owner = owner, + Project = project, + Repo = repo, + NumberOfDays = numberOfDays, + DeploymentFrequency = deploymentFrequencyModel.DeploymentsPerDayMetric, + DeploymentFrequencyBadgeURL = deploymentFrequencyModel.BadgeURL, + DeploymentFrequencyBadgeWithMetricURL = deploymentFrequencyModel.BadgeWithMetricURL, + LeadTimeForChanges = leadTimeForChangesModel.LeadTimeForChangesMetric, + LeadTimeForChangesBadgeURL = leadTimeForChangesModel.BadgeURL, + LeadTimeForChangesBadgeWithMetricURL = leadTimeForChangesModel.BadgeWithMetricURL, + MeanTimeToRestore = meanTimeToRestoreModel.MTTRAverageDurationInHours, + MeanTimeToRestoreBadgeURL = meanTimeToRestoreModel.BadgeURL, + MeanTimeToRestoreBadgeWithMetricURL = meanTimeToRestoreModel.BadgeWithMetricURL, + ChangeFailureRate = changeFailureRateModel.ChangeFailureRateMetric, + ChangeFailureRateBadgeURL = changeFailureRateModel.BadgeURL, + ChangeFailureRateBadgeWithMetricURL = changeFailureRateModel.BadgeWithMetricURL, + LastUpdatedMessage = processingLogMessage, + LastUpdated = DateTime.Now + }; + //Serialize the summary into an Azure storage table + await AzureTableStorageDA.UpdateDORASummaryItem(tableStorageConfig, owner, project, repo, DORASummary); + } + catch + { + //Do nothing, we handled the error above + } + + if (projectLog != null) + { + await azureTableStorageDA.UpdateProjectLogInStorage(tableStorageConfig, projectLog); + } + //throw new Exception(error, ex); + } + + return result; + } + } } \ No newline at end of file diff --git a/src/DevOpsMetrics.Service/Controllers/DeploymentFrequencyController.cs b/src/DevOpsMetrics.Service/Controllers/DeploymentFrequencyController.cs index 8d0fe966..6c6f925f 100644 --- a/src/DevOpsMetrics.Service/Controllers/DeploymentFrequencyController.cs +++ b/src/DevOpsMetrics.Service/Controllers/DeploymentFrequencyController.cs @@ -35,10 +35,10 @@ public async Task GetAzureDevOpsDeploymentFrequency(bo string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; - if (string.IsNullOrEmpty(patToken) == true) - { - throw new Exception($"patToken '{patTokenName}' not found in key vault"); - } + //if (string.IsNullOrEmpty(patToken)) + //{ + // throw new Exception($"patToken '{patTokenName}' not found in key vault"); + //} DeploymentFrequencyDA da = new(); model = await DeploymentFrequencyDA.GetAzureDevOpsDeploymentFrequency(getSampleData, patToken, tableStorageConfig, organization, project, branch, buildName, numberOfDays, maxNumberOfItems, useCache); @@ -76,10 +76,10 @@ public async Task GetGitHubDeploymentFrequency(bool ge clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; - if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) - { - throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); - } + //if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) + //{ + // throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); + //} DeploymentFrequencyDA da = new(); model = await DeploymentFrequencyDA.GetGitHubDeploymentFrequency(getSampleData, clientId, clientSecret, tableStorageConfig, owner, repo, branch, workflowName, workflowId, numberOfDays, maxNumberOfItems, useCache); diff --git a/src/DevOpsMetrics.Service/Controllers/LeadTimeForChangesController.cs b/src/DevOpsMetrics.Service/Controllers/LeadTimeForChangesController.cs index 9cde7844..788804bd 100644 --- a/src/DevOpsMetrics.Service/Controllers/LeadTimeForChangesController.cs +++ b/src/DevOpsMetrics.Service/Controllers/LeadTimeForChangesController.cs @@ -33,12 +33,11 @@ public async Task GetAzureDevOpsLeadTimeForChanges(bool //Get the PAT token from the key vault string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); - patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; - if (string.IsNullOrEmpty(patToken) == true) - { - throw new Exception($"patToken '{patTokenName}' not found in key vault"); - } + //if (string.IsNullOrEmpty(patToken)) + //{ + // throw new Exception($"patToken '{patTokenName}' not found in key vault"); + //} LeadTimeForChangesDA da = new(); model = await LeadTimeForChangesDA.GetAzureDevOpsLeadTimesForChanges(getSampleData, patToken, tableStorageConfig, @@ -77,10 +76,10 @@ public async Task GetGitHubLeadTimeForChanges(bool getS clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; - if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) - { - throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); - } + //if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) + //{ + // throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); + //} LeadTimeForChangesDA da = new(); model = await LeadTimeForChangesDA.GetGitHubLeadTimesForChanges(getSampleData, clientId, clientSecret, tableStorageConfig, diff --git a/src/DevOpsMetrics.Service/Controllers/MeanTimeToRestoreController.cs b/src/DevOpsMetrics.Service/Controllers/MeanTimeToRestoreController.cs index b636b44d..f0d54996 100644 --- a/src/DevOpsMetrics.Service/Controllers/MeanTimeToRestoreController.cs +++ b/src/DevOpsMetrics.Service/Controllers/MeanTimeToRestoreController.cs @@ -1,4 +1,5 @@ -using DevOpsMetrics.Core.DataAccess; +using System.Threading.Tasks; +using DevOpsMetrics.Core.DataAccess; using DevOpsMetrics.Core.Models.Common; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -17,12 +18,12 @@ public MeanTimeToRestoreController(IConfiguration configuration) } [HttpGet("GetAzureMeanTimeToRestore")] - public MeanTimeToRestoreModel GetAzureMeanTimeToRestore(bool getSampleData, + public async Task GetAzureMeanTimeToRestore(bool getSampleData, DevOpsPlatform targetDevOpsPlatform, string resourceGroup, int numberOfDays, int maxNumberOfItems) { TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - MeanTimeToRestoreModel model = MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, + MeanTimeToRestoreModel model = await MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, targetDevOpsPlatform, resourceGroup, numberOfDays, maxNumberOfItems); return model; diff --git a/src/DevOpsMetrics.Service/Controllers/PullRequestsController.cs b/src/DevOpsMetrics.Service/Controllers/PullRequestsController.cs index 62fff012..672d95aa 100644 --- a/src/DevOpsMetrics.Service/Controllers/PullRequestsController.cs +++ b/src/DevOpsMetrics.Service/Controllers/PullRequestsController.cs @@ -35,10 +35,10 @@ public async Task UpdateAzureDevOpsPullRequests( string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; - if (string.IsNullOrEmpty(patToken) == true) - { - throw new Exception($"patToken '{patTokenName}' not found in key vault"); - } + //if (string.IsNullOrEmpty(patToken)) + //{ + // throw new Exception($"patToken '{patTokenName}' not found in key vault"); + //} numberOfRecordsSaved = await AzureTableStorageDA.UpdateAzureDevOpsPullRequestsInStorage(patToken, tableStorageConfig, organization, project, repository, numberOfDays, maxNumberOfItems); @@ -74,10 +74,10 @@ public async Task UpdateGitHubActionPullRequests( clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; - if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) - { - throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); - } + //if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) + //{ + // throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); + //} numberOfRecordsSaved = await AzureTableStorageDA.UpdateGitHubActionPullRequestsInStorage(clientId, clientSecret, tableStorageConfig, owner, repo, branch, numberOfDays, maxNumberOfItems); @@ -110,10 +110,10 @@ public async Task UpdateAzureDevOpsPullRequestCommits( string patTokenName = PartitionKeys.CreateAzureDevOpsSettingsPartitionKeyPatToken(organization, project, repository); patTokenName = SecretsProcessing.CleanKey(patTokenName); string patToken = Configuration[patTokenName]; - if (string.IsNullOrEmpty(patToken) == true) - { - throw new Exception($"patToken '{patTokenName}' not found in key vault"); - } + //if (string.IsNullOrEmpty(patToken)) + //{ + // throw new Exception($"patToken '{patTokenName}' not found in key vault"); + //} numberOfRecordsSaved = await AzureTableStorageDA.UpdateAzureDevOpsPullRequestCommitsInStorage(patToken, tableStorageConfig, organization, project, repository, pullRequestId, numberOfDays, maxNumberOfItems); @@ -148,10 +148,10 @@ public async Task UpdateGitHubActionPullRequestCommits( clientSecretName = SecretsProcessing.CleanKey(clientSecretName); string clientId = Configuration[clientIdName]; string clientSecret = Configuration[clientSecretName]; - if (string.IsNullOrEmpty(clientId) == true | string.IsNullOrEmpty(clientSecret) == true) - { - throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); - } + //if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret)) + //{ + // throw new Exception($"clientId '{clientId}' or clientSecret '{clientSecret}' not found in key vault"); + //} numberOfRecordsSaved = await AzureTableStorageDA.UpdateGitHubActionPullRequestCommitsInStorage(clientId, clientSecret, tableStorageConfig, owner, repo, pull_number); diff --git a/src/DevOpsMetrics.Service/Controllers/SettingsController.cs b/src/DevOpsMetrics.Service/Controllers/SettingsController.cs index e9e3b8fa..ebbfefbb 100644 --- a/src/DevOpsMetrics.Service/Controllers/SettingsController.cs +++ b/src/DevOpsMetrics.Service/Controllers/SettingsController.cs @@ -28,18 +28,28 @@ public SettingsController(IConfiguration configuration, IAzureTableStorageDA azu } [HttpGet("GetAzureDevOpsSettings")] - public List GetAzureDevOpsSettings(string rowKey = null) + public async Task> GetAzureDevOpsSettings(string rowKey = null) { TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - List settings = AzureTableStorageDA.GetAzureDevOpsSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsSettings, rowKey); + //If the storage settings are missing, return an empty list instead of throwing an error + if (IsTableStorageConfigured(tableStorageConfig, tableStorageConfig.TableAzureDevOpsSettings) == false) + { + return new List(); + } + List settings = await AzureTableStorageDA.GetAzureDevOpsSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsSettings, rowKey); return settings; } [HttpGet("GetGitHubSettings")] - public List GetGitHubSettings(string rowKey = null) + public async Task> GetGitHubSettings(string rowKey = null) { TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - List settings = AzureTableStorageDA.GetGitHubSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubSettings, rowKey); + //If the storage settings are missing, return an empty list instead of throwing an error + if (IsTableStorageConfigured(tableStorageConfig, tableStorageConfig.TableGitHubSettings) == false) + { + return new List(); + } + List settings = await AzureTableStorageDA.GetGitHubSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubSettings, rowKey); return settings; } @@ -59,6 +69,10 @@ public async Task UpdateAzureDevOpsSetting(string patToken, //Save everything else to table storage TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + if (IsTableStorageConfigured(tableStorageConfig, tableStorageConfig.TableAzureDevOpsSettings) == false) + { + return false; + } return await AzureTableStorageDA.UpdateAzureDevOpsSettingInStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsSettings, organization, project, repository, branch, buildName, buildId, resourceGroup, itemOrder, showSetting); } @@ -85,6 +99,10 @@ public async Task UpdateGitHubSetting(string clientId, string clientSecret //Save everything else to table storage TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + if (IsTableStorageConfigured(tableStorageConfig, tableStorageConfig.TableGitHubSettings) == false) + { + return false; + } return await AzureTableStorageDA.UpdateGitHubSettingInStorage(tableStorageConfig, tableStorageConfig.TableGitHubSettings, owner, repo, branch, workflowName, workflowId, resourceGroup, itemOrder, showSetting); } @@ -93,16 +111,24 @@ public async Task UpdateGitHubSetting(string clientId, string clientSecret public async Task UpdateDevOpsMonitoringEvent([FromBody] MonitoringEvent monitoringEvent) { TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + if (IsTableStorageConfigured(tableStorageConfig, tableStorageConfig.TableLog) == false) + { + return false; + } return await AzureTableStorageDA.UpdateDevOpsMonitoringEventInStorage(tableStorageConfig, monitoringEvent); } [HttpGet("GetAzureDevOpsProjectLog")] - public List GetAzureDevOpsProjectLog(string organization, string project, string repository) + public async Task> GetAzureDevOpsProjectLog(string organization, string project, string repository) { string partitionKey = PartitionKeys.CreateAzureDevOpsSettingsPartitionKey(organization, project, repository); TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - return AzureTableStorageDA.GetProjectLogsFromStorage(tableStorageConfig, partitionKey); + if (IsTableStorageConfigured(tableStorageConfig, tableStorageConfig.TableLog) == false) + { + return new List(); + } + return await AzureTableStorageDA.GetProjectLogsFromStorage(tableStorageConfig, partitionKey); } [HttpGet("UpdateAzureDevOpsProjectLog")] @@ -115,16 +141,24 @@ public async Task UpdateAzureDevOpsProjectLog(string organization, string buildsUpdated, prsUpdated, HttpUtility.UrlDecode(buildUrl), HttpUtility.UrlDecode(prUrl), exceptionMessage, exceptionStackTrace); TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + if (IsTableStorageConfigured(tableStorageConfig, tableStorageConfig.TableLog) == false) + { + return false; + } return await AzureTableStorageDA.UpdateProjectLogInStorage(tableStorageConfig, log); } [HttpGet("GetGitHubProjectLog")] - public List GetGitHubProjectLog(string owner, string repo) + public async Task> GetGitHubProjectLog(string owner, string repo) { string partitionKey = PartitionKeys.CreateGitHubSettingsPartitionKey(owner, repo); TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - return AzureTableStorageDA.GetProjectLogsFromStorage(tableStorageConfig, partitionKey); + if (IsTableStorageConfigured(tableStorageConfig, tableStorageConfig.TableLog) == false) + { + return new List(); + } + return await AzureTableStorageDA.GetProjectLogsFromStorage(tableStorageConfig, partitionKey); } [HttpGet("UpdateGitHubProjectLog")] @@ -137,12 +171,29 @@ public async Task UpdateGitHubProjectLog(string owner, string repo, buildsUpdated, prsUpdated, HttpUtility.UrlDecode(buildUrl), HttpUtility.UrlDecode(prUrl), exceptionMessage, exceptionStackTrace); TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + if (IsTableStorageConfigured(tableStorageConfig, tableStorageConfig.TableLog) == false) + { + return false; + } return await AzureTableStorageDA.UpdateProjectLogInStorage(tableStorageConfig, log); } + //If the table storage settings aren't available, we skip the call rather than throwing an error + private static bool IsTableStorageConfigured(TableStorageConfiguration tableStorageConfig, string tableName) + { + return tableStorageConfig != null && + string.IsNullOrEmpty(tableStorageConfig.StorageAccountConnectionString) == false && + string.IsNullOrEmpty(tableName) == false; + } + private async Task CreateKeyVaultSecret(string secretName, string secretValue) { string keyVaultURI = Configuration["AppSettings:KeyVaultURL"]; + //If the key vault isn't setup, or the secret is empty, skip saving the secret rather than throwing an error + if (string.IsNullOrEmpty(keyVaultURI) || string.IsNullOrEmpty(secretValue)) + { + return null; + } SecretClient secretClient = new(new Uri(keyVaultURI), new DefaultAzureCredential()); return await secretClient.SetSecretAsync(secretName, secretValue); } diff --git a/src/DevOpsMetrics.Service/DevOpsMetrics.Service.csproj b/src/DevOpsMetrics.Service/DevOpsMetrics.Service.csproj index 905c9a6b..12e25b56 100644 --- a/src/DevOpsMetrics.Service/DevOpsMetrics.Service.csproj +++ b/src/DevOpsMetrics.Service/DevOpsMetrics.Service.csproj @@ -1,27 +1,27 @@  - - net7.0 - /subscriptions/07db7d0b-a6cb-4e58-b07e-e1d541c39f5b/resourcegroups/DevOpsMetrics/providers/microsoft.insights/components/devops-prod-eu-appinsights - 4d9063eb-b4d7-4507-badc-6b916c1b050b - + + net10.0 + /subscriptions/07db7d0b-a6cb-4e58-b07e-e1d541c39f5b/resourcegroups/DevOpsMetrics/providers/microsoft.insights/components/devops-prod-eu-appinsights + 4d9063eb-b4d7-4507-badc-6b916c1b050b + - - - - - - - - + + + + + + + + - - - + + + - - - + + + diff --git a/src/DevOpsMetrics.Service/Program.cs b/src/DevOpsMetrics.Service/Program.cs index 0d1d9ec4..424f112a 100644 --- a/src/DevOpsMetrics.Service/Program.cs +++ b/src/DevOpsMetrics.Service/Program.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Hosting; -using Microsoft.Azure.KeyVault; -using Microsoft.Azure.Services.AppAuthentication; +using Azure.Core; +using Azure.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -29,15 +29,27 @@ public static IHostBuilder CreateHostBuilder(string[] args) } //Load a connection to our Azure key vault instance - string keyVaultURL = configuration["AppSettings:KeyVaultURL"]; + string azureKeyVaultURL = configuration["AppSettings:KeyVaultURL"]; string clientId = configuration["AppSettings:KeyVaultClientId"]; string clientSecret = configuration["AppSettings:KeyVaultClientSecret"]; - AzureServiceTokenProvider azureServiceTokenProvider = new(); - KeyVaultClient keyVaultClient = new(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); + string tenantId = configuration["AppSettings:TenantId"]; + //AzureServiceTokenProvider azureServiceTokenProvider = new(); + //KeyVaultClient keyVaultClient = new(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); //builder.AddAzureKeyVault(keyVaultURL, keyVaultClient, new DefaultKeyVaultSecretManager()); - builder.AddAzureKeyVault(keyVaultURL, clientId, clientSecret); + //builder.AddAzureKeyVault(keyVaultURL, clientId, clientSecret); //configuration = builder.Build(); + if (azureKeyVaultURL != null && clientId != null && clientSecret != null && tenantId != null) + { + TokenCredential tokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret); + builder.AddAzureKeyVault(new(azureKeyVaultURL), tokenCredential); + } + else + { + throw new System.Exception("Missing configuration for Azure Key Vault"); + } + + }) .ConfigureWebHostDefaults(webBuilder => { diff --git a/src/DevOpsMetrics.Service/appsettings.json b/src/DevOpsMetrics.Service/appsettings.json index db457ad3..761e2a7d 100644 --- a/src/DevOpsMetrics.Service/appsettings.json +++ b/src/DevOpsMetrics.Service/appsettings.json @@ -26,9 +26,10 @@ "AzureStorageAccountContainerTableLog": "DevOpsLog", "KeyVaultURL": "https://devops-prod-eu-vault.vault.azure.net/", "KeyVaultClientId": "", - "KeyVaultClientSecret": "" + "KeyVaultClientSecret": "", + "TenantId": "40244aae-8aee-4af5-b221-b480db24698b" }, "ApplicationInsights": { - "InstrumentationKey": "dc3fb590-3612-4f09-8c95-e9378cba8654" + "ConnectionString": "InstrumentationKey=dc3fb590-3612-4f09-8c95-e9378cba8654;IngestionEndpoint=https://eastus-3.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=54a63930-44fb-4776-b760-93eceafdfe01" } } \ No newline at end of file diff --git a/src/DevOpsMetrics.Tests/BaseConfiguration.cs b/src/DevOpsMetrics.Tests/BaseConfiguration.cs index 0e60c8ec..c62be23f 100644 --- a/src/DevOpsMetrics.Tests/BaseConfiguration.cs +++ b/src/DevOpsMetrics.Tests/BaseConfiguration.cs @@ -1,4 +1,6 @@ using System; +using Azure.Core; +using Azure.Identity; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -20,12 +22,22 @@ public void TestStartUp() string keyVaultURL = Configuration["AppSettings:KeyVaultURL"]; string clientId = Configuration["AppSettings:KeyVaultClientId"]; string clientSecret = Configuration["AppSettings:KeyVaultClientSecret"]; + string tenantId = Configuration["AppSettings:TenantId"]; //AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider(); //KeyVaultClient keyVaultClient = new KeyVaultClient( // new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); //config.AddAzureKeyVault(azureKeyVaultURL, keyVaultClient, new DefaultKeyVaultSecretManager()); - configBuilder.AddAzureKeyVault(keyVaultURL, clientId, clientSecret); - Configuration = configBuilder.Build(); + //configBuilder.AddAzureKeyVault(keyVaultURL, clientId, clientSecret); + if (keyVaultURL != null && clientId != null && clientSecret != null && tenantId != null) + { + TokenCredential tokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret); + configBuilder.AddAzureKeyVault(new(keyVaultURL), tokenCredential); + Configuration = configBuilder.Build(); + } + else + { + throw new System.Exception("Missing configuration for Azure Key Vault"); + } } } diff --git a/src/DevOpsMetrics.Tests/Core/AzureMonitorTests.cs b/src/DevOpsMetrics.Tests/Core/AzureMonitorTests.cs index ab04450c..0bc241c9 100644 --- a/src/DevOpsMetrics.Tests/Core/AzureMonitorTests.cs +++ b/src/DevOpsMetrics.Tests/Core/AzureMonitorTests.cs @@ -7,7 +7,7 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("IntegrationTest")] [TestClass] public class AzureMonitorTests : BaseConfiguration { @@ -25,7 +25,7 @@ public async Task AzureMonitorProcessingTest() ""status"": ""Activated"", ""context"": { ""timestamp"": ""2020-05-25T00:11:28.7763615Z"", - ""id"": ""/subscriptions/07db7d0b-a6cb-4e58-b07e-e1d541c39f5b/resourceGroups/SamLearnsAzureFeatureFlags/providers/microsoft.insights/metricAlerts/ServerErrors%20featureflags-data-eu-service"", + ""id"": ""/subscriptions/07db7d0b-a6cb-4e58-b07e-e1d541c39f5b/resourceGroups/SamsFeatureFlags/providers/microsoft.insights/metricAlerts/ServerErrors%20featureflags-data-eu-service"", ""name"": ""ServerErrors featureflags-data-eu-service"", ""description"": """", ""conditionType"": ""SingleResourceMultipleMetricCriteria"", @@ -51,11 +51,11 @@ public async Task AzureMonitorProcessingTest() ] }, ""subscriptionId"": ""07db7d0b-a6cb-4e58-b07e-e1d541c39f5b"", - ""resourceGroupName"": ""SamLearnsAzureTest"", + ""resourceGroupName"": ""SamsFeatureFlags"", ""resourceName"": ""featureflags-data-eu-service"", ""resourceType"": ""Microsoft.Web/sites"", - ""resourceId"": ""/subscriptions/07db7d0b-a6cb-4e58-b07e-e1d541c39f5b/resourceGroups/SamLearnsAzureFeatureFlags/providers/Microsoft.Web/sites/featureflags-data-eu-service"", - ""portalLink"": ""https://portal.azure.com/#resource/subscriptions/07db7d0b-a6cb-4e58-b07e-e1d541c39f5b/resourceGroups/SamLearnsAzureFeatureFlags/providers/Microsoft.Web/sites/featureflags-data-eu-service"" + ""resourceId"": ""/subscriptions/07db7d0b-a6cb-4e58-b07e-e1d541c39f5b/resourceGroups/SamsFeatureFlags/providers/Microsoft.Web/sites/featureflags-data-eu-service"", + ""portalLink"": ""https://portal.azure.com/#resource/subscriptions/07db7d0b-a6cb-4e58-b07e-e1d541c39f5b/resourceGroups/SamsFeatureFlags/providers/Microsoft.Web/sites/featureflags-data-eu-service"" } } } @@ -66,7 +66,7 @@ public async Task AzureMonitorProcessingTest() bool result = await da.UpdateDevOpsMonitoringEventInStorage(tableStorageConfig, monitoringEvent); //Assert - Assert.IsTrue(result == true); + Assert.IsTrue(result); } } } diff --git a/src/DevOpsMetrics.Tests/Core/ChangeFailureRateDATests.cs b/src/DevOpsMetrics.Tests/Core/ChangeFailureRateDATests.cs index 258ab638..17b416c4 100644 --- a/src/DevOpsMetrics.Tests/Core/ChangeFailureRateDATests.cs +++ b/src/DevOpsMetrics.Tests/Core/ChangeFailureRateDATests.cs @@ -7,27 +7,28 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("IntegrationTest")] [TestClass] public class ChangeFailureRateDATests : BaseConfiguration { + [TestCategory("UnitTest")] [TestMethod] - public void AzChangeFailureRateDAIntegrationTest() + public async Task AzChangeFailureRateDAIntegrationTest() { //Arrange bool getSampleData = true; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.AzureDevOps; int numberOfDays = 30; int maxNumberOfItems = 20; //Act - ChangeFailureRateModel model = ChangeFailureRateDA.GetChangeFailureRate(getSampleData, tableStorageConfig, + ChangeFailureRateModel model = await ChangeFailureRateDA.GetChangeFailureRate(getSampleData, tableStorageConfig, targetDevOpsPlatform, organization, project, branch, buildName, numberOfDays, maxNumberOfItems); //Assert @@ -74,22 +75,23 @@ public void AzChangeFailureRateDAIntegrationTest() // Assert.AreNotEqual("Elite", model.ChangeFailureRateMetricDescription); //} + [TestCategory("UnitTest")] [TestMethod] - public void GHChangeFailureRateDAIntegrationTest() + public async Task GHChangeFailureRateDAIntegrationTest() { //Arrange bool getSampleData = true; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "DevOpsMetrics"; string branch = "main"; - string workflowName = "DevOpsMetrics CI/CD"; + string workflowName = "CI/CD"; DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.GitHub; int numberOfDays = 30; int maxNumberOfItems = 20; //Act - ChangeFailureRateModel model = ChangeFailureRateDA.GetChangeFailureRate(getSampleData, tableStorageConfig, + ChangeFailureRateModel model = await ChangeFailureRateDA.GetChangeFailureRate(getSampleData, tableStorageConfig, targetDevOpsPlatform, owner, repo, branch, workflowName, numberOfDays, maxNumberOfItems); //Assert @@ -111,8 +113,8 @@ public async Task UpdateChangeFailureRateDAIntegrationTest() //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string buildName = "azure-pipelines.yml"; int percent = 50; int numberOfDays = 1; @@ -121,7 +123,7 @@ public async Task UpdateChangeFailureRateDAIntegrationTest() organization, project, buildName, percent, numberOfDays); //Assert - Assert.IsTrue(result == true); + Assert.IsTrue(result); } diff --git a/src/DevOpsMetrics.Tests/Core/ChangeFailureRateTests.cs b/src/DevOpsMetrics.Tests/Core/ChangeFailureRateTests.cs index b251aa9c..d436a32c 100644 --- a/src/DevOpsMetrics.Tests/Core/ChangeFailureRateTests.cs +++ b/src/DevOpsMetrics.Tests/Core/ChangeFailureRateTests.cs @@ -7,13 +7,13 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] public class ChangeFailureRateTests { [TestMethod] - public void ChangeFailureRateEliteTest() + public void ChangeFailureRateHigh2Test() { //Arrange ChangeFailureRate metrics = new(); @@ -39,12 +39,12 @@ public void ChangeFailureRateEliteTest() //Assert Assert.IsTrue(model != null); Assert.AreEqual(0f, model.ChangeFailureRateMetric); - Assert.AreEqual("Elite", model.ChangeFailureRateMetricDescription); + Assert.AreEqual("High", model.ChangeFailureRateMetricDescription); Assert.AreEqual(false, model.IsProjectView); } [TestMethod] - public void ChangeFailureRateHighTest() + public void ChangeFailureRateMediumTest() { //Arrange ChangeFailureRate metrics = new(); @@ -64,11 +64,11 @@ public void ChangeFailureRateHighTest() //Assert Assert.AreEqual(0.2f, result); - Assert.AreEqual("High", rating); + Assert.AreEqual("Medium", rating); } [TestMethod] - public void ChangeFailureRateMediumTest() + public void ChangeFailureRateLowTest() { //Arrange ChangeFailureRate metrics = new(); @@ -88,11 +88,11 @@ public void ChangeFailureRateMediumTest() //Assert Assert.AreEqual(0.4f, result); - Assert.AreEqual("Medium", rating); + Assert.AreEqual("Low", rating); } [TestMethod] - public void ChangeFailureRateLowTest() + public void ChangeFailureRateLow2Test() { //Arrange ChangeFailureRate metrics = new(); diff --git a/src/DevOpsMetrics.Tests/Core/DeploymentFrequencyDATests.cs b/src/DevOpsMetrics.Tests/Core/DeploymentFrequencyDATests.cs index 5235dd3a..fd4c97e7 100644 --- a/src/DevOpsMetrics.Tests/Core/DeploymentFrequencyDATests.cs +++ b/src/DevOpsMetrics.Tests/Core/DeploymentFrequencyDATests.cs @@ -7,10 +7,11 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("IntegrationTest")] [TestClass] public class DeploymentFrequencyDATests : BaseConfiguration { + [TestCategory("UnitTest")] [TestMethod] public async Task AzDeploymentFrequencyDAIntegrationTest() { @@ -19,9 +20,9 @@ public async Task AzDeploymentFrequencyDAIntegrationTest() string patToken = base.Configuration["AppSettings:AzureDevOpsPatToken"]; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 30; int maxNumberOfItems = 20; bool useCache = true; @@ -42,6 +43,7 @@ public async Task AzDeploymentFrequencyDAIntegrationTest() Assert.IsTrue(model.ItemOrder == 0); } + [TestCategory("UnitTest")] [TestMethod] public async Task GHDeploymentFrequencyDAIntegrationTest() { @@ -50,10 +52,10 @@ public async Task GHDeploymentFrequencyDAIntegrationTest() string clientId = base.Configuration["AppSettings:GitHubClientId"]; string clientSecret = base.Configuration["AppSettings:GitHubClientSecret"]; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "DevOpsMetrics"; string branch = "main"; - string workflowName = "DevOpsMetrics CI/CD"; + string workflowName = "CI/CD"; string workflowId = "1162561"; int numberOfDays = 30; int maxNumberOfItems = 20; diff --git a/src/DevOpsMetrics.Tests/Core/DeploymentFrequencyTests.cs b/src/DevOpsMetrics.Tests/Core/DeploymentFrequencyTests.cs index cbdbff3a..62cf7089 100644 --- a/src/DevOpsMetrics.Tests/Core/DeploymentFrequencyTests.cs +++ b/src/DevOpsMetrics.Tests/Core/DeploymentFrequencyTests.cs @@ -7,7 +7,7 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] public class DeploymentFrequencyTests { @@ -33,7 +33,7 @@ public void DeploymentFrequencySingleOneDayTest() //Assert Assert.IsTrue(model != null); Assert.AreEqual(1f, model.DeploymentsPerDayMetric); - Assert.AreEqual("Elite", model.DeploymentsPerDayMetricDescription); + Assert.AreEqual("High", model.DeploymentsPerDayMetricDescription); Assert.AreEqual(7, model.DeploymentsToDisplayMetric); Assert.AreEqual("times per week", model.DeploymentsToDisplayUnit); } @@ -95,13 +95,13 @@ public void DeploymentFrequencyFiveSevenDaysTest() //Assert Assert.IsTrue(model != null); Assert.AreEqual(0.7143f, model.DeploymentsPerDayMetric); - Assert.AreEqual("High", model.DeploymentsPerDayMetricDescription); + Assert.AreEqual("Medium", model.DeploymentsPerDayMetricDescription); Assert.AreEqual(5.0000997f, model.DeploymentsToDisplayMetric); Assert.AreEqual("times per week", model.DeploymentsToDisplayUnit); } [TestMethod] - public void DeploymentFrequencyRatingEliteTest() + public void DeploymentFrequencyRatingHigh2Test() { //Arrange float metric = 1.01f; //daily @@ -116,7 +116,7 @@ public void DeploymentFrequencyRatingEliteTest() //Assert Assert.IsTrue(model != null); Assert.AreEqual(1.01f, model.DeploymentsPerDayMetric); - Assert.AreEqual("Elite", model.DeploymentsPerDayMetricDescription); + Assert.AreEqual("High", model.DeploymentsPerDayMetricDescription); Assert.AreEqual(1.01f, model.DeploymentsToDisplayMetric); Assert.AreEqual("per day", model.DeploymentsToDisplayUnit); } @@ -137,7 +137,7 @@ public void DeploymentFrequencyRatingHighTest() //Assert Assert.IsTrue(model != null); Assert.AreEqual(1f / 7f, model.DeploymentsPerDayMetric); - Assert.AreEqual("High", model.DeploymentsPerDayMetricDescription); + Assert.AreEqual("Medium", model.DeploymentsPerDayMetricDescription); Assert.AreEqual(1f, model.DeploymentsToDisplayMetric); Assert.AreEqual("times per week", model.DeploymentsToDisplayUnit); } @@ -158,7 +158,7 @@ public void DeploymentFrequencyRatingMediumTest() //Assert Assert.IsTrue(model != null); Assert.AreEqual(0.0055555557f, model.DeploymentsPerDayMetric); - Assert.AreEqual("Medium", model.DeploymentsPerDayMetricDescription); + Assert.AreEqual("Low", model.DeploymentsPerDayMetricDescription); Assert.AreEqual(0.16666667f, model.DeploymentsToDisplayMetric); Assert.AreEqual("times per month", model.DeploymentsToDisplayUnit); diff --git a/src/DevOpsMetrics.Tests/Core/FractionConverterTests.cs b/src/DevOpsMetrics.Tests/Core/FractionConverterTests.cs index bda6d09d..93d59968 100644 --- a/src/DevOpsMetrics.Tests/Core/FractionConverterTests.cs +++ b/src/DevOpsMetrics.Tests/Core/FractionConverterTests.cs @@ -4,7 +4,7 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] public class FractionConverterTests { diff --git a/src/DevOpsMetrics.Tests/Core/LeadTimeForChangesDATests.cs b/src/DevOpsMetrics.Tests/Core/LeadTimeForChangesDATests.cs index 836b04e2..6694a702 100644 --- a/src/DevOpsMetrics.Tests/Core/LeadTimeForChangesDATests.cs +++ b/src/DevOpsMetrics.Tests/Core/LeadTimeForChangesDATests.cs @@ -8,10 +8,11 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("IntegrationTest")] [TestClass] public class LeadTimeForChangesDATests : BaseConfiguration { + [TestCategory("UnitTest")] [TestMethod] public async Task AzLeadTimeForChangesDAIntegrationTest() { @@ -20,17 +21,17 @@ public async Task AzLeadTimeForChangesDAIntegrationTest() string patToken = base.Configuration["AppSettings:AzureDevOpsPatToken"]; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string mainBranch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 7; int maxNumberOfItems = 20; bool useCache = true; //Act LeadTimeForChangesModel model = await LeadTimeForChangesDA.GetAzureDevOpsLeadTimesForChanges(getSampleData, patToken, tableStorageConfig, - organization, project, repository, mainBranch, buildName, + organization, project, repository, branch, buildName, numberOfDays, maxNumberOfItems, useCache); //Assert @@ -52,12 +53,13 @@ public async Task AzLeadTimeForChangesDAIntegrationTest() Assert.AreEqual(1f, model.AverageBuildHours); Assert.AreEqual(12f, model.AveragePullRequestHours); Assert.AreEqual(13f, model.LeadTimeForChangesMetric); - Assert.AreEqual("Elite", model.LeadTimeForChangesMetricDescription); + Assert.AreEqual("High", model.LeadTimeForChangesMetricDescription); Assert.AreEqual(numberOfDays, model.NumberOfDays); Assert.IsTrue(model.MaxNumberOfItems > 0); Assert.IsTrue(model.TotalItems > 0); } + [TestCategory("UnitTest")] [TestMethod] public async Task GHLeadTimeForChangesDAIntegrationTest() { @@ -66,10 +68,10 @@ public async Task GHLeadTimeForChangesDAIntegrationTest() string clientId = base.Configuration["AppSettings:GitHubClientId"]; string clientSecret = base.Configuration["AppSettings:GitHubClientSecret"]; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "devopsmetrics"; string mainBranch = "main"; - string workflowName = "DevOpsMetrics.CI"; + string workflowName = "CI/CD"; string workflowId = "1162561"; int numberOfDays = 7; int maxNumberOfItems = 20; @@ -99,7 +101,7 @@ public async Task GHLeadTimeForChangesDAIntegrationTest() Assert.AreEqual(1f, model.AverageBuildHours); Assert.AreEqual(20.33f, model.AveragePullRequestHours); Assert.AreEqual(21.33f, model.LeadTimeForChangesMetric); - Assert.AreEqual("Elite", model.LeadTimeForChangesMetricDescription); + Assert.AreEqual("High", model.LeadTimeForChangesMetricDescription); Assert.AreEqual(numberOfDays, model.NumberOfDays); Assert.IsTrue(model.MaxNumberOfItems > 0); Assert.IsTrue(model.TotalItems > 0); diff --git a/src/DevOpsMetrics.Tests/Core/LeadTimeForChangesTests.cs b/src/DevOpsMetrics.Tests/Core/LeadTimeForChangesTests.cs index 3db94247..6cf623e2 100644 --- a/src/DevOpsMetrics.Tests/Core/LeadTimeForChangesTests.cs +++ b/src/DevOpsMetrics.Tests/Core/LeadTimeForChangesTests.cs @@ -7,13 +7,13 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] public class LeadTimeForChangesTests { [TestMethod] - public void LeadTimeForChangesSingleOneDayTest() + public void LeadTimeForChangesSingleOneDayHighTest() { //Arrange LeadTimeForChanges metrics = new(); @@ -37,7 +37,7 @@ public void LeadTimeForChangesSingleOneDayTest() //Assert Assert.IsTrue(model != null); Assert.AreEqual(0.75f, model.LeadTimeForChangesMetric); - Assert.AreEqual("Elite", model.LeadTimeForChangesMetricDescription); + Assert.AreEqual("High", model.LeadTimeForChangesMetricDescription); Assert.AreEqual(0.75f, model.LeadTimeForChangesMetricDisplayMetric); Assert.AreEqual("hours", model.LeadTimeForChangesMetricDisplayUnit); Assert.AreEqual(true, model.IsProjectView); @@ -61,7 +61,7 @@ public void LeadTimeForChangesNullOneDayTest() } [TestMethod] - public void LeadTimeForChangesFiveSevenDaysEliteTest() + public void LeadTimeForChangesFiveSevenDaysHighTest() { //Arrange LeadTimeForChanges metrics = new(); @@ -82,11 +82,11 @@ public void LeadTimeForChangesFiveSevenDaysEliteTest() //Assert Assert.AreEqual(1.05, Math.Round((double)result, 4)); - Assert.AreEqual("Elite", rating); + Assert.AreEqual("High", rating); } [TestMethod] - public void LeadTimeForChangesFiveSevenDaysHighTest() + public void LeadTimeForChangesFiveSevenDaysHighTest2() { //Arrange LeadTimeForChanges metrics = new(); @@ -145,7 +145,7 @@ public void LeadTimeForChangesFiveSevenDaysLowTest() //Assert Assert.AreEqual((24 * 31), Math.Round((double)result, 4)); - Assert.AreEqual("Medium", rating); + Assert.AreEqual("Low", rating); } diff --git a/src/DevOpsMetrics.Tests/Core/MeanTimeToRestoreDATests.cs b/src/DevOpsMetrics.Tests/Core/MeanTimeToRestoreDATests.cs index 19e07d5f..94a51d3f 100644 --- a/src/DevOpsMetrics.Tests/Core/MeanTimeToRestoreDATests.cs +++ b/src/DevOpsMetrics.Tests/Core/MeanTimeToRestoreDATests.cs @@ -1,4 +1,5 @@ -using DevOpsMetrics.Core.DataAccess; +using System.Threading.Tasks; +using DevOpsMetrics.Core.DataAccess; using DevOpsMetrics.Core.Models.Common; using DevOpsMetrics.Service; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -6,12 +7,13 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("IntegrationTest")] [TestClass] public class MeanTimeToRestoreDATests : BaseConfiguration { + [TestCategory("UnitTest")] [TestMethod] - public void MeanTimeToRestoreDAIntegrationTest() + public async Task MeanTimeToRestoreDAIntegrationTest() { //Arrange bool getSampleData = true; @@ -22,7 +24,7 @@ public void MeanTimeToRestoreDAIntegrationTest() int maxNumberOfItems = 20; //Act - MeanTimeToRestoreModel model = MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, targetDevOpsPlatform, resourceGroup, numberOfDays, maxNumberOfItems); + MeanTimeToRestoreModel model = await MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, targetDevOpsPlatform, resourceGroup, numberOfDays, maxNumberOfItems); //Assert Assert.IsTrue(model != null); @@ -36,18 +38,18 @@ public void MeanTimeToRestoreDAIntegrationTest() } [TestMethod] - public void MeanTimeToRestoreDASamLearnsAzureProdIntegrationTest() + public async Task MeanTimeToRestoreDADevOpsMetricsProdIntegrationTest() { //Arrange bool getSampleData = false; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string resourceGroup = "SamLearnsAzureProd"; + string resourceGroup = "DevOpsMetrics"; DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.AzureDevOps; int numberOfDays = 30; int maxNumberOfItems = 20; //Act - MeanTimeToRestoreModel model = MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, targetDevOpsPlatform, resourceGroup, numberOfDays, maxNumberOfItems); + MeanTimeToRestoreModel model = await MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, targetDevOpsPlatform, resourceGroup, numberOfDays, maxNumberOfItems); //Assert Assert.IsTrue(model != null); @@ -62,18 +64,18 @@ public void MeanTimeToRestoreDASamLearnsAzureProdIntegrationTest() [TestMethod] - public void TimeToRestoreServiceDAIntegrationTest() + public async Task TimeToRestoreServiceDAIntegrationTest() { //Arrange bool getSampleData = false; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string resourceGroup = "SamLearnsAzureProd"; + string resourceGroup = "DevOpsMetrics"; DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.AzureDevOps; int numberOfDays = 60; int maxNumberOfItems = 20; //Act - MeanTimeToRestoreModel model = MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, targetDevOpsPlatform, resourceGroup, numberOfDays, maxNumberOfItems); + MeanTimeToRestoreModel model = await MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, targetDevOpsPlatform, resourceGroup, numberOfDays, maxNumberOfItems); //Assert Assert.IsTrue(model != null); @@ -88,18 +90,18 @@ public void TimeToRestoreServiceDAIntegrationTest() } [TestMethod] - public void TimeToRestoreServiceDALiveIntegrationTest() + public async Task TimeToRestoreServiceDALiveIntegrationTest() { //Arrange bool getSampleData = false; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string resourceGroup = "SamLearnsAzureProd"; + string resourceGroup = "DevOpsMetrics"; DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.AzureDevOps; int numberOfDays = 60; int maxNumberOfItems = 20; //Act - MeanTimeToRestoreModel model = MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, targetDevOpsPlatform, resourceGroup, numberOfDays, maxNumberOfItems); + MeanTimeToRestoreModel model = await MeanTimeToRestoreDA.GetAzureMeanTimeToRestore(getSampleData, tableStorageConfig, targetDevOpsPlatform, resourceGroup, numberOfDays, maxNumberOfItems); //Assert Assert.IsTrue(model != null); diff --git a/src/DevOpsMetrics.Tests/Core/MeanTimeToRestoreTests.cs b/src/DevOpsMetrics.Tests/Core/MeanTimeToRestoreTests.cs index 99153d2d..dad82f8f 100644 --- a/src/DevOpsMetrics.Tests/Core/MeanTimeToRestoreTests.cs +++ b/src/DevOpsMetrics.Tests/Core/MeanTimeToRestoreTests.cs @@ -7,13 +7,13 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] public class MeanTimeToRestoreTests { [TestMethod] - public void MTTREliteTest() + public void MTTRHigh2Test() { //Arrange MeanTimeToRestore metrics = new(); @@ -41,7 +41,7 @@ public void MTTREliteTest() //Assert Assert.IsTrue(model != null); Assert.AreEqual(0.75f, model.MTTRAverageDurationInHours); - Assert.AreEqual("Elite", model.MTTRAverageDurationDescription); + Assert.AreEqual("High", model.MTTRAverageDurationDescription); Assert.AreEqual(false, model.IsProjectView); Assert.AreEqual(1, model.ItemOrder); Assert.AreEqual(0.9910714f, model.SLA); diff --git a/src/DevOpsMetrics.Tests/Core/NightlyProcessTests.cs b/src/DevOpsMetrics.Tests/Core/NightlyProcessTests.cs deleted file mode 100644 index 843d218d..00000000 --- a/src/DevOpsMetrics.Tests/Core/NightlyProcessTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using DevOpsMetrics.Core.DataAccess.TableStorage; -using DevOpsMetrics.Core.Models.Common; -using DevOpsMetrics.Core.Models.GitHub; -using DevOpsMetrics.Function; -using DevOpsMetrics.Service; -using DevOpsMetrics.Service.Controllers; -using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DevOpsMetrics.Tests.Core -{ - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] - [TestClass] - public class NightlyProcessTests : BaseConfiguration - { - [TestMethod] - public async Task GitHubProcessingTest() - { - //Arrange - string clientId = Configuration["AppSettings:GitHubClientId"]; - string clientSecret = Configuration["AppSettings:GitHubClientSecret"]; - ProcessingResult result = null; - AzureTableStorageDA da = new(); - BuildsController buildsController = new(base.Configuration, da); - PullRequestsController pullRequestsController = new(base.Configuration, da); - SettingsController settingsController = new(base.Configuration, da); - using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder - .SetMinimumLevel(LogLevel.Trace) - .AddConsole()); - ILogger log = loggerFactory.CreateLogger(); - TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - - //Act - List results = da.GetGitHubSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubSettings, null); - foreach (GitHubSettings item in results) - { - if (item.Repo == "AzurePipelinesToGitHubActionsConverter") - { - result = await Processing.ProcessGitHubItem(item, clientId, clientSecret, tableStorageConfig, - 30, 20, - buildsController, pullRequestsController, settingsController, - log, 0); - } - } - - //Assert - Assert.IsNotNull(result); - } - - - - //[TestMethod] - //public async Task GHUpdateAzurePipelinesToGitHubActionsConverterWebSettingDAIntegrationTest() - //{ - // //Arrange - // TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - // string owner = "samsmithnz"; - // string repo = "AzurePipelinesToGitHubActionsConverterWeb"; - // string branch = "main"; - // string workflowName = "Pipelines to Actions website CI/CD"; - // string workflowId = "43084"; - // string resourceGroupName = "PipelinesToActions"; - // int itemOrder = 3; - // bool showSetting = true; - - // //Act - // AzureTableStorageDA da = new(); - // bool result = await da.UpdateGitHubSettingInStorage(tableStorageConfig, tableStorageConfig.TableGitHubSettings, - // owner, repo, branch, workflowName, workflowId, resourceGroupName, itemOrder, showSetting); - - // //Assert - // Assert.IsTrue(result == true); - //} - - - //[TestMethod] - //public async Task GHUpdateAllDevOpsMetricsIntegrationTest() - //{ - // //Arrange - // string clientId = Configuration["AppSettings:GitHubClientId"]; - // string clientSecret = Configuration["AppSettings:GitHubClientSecret"]; - // int numberOfDays = 30; - // int maxNumberOfItems = 20; - // int totalResults = 0; - // using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder - // .SetMinimumLevel(LogLevel.Trace) - // .AddConsole()); - // ILogger log = loggerFactory.CreateLogger(); - // TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); - - // //Act - // AzureTableStorageDA azureTableStorageDA = new(); - // BuildsController buildsController = new(Configuration, azureTableStorageDA); - // PullRequestsController pullRequestsController = new(Configuration, azureTableStorageDA); - // SettingsController settingsController = new(Configuration, azureTableStorageDA); - // List ghSettings = settingsController.GetGitHubSettings(); - - // foreach (GitHubSettings item in ghSettings) - // { - // ProcessingResult ghResult = await Processing.ProcessGitHubItem(item, - // clientId, clientSecret, tableStorageConfig, - // numberOfDays, maxNumberOfItems, - // buildsController, pullRequestsController, settingsController, log, totalResults); - // totalResults = ghResult.TotalResults; - // } - - // //Assert - // Assert.IsTrue(totalResults >= 0); - //} - - } -} diff --git a/src/DevOpsMetrics.Tests/Core/PostiveNegativeListDATests.cs b/src/DevOpsMetrics.Tests/Core/PostiveNegativeListTests.cs similarity index 98% rename from src/DevOpsMetrics.Tests/Core/PostiveNegativeListDATests.cs rename to src/DevOpsMetrics.Tests/Core/PostiveNegativeListTests.cs index 6ad323b9..a1dd999d 100644 --- a/src/DevOpsMetrics.Tests/Core/PostiveNegativeListDATests.cs +++ b/src/DevOpsMetrics.Tests/Core/PostiveNegativeListTests.cs @@ -7,9 +7,9 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] - public class PostiveNegativeListDATests + public class PostiveNegativeListTests { [TestMethod] diff --git a/src/DevOpsMetrics.Tests/Core/SLATests.cs b/src/DevOpsMetrics.Tests/Core/SLATests.cs index a7ff3316..dc8bf694 100644 --- a/src/DevOpsMetrics.Tests/Core/SLATests.cs +++ b/src/DevOpsMetrics.Tests/Core/SLATests.cs @@ -6,7 +6,7 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] public class SLATests { diff --git a/src/DevOpsMetrics.Tests/Core/SettingsDATests.cs b/src/DevOpsMetrics.Tests/Core/SettingsDATests.cs index 2bcfcef1..34b67286 100644 --- a/src/DevOpsMetrics.Tests/Core/SettingsDATests.cs +++ b/src/DevOpsMetrics.Tests/Core/SettingsDATests.cs @@ -10,7 +10,7 @@ namespace DevOpsMetrics.Tests.Core { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("IntegrationTest")] [TestClass] public class SettingsDATests : BaseConfiguration { @@ -26,52 +26,53 @@ public void TestAzureSettingsExistTest() //Act //Assert - Assert.IsTrue(string.IsNullOrEmpty(AzureDevOpsPatToken) == false); - Assert.IsTrue(string.IsNullOrEmpty(GitHubClientId) == false); - Assert.IsTrue(string.IsNullOrEmpty(GitHubClientSecret) == false); + //Assert.IsTrue(string.IsNullOrEmpty(AzureDevOpsPatToken) == false); + //Assert.IsTrue(string.IsNullOrEmpty(GitHubClientId) == false); + //Assert.IsTrue(string.IsNullOrEmpty(GitHubClientSecret) == false); Assert.IsTrue(string.IsNullOrEmpty(StorageAccountConnectionString) == false); } [TestMethod] - public void AzGetSamLearnsAzureSettingDAIntegrationTest() + public async Task AzGetAzDoDevOpsMetricsSettingDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); //Act AzureTableStorageDA da = new(); - List results = da.GetAzureDevOpsSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsSettings, null); + List results = await da.GetAzureDevOpsSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsSettings, null); //Assert Assert.IsTrue(results.Count > 0); } [TestMethod] - public void GHGetSamLearnsAzureSettingDAIntegrationTest() + public async Task GHGetSettingDAIntegrationTest() { + //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); //Act AzureTableStorageDA da = new(); - List results = da.GetGitHubSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubSettings, null); + List results = await da.GetGitHubSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubSettings, null); //Assert Assert.IsTrue(results.Count > 0); } [TestMethod] - public async Task AzUpdateSamLearnsAzureSettingDAIntegrationTest() + public async Task AzUpdateAzDoDevOpsMetricsSettingDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; string buildId = "3673"; - string resourceGroupName = "SamLearnsAzureProd"; + string resourceGroupName = "DevOpsMetrics"; int itemOrder = 1; bool showSetting = true; @@ -81,7 +82,7 @@ public async Task AzUpdateSamLearnsAzureSettingDAIntegrationTest() organization, project, repository, branch, buildName, buildId, resourceGroupName, itemOrder, showSetting); //Assert - Assert.IsTrue(result == true); + Assert.IsTrue(result); } [TestMethod] @@ -89,7 +90,7 @@ public async Task GHUpdateDevOpsMetricsSettingDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "DevOpsMetrics"; string branch = "main"; string workflowName = "CI/CD"; @@ -104,7 +105,7 @@ public async Task GHUpdateDevOpsMetricsSettingDAIntegrationTest() owner, repo, branch, workflowName, workflowId, resourceGroupName, itemOrder, showSetting); //Assert - Assert.IsTrue(result == true); + Assert.IsTrue(result); } [TestMethod] @@ -117,7 +118,7 @@ public async Task GHUpdateSamsFeatureFlagsSettingDAIntegrationTest() string branch = "main"; string workflowName = "SamsFeatureFlags CI/CD"; string workflowId = "108084"; - string resourceGroupName = "SamLearnsAzureFeatureFlags"; + string resourceGroupName = "SamsFeatureFlags"; int itemOrder = 3; bool showSetting = true; @@ -127,7 +128,7 @@ public async Task GHUpdateSamsFeatureFlagsSettingDAIntegrationTest() owner, repo, branch, workflowName, workflowId, resourceGroupName, itemOrder, showSetting); //Assert - Assert.IsTrue(result == true); + Assert.IsTrue(result); } [TestMethod] @@ -150,7 +151,7 @@ public async Task GHUpdateAzurePipelinesToGitHubActionsConverterWebSettingDAInte owner, repo, branch, workflowName, workflowId, resourceGroupName, itemOrder, showSetting); //Assert - Assert.IsTrue(result == true); + Assert.IsTrue(result); } } diff --git a/src/DevOpsMetrics.Tests/DevOpsMetrics.Tests.csproj b/src/DevOpsMetrics.Tests/DevOpsMetrics.Tests.csproj index 71c29b30..fca76064 100644 --- a/src/DevOpsMetrics.Tests/DevOpsMetrics.Tests.csproj +++ b/src/DevOpsMetrics.Tests/DevOpsMetrics.Tests.csproj @@ -1,46 +1,48 @@  - - net7.0 - - false - - 0e2b71e7-af1c-40d5-a334-1118f60b467e - - - - - - - - - PreserveNewest - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - + + net10.0 + + false + + 0e2b71e7-af1c-40d5-a334-1118f60b467e + + + + + + + + + PreserveNewest + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/src/DevOpsMetrics.Tests/Service/BadgeTests.cs b/src/DevOpsMetrics.Tests/Service/BadgeTests.cs index 1d60fb4a..4e94b99b 100644 --- a/src/DevOpsMetrics.Tests/Service/BadgeTests.cs +++ b/src/DevOpsMetrics.Tests/Service/BadgeTests.cs @@ -4,12 +4,11 @@ namespace DevOpsMetrics.Tests.Service { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] public class BadgeTests { - [TestCategory("L0Test")] [TestMethod] public void DeploymentFrequencyEliteBadgeTest() { @@ -29,7 +28,6 @@ public void DeploymentFrequencyEliteBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void DeploymentFrequencyHighBadgeTest() { @@ -47,7 +45,6 @@ public void DeploymentFrequencyHighBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void DeploymentFrequencyMediumBadgeTest() { @@ -65,7 +62,6 @@ public void DeploymentFrequencyMediumBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void DeploymentFrequencyLowBadgeTest() { @@ -83,7 +79,6 @@ public void DeploymentFrequencyLowBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void DeploymentFrequencyNoneBadgeTest() { @@ -100,26 +95,6 @@ public void DeploymentFrequencyNoneBadgeTest() Assert.AreEqual("https://img.shields.io/badge/Deployment%20frequency-None-lightgrey", model.BadgeURL); } - [TestCategory("L0Test")] - [TestMethod] - public void ChangeFailureRateControllerEliteBadgeTest() - { - //Arrange - ChangeFailureRateModel model = new() - { - ChangeFailureRateMetricDescription = "Elite", - ChangeFailureRateMetric = 0 - }; - - //Act - - //Assert - Assert.AreEqual("Elite", model.ChangeFailureRateMetricDescription); - Assert.AreEqual("https://img.shields.io/badge/Change%20failure%20rate-Elite-brightgreen", model.BadgeURL); - Assert.AreEqual("https://img.shields.io/badge/Change%20failure%20rate%20%280.00%25%29-Elite-brightgreen", model.BadgeWithMetricURL); - } - - [TestCategory("L0Test")] [TestMethod] public void ChangeFailureRateControllerHighBadgeTest() { @@ -137,7 +112,6 @@ public void ChangeFailureRateControllerHighBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void ChangeFailureRateControllerMediumBadgeTest() { @@ -155,7 +129,6 @@ public void ChangeFailureRateControllerMediumBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void ChangeFailureRateControllerLowBadgeTest() { @@ -173,7 +146,6 @@ public void ChangeFailureRateControllerLowBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void ChangeFailureRateControllerNoneBadgeTest() { @@ -190,7 +162,6 @@ public void ChangeFailureRateControllerNoneBadgeTest() Assert.AreEqual("https://img.shields.io/badge/Change%20failure%20rate-None-lightgrey", model.BadgeURL); } - [TestCategory("L0Test")] [TestMethod] public void LeadTimeForChangesEliteBadgeTest() { @@ -210,7 +181,6 @@ public void LeadTimeForChangesEliteBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void LeadTimeForChangesHighBadgeTest() { @@ -228,7 +198,6 @@ public void LeadTimeForChangesHighBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void LeadTimeForChangesMediumBadgeTest() { @@ -246,7 +215,6 @@ public void LeadTimeForChangesMediumBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void LeadTimeForChangesLowBadgeTest() { @@ -264,7 +232,6 @@ public void LeadTimeForChangesLowBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void LeadTimeForChangesNoneBadgeTest() { @@ -282,7 +249,6 @@ public void LeadTimeForChangesNoneBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void MeanTimeToRestoreEliteBadgeTest() { @@ -302,7 +268,6 @@ public void MeanTimeToRestoreEliteBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void MeanTimeToRestoreHighBadgeTest() { @@ -320,7 +285,6 @@ public void MeanTimeToRestoreHighBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void MeanTimeToRestoreMediumBadgeTest() { @@ -338,7 +302,6 @@ public void MeanTimeToRestoreMediumBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void MeanTimeToRestoreLowBadgeTest() { @@ -356,7 +319,6 @@ public void MeanTimeToRestoreLowBadgeTest() } - [TestCategory("L0Test")] [TestMethod] public void MeanTimeToRestoreNoneBadgeTest() { diff --git a/src/DevOpsMetrics.Tests/Service/ChangeFailureRateTests.cs b/src/DevOpsMetrics.Tests/Service/ChangeFailureRateControllerTests.cs similarity index 59% rename from src/DevOpsMetrics.Tests/Service/ChangeFailureRateTests.cs rename to src/DevOpsMetrics.Tests/Service/ChangeFailureRateControllerTests.cs index 6ed92ecf..9926219b 100644 --- a/src/DevOpsMetrics.Tests/Service/ChangeFailureRateTests.cs +++ b/src/DevOpsMetrics.Tests/Service/ChangeFailureRateControllerTests.cs @@ -1,30 +1,31 @@ -using DevOpsMetrics.Core.Models.Common; +using System.Threading.Tasks; +using DevOpsMetrics.Core.Models.Common; using DevOpsMetrics.Service.Controllers; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace DevOpsMetrics.Tests.Service { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("UnitTest")] [TestClass] - public class ChangeFailureRateTests : BaseConfiguration + public class ChangeFailureRateControllerTests : BaseConfiguration { [TestMethod] - public void AzChangeFailureRateSampleControllerIntegrationTest() + public async Task AzChangeFailureRateSampleControllerIntegrationTest() { //Arrange bool getSampleData = true; string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.AzureDevOps; int numberOfDays = 7; int maxNumberOfItems = 20; ChangeFailureRateController controller = new(base.Configuration); //Act - ChangeFailureRateModel model = controller.GetChangeFailureRate(getSampleData, + ChangeFailureRateModel model = await controller.GetChangeFailureRate(getSampleData, targetDevOpsPlatform, organization, project, branch, buildName, numberOfDays, maxNumberOfItems); //Assert @@ -39,16 +40,16 @@ public void AzChangeFailureRateSampleControllerIntegrationTest() Assert.IsTrue(model.TotalItems > 0); } - //[TestCategory("L1Test")] + //[TestCategory("IntegrationTest")] //[TestMethod] //public void AzChangeFailureRateLiveControllerIntegrationTest() //{ // //Arrange // bool getSampleData = false; // string organization = "samsmithnz"; - // string project = "SamLearnsAzure"; - // string branch = "refs/heads/master"; - // string buildName = "SamLearnsAzure.CI"; + //string project = "AzDoDevOpsMetrics"; + //string branch = "refs/heads/main"; + //string buildName = "azure-pipelines.yml"; // DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.AzureDevOps; // int numberOfDays = 30; // int maxNumberOfItems = 20; @@ -69,23 +70,26 @@ public void AzChangeFailureRateSampleControllerIntegrationTest() // Assert.IsTrue(model.TotalItems > 0); //} - [TestCategory("L1Test")] [TestMethod] - public void GHChangeFailureRateSampleControllerIntegrationTest() + public async Task GHChangeFailureRateSampleControllerIntegrationTest() { //Arrange bool getSampleData = true; + //string owner = "samsmithnz"; + //string repo = "SamsFeatureFlags"; + //string branch = "main"; + //string workflowName = "SamsFeatureFlags.CI/CD"; string owner = "samsmithnz"; - string repo = "SamsFeatureFlags"; + string repo = "AzurePipelinesToGitHubActionsConverter"; string branch = "main"; - string workflowName = "SamsFeatureFlags.CI/CD"; + string workflowName = "CI/ CD"; DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.GitHub; int numberOfDays = 7; int maxNumberOfItems = 20; ChangeFailureRateController controller = new(base.Configuration); //Act - ChangeFailureRateModel model = controller.GetChangeFailureRate(getSampleData, + ChangeFailureRateModel model = await controller.GetChangeFailureRate(getSampleData, targetDevOpsPlatform, owner, repo, branch, workflowName, numberOfDays, maxNumberOfItems); //Assert @@ -100,36 +104,36 @@ public void GHChangeFailureRateSampleControllerIntegrationTest() Assert.IsTrue(model.TotalItems > 0); } - //[TestCategory("L1Test")] - //[TestMethod] - //public void GHChangeFailureRateLiveControllerIntegrationTest() - //{ - // //Arrange - // bool getSampleData = false; - // string owner = "samsmithnz"; - // string repo = "SamsFeatureFlags"; - // string branch = "main"; - // string workflowName = "SamsFeatureFlags.CI/CD"; - // DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.GitHub; - // int numberOfDays = 7; - // int maxNumberOfItems = 20; - // ChangeFailureRateController controller = new(base.Configuration); + [TestCategory("IntegrationTest")] + [TestMethod] + public async Task GHChangeFailureRateLiveControllerIntegrationTest() + { + //Arrange + bool getSampleData = false; + string owner = "samsmithnz"; + string repo = "AzurePipelinesToGitHubActionsConverter"; + string branch = "main"; + string workflowName = "CI/ CD"; + DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.GitHub; + int numberOfDays = 30; + int maxNumberOfItems = 20; + ChangeFailureRateController controller = new(base.Configuration); - // //Act - // ChangeFailureRateModel model = controller.GetChangeFailureRate(getSampleData, - // targetDevOpsPlatform, owner, repo, branch, workflowName, numberOfDays, maxNumberOfItems); + //Act + ChangeFailureRateModel model = await controller.GetChangeFailureRate(getSampleData, + targetDevOpsPlatform, owner, repo, branch, workflowName, numberOfDays, maxNumberOfItems); - // //Assert - // Assert.IsTrue(model != null); - // Assert.IsTrue(model.TargetDevOpsPlatform == targetDevOpsPlatform); - // Assert.IsTrue(model.DeploymentName == workflowName); - // Assert.IsTrue(model.ChangeFailureRateMetric >= 0f); - // Assert.AreEqual(false, string.IsNullOrEmpty(model.ChangeFailureRateMetricDescription)); - // Assert.AreEqual(numberOfDays, model.NumberOfDays); - // Assert.IsTrue(model.MaxNumberOfItems > 0); - // Assert.IsTrue(model.TotalItems > 0); + //Assert + Assert.IsTrue(model != null); + Assert.IsTrue(model.TargetDevOpsPlatform == targetDevOpsPlatform); + Assert.IsTrue(model.DeploymentName == workflowName); + Assert.IsTrue(model.ChangeFailureRateMetric >= -1f); + Assert.AreEqual(false, string.IsNullOrEmpty(model.ChangeFailureRateMetricDescription)); + Assert.AreEqual(numberOfDays, model.NumberOfDays); + Assert.IsTrue(model.MaxNumberOfItems >= 0); + Assert.IsTrue(model.TotalItems >= 0); - //} + } } diff --git a/src/DevOpsMetrics.Tests/Service/DORASummaryControllerTests.cs b/src/DevOpsMetrics.Tests/Service/DORASummaryControllerTests.cs index e964deab..513012c2 100644 --- a/src/DevOpsMetrics.Tests/Service/DORASummaryControllerTests.cs +++ b/src/DevOpsMetrics.Tests/Service/DORASummaryControllerTests.cs @@ -1,40 +1,218 @@ -using DevOpsMetrics.Core.Models.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using DevOpsMetrics.Core.DataAccess.TableStorage; +using DevOpsMetrics.Core.Models.AzureDevOps; +using DevOpsMetrics.Core.Models.Common; +using DevOpsMetrics.Core.Models.GitHub; using DevOpsMetrics.Service.Controllers; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace DevOpsMetrics.Tests.Service { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("IntegrationTest")] [TestClass] public class DORASummaryControllerTests : BaseConfiguration { [TestMethod] - public void DORASummaryControllerGetIntegrationTest() + public async Task DORASummaryControllerGetIntegrationTest() { //Arrange + string organization = "DeveloperMetrics"; + string project = null; + string repo = "DevOpsMetrics"; + DORASummaryController controller = new(base.Configuration); + + //Act + DORASummaryItem model = await controller.GetDORASummaryItem(organization, project, repo); + + //Assert + Assert.IsNotNull(model); + + } + + [TestMethod] + public async Task DORASummaryControllerGitHubUpdateIntegrationTest() + { + //Arrange + string project = null; + //string organization = "DeveloperMetrics"; + //string repo = "DevOpsMetrics"; string organization = "samsmithnz"; - string repository = "AzurePipelinesToGitHubActionsConverter"; + string repo = "SamsFeatureFlags"; + //string organization = "samsmithnz"; + //string repo = "CustomQueue"; + //string organization = "samsmithnz"; + //string project = "SamLearnsAzure"; + //string repo = "SamLearnsAzure"; + int numberOfDays = 30; + int maxNumberOfItems = 20; DORASummaryController controller = new(base.Configuration); + ProcessingResult model; + DORASummaryItem doraSummaryItem; //Act - DORASummaryItem model = controller.GetDORASummaryItems(organization, repository); + (AzureDevOpsSettings, GitHubSettings) setting = await GetSettingWithName(organization, project, repo); + if (setting.Item1 != null) + { + AzureDevOpsSettings azureDevOpsSettings = setting.Item1; + model = await controller.UpdateDORASummaryItem(azureDevOpsSettings.Organization, + azureDevOpsSettings.Project, azureDevOpsSettings.Repository, + azureDevOpsSettings.Branch, + azureDevOpsSettings.BuildName, azureDevOpsSettings.BuildId, + azureDevOpsSettings.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, + null, true, false); + doraSummaryItem = await controller.GetDORASummaryItem(azureDevOpsSettings.Organization, azureDevOpsSettings.Project, azureDevOpsSettings.Repository); + } + else if (setting.Item2 != null) + { + GitHubSettings gitHubSettings = setting.Item2; + model = await controller.UpdateDORASummaryItem(gitHubSettings.Owner, "", gitHubSettings.Repo, + gitHubSettings.Branch, gitHubSettings.WorkflowName, gitHubSettings.WorkflowId, gitHubSettings.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, + null, true, true); + doraSummaryItem = await controller.GetDORASummaryItem(gitHubSettings.Owner, null, gitHubSettings.Repo); + } + else + { + model = await controller.UpdateDORASummaryItem(organization, "", repo, + "main", "", "", "", + numberOfDays, maxNumberOfItems, + null, true, true); + doraSummaryItem = await controller.GetDORASummaryItem(organization, project, repo); + } + //Assert Assert.IsNotNull(model); - //Assert.AreEqual(DevOpsPlatform.AzureDevOps, model.TargetDevOpsPlatform); - //Assert.AreEqual(buildName, model.DeploymentName); - //Assert.AreEqual(10f, model.DeploymentsPerDayMetric); - //Assert.AreEqual("Elite", model.DeploymentsPerDayMetricDescription); - //Assert.AreEqual(10, model.BuildList.Count); - //Assert.AreEqual(70, model.BuildList[0].BuildDurationPercent); - //Assert.AreEqual("1", model.BuildList[0].BuildNumber); - //Assert.AreEqual("main", model.BuildList[0].Branch); - //Assert.AreEqual("completed", model.BuildList[0].Status); - //Assert.AreEqual("https://dev.azure.com/samsmithnz/samlearnsazure/1", model.BuildList[0].Url); - //Assert.IsTrue(model.BuildList[0].StartTime > DateTime.MinValue); - //Assert.IsTrue(model.BuildList[0].EndTime > DateTime.MinValue); + Assert.IsNotNull(doraSummaryItem); + Assert.IsTrue(doraSummaryItem.DeploymentFrequency >= 0); + Assert.IsTrue(doraSummaryItem.LeadTimeForChanges >= 0); + Assert.IsTrue(doraSummaryItem.MeanTimeToRestore >= 0); + Assert.IsTrue(doraSummaryItem.ChangeFailureRate >= -1); //Change failure rate is -1 when there is no data (since 0 means something different from this metric) + Assert.IsTrue(doraSummaryItem.LastUpdatedMessage.IndexOf("Error") == -1); + Assert.IsTrue(doraSummaryItem.LastUpdated > DateTime.MinValue); + } + + [TestMethod] + public async Task DORASummaryControllerAzureDevOpsUpdateIntegrationTest() + { + //Arrange + string organization = "samsmithnz"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + int numberOfDays = 30; + int maxNumberOfItems = 20; + DORASummaryController controller = new(base.Configuration); + ProcessingResult model; + + //Act + (AzureDevOpsSettings, GitHubSettings) setting = await GetSettingWithName(organization, project, repository); + if (setting.Item1 != null) + { + AzureDevOpsSettings azureDevOpsSettings = setting.Item1; + model = await controller.UpdateDORASummaryItem(azureDevOpsSettings.Organization, + azureDevOpsSettings.Project, azureDevOpsSettings.Repository, + azureDevOpsSettings.Branch, + azureDevOpsSettings.BuildName, azureDevOpsSettings.BuildId, + azureDevOpsSettings.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, + null, true, false); + } + else + { + GitHubSettings gitHubSettings = setting.Item2; + model = await controller.UpdateDORASummaryItem(gitHubSettings.Owner, "", gitHubSettings.Repo, + gitHubSettings.Branch, gitHubSettings.WorkflowName, gitHubSettings.WorkflowId, gitHubSettings.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, + null, true, true); + } + + //Assert + Assert.IsNotNull(model); + } + + [TestMethod] + public async Task DORASummaryControllerUpdateALLIntegrationTest() + { + //Arrange + int numberOfDays = 30; + int maxNumberOfItems = 20; + int totalResults = 0; + DORASummaryController controller = new(base.Configuration); + SettingsController settingsController = new(base.Configuration, new AzureTableStorageDA()); + bool runExpensiveTest = false; + + //Act + if (runExpensiveTest) + { + List azSettings = await settingsController.GetAzureDevOpsSettings(); + List ghSettings = await settingsController.GetGitHubSettings(); + + foreach (AzureDevOpsSettings azSetting in azSettings) + { + Debug.WriteLine($"Processing Azure DevOps organization {azSetting.Organization}, project {azSetting.Project}"); + ProcessingResult ghResult = await controller.UpdateDORASummaryItem( + azSetting.Organization, azSetting.Project, azSetting.Repository, + azSetting.Branch, azSetting.BuildName, azSetting.BuildId, + azSetting.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, + null, true, false); + totalResults += ghResult.TotalResults; + } + + foreach (GitHubSettings ghSetting in ghSettings) + { + Debug.WriteLine($"Processing GitHub owner {ghSetting.Owner}, repo {ghSetting.Repo}"); + ProcessingResult ghResult = await controller.UpdateDORASummaryItem( + ghSetting.Owner, "", ghSetting.Repo, ghSetting.Branch, + ghSetting.WorkflowName, ghSetting.WorkflowId, + ghSetting.ProductionResourceGroup, + numberOfDays, maxNumberOfItems, + null, true, true); + totalResults += ghResult.TotalResults; + } + } + + //Assert + if (runExpensiveTest) + { + Assert.IsTrue(totalResults > 0); + } + else + { + Assert.IsTrue(totalResults == 0); + } + } + + private async Task<(AzureDevOpsSettings, GitHubSettings)> GetSettingWithName(string owner, string project, string repo) + { + (AzureDevOpsSettings, GitHubSettings) results = (null, null); + SettingsController settingsController = new(base.Configuration, new AzureTableStorageDA()); + List azSettings = await settingsController.GetAzureDevOpsSettings(); + List ghSettings = await settingsController.GetGitHubSettings(); + + foreach (AzureDevOpsSettings azSetting in azSettings) + { + if (azSetting.Organization == owner && azSetting.Project == project && azSetting.Repository == repo) + { + results = (azSetting, null); + break; + } + } + foreach (GitHubSettings ghSetting in ghSettings) + { + if (ghSetting.Owner == owner && ghSetting.Repo == repo) + { + results = (null, ghSetting); + break; + } + } + return results; } } -} +} \ No newline at end of file diff --git a/src/DevOpsMetrics.Tests/Service/DeploymentFrequencyControllerTests.cs b/src/DevOpsMetrics.Tests/Service/DeploymentFrequencyControllerTests.cs index f3572d71..a119fa67 100644 --- a/src/DevOpsMetrics.Tests/Service/DeploymentFrequencyControllerTests.cs +++ b/src/DevOpsMetrics.Tests/Service/DeploymentFrequencyControllerTests.cs @@ -7,20 +7,21 @@ namespace DevOpsMetrics.Tests.Service { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("IntegrationTest")] [TestClass] - public class DeploymentFrequencyL1s : BaseConfiguration + public class DeploymentFrequencyControllerTests : BaseConfiguration { + [TestCategory("UnitTest")] [TestMethod] public async Task AzDeploymentsSampleControllerIntegrationTest() { //Arrange bool getSampleData = true; string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 7; int maxNumberOfItems = 20; bool useCache = false; @@ -33,7 +34,7 @@ public async Task AzDeploymentsSampleControllerIntegrationTest() Assert.AreEqual(DevOpsPlatform.AzureDevOps, model.TargetDevOpsPlatform); Assert.AreEqual(buildName, model.DeploymentName); Assert.AreEqual(10f, model.DeploymentsPerDayMetric); - Assert.AreEqual("Elite", model.DeploymentsPerDayMetricDescription); + Assert.AreEqual("High", model.DeploymentsPerDayMetricDescription); Assert.AreEqual(10, model.BuildList.Count); Assert.AreEqual(70, model.BuildList[0].BuildDurationPercent); Assert.AreEqual("1", model.BuildList[0].BuildNumber); @@ -51,10 +52,10 @@ public async Task AzDeploymentsAPIControllerIntegrationTest() //Arrange bool getSampleData = false; string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 30; int maxNumberOfItems = 20; bool useCache = false; @@ -92,15 +93,15 @@ public async Task AzDeploymentsAPIControllerIntegrationTest() [TestMethod] public async Task AzDeploymentsCacheControllerIntegrationTest() { - //https://devops-prod-eu-service.azurewebsites.net//api/DeploymentFrequency/GetAzureDevOpsDeploymentFrequency?getSampleData=False&organization=samsmithnz&project=SamLearnsAzure&repository=SamLearnsAzure&branch=refs/heads/master&buildName=SamLearnsAzure.CI&buildId=3673&numberOfDays=30&maxNumberOfItems=20&useCache=true + //https://devops-prod-eu-service.azurewebsites.net//api/DeploymentFrequency/GetAzureDevOpsDeploymentFrequency?getSampleData=False&organization=samsmithnz&project=AzDoDevOpsMetrics&repository=SamLearnsAzure&branch=refs/heads/main&buildName=azure-pipelines.yml&buildId=3673&numberOfDays=30&maxNumberOfItems=20&useCache=true //Arrange bool getSampleData = false; string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 30; int maxNumberOfItems = 20; bool useCache = true; @@ -135,6 +136,7 @@ public async Task AzDeploymentsCacheControllerIntegrationTest() } + [TestCategory("UnitTest")] [TestMethod] public async Task GHDeploymentsSampleControllerIntegrationTest() { @@ -157,7 +159,7 @@ public async Task GHDeploymentsSampleControllerIntegrationTest() Assert.AreEqual(DevOpsPlatform.GitHub, model.TargetDevOpsPlatform); Assert.AreEqual(workflowName, model.DeploymentName); Assert.AreEqual(10f, model.DeploymentsPerDayMetric); - Assert.AreEqual("Elite", model.DeploymentsPerDayMetricDescription); + Assert.AreEqual("High", model.DeploymentsPerDayMetricDescription); Assert.AreEqual(10, model.BuildList.Count); Assert.AreEqual(70, model.BuildList[0].BuildDurationPercent); Assert.AreEqual("1", model.BuildList[0].BuildNumber); @@ -267,10 +269,10 @@ public async Task AzDeploymentsControllerAPILiveWithCacheIntegrationTest() //Arrange bool getSampleData = false; string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 7; int maxNumberOfItems = 20; bool useCache = true; @@ -306,6 +308,7 @@ public async Task AzDeploymentsControllerAPILiveWithCacheIntegrationTest() + [TestCategory("UnitTest")] [TestMethod] public async Task GHDeploymentsControllerAPILiveWithCacheIntegrationTest() { @@ -328,7 +331,7 @@ public async Task GHDeploymentsControllerAPILiveWithCacheIntegrationTest() Assert.AreEqual(DevOpsPlatform.GitHub, model.TargetDevOpsPlatform); Assert.AreEqual(workflowName, model.DeploymentName); Assert.AreEqual(10f, model.DeploymentsPerDayMetric); - Assert.AreEqual("Elite", model.DeploymentsPerDayMetricDescription); + Assert.AreEqual("High", model.DeploymentsPerDayMetricDescription); Assert.AreEqual(10, model.BuildList.Count); Assert.AreEqual(70, model.BuildList[0].BuildDurationPercent); Assert.AreEqual("1", model.BuildList[0].BuildNumber); @@ -350,10 +353,10 @@ public async Task AzDeploymentsControllerAPILiveIntegrationTest() //Arrange bool getSampleData = false; string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 7; int maxNumberOfItems = 20; bool useCache = false; diff --git a/src/DevOpsMetrics.Tests/Service/LeadTimeForChangesControllerTests.cs b/src/DevOpsMetrics.Tests/Service/LeadTimeForChangesControllerTests.cs index a2169863..c43d2c06 100644 --- a/src/DevOpsMetrics.Tests/Service/LeadTimeForChangesControllerTests.cs +++ b/src/DevOpsMetrics.Tests/Service/LeadTimeForChangesControllerTests.cs @@ -7,20 +7,21 @@ namespace DevOpsMetrics.Tests.Service { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [TestCategory("IntegrationTest")] [TestClass] - public class LeadTimeForChangesL1s : BaseConfiguration + public class LeadTimeForChangesControllerTests : BaseConfiguration { - [TestCategory("L1Test")] + [TestCategory("UnitTest")] [TestMethod] public async Task AzLeadTimeControllerIntegrationTest() { //Arrange bool getSampleData = true; string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 7; int maxNumberOfItems = 20; bool useCache = true; @@ -47,13 +48,13 @@ public async Task AzLeadTimeControllerIntegrationTest() Assert.AreEqual(1f, model.AverageBuildHours); Assert.AreEqual(12f, model.AveragePullRequestHours); Assert.AreEqual(13f, model.LeadTimeForChangesMetric); - Assert.AreEqual("Elite", model.LeadTimeForChangesMetricDescription); + Assert.AreEqual("High", model.LeadTimeForChangesMetricDescription); Assert.AreEqual(numberOfDays, model.NumberOfDays); Assert.IsTrue(model.MaxNumberOfItems > 0); Assert.IsTrue(model.TotalItems > 0); } - [TestCategory("L1Test")] + [TestCategory("UnitTest")] [TestMethod] public async Task GHLeadTimeControllerIntegrationTest() { @@ -62,10 +63,10 @@ public async Task GHLeadTimeControllerIntegrationTest() //branch=master&workflowId=1162561&numberOfDays=30&maxNumberOfItems=20 //Arrange bool getSampleData = true; - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "devopsmetrics"; string branch = "main"; - string workflowName = "DevOpsMetrics.CICD"; + string workflowName = "CI/CD"; string workflowId = "1162561"; int numberOfDays = 30; int maxNumberOfItems = 20; @@ -93,24 +94,24 @@ public async Task GHLeadTimeControllerIntegrationTest() Assert.AreEqual(1f, model.AverageBuildHours); Assert.AreEqual(20.33f, model.AveragePullRequestHours); Assert.AreEqual(21.33f, model.LeadTimeForChangesMetric); - Assert.AreEqual("Elite", model.LeadTimeForChangesMetricDescription); + Assert.AreEqual("High", model.LeadTimeForChangesMetricDescription); Assert.AreEqual(numberOfDays, model.NumberOfDays); Assert.IsTrue(model.MaxNumberOfItems > 0); Assert.IsTrue(model.TotalItems > 0); } - [TestCategory("L1Test")] + [TestCategory("UnitTest")] [TestMethod] public async Task AzLeadTimeControllerAPIIntegrationTest() { //Arrange bool getSampleData = true; string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 7; int maxNumberOfItems = 20; bool useCache = true; @@ -137,23 +138,23 @@ public async Task AzLeadTimeControllerAPIIntegrationTest() Assert.AreEqual(1f, model.AverageBuildHours); Assert.AreEqual(12f, model.AveragePullRequestHours); Assert.AreEqual(13f, model.LeadTimeForChangesMetric); - Assert.AreEqual("Elite", model.LeadTimeForChangesMetricDescription); + Assert.AreEqual("High", model.LeadTimeForChangesMetricDescription); Assert.AreEqual(numberOfDays, model.NumberOfDays); Assert.IsTrue(model.MaxNumberOfItems > 0); Assert.IsTrue(model.TotalItems > 0); } - [TestCategory("L1Test")] + [TestCategory("UnitTest")] [TestMethod] public async Task GHLeadTimeControllerAPIIntegrationTest() { //Arrange bool getSampleData = true; - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "devopsmetrics"; string branch = "main"; - string workflowName = "DevOpsMetrics.CICD"; + string workflowName = "CI/CD"; string workflowId = "1162561"; int numberOfDays = 7; int maxNumberOfItems = 20; @@ -181,24 +182,23 @@ public async Task GHLeadTimeControllerAPIIntegrationTest() Assert.AreEqual(1f, model.AverageBuildHours); Assert.AreEqual(20.33f, model.AveragePullRequestHours); Assert.AreEqual(21.33f, model.LeadTimeForChangesMetric); - Assert.AreEqual("Elite", model.LeadTimeForChangesMetricDescription); + Assert.AreEqual("High", model.LeadTimeForChangesMetricDescription); Assert.AreEqual(numberOfDays, model.NumberOfDays); Assert.IsTrue(model.MaxNumberOfItems > 0); Assert.IsTrue(model.TotalItems > 0); } - [TestCategory("L1Test")] [TestMethod] public async Task AzLeadTimeControllerAPILiveIntegrationTest() { //Arrange bool getSampleData = false; string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; - string branch = "refs/heads/master"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; + string branch = "refs/heads/main"; + string buildName = "azure-pipelines.yml"; int numberOfDays = 30; int maxNumberOfItems = 20; bool useCache = true; @@ -241,16 +241,15 @@ public async Task AzLeadTimeControllerAPILiveIntegrationTest() } - [TestCategory("L1Test")] [TestMethod] public async Task GHLeadTimeControllerAPILiveIntegrationTest() { //Arrange bool getSampleData = false; - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "devopsmetrics"; string branch = "main"; - string workflowName = "DevOpsMetrics.CICD"; + string workflowName = "CI/CD"; string workflowId = "1162561"; int numberOfDays = 20; int maxNumberOfItems = 60; @@ -294,7 +293,6 @@ public async Task GHLeadTimeControllerAPILiveIntegrationTest() } - //[TestCategory("L1Test")] //[TestMethod] //public async Task GHFeatureFlagsLeadTimeControllerAPILiveIntegrationTest() //{ diff --git a/src/DevOpsMetrics.Tests/Service/MeanTimeToRestoreControllerTests.cs b/src/DevOpsMetrics.Tests/Service/MeanTimeToRestoreControllerTests.cs index 7def1c4f..a88384e1 100644 --- a/src/DevOpsMetrics.Tests/Service/MeanTimeToRestoreControllerTests.cs +++ b/src/DevOpsMetrics.Tests/Service/MeanTimeToRestoreControllerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using DevOpsMetrics.Core.Models.Common; using DevOpsMetrics.Service.Controllers; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -6,23 +7,24 @@ namespace DevOpsMetrics.Tests.Service { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [TestCategory("IntegrationTest")] [TestClass] - public class MeanTimeToRestoreL1s : BaseConfiguration + public class MeanTimeToRestoreControllerTests : BaseConfiguration { - [TestCategory("L1Test")] + [TestCategory("UnitTest")] [TestMethod] - public void AzureMTTRSampleControllerIntegrationTest() + public async Task AzureMTTRSampleControllerIntegrationTest() { //Arrange bool getSampleData = true; - string resourceGroupName = "SamLearnsAzureProd"; + string resourceGroupName = "DevOpsMetrics"; DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.AzureDevOps; int numberOfDays = 7; int maxNumberOfItems = 20; MeanTimeToRestoreController controller = new(base.Configuration); //Act - MeanTimeToRestoreModel model = controller.GetAzureMeanTimeToRestore(getSampleData, targetDevOpsPlatform, resourceGroupName, numberOfDays, maxNumberOfItems); + MeanTimeToRestoreModel model = await controller.GetAzureMeanTimeToRestore(getSampleData, targetDevOpsPlatform, resourceGroupName, numberOfDays, maxNumberOfItems); //Assert Assert.IsTrue(model != null); @@ -44,20 +46,19 @@ public void AzureMTTRSampleControllerIntegrationTest() Assert.IsTrue(model.TotalItems > 0); } - [TestCategory("L1Test")] [TestMethod] - public void AzureMTTRsAPIControllerIntegrationTest() + public async Task AzureMTTRsAPIControllerIntegrationTest() { //Arrange bool getSampleData = false; - string resourceGroupName = "SamLearnsAzureProd"; + string resourceGroupName = "DevOpsMetrics"; DevOpsPlatform targetDevOpsPlatform = DevOpsPlatform.AzureDevOps; int numberOfDays = 60; int maxNumberOfItems = 20; MeanTimeToRestoreController controller = new(base.Configuration); //Act - MeanTimeToRestoreModel model = controller.GetAzureMeanTimeToRestore(getSampleData, targetDevOpsPlatform, resourceGroupName, numberOfDays, maxNumberOfItems); + MeanTimeToRestoreModel model = await controller.GetAzureMeanTimeToRestore(getSampleData, targetDevOpsPlatform, resourceGroupName, numberOfDays, maxNumberOfItems); //Assert Assert.IsTrue(model != null); @@ -82,9 +83,8 @@ public void AzureMTTRsAPIControllerIntegrationTest() Assert.IsTrue(model.TotalItems >= 0); } - [TestCategory("L1Test")] [TestMethod] - public void AzureMTTRsAPINullIntegrationTest() + public async Task AzureMTTRsAPINullIntegrationTest() { //Arrange bool getSampleData = false; @@ -95,7 +95,7 @@ public void AzureMTTRsAPINullIntegrationTest() MeanTimeToRestoreController controller = new(base.Configuration); //Act - MeanTimeToRestoreModel model = controller.GetAzureMeanTimeToRestore(getSampleData, targetDevOpsPlatform, resourceGroupName, numberOfDays, maxNumberOfItems); + MeanTimeToRestoreModel model = await controller.GetAzureMeanTimeToRestore(getSampleData, targetDevOpsPlatform, resourceGroupName, numberOfDays, maxNumberOfItems); //Assert Assert.IsTrue(model != null); @@ -120,9 +120,8 @@ public void AzureMTTRsAPINullIntegrationTest() Assert.IsTrue(model.TotalItems >= 0); } - [TestCategory("L1Test")] [TestMethod] - public void AzureMTTRsAPIEmptyIntegrationTest() + public async Task AzureMTTRsAPIEmptyIntegrationTest() { //Arrange bool getSampleData = false; @@ -133,7 +132,7 @@ public void AzureMTTRsAPIEmptyIntegrationTest() MeanTimeToRestoreController controller = new(base.Configuration); //Act - MeanTimeToRestoreModel model = controller.GetAzureMeanTimeToRestore(getSampleData, targetDevOpsPlatform, resourceGroupName, numberOfDays, maxNumberOfItems); + MeanTimeToRestoreModel model = await controller.GetAzureMeanTimeToRestore(getSampleData, targetDevOpsPlatform, resourceGroupName, numberOfDays, maxNumberOfItems); //Assert Assert.IsTrue(model != null); diff --git a/src/DevOpsMetrics.Tests/Service/NightlyProcessTests.cs b/src/DevOpsMetrics.Tests/Service/NightlyProcessTests.cs new file mode 100644 index 00000000..87d5144d --- /dev/null +++ b/src/DevOpsMetrics.Tests/Service/NightlyProcessTests.cs @@ -0,0 +1,114 @@ +//using System.Collections.Generic; +//using System.Threading.Tasks; +//using DevOpsMetrics.Core.DataAccess.TableStorage; +//using DevOpsMetrics.Core.Models.Common; +//using DevOpsMetrics.Core.Models.GitHub; +//using DevOpsMetrics.Service; +//using DevOpsMetrics.Service.Controllers; +//using Microsoft.Extensions.Logging; +//using Microsoft.VisualStudio.TestTools.UnitTesting; + +//namespace DevOpsMetrics.Tests.Service +//{ +// [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +// [TestCategory("IntegrationTest")] +// [TestClass] +// public class NightlyProcessTests : BaseConfiguration +// { +// [TestMethod] +// public async Task GitHubProcessingTest() +// { +// //Arrange +// string clientId = Configuration["AppSettings:GitHubClientId"]; +// string clientSecret = Configuration["AppSettings:GitHubClientSecret"]; +// ProcessingResult result = null; +// AzureTableStorageDA da = new(); +// BuildsController buildsController = new(Configuration, da); +// PullRequestsController pullRequestsController = new(Configuration, da); +// SettingsController settingsController = new(Configuration, da); +// using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder +// .SetMinimumLevel(LogLevel.Trace) +// .AddConsole()); +// ILogger log = loggerFactory.CreateLogger(); +// TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + +// //Act +// List results = da.GetGitHubSettingsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubSettings, null); +// foreach (GitHubSettings item in results) +// { +// if (item.Repo == "AzurePipelinesToGitHubActionsConverter") +// { +// result = await Processing.ProcessGitHubItem(item, clientId, clientSecret, tableStorageConfig, +// 30, 20, +// buildsController, pullRequestsController, settingsController, +// log, 0); +// } +// } + +// //Assert +// Assert.IsNotNull(result); +// } + + + +// //[TestMethod] +// //public async Task GHUpdateAzurePipelinesToGitHubActionsConverterWebSettingDAIntegrationTest() +// //{ +// // //Arrange +// // TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); +// // string owner = "samsmithnz"; +// // string repo = "AzurePipelinesToGitHubActionsConverterWeb"; +// // string branch = "main"; +// // string workflowName = "Pipelines to Actions website CI/CD"; +// // string workflowId = "43084"; +// // string resourceGroupName = "PipelinesToActions"; +// // int itemOrder = 3; +// // bool showSetting = true; + +// // //Act +// // AzureTableStorageDA da = new(); +// // bool result = await da.UpdateGitHubSettingInStorage(tableStorageConfig, tableStorageConfig.TableGitHubSettings, +// // owner, repo, branch, workflowName, workflowId, resourceGroupName, itemOrder, showSetting); + +// // //Assert +// // Assert.IsTrue(result); +// //} + + +// //[TestMethod] +// //public async Task GHUpdateAllDevOpsMetricsIntegrationTest() +// //{ +// // //Arrange +// // string clientId = Configuration["AppSettings:GitHubClientId"]; +// // string clientSecret = Configuration["AppSettings:GitHubClientSecret"]; +// // int numberOfDays = 30; +// // int maxNumberOfItems = 20; +// // int totalResults = 0; +// // using var loggerFactory = LoggerFactory.Create(loggingBuilder => loggingBuilder +// // .SetMinimumLevel(LogLevel.Trace) +// // .AddConsole()); +// // ILogger log = loggerFactory.CreateLogger(); +// // TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(Configuration); + +// // //Act +// // AzureTableStorageDA azureTableStorageDA = new(); +// // BuildsController buildsController = new(Configuration, azureTableStorageDA); +// // PullRequestsController pullRequestsController = new(Configuration, azureTableStorageDA); +// // SettingsController settingsController = new(Configuration, azureTableStorageDA); +// // List ghSettings = settingsController.GetGitHubSettings(); + +// // foreach (GitHubSettings item in ghSettings) +// // { +// // ProcessingResult ghResult = await Processing.ProcessGitHubItem(item, +// // clientId, clientSecret, tableStorageConfig, +// // numberOfDays, maxNumberOfItems, +// // buildsController, pullRequestsController, settingsController, log, totalResults); +// // totalResults = ghResult.TotalResults; +// // } + +// // //Assert +// // Assert.IsTrue(totalResults >= 0); +// //} + +// } +//} diff --git a/src/DevOpsMetrics.Tests/Service/SecretsProcessingTests.cs b/src/DevOpsMetrics.Tests/Service/SecretsProcessingTests.cs index d1f27809..2de03226 100644 --- a/src/DevOpsMetrics.Tests/Service/SecretsProcessingTests.cs +++ b/src/DevOpsMetrics.Tests/Service/SecretsProcessingTests.cs @@ -4,7 +4,7 @@ namespace DevOpsMetrics.Tests.Service { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] public class SecretsProcessingTests { @@ -12,65 +12,65 @@ public class SecretsProcessingTests public void SecretWithDotTest() { //Arrange - string name = "SamLearnsAzure.CI"; + string name = "Secret.CI"; //Act string result = SecretsProcessing.CleanKey(name); //Assert - Assert.AreEqual("SamLearnsAzure-CI", result); + Assert.AreEqual("Secret-CI", result); } [TestMethod] public void SecretWithSpaceTest() { //Arrange - string name = "SamLearnsAzure CI"; + string name = "Secret CI"; //Act string result = SecretsProcessing.CleanKey(name); //Assert - Assert.AreEqual("SamLearnsAzure-CI", result); + Assert.AreEqual("Secret-CI", result); } [TestMethod] public void SecretWithColonTest() { //Arrange - string name = "SamLearnsAzure:CI"; + string name = "Secret:CI"; //Act string result = SecretsProcessing.CleanKey(name); //Assert - Assert.AreEqual("SamLearnsAzure-CI", result); + Assert.AreEqual("Secret-CI", result); } [TestMethod] public void SecretWithQuestionTest() { //Arrange - string name = "SamLearnsAzure?CI"; + string name = "Secret?CI"; //Act string result = SecretsProcessing.CleanKey(name); //Assert - Assert.AreEqual("SamLearnsAzure-CI", result); + Assert.AreEqual("Secret-CI", result); } [TestMethod] public void SecretThatIsValidTest() { //Arrange - string name = "SamLearnsAzure123abcCI"; + string name = "Secret123abcCI"; //Act string result = SecretsProcessing.CleanKey(name); //Assert - Assert.AreEqual("SamLearnsAzure123abcCI", result); + Assert.AreEqual("Secret123abcCI", result); } } diff --git a/src/DevOpsMetrics.Tests/Service/TableStorageControllerDATests.cs b/src/DevOpsMetrics.Tests/Service/TableStorageControllerDATests.cs index 5d7d9ae6..9fcb6298 100644 --- a/src/DevOpsMetrics.Tests/Service/TableStorageControllerDATests.cs +++ b/src/DevOpsMetrics.Tests/Service/TableStorageControllerDATests.cs @@ -7,12 +7,12 @@ using DevOpsMetrics.Service.Controllers; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; +using NSubstitute; namespace DevOpsMetrics.Tests.Service { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L0Test")] + [TestCategory("UnitTest")] [TestClass] public class TableStorageControllerDATests { @@ -51,10 +51,10 @@ public void TestStart() public async Task UpdateAzureDevOpsBuildsTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateAzureDevOpsBuildsInStorage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(GetSampleUpdateData())); - mockDA.Setup(repo => repo.GetAzureDevOpsSettingsFromStorage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(new List { new AzureDevOpsSettings() }); - BuildsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateAzureDevOpsBuildsInStorage(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(GetSampleUpdateData())); + mockDA.GetAzureDevOpsSettingsFromStorage(Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(new List { new AzureDevOpsSettings() })); + BuildsController controller = new(_configuration, mockDA); string organization = ""; string project = ""; string repository = ""; @@ -75,10 +75,10 @@ public async Task UpdateAzureDevOpsBuildsTest() public async Task UpdateGitHubActionRunsTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateGitHubActionRunsInStorage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(GetSampleUpdateData())); - mockDA.Setup(repo => repo.GetGitHubSettingsFromStorage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(new List { new GitHubSettings() }); - BuildsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateGitHubActionRunsInStorage(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(GetSampleUpdateData())); + mockDA.GetGitHubSettingsFromStorage(Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(new List { new GitHubSettings() })); + BuildsController controller = new(_configuration, mockDA); string owner = ""; string repo = ""; string branch = ""; @@ -98,10 +98,10 @@ public async Task UpdateGitHubActionRunsTest() public async Task UpdateAzureDevOpsPullRequestsTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateAzureDevOpsPullRequestsInStorage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(GetSampleUpdateData())); - mockDA.Setup(repo => repo.GetAzureDevOpsSettingsFromStorage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(new List { new AzureDevOpsSettings() }); - PullRequestsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateAzureDevOpsPullRequestsInStorage(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(GetSampleUpdateData())); + mockDA.GetAzureDevOpsSettingsFromStorage(Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(new List { new AzureDevOpsSettings() })); + PullRequestsController controller = new(_configuration, mockDA); string organization = ""; string project = ""; string repository = ""; @@ -119,10 +119,10 @@ public async Task UpdateAzureDevOpsPullRequestsTest() public async Task UpdateGitHubActionPullRequestsTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateGitHubActionPullRequestsInStorage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(GetSampleUpdateData())); - mockDA.Setup(repo => repo.GetGitHubSettingsFromStorage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(new List { new GitHubSettings() }); - PullRequestsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateGitHubActionPullRequestsInStorage(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(GetSampleUpdateData())); + mockDA.GetGitHubSettingsFromStorage(Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(new List { new GitHubSettings() })); + PullRequestsController controller = new(_configuration, mockDA); string owner = ""; string repo = ""; string branch = ""; @@ -140,10 +140,10 @@ public async Task UpdateGitHubActionPullRequestsTest() public async Task UpdateAzureDevOpsPullRequestCommitsTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateAzureDevOpsPullRequestCommitsInStorage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(GetSampleUpdateData())); - mockDA.Setup(repo => repo.GetAzureDevOpsSettingsFromStorage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(new List { new AzureDevOpsSettings() }); - PullRequestsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateAzureDevOpsPullRequestCommitsInStorage(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(GetSampleUpdateData())); + mockDA.GetAzureDevOpsSettingsFromStorage(Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(new List { new AzureDevOpsSettings() })); + PullRequestsController controller = new(_configuration, mockDA); string organization = ""; string project = ""; string repository = ""; @@ -162,10 +162,10 @@ public async Task UpdateAzureDevOpsPullRequestCommitsTest() public async Task UpdateGitHubActionPullRequestCommitsTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateGitHubActionPullRequestCommitsInStorage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(GetSampleUpdateData())); - mockDA.Setup(repo => repo.GetGitHubSettingsFromStorage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(new List { new GitHubSettings() }); - PullRequestsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateGitHubActionPullRequestCommitsInStorage(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(GetSampleUpdateData())); + mockDA.GetGitHubSettingsFromStorage(Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(new List { new GitHubSettings() })); + PullRequestsController controller = new(_configuration, mockDA); string owner = ""; string repo = ""; string pull_number = ""; @@ -179,30 +179,30 @@ public async Task UpdateGitHubActionPullRequestCommitsTest() [TestMethod] - public void GetAzureDevOpsSettingsTest() + public async Task GetAzureDevOpsSettingsTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.GetAzureDevOpsSettingsFromStorage(It.IsAny(), It.IsAny(), null)).Returns(GetSampleAzureDevOpsSettingsData()); - SettingsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.GetAzureDevOpsSettingsFromStorage(Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(GetSampleAzureDevOpsSettingsData())); + SettingsController controller = new(_configuration, mockDA); //Act - List result = controller.GetAzureDevOpsSettings(); + List result = await controller.GetAzureDevOpsSettings(); //Assert Assert.IsTrue(result != null); } [TestMethod] - public void GetGitHubSettingsTest() + public async Task GetGitHubSettingsTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.GetGitHubSettingsFromStorage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(GetSampleGitHubSettingsData()); - SettingsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.GetGitHubSettingsFromStorage(Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(GetSampleGitHubSettingsData())); + SettingsController controller = new(_configuration, mockDA); //Act - List result = controller.GetGitHubSettings(); + List result = await controller.GetGitHubSettings(); //Assert Assert.IsTrue(result != null); @@ -213,9 +213,9 @@ public void GetGitHubSettingsTest() public async Task UpdateAzureDevOpsSettingTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateAzureDevOpsSettingInStorage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); - SettingsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateAzureDevOpsSettingInStorage(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + SettingsController controller = new(_configuration, mockDA); string patToken = ""; string organization = ""; string project = ""; @@ -231,16 +231,16 @@ public async Task UpdateAzureDevOpsSettingTest() bool result = await controller.UpdateAzureDevOpsSetting(patToken, organization, project, repository, branch, buildName, buildId, resourceGroup, itemOrder, showSetting); //Assert - Assert.AreEqual(true, result); + Assert.AreEqual(false, result); } [TestMethod] public async Task UpdateGitHubSettingTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateGitHubSettingInStorage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); - SettingsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateGitHubSettingInStorage(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + SettingsController controller = new(_configuration, mockDA); string clientId = ""; string clientSecret = ""; string owner = ""; @@ -256,38 +256,38 @@ public async Task UpdateGitHubSettingTest() bool result = await controller.UpdateGitHubSetting(clientId, clientSecret, owner, repo, branch, workflowName, workflowId, resourceGroup, itemOrder, showSetting); //Assert - Assert.AreEqual(true, result); + Assert.AreEqual(false, result); } [TestMethod] public async Task UpdateDevOpsMonitoringEventTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateDevOpsMonitoringEventInStorage(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); - SettingsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateDevOpsMonitoringEventInStorage(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + SettingsController controller = new(_configuration, mockDA); MonitoringEvent newEvent = new(); //Act bool result = await controller.UpdateDevOpsMonitoringEvent(newEvent); //Assert - Assert.AreEqual(true, result); + Assert.AreEqual(false, result); } [TestMethod] - public void GetAzureDevOpsLogTest() + public async Task GetAzureDevOpsLogTest() { //Arrange - Mock mockDA = new(); - mockDA.Setup(repo => repo.GetProjectLogsFromStorage(It.IsAny(), It.IsAny())).Returns(new List { new ProjectLog() }); - SettingsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.GetProjectLogsFromStorage(Arg.Any(), Arg.Any()).Returns(Task.FromResult(new List { new ProjectLog() })); + SettingsController controller = new(_configuration, mockDA); string organization = "TestOrg"; string project = "TestProject"; string repository = "TestRepo"; //Act - List results = controller.GetAzureDevOpsProjectLog(organization, project, repository); + List results = await controller.GetAzureDevOpsProjectLog(organization, project, repository); //Assert Assert.IsTrue(results != null); @@ -299,9 +299,9 @@ public async Task UpdateAzureDevOpsLogTest() { //Arrange //Mock mockConfig = new Mock(); - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateProjectLogInStorage(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); - SettingsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateProjectLogInStorage(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + SettingsController controller = new(_configuration, mockDA); string organization = "TestOrg"; string project = "TestProject"; string repository = "TestRepo"; @@ -317,22 +317,22 @@ public async Task UpdateAzureDevOpsLogTest() buildsUpdated, prsUpdated, buildUrl, prUrl, exceptionMessage, exceptionStackTrace); //Assert - Assert.AreEqual(true, result); + Assert.AreEqual(false, result); } [TestMethod] - public void GetGitHubLogTest() + public async Task GetGitHubLogTest() { //Arrange //Mock mockConfig = new Mock(); - Mock mockDA = new(); - mockDA.Setup(repo => repo.GetProjectLogsFromStorage(It.IsAny(), It.IsAny())).Returns(new List { new ProjectLog() }); - SettingsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.GetProjectLogsFromStorage(Arg.Any(), Arg.Any()).Returns(Task.FromResult(new List { new ProjectLog() })); + SettingsController controller = new(_configuration, mockDA); string owner = "TestOrg"; string repo = "TestRepo"; //Act - List results = controller.GetGitHubProjectLog(owner, repo); + List results = await controller.GetGitHubProjectLog(owner, repo); //Assert Assert.IsTrue(results != null); @@ -344,9 +344,9 @@ public async Task UpdateGitHubLogTest() { //Arrange //Mock mockConfig = new Mock(); - Mock mockDA = new(); - mockDA.Setup(repo => repo.UpdateProjectLogInStorage(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); - SettingsController controller = new(_configuration, mockDA.Object); + IAzureTableStorageDA mockDA = Substitute.For(); + mockDA.UpdateProjectLogInStorage(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + SettingsController controller = new(_configuration, mockDA); string owner = "TestOrg"; string repo = "TestRepo"; int buildsUpdated = 0; @@ -361,7 +361,7 @@ public async Task UpdateGitHubLogTest() buildsUpdated, prsUpdated, buildUrl, prUrl, exceptionMessage, exceptionStackTrace); //Assert - Assert.AreEqual(true, result); + Assert.AreEqual(false, result); } private static int GetSampleUpdateData() diff --git a/src/DevOpsMetrics.Tests/Service/TableStorageDATests.cs b/src/DevOpsMetrics.Tests/Service/TableStorageDATests.cs index 038dad81..52a6237f 100644 --- a/src/DevOpsMetrics.Tests/Service/TableStorageDATests.cs +++ b/src/DevOpsMetrics.Tests/Service/TableStorageDATests.cs @@ -11,22 +11,22 @@ namespace DevOpsMetrics.Tests.Service { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [TestCategory("L1Test")] + [TestCategory("IntegrationTest")] [TestClass] public class TableStorageDATests : BaseConfiguration { [TestMethod] - public void AzGetBuildsDAIntegrationTest() + public async Task AzGetBuildsDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string buildName = "SamLearnsAzure.CI"; + string project = "AzDoDevOpsMetrics"; + string buildName = "azure-pipelines.yml"; //Act AzureTableStorageDA da = new(); - JArray list = da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsBuilds, PartitionKeys.CreateBuildWorkflowPartitionKey(organization, project, buildName)); + JArray list = await da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsBuilds, PartitionKeys.CreateBuildWorkflowPartitionKey(organization, project, buildName)); //Assert Assert.IsTrue(list.Count >= 0); @@ -39,9 +39,9 @@ public async Task AzUpdateBuildsDAIntegrationTest() string patToken = base.Configuration["AppSettings:AzureDevOpsPatToken"]; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); string organization = "samsmithnz"; - string project = "AzDevOpsMetricsTest"; + string project = "AzDoDevOpsMetrics"; string branch = "refs/heads/main"; - string buildName = "AzDevOpsMetricsTest"; + string buildName = "azure-pipelines.yml"; string buildId = "217"; int numberOfDays = 30; int maxNumberOfItems = 20; @@ -55,16 +55,16 @@ public async Task AzUpdateBuildsDAIntegrationTest() } [TestMethod] - public void AzGetPRsDAIntegrationTest() + public async Task AzGetPRsDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); string organization = "samsmithnz"; - string project = "SamLearnsAzure"; + string project = "AzDoDevOpsMetrics"; //Act AzureTableStorageDA da = new(); - JArray list = da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsBuilds, PartitionKeys.CreateAzureDevOpsPRPartitionKey(organization, project)); + JArray list = await da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsBuilds, PartitionKeys.CreateAzureDevOpsPRPartitionKey(organization, project)); //Assert Assert.IsTrue(list.Count >= 0); @@ -77,8 +77,8 @@ public async Task AzUpdatePRsDAIntegrationTest() string patToken = base.Configuration["AppSettings:AzureDevOpsPatToken"]; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); string organization = "samsmithnz"; - string project = "AzDevOpsMetricsTest"; - string repository = "AzDevOpsMetricsTest"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; int numberOfDays = 30; int maxNumberOfItems = 20; @@ -92,22 +92,22 @@ public async Task AzUpdatePRsDAIntegrationTest() } [TestMethod] - public void AzGetPRCommitsDAIntegrationTest() + public async Task AzGetPRCommitsDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); string organization = "samsmithnz"; - string project = "SamLearnsAzure"; + string project = "AzDoDevOpsMetrics"; //Act AzureTableStorageDA da = new(); - JArray prList = da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsPRs, PartitionKeys.CreateAzureDevOpsPRPartitionKey(organization, project)); + JArray prList = await da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsPRs, PartitionKeys.CreateAzureDevOpsPRPartitionKey(organization, project)); int itemsAdded = 0; foreach (JToken item in prList) { AzureDevOpsPR pullRequest = JsonConvert.DeserializeObject(item.ToString()); string pullRequestId = pullRequest.PullRequestId; - JArray list = da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsPRCommits, PartitionKeys.CreateAzureDevOpsPRCommitPartitionKey(organization, project, pullRequestId)); + JArray list = await da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableAzureDevOpsPRCommits, PartitionKeys.CreateAzureDevOpsPRCommitPartitionKey(organization, project, pullRequestId)); if (list.Count > 0) { itemsAdded = list.Count; @@ -120,17 +120,17 @@ public void AzGetPRCommitsDAIntegrationTest() } [TestMethod] - public void AzGetSamLearnsAzureLogsDAIntegrationTest() + public async Task AzGetAzDoDevOpsMetricsLogsDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); string organization = "samsmithnz"; - string project = "SamLearnsAzure"; - string repository = "SamLearnsAzure"; + string project = "AzDoDevOpsMetrics"; + string repository = "AzDoDevOpsMetrics"; //Act AzureTableStorageDA da = new(); - List logs = da.GetProjectLogsFromStorage(tableStorageConfig, PartitionKeys.CreateAzureDevOpsSettingsPartitionKey(organization, project, repository)); + List logs = await da.GetProjectLogsFromStorage(tableStorageConfig, PartitionKeys.CreateAzureDevOpsSettingsPartitionKey(organization, project, repository)); //Assert Assert.IsTrue(logs != null); @@ -138,17 +138,17 @@ public void AzGetSamLearnsAzureLogsDAIntegrationTest() } [TestMethod] - public void GHGetBuildsDAIntegrationTest() + public async Task GHGetBuildsDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "DevOpsMetrics"; - string workflowName = "DevOpsMetrics CI/CD"; + string workflowName = "CI/CD"; //Act AzureTableStorageDA da = new(); - JArray list = da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubRuns, PartitionKeys.CreateBuildWorkflowPartitionKey(owner, repo, workflowName)); + JArray list = await da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubRuns, PartitionKeys.CreateBuildWorkflowPartitionKey(owner, repo, workflowName)); //Assert Assert.IsTrue(list.Count >= 0); @@ -161,10 +161,10 @@ public async Task GHUpdateDevOpsMetricsBuildsDAIntegrationTest() string clientId = base.Configuration["AppSettings:GitHubClientId"]; string clientSecret = base.Configuration["AppSettings:GitHubClientSecret"]; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "DevOpsMetrics"; string branch = "main"; - string workflowName = "DevOpsMetrics CI/CD"; + string workflowName = "CI/CD"; string workflowId = "1162561"; int numberOfDays = 30; int maxNumberOfItems = 20; @@ -203,16 +203,16 @@ public async Task GHUpdateSamsFeatureFlagsBuildsDAIntegrationTest() } [TestMethod] - public void GHGetPRsDAIntegrationTest() + public async Task GHGetPRsDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "DevOpsMetrics"; //Act AzureTableStorageDA da = new(); - JArray list = da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubPRs, PartitionKeys.CreateGitHubPRPartitionKey(owner, repo)); + JArray list = await da.GetTableStorageItemsFromStorage(tableStorageConfig, tableStorageConfig.TableGitHubPRs, PartitionKeys.CreateGitHubPRPartitionKey(owner, repo)); //Assert Assert.IsTrue(list.Count >= 0); @@ -225,7 +225,7 @@ public async Task GHUpdateBuildsDAIntegrationTest() string clientId = base.Configuration["AppSettings:GitHubClientId"]; string clientSecret = base.Configuration["AppSettings:GitHubClientSecret"]; TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); - string owner = "samsmithnz"; + string owner = "DeveloperMetrics"; string repo = "DevOpsMetrics"; string branch = "main"; string workflowName = "CI/CD"; @@ -263,7 +263,7 @@ public async Task GHUpdatePRsDAIntegrationTest() } [TestMethod] - public void GHGetSamsFeatureFlagsLogsDAIntegrationTest() + public async Task GHGetSamsFeatureFlagsLogsDAIntegrationTest() { //Arrange TableStorageConfiguration tableStorageConfig = Common.GenerateTableStorageConfiguration(base.Configuration); @@ -272,7 +272,7 @@ public void GHGetSamsFeatureFlagsLogsDAIntegrationTest() //Act AzureTableStorageDA da = new(); - List logs = da.GetProjectLogsFromStorage(tableStorageConfig, PartitionKeys.CreateGitHubSettingsPartitionKey(owner, repo)); + List logs = await da.GetProjectLogsFromStorage(tableStorageConfig, PartitionKeys.CreateGitHubSettingsPartitionKey(owner, repo)); //Assert Assert.IsTrue(logs != null); diff --git a/src/DevOpsMetrics.Tests/appsettings.json b/src/DevOpsMetrics.Tests/appsettings.json index 59096f1a..b734d7e0 100644 --- a/src/DevOpsMetrics.Tests/appsettings.json +++ b/src/DevOpsMetrics.Tests/appsettings.json @@ -19,7 +19,8 @@ "WebServiceURL": "https://devops-prod-eu-service.azurewebsites.net/", "KeyVaultURL": "https://devops-prod-eu-vault.vault.azure.net/", "KeyVaultClientId": "", - "KeyVaultClientSecret": "" + "KeyVaultClientSecret": "", + "TenantId": "40244aae-8aee-4af5-b221-b480db24698b" }, "ApplicationInsights": { "InstrumentationKey": "dc3fb590-3612-4f09-8c95-e9378cba8654" diff --git a/src/DevOpsMetrics.Web/Controllers/HomeController.cs b/src/DevOpsMetrics.Web/Controllers/HomeController.cs index d6bd438c..a1b22869 100644 --- a/src/DevOpsMetrics.Web/Controllers/HomeController.cs +++ b/src/DevOpsMetrics.Web/Controllers/HomeController.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -23,7 +24,7 @@ public HomeController(IConfiguration configuration) Configuration = configuration; } - public async Task Index() + public async Task Index(string projectId = null, string log = null) { //Get a list of settings ServiceApiClient serviceApiClient = new(Configuration); @@ -31,14 +32,69 @@ public async Task Index() List githubSettings = await serviceApiClient.GetGitHubSettings(); //Return the resultant list - (List, List) result = (azureDevOpsSettings, githubSettings); + IndexViewModel result = new() + { + AzureDevOpsSettings = azureDevOpsSettings, + GitHubSettings = githubSettings, + ProjectId = projectId, + Log = log + }; return View(result); } + [HttpGet("RefreshMetric")] + public async Task RefreshMetric(string projectId) + { + ServiceApiClient serviceApiClient = new(Configuration); + List azureDevOpsSettings = await serviceApiClient.GetAzureDevOpsSettings(); + List githubSettings = await serviceApiClient.GetGitHubSettings(); + bool foundRecord = false; + string log = ""; + foreach (AzureDevOpsSettings item in azureDevOpsSettings) + { + if (item.RowKey == projectId) + { + foundRecord = true; + DateTime startTime = DateTime.Now; + await serviceApiClient.UpdateDORASummaryItem(item.Organization, + item.Project, item.Repository, item.Branch, + item.BuildName, item.BuildId, + item.ProductionResourceGroup, + 30, 20, false); + DateTime endTime = DateTime.Now; + projectId = item.RowKey; + log = $"Successfully refreshed {item.Organization} {item.Project} {item.Repository} in {(endTime - startTime).TotalSeconds} seconds"; + break; + } + } + if (foundRecord == false) + { + foreach (GitHubSettings item in githubSettings) + { + if (item.RowKey == projectId) + { + foundRecord = true; + DateTime startTime = DateTime.Now; + await serviceApiClient.UpdateDORASummaryItem(item.Owner, + "", item.Repo, item.Branch, + item.WorkflowName, item.WorkflowId, + item.ProductionResourceGroup, + 30, 20, true); + DateTime endTime = DateTime.Now; + projectId = item.RowKey; + log = $"Successfully refreshed {item.Owner} {item.Repo} in {(endTime - startTime).TotalSeconds} seconds"; + break; + } + } + } + + return RedirectToAction("Index", "Home", new { projectId = projectId, log = log }); + } + [HttpPost] public IActionResult ProjectUpdate(string RowKey, int NumberOfDaysSelected = 30) { - return RedirectToAction("Project", "Home", new { rowKey = RowKey, numberOfDays = NumberOfDaysSelected }); + return RedirectToAction("Project", "Home", new { projectId = RowKey, numberOfDays = NumberOfDaysSelected }); } [HttpGet] @@ -49,7 +105,7 @@ public async Task Project(string projectId, int numberOfDays = 30 bool useCache = true; string clientId = Configuration["AppSettings:GitHubClientId"]; string clientSecret = Configuration["AppSettings:GitHubClientSecret"]; - ProjectViewModel model = new(); + ProjectViewModel model = null; //Find the right project to load ServiceApiClient serviceApiClient = new(Configuration); @@ -102,43 +158,49 @@ public async Task Project(string projectId, int numberOfDays = 30 NumberOfDays = new SelectList(numberOfDaysList, "NumberOfDays", "NumberOfDays"), NumberOfDaysSelected = numberOfDays }; + break; } } + //Get GitHub project details GitHubSettings githubSetting; - foreach (GitHubSettings item in githubSettings) + if (model == null) { - if (item.RowKey == projectId) + foreach (GitHubSettings item in githubSettings) { - githubSetting = item; - - DeploymentFrequencyModel deploymentFrequencyModel = await serviceApiClient.GetGitHubDeploymentFrequency(getSampleData, clientId, clientSecret, - item.Owner, item.Repo, item.Branch, item.WorkflowName, item.WorkflowId, - numberOfDays, maxNumberOfItems, useCache); - LeadTimeForChangesModel leadTimeForChangesModel = await serviceApiClient.GetGitHubLeadTimeForChanges(getSampleData, clientId, clientSecret, - item.Owner, item.Repo, item.Branch, item.WorkflowName, item.WorkflowId, - numberOfDays, maxNumberOfItems, useCache); - MeanTimeToRestoreModel meanTimeToRestoreModel = await serviceApiClient.GetAzureMeanTimeToRestore(getSampleData, - DevOpsPlatform.GitHub, item.ProductionResourceGroup, numberOfDays, maxNumberOfItems); - ChangeFailureRateModel changeFailureRateModel = await serviceApiClient.GetChangeFailureRate(getSampleData, - DevOpsPlatform.GitHub, item.Owner, item.Repo, item.Branch, item.WorkflowName, - numberOfDays, maxNumberOfItems); - deploymentFrequencyModel.IsProjectView = true; - leadTimeForChangesModel.IsProjectView = true; - meanTimeToRestoreModel.IsProjectView = true; - changeFailureRateModel.IsProjectView = true; - model = new ProjectViewModel + if (item.RowKey == projectId) { - RowKey = item.RowKey, - ProjectName = item.Repo, - TargetDevOpsPlatform = DevOpsPlatform.GitHub, - DeploymentFrequency = deploymentFrequencyModel, - LeadTimeForChanges = leadTimeForChangesModel, - MeanTimeToRestore = meanTimeToRestoreModel, - ChangeFailureRate = changeFailureRateModel, - NumberOfDays = new SelectList(numberOfDaysList, "NumberOfDays", "NumberOfDays"), - NumberOfDaysSelected = numberOfDays - }; + githubSetting = item; + + DeploymentFrequencyModel deploymentFrequencyModel = await serviceApiClient.GetGitHubDeploymentFrequency(getSampleData, clientId, clientSecret, + item.Owner, item.Repo, item.Branch, item.WorkflowName, item.WorkflowId, + numberOfDays, maxNumberOfItems, useCache); + LeadTimeForChangesModel leadTimeForChangesModel = await serviceApiClient.GetGitHubLeadTimeForChanges(getSampleData, clientId, clientSecret, + item.Owner, item.Repo, item.Branch, item.WorkflowName, item.WorkflowId, + numberOfDays, maxNumberOfItems, useCache); + MeanTimeToRestoreModel meanTimeToRestoreModel = await serviceApiClient.GetAzureMeanTimeToRestore(getSampleData, + DevOpsPlatform.GitHub, item.ProductionResourceGroup, numberOfDays, maxNumberOfItems); + ChangeFailureRateModel changeFailureRateModel = await serviceApiClient.GetChangeFailureRate(getSampleData, + DevOpsPlatform.GitHub, item.Owner, item.Repo, item.Branch, item.WorkflowName, + numberOfDays, maxNumberOfItems); + deploymentFrequencyModel.IsProjectView = true; + leadTimeForChangesModel.IsProjectView = true; + meanTimeToRestoreModel.IsProjectView = true; + changeFailureRateModel.IsProjectView = true; + model = new ProjectViewModel + { + RowKey = item.RowKey, + ProjectName = item.Repo, + TargetDevOpsPlatform = DevOpsPlatform.GitHub, + DeploymentFrequency = deploymentFrequencyModel, + LeadTimeForChanges = leadTimeForChangesModel, + MeanTimeToRestore = meanTimeToRestoreModel, + ChangeFailureRate = changeFailureRateModel, + NumberOfDays = new SelectList(numberOfDaysList, "NumberOfDays", "NumberOfDays"), + NumberOfDaysSelected = numberOfDays + }; + break; + } } } @@ -427,18 +489,23 @@ public async Task UpdateChangeFailureRate(string ProjectIdSelecte project_repo = item.Project; repository = item.Repository; buildName_workflowName = item.BuildName; + break; } } //Update each GitHub setting - foreach (GitHubSettings item in githubSettings) + if (targetDevOpsPlatform == DevOpsPlatform.UnknownDevOpsPlatform) { - if (item.RowKey == ProjectIdSelected) + foreach (GitHubSettings item in githubSettings) { - targetDevOpsPlatform = DevOpsPlatform.GitHub; - organization_owner = item.Owner; - project_repo = item.Repo; - repository = ""; - buildName_workflowName = item.WorkflowName; + if (item.RowKey == ProjectIdSelected) + { + targetDevOpsPlatform = DevOpsPlatform.GitHub; + organization_owner = item.Owner; + project_repo = item.Repo; + repository = ""; + buildName_workflowName = item.WorkflowName; + break; + } } } @@ -451,11 +518,11 @@ public async Task UpdateChangeFailureRate(string ProjectIdSelecte //Redirect to the correct project page to see the changes if (targetDevOpsPlatform == DevOpsPlatform.AzureDevOps) { - return RedirectToAction("Project", "Home", new { rowKey = organization_owner + "_" + project_repo + "_" + repository + "_" + buildName_workflowName }); + return RedirectToAction("Project", "Home", new { projectId = organization_owner + "_" + project_repo + "_" + repository });// + "_" + buildName_workflowName }); } else if (targetDevOpsPlatform == DevOpsPlatform.GitHub) { - return RedirectToAction("Project", "Home", new { rowKey = organization_owner + "_" + project_repo + "_" + buildName_workflowName }); + return RedirectToAction("Project", "Home", new { projectId = organization_owner + "_" + project_repo });//+ "_" + buildName_workflowName }); } else { @@ -481,19 +548,13 @@ public async Task Logs(string projectId = null) }; foreach (AzureDevOpsSettings item in azureDevOpsSettings) { - //if (item.ShowSetting == true) - //{ string partitionKey = PartitionKeys.CreateAzureDevOpsSettingsPartitionKey(item.Organization, item.Project, item.Repository); projects.Add(new KeyValuePair(partitionKey, item.Project)); - //} } foreach (GitHubSettings item in githubSettings) { - //if (item.ShowSetting == true) - //{ string partitionKey = PartitionKeys.CreateGitHubSettingsPartitionKey(item.Owner, item.Repo); projects.Add(new KeyValuePair(partitionKey, item.Repo)); - //} } List logs = new(); diff --git a/src/DevOpsMetrics.Web/DevOpsMetrics.Web.csproj b/src/DevOpsMetrics.Web/DevOpsMetrics.Web.csproj index 0800bf16..d99b998a 100644 --- a/src/DevOpsMetrics.Web/DevOpsMetrics.Web.csproj +++ b/src/DevOpsMetrics.Web/DevOpsMetrics.Web.csproj @@ -1,13 +1,13 @@  - net7.0 + net10.0 38d23a12-3b6c-4c42-b180-9c27ef53abf9 /subscriptions/65b8d298-e5bd-4735-912e-8b9c510c4e00/resourceGroups/devopsmetrics/providers/microsoft.insights/components/devops-prod-eu-appinsights - + diff --git a/src/DevOpsMetrics.Web/Models/IndexViewModel.cs b/src/DevOpsMetrics.Web/Models/IndexViewModel.cs new file mode 100644 index 00000000..a002fdab --- /dev/null +++ b/src/DevOpsMetrics.Web/Models/IndexViewModel.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using DevOpsMetrics.Core.Models.AzureDevOps; +using DevOpsMetrics.Core.Models.GitHub; + +namespace DevOpsMetrics.Web.Models +{ + public class IndexViewModel + { + public List AzureDevOpsSettings + { + get; set; + } + public List GitHubSettings + { + get; set; + } + public string ProjectId + { + get; set; + } + public string Log + { + get; set; + } + } +} diff --git a/src/DevOpsMetrics.Web/Services/ServiceAPIClient.cs b/src/DevOpsMetrics.Web/Services/ServiceAPIClient.cs index 37de356e..0874aab5 100644 --- a/src/DevOpsMetrics.Web/Services/ServiceAPIClient.cs +++ b/src/DevOpsMetrics.Web/Services/ServiceAPIClient.cs @@ -196,6 +196,16 @@ public async Task> GetGitHubProjectLogs(string owner, string re return await GetResponse>(Client, url); } + public async Task UpdateDORASummaryItem( + string owner, string project, string repository, + string branch, string workflowName, string workflowId, + string resourceGroup, int numberOfDays, int maxNumberOfItems, + bool isGitHub = true) + { + string url = $"/api/DORASummary/UpdateDORASummaryItem?owner={owner}&project={project}&repo={repository}&branch={branch}&workflowName={workflowName}&workflowId={workflowId}&resourceGroup={resourceGroup}&numberOfDays={numberOfDays}&maxNumberOfItems={maxNumberOfItems}&log=&useCache=true&isGitHub={isGitHub}"; + return await GetResponse(Client, url); + } + private static async Task GetResponse(HttpClient client, string url) { T obj = default; @@ -205,10 +215,10 @@ private static async Task GetResponse(HttpClient client, string url) Console.WriteLine("Running url: " + client.BaseAddress.ToString() + url); using (HttpResponseMessage response = await client.GetAsync(url)) { - if (response.IsSuccessStatusCode == true) + if (response.IsSuccessStatusCode) { string responseBody = await response.Content.ReadAsStringAsync(); - if (string.IsNullOrEmpty(responseBody) == false) + if (!string.IsNullOrEmpty(responseBody)) { obj = JsonConvert.DeserializeObject(responseBody); } diff --git a/src/DevOpsMetrics.Web/Startup.cs b/src/DevOpsMetrics.Web/Startup.cs index 5b641e68..edffcc33 100644 --- a/src/DevOpsMetrics.Web/Startup.cs +++ b/src/DevOpsMetrics.Web/Startup.cs @@ -22,7 +22,7 @@ public IConfiguration Configuration public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); - services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_CONNECTIONSTRING"]); + services.AddApplicationInsightsTelemetry(Configuration); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/DevOpsMetrics.Web/Views/Home/AddAzureDevOpsSetting.cshtml b/src/DevOpsMetrics.Web/Views/Home/AddAzureDevOpsSetting.cshtml index 0e3f6123..e6afb0af 100644 --- a/src/DevOpsMetrics.Web/Views/Home/AddAzureDevOpsSetting.cshtml +++ b/src/DevOpsMetrics.Web/Views/Home/AddAzureDevOpsSetting.cshtml @@ -9,7 +9,7 @@ Note: This is a POC. Please don't put your secrets/PAT tokens in here. While they are safely stored in an Azure Key Vault - I can still see them, and I don't want to have access to your secrets. Besides - you don't know me. Be smart. Only upload secrets to a website with a verified publisher or one you control.
-

  Azure DevOps

+

Azure DevOps icon  Azure DevOps

Add new Azure DevOps setting
diff --git a/src/DevOpsMetrics.Web/Views/Home/AddGitHubSetting.cshtml b/src/DevOpsMetrics.Web/Views/Home/AddGitHubSetting.cshtml index 19067f26..e08b2c7b 100644 --- a/src/DevOpsMetrics.Web/Views/Home/AddGitHubSetting.cshtml +++ b/src/DevOpsMetrics.Web/Views/Home/AddGitHubSetting.cshtml @@ -9,7 +9,7 @@ Note: This is a POC. Please don't put your secrets/PAT tokens in here. While they are safely stored in an Azure Key Vault - I can still see them, and I don't want to have access to your secrets. Besides - you don't know me. Be smart. Only upload secrets to a website with a verified publisher or one you control.
-

  GitHub

+

GitHub icon  GitHub

Add new GitHub setting
diff --git a/src/DevOpsMetrics.Web/Views/Home/Index.cshtml b/src/DevOpsMetrics.Web/Views/Home/Index.cshtml index 25bedbd4..04b1c11b 100644 --- a/src/DevOpsMetrics.Web/Views/Home/Index.cshtml +++ b/src/DevOpsMetrics.Web/Views/Home/Index.cshtml @@ -1,7 +1,7 @@ @using DevOpsMetrics.Core.Models.AzureDevOps; @using DevOpsMetrics.Core.Models.Common; @using DevOpsMetrics.Core.Models.GitHub; -@model (List, List) +@model IndexViewModel @{ ViewData["Title"] = "Home Page"; } @@ -22,18 +22,32 @@
View all metrics by project
    - @foreach (AzureDevOpsSettings item in Model.Item1) + @foreach (AzureDevOpsSettings item in Model.AzureDevOpsSettings) { - if (item.ShowSetting == true) + if (item.ShowSetting) { -
  •   @item.Project
  • +
  • + Azure DevOps icon  @item.Project | Refresh metric + @if (item.RowKey == Model.ProjectId) + { +
    + @Model.Log + } +
  • } } - @foreach (GitHubSettings item in Model.Item2) + @foreach (GitHubSettings item in Model.GitHubSettings) { - if (item.ShowSetting == true) + if (item.ShowSetting) { -
  •   @item.Repo
  • +
  • + GitHub icon  @item.Repo | Refresh metric + @if (item.RowKey == Model.ProjectId) + { +
    + @Model.Log + } +
  • } }
diff --git a/src/DevOpsMetrics.Web/Views/Home/Logs.cshtml b/src/DevOpsMetrics.Web/Views/Home/Logs.cshtml index bf97937b..66686754 100644 --- a/src/DevOpsMetrics.Web/Views/Home/Logs.cshtml +++ b/src/DevOpsMetrics.Web/Views/Home/Logs.cshtml @@ -46,7 +46,7 @@ {
- + Partition key icon @item.RowKey diff --git a/src/DevOpsMetrics.Web/Views/Home/Project.cshtml b/src/DevOpsMetrics.Web/Views/Home/Project.cshtml index 9c8349a7..26688efd 100644 --- a/src/DevOpsMetrics.Web/Views/Home/Project.cshtml +++ b/src/DevOpsMetrics.Web/Views/Home/Project.cshtml @@ -23,7 +23,7 @@ widgetName = "Unknown"; } -

  @widgetName - @Model.ProjectName high performing DevOps metrics

+

Widget image name  @widgetName - @Model.ProjectName high performing DevOps metrics


diff --git a/src/DevOpsMetrics.Web/Views/Home/Settings.cshtml b/src/DevOpsMetrics.Web/Views/Home/Settings.cshtml index 39a81fd4..96de2dd1 100644 --- a/src/DevOpsMetrics.Web/Views/Home/Settings.cshtml +++ b/src/DevOpsMetrics.Web/Views/Home/Settings.cshtml @@ -12,7 +12,7 @@

View settings (secrets hidden)


-

  Azure DevOps

+

Azure DevOps icon  Azure DevOps

Add new Azure DevOps setting
@@ -48,7 +48,7 @@ }
-

  GitHub

+

GitHub icon  GitHub

Add new GitHub setting
diff --git a/src/DevOpsMetrics.Web/Views/Shared/_ChangeFailureRate.cshtml b/src/DevOpsMetrics.Web/Views/Shared/_ChangeFailureRate.cshtml index 7903ca0b..6877385a 100644 --- a/src/DevOpsMetrics.Web/Views/Shared/_ChangeFailureRate.cshtml +++ b/src/DevOpsMetrics.Web/Views/Shared/_ChangeFailureRate.cshtml @@ -23,13 +23,13 @@ @* HTML based on example at https://codepen.io/vmz/pen/GpKbjm*@
- @if (Model.IsProjectView == true) + @if (Model.IsProjectView) {

Change Failure Rate

} else { -

  @widgetName - @Model.DeploymentName

+

Widget image name  @widgetName - @Model.DeploymentName

} @if (Model.Exception != null) { @@ -49,7 +49,7 @@ foreach (ChangeFailureRateBuild item in Model.ChangeFailureRateBuildList) { string progressFillStatus = ""; - if (item.DeploymentWasSuccessful == true) + if (item.DeploymentWasSuccessful) { progressFillStatus = "completed"; } @@ -78,5 +78,5 @@ Change failure rate: @Model.ChangeFailureRateMetric.ToString("0.00%")
} Change failure rate ranking: @Model.ChangeFailureRateMetricDescription
- Badge: + Badge: Badge with metric image
\ No newline at end of file diff --git a/src/DevOpsMetrics.Web/Views/Shared/_Deployments.cshtml b/src/DevOpsMetrics.Web/Views/Shared/_Deployments.cshtml index 33fc0c9a..b2191ced 100644 --- a/src/DevOpsMetrics.Web/Views/Shared/_Deployments.cshtml +++ b/src/DevOpsMetrics.Web/Views/Shared/_Deployments.cshtml @@ -23,13 +23,13 @@ @* HTML based on example at https://codepen.io/vmz/pen/GpKbjm*@
- @if (Model.IsProjectView == true) + @if (Model.IsProjectView) {

Deployment frequency

} else { -

  @widgetName - @Model.DeploymentName

+

Widget image name  @widgetName - @Model.DeploymentName

} @if (Model.Exception != null) { @@ -38,7 +38,7 @@ There was an exception retreiving data from this service request.
} - else if (Model.RateLimitHit == true) + else if (Model.RateLimitHit) {
API rate limit has been reached, please try again later @@ -74,6 +74,6 @@
Deployment frequency: @Model.DeploymentsToDisplayMetric.ToString("0.00") @Model.DeploymentsToDisplayUnit
Deployment frequency ranking: @Model.DeploymentsPerDayMetricDescription
- Badge: + Badge: Badge with metric image
} diff --git a/src/DevOpsMetrics.Web/Views/Shared/_LeadTimeForChanges.cshtml b/src/DevOpsMetrics.Web/Views/Shared/_LeadTimeForChanges.cshtml index b4e19b93..4d0e8a7c 100644 --- a/src/DevOpsMetrics.Web/Views/Shared/_LeadTimeForChanges.cshtml +++ b/src/DevOpsMetrics.Web/Views/Shared/_LeadTimeForChanges.cshtml @@ -23,13 +23,13 @@ @* HTML based on example at https://codepen.io/vmz/pen/GpKbjm*@
- @if (Model.IsProjectView == true) + @if (Model.IsProjectView) {

Lead time for changes

} else { -

  @widgetName - @Model.ProjectName

+

Widget image name  @widgetName - @Model.ProjectName

} @if (Model.Exception != null) { @@ -38,7 +38,7 @@ There was an exception retreiving data from this service request.
} - else if (Model.RateLimitHit == true) + else if (Model.RateLimitHit) {
API rate limit has been reached, please try again later @@ -76,6 +76,6 @@ Pull request time: @Model.AveragePullRequestHours.ToString("0.0") hours
*@ Lead time for changes: @Model.LeadTimeForChangesMetricDisplayMetric.ToString("0.0") @Model.LeadTimeForChangesMetricDisplayUnit
Lead time for changes ranking: @Model.LeadTimeForChangesMetricDescription
- Badge: + Badge: Badge with metric image
} \ No newline at end of file diff --git a/src/DevOpsMetrics.Web/Views/Shared/_MeanTimeToRestore.cshtml b/src/DevOpsMetrics.Web/Views/Shared/_MeanTimeToRestore.cshtml index 641a8537..707fddbd 100644 --- a/src/DevOpsMetrics.Web/Views/Shared/_MeanTimeToRestore.cshtml +++ b/src/DevOpsMetrics.Web/Views/Shared/_MeanTimeToRestore.cshtml @@ -23,13 +23,13 @@ @* HTML based on example at https://codepen.io/vmz/pen/GpKbjm*@
- @if (Model.IsProjectView == true) + @if (Model.IsProjectView) {

Time to restore service

} else { -

  @widgetName - @Model.ResourceGroup

+

Widget image name  @widgetName - @Model.ResourceGroup

} @if (Model.Exception != null) { @@ -72,7 +72,7 @@
Mean time to restore: @Model.MTTRAverageDurationInHours.ToString("0.00") hours
Time to restore ranking: @Model.MTTRAverageDurationDescription
- Badge:
+ Badge: Badge with metric image
@if (@Model.SLA >= 0) { SLA: @Model.SLA.ToString("0.00%")
diff --git a/src/DevOpsMetrics.Web/appsettings.json b/src/DevOpsMetrics.Web/appsettings.json index b273cdb5..766149e3 100644 --- a/src/DevOpsMetrics.Web/appsettings.json +++ b/src/DevOpsMetrics.Web/appsettings.json @@ -13,6 +13,6 @@ "WebServiceURL": "https://devops-prod-eu-service.azurewebsites.net/" }, "ApplicationInsights": { - "ConnectionString": "InstrumentationKey=dc3fb590-3612-4f09-8c95-e9378cba8654;IngestionEndpoint=https://eastus-3.in.applicationinsights.azure.com/" + "ConnectionString": "InstrumentationKey=dc3fb590-3612-4f09-8c95-e9378cba8654;IngestionEndpoint=https://eastus-3.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=54a63930-44fb-4776-b760-93eceafdfe01" } } \ No newline at end of file diff --git a/src/DevOpsMetrics.Web/wwwroot/css/site.css b/src/DevOpsMetrics.Web/wwwroot/css/site.css index dd938a56..2cb9a28f 100644 --- a/src/DevOpsMetrics.Web/wwwroot/css/site.css +++ b/src/DevOpsMetrics.Web/wwwroot/css/site.css @@ -28,6 +28,8 @@ a { -------------------------------------------------- */ html { font-size: 14px; + position: relative; + min-height: 100%; } @media (min-width: 768px) { @@ -55,14 +57,11 @@ button.accept-policy { /* Sticky footer styles -------------------------------------------------- */ -html { - position: relative; - min-height: 100%; -} body { /* Margin bottom by footer height */ margin-bottom: 60px; + background: #fff; } .footer { @@ -80,10 +79,6 @@ body { box-sizing: border-box; } -body { - background: #fff; -} - h4 { margin: 0 0 20px 0; padding: 0 0 5px 0; @@ -128,7 +123,6 @@ h4 { /*background: #107c10;*/ height: 50%; width: 18px; - color: #fff; text-align: center; font-family: "Lato","Verdana",sans-serif; font-size: 12px; diff --git a/src/DevOpsMetrics.sln b/src/DevOpsMetrics.sln index 8495ebb6..ec99dc33 100644 --- a/src/DevOpsMetrics.sln +++ b/src/DevOpsMetrics.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.3.32929.385 +# Visual Studio Version 18 +VisualStudioVersion = 18.7.11911.148 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevOpsMetrics.Core", "DevOpsMetrics.Core\DevOpsMetrics.Core.csproj", "{274A42BF-52C4-4AD9-9A1C-E65076D7F81B}" EndProject @@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\ReadmeImages\Architecture.png = ..\ReadmeImages\Architecture.png ..\ReadmeImages\ChangeFailureRate.png = ..\ReadmeImages\ChangeFailureRate.png ..\.github\workflows\codeql-analysis.yml = ..\.github\workflows\codeql-analysis.yml + ..\.github\dependabot.yml = ..\.github\dependabot.yml ..\ReadmeImages\DeploymentFrequencyDemo.png = ..\ReadmeImages\DeploymentFrequencyDemo.png ..\.github\workflows\dotnetcore.yml = ..\.github\workflows\dotnetcore.yml ..\GitVersion.yml = ..\GitVersion.yml @@ -29,17 +30,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevOpsMetrics.Function", "D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevOpsMetrics.FunctionalTests", "DevOpsMetrics.FunctionalTests\DevOpsMetrics.FunctionalTests.csproj", "{613B7F21-62CF-412F-BCF4-B0500EAE6FCB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Probot", "Probot", "{91299047-588F-41CA-8ED2-9876284606F9}" - ProjectSection(SolutionItems) = preProject - ProbotMetrics\app.yml = ProbotMetrics\app.yml - ProbotMetrics\src\generateDevopsMetrics.js = ProbotMetrics\src\generateDevopsMetrics.js - ProbotMetrics\src\getGithubSettings.js = ProbotMetrics\src\getGithubSettings.js - ProbotMetrics\index.js = ProbotMetrics\index.js - ProbotMetrics\src\postDevopsMetrics.js = ProbotMetrics\src\postDevopsMetrics.js - ProbotMetrics\src\prepareDevOpsMetrics.js = ProbotMetrics\src\prepareDevOpsMetrics.js - EndProjectSection -EndProject -Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "DevOpsMetrics.Infrastructure", "DevOpsMetrics.Infrastructure\DevOpsMetrics.Infrastructure.deployproj", "{0465BF7A-C791-49CA-A7DE-27B4EECD2020}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevOpsMetrics.Cmd", "DevOpsMetrics.Cmd\DevOpsMetrics.Cmd.csproj", "{D15713F2-D089-45A7-A0FA-1F16D664130D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -71,10 +62,10 @@ Global {613B7F21-62CF-412F-BCF4-B0500EAE6FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU {613B7F21-62CF-412F-BCF4-B0500EAE6FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU {613B7F21-62CF-412F-BCF4-B0500EAE6FCB}.Release|Any CPU.Build.0 = Release|Any CPU - {0465BF7A-C791-49CA-A7DE-27B4EECD2020}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0465BF7A-C791-49CA-A7DE-27B4EECD2020}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0465BF7A-C791-49CA-A7DE-27B4EECD2020}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0465BF7A-C791-49CA-A7DE-27B4EECD2020}.Release|Any CPU.Build.0 = Release|Any CPU + {D15713F2-D089-45A7-A0FA-1F16D664130D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D15713F2-D089-45A7-A0FA-1F16D664130D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D15713F2-D089-45A7-A0FA-1F16D664130D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D15713F2-D089-45A7-A0FA-1F16D664130D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ProbotMetrics/.deployment b/src/ProbotMetrics/.deployment deleted file mode 100644 index 62783318..00000000 --- a/src/ProbotMetrics/.deployment +++ /dev/null @@ -1,2 +0,0 @@ -[config] -SCM_DO_BUILD_DURING_DEPLOYMENT=true \ No newline at end of file diff --git a/src/ProbotMetrics/.env.example b/src/ProbotMetrics/.env.example deleted file mode 100644 index 6e106aa9..00000000 --- a/src/ProbotMetrics/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -# The ID of your GitHub App -APP_ID= -WEBHOOK_SECRET=development - -# Use `trace` to get verbose logging or `info` to show less -LOG_LEVEL=debug - -# Go to https://smee.io/new set this to the URL that you are redirected to. -WEBHOOK_PROXY_URL= diff --git a/src/ProbotMetrics/.github/probotmetrics.yml b/src/ProbotMetrics/.github/probotmetrics.yml deleted file mode 100644 index 3b943dce..00000000 --- a/src/ProbotMetrics/.github/probotmetrics.yml +++ /dev/null @@ -1,6 +0,0 @@ -github_settings_url: "https://devopsmetrics-prod-eu-service.azurewebsites.net/api/TableStorage/GetGitHubSettings" -deployment_frequency_url: "https://devopsmetrics-prod-eu-service.azurewebsites.net/api/DeploymentFrequency/GetGitHubDeploymentFrequency?getSampleData=False&numberOfDays=30&maxNumberOfItems=20&useCache=True" -meantime_restore_url: "https://devopsmetrics-prod-eu-service.azurewebsites.net/api/MeanTimeToRestore/GetAzureMeanTimeToRestore?getSampleData=False&targetDevOpsPlatform=GitHub&resourceGroup=DevOpsMetrics&numberOfDays=30&maxNumberOfItems=20" -lead_time_change_url: "https://devopsmetrics-prod-eu-service.azurewebsites.net/api/LeadTimeForChanges/GetGitHubLeadTimeForChanges?getSampleData=False&numberOfDays=30&maxNumberOfItems=20&useCache=True" -change_failure_rate_url: "https://devopsmetrics-prod-eu-service.azurewebsites.net/api/ChangeFailureRate/GetChangeFailureRate?getSampleData=False&targetDevOpsPlatform=GitHub&numberOfDays=30&maxNumberOfItems=20" -dashboard_url: "https://devopsmetrics-prod-eu-web.azurewebsites.net/Home/Project?rowKey=" diff --git a/src/ProbotMetrics/LICENSE b/src/ProbotMetrics/LICENSE deleted file mode 100644 index de6b3cdf..00000000 --- a/src/ProbotMetrics/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2020, Chris Hanna - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/src/ProbotMetrics/README.md b/src/ProbotMetrics/README.md deleted file mode 100644 index c764b9d4..00000000 --- a/src/ProbotMetrics/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# ProbotMetrics - -> A GitHub App built with [Probot](https://github.com/probot/probot) that Generates a MD file with your DevOps Performance Metrics - -## Steps -1. Ensure you have a Github Repo, where you have the ability to install applications -2. Make sure you have deployed the DevOpsMetrics Solution, as it is critical we can call the APIs in that project. -3. Checkin a .github/probotmetrics.yml, this file stores the locations of all the APIs that are in the DevOpsMetrics .netcore solution -4. Make sure you don't have a .env file in the solution if this is the first time you are running, probot will walk you through the app generation and create your .env file -5. npm install -6. npm start -7. Click on Register App -![ProbotSetup](https://github.com/samsmithnz/DevOpsMetrics/blob/master/ReadmeImages/ProbotSetup.png) -8. Set the App Name -![ProbotSetup](https://github.com/samsmithnz/DevOpsMetrics/blob/master/ReadmeImages/ProbotAppSetup1.png) -9. Select the repo you want to install -![ProbotSetup](https://github.com/samsmithnz/DevOpsMetrics/blob/master/ReadmeImages/ProbotAppSetup2.png) -10. Ensure you have a .env -11. Restart the app - -## License -Enjoy, but don't hold us accountable : ) -© 2020 Chris Hanna diff --git a/src/ProbotMetrics/app.yml b/src/ProbotMetrics/app.yml deleted file mode 100644 index ee6f440c..00000000 --- a/src/ProbotMetrics/app.yml +++ /dev/null @@ -1,139 +0,0 @@ -# This is a GitHub App Manifest. These settings will be used by default when -# initially configuring your GitHub App. -# -# NOTE: changing this file will not update your GitHub App settings. -# You must visit github.com/settings/apps/your-app-name to edit them. -# -# Read more about configuring your GitHub App: -# https://probot.github.io/docs/development/#configuring-a-github-app -# -# Read more about GitHub App Manifests: -# https://developer.github.com/apps/building-github-apps/creating-github-apps-from-a-manifest/ - -# The list of events the GitHub App subscribes to. -# Uncomment the event names below to enable them. -default_events: -# - check_run -# - check_suite -# - commit_comment -# - create -# - delete -# - deployment -# - deployment_status -# - fork -# - gollum -# - issue_comment -- issues -# - label -# - milestone -# - member -# - membership -# - org_block -# - organization -# - page_build -# - project -# - project_card -# - project_column -# - public -# - pull_request -# - pull_request_review -# - pull_request_review_comment -# - push -# - release -# - repository -# - repository_import -# - status -# - team -# - team_add -# - watch - -# The set of permissions needed by the GitHub App. The format of the object uses -# the permission name for the key (for example, issues) and the access type for -# the value (for example, write). -# Valid values are `read`, `write`, and `none` -default_permissions: - # Repository creation, deletion, settings, teams, and collaborators. - # https://developer.github.com/v3/apps/permissions/#permission-on-administration - # administration: read - - # Checks on code. - # https://developer.github.com/v3/apps/permissions/#permission-on-checks - # checks: read - - # Repository contents, commits, branches, downloads, releases, and merges. - # https://developer.github.com/v3/apps/permissions/#permission-on-contents - # contents: read - - # Deployments and deployment statuses. - # https://developer.github.com/v3/apps/permissions/#permission-on-deployments - # deployments: read - - # Issues and related comments, assignees, labels, and milestones. - # https://developer.github.com/v3/apps/permissions/#permission-on-issues - issues: write - - # Search repositories, list collaborators, and access repository metadata. - # https://developer.github.com/v3/apps/permissions/#metadata-permissions - metadata: read - - # Retrieve Pages statuses, configuration, and builds, as well as create new builds. - # https://developer.github.com/v3/apps/permissions/#permission-on-pages - # pages: read - - # Pull requests and related comments, assignees, labels, milestones, and merges. - # https://developer.github.com/v3/apps/permissions/#permission-on-pull-requests - # pull_requests: read - - # Manage the post-receive hooks for a repository. - # https://developer.github.com/v3/apps/permissions/#permission-on-repository-hooks - # repository_hooks: read - - # Manage repository projects, columns, and cards. - # https://developer.github.com/v3/apps/permissions/#permission-on-repository-projects - # repository_projects: read - - # Retrieve security vulnerability alerts. - # https://developer.github.com/v4/object/repositoryvulnerabilityalert/ - # vulnerability_alerts: read - - # Commit statuses. - # https://developer.github.com/v3/apps/permissions/#permission-on-statuses - # statuses: read - - # Organization members and teams. - # https://developer.github.com/v3/apps/permissions/#permission-on-members - # members: read - - # View and manage users blocked by the organization. - # https://developer.github.com/v3/apps/permissions/#permission-on-organization-user-blocking - # organization_user_blocking: read - - # Manage organization projects, columns, and cards. - # https://developer.github.com/v3/apps/permissions/#permission-on-organization-projects - # organization_projects: read - - # Manage team discussions and related comments. - # https://developer.github.com/v3/apps/permissions/#permission-on-team-discussions - # team_discussions: read - - # Manage the post-receive hooks for an organization. - # https://developer.github.com/v3/apps/permissions/#permission-on-organization-hooks - # organization_hooks: read - - # Get notified of, and update, content references. - # https://developer.github.com/v3/apps/permissions/ - # organization_administration: read - - -# The name of the GitHub App. Defaults to the name specified in package.json -# name: My Probot App - -# The homepage of your GitHub App. -# url: https://example.com/ - -# A description of the GitHub App. -# description: A description of my awesome app - -# Set to true when your GitHub App is available to the public or false when it is only accessible to the owner of the app. -# Default: true -# public: false diff --git a/src/ProbotMetrics/index.js b/src/ProbotMetrics/index.js deleted file mode 100644 index a2bc87e6..00000000 --- a/src/ProbotMetrics/index.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * This is the main entrypoint to your Probot app - * @param {import('probot').Application} app - */ - -const createScheduler = require('probot-scheduler') -const { generateDevopsMetrics } = require('./src/generateDevopsMetrics'); - -module.exports = (app) => { - // Your code here - app.log('Yay, the app was loaded!') - - createScheduler(app, { - delay: !!process.env.DISABLE_DELAY, // delay is enabled on first run - interval: 60 * 1000 * 60 * 24 * 7 //weekly - }) - - app.on('schedule.repository', async context => { - - app.log('Application is running per schedule'); - context.log.debug('App is running as per schedule.repository'); - - await generateDevopsMetrics(context); - - }) - -} - - - diff --git a/src/ProbotMetrics/package-lock.json b/src/ProbotMetrics/package-lock.json deleted file mode 100644 index 71044b30..00000000 --- a/src/ProbotMetrics/package-lock.json +++ /dev/null @@ -1,6894 +0,0 @@ -{ - "name": "ProbotMetrics", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.3.tgz", - "integrity": "sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw==", - "dev": true - }, - "@babel/core": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", - "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.3", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helpers": "^7.19.0", - "@babel/parser": "^7.19.3", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.3", - "@babel/types": "^7.19.3", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.3.tgz", - "integrity": "sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==", - "dev": true, - "requires": { - "@babel/types": "^7.19.3", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz", - "integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.19.3", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "dev": true, - "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", - "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", - "dev": true, - "requires": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz", - "integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/traverse": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.3.tgz", - "integrity": "sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.3", - "@babel/types": "^7.19.3", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz", - "integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", - "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.1", - "globals": "^13.9.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "globals": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", - "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@hapi/bourne": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz", - "integrity": "sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q==" - }, - "@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.1.2.tgz", - "integrity": "sha512-ujEBCcYs82BTmRxqfHMQggSlkUZP63AE5YEaTPj7eFyJOzukkTorstOUC7L6nE3w5SYadGVAnTsQ/ZjTGL0qYQ==", - "dev": true, - "requires": { - "@jest/types": "^29.1.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.1.2", - "jest-util": "^29.1.2", - "slash": "^3.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jest/core": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.1.2.tgz", - "integrity": "sha512-sCO2Va1gikvQU2ynDN8V4+6wB7iVrD2CvT0zaRst4rglf56yLly0NQ9nuRRAWFeimRf+tCdFsb1Vk1N9LrrMPA==", - "dev": true, - "requires": { - "@jest/console": "^29.1.2", - "@jest/reporters": "^29.1.2", - "@jest/test-result": "^29.1.2", - "@jest/transform": "^29.1.2", - "@jest/types": "^29.1.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.0.0", - "jest-config": "^29.1.2", - "jest-haste-map": "^29.1.2", - "jest-message-util": "^29.1.2", - "jest-regex-util": "^29.0.0", - "jest-resolve": "^29.1.2", - "jest-resolve-dependencies": "^29.1.2", - "jest-runner": "^29.1.2", - "jest-runtime": "^29.1.2", - "jest-snapshot": "^29.1.2", - "jest-util": "^29.1.2", - "jest-validate": "^29.1.2", - "jest-watcher": "^29.1.2", - "micromatch": "^4.0.4", - "pretty-format": "^29.1.2", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "@jest/environment": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.1.2.tgz", - "integrity": "sha512-rG7xZ2UeOfvOVzoLIJ0ZmvPl4tBEQ2n73CZJSlzUjPw4or1oSWC0s0Rk0ZX+pIBJ04aVr6hLWFn1DFtrnf8MhQ==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.1.2", - "@jest/types": "^29.1.2", - "@types/node": "*", - "jest-mock": "^29.1.2" - } - }, - "@jest/expect": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.1.2.tgz", - "integrity": "sha512-FXw/UmaZsyfRyvZw3M6POgSNqwmuOXJuzdNiMWW9LCYo0GRoRDhg+R5iq5higmRTHQY7hx32+j7WHwinRmoILQ==", - "dev": true, - "requires": { - "expect": "^29.1.2", - "jest-snapshot": "^29.1.2" - } - }, - "@jest/expect-utils": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.1.2.tgz", - "integrity": "sha512-4a48bhKfGj/KAH39u0ppzNTABXQ8QPccWAFUFobWBaEMSMp+sB31Z2fK/l47c4a/Mu1po2ffmfAIPxXbVTXdtg==", - "dev": true, - "requires": { - "jest-get-type": "^29.0.0" - } - }, - "@jest/fake-timers": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.1.2.tgz", - "integrity": "sha512-GppaEqS+QQYegedxVMpCe2xCXxxeYwQ7RsNx55zc8f+1q1qevkZGKequfTASI7ejmg9WwI+SJCrHe9X11bLL9Q==", - "dev": true, - "requires": { - "@jest/types": "^29.1.2", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.1.2", - "jest-mock": "^29.1.2", - "jest-util": "^29.1.2" - } - }, - "@jest/globals": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.1.2.tgz", - "integrity": "sha512-uMgfERpJYoQmykAd0ffyMq8wignN4SvLUG6orJQRe9WAlTRc9cdpCaE/29qurXixYJVZWUqIBXhSk8v5xN1V9g==", - "dev": true, - "requires": { - "@jest/environment": "^29.1.2", - "@jest/expect": "^29.1.2", - "@jest/types": "^29.1.2", - "jest-mock": "^29.1.2" - } - }, - "@jest/reporters": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.1.2.tgz", - "integrity": "sha512-X4fiwwyxy9mnfpxL0g9DD0KcTmEIqP0jUdnc2cfa9riHy+I6Gwwp5vOZiwyg0vZxfSDxrOlK9S4+340W4d+DAA==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.1.2", - "@jest/test-result": "^29.1.2", - "@jest/transform": "^29.1.2", - "@jest/types": "^29.1.2", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.1.2", - "jest-util": "^29.1.2", - "jest-worker": "^29.1.2", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.0.0.tgz", - "integrity": "sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "@jest/test-result": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.1.2.tgz", - "integrity": "sha512-jjYYjjumCJjH9hHCoMhA8PCl1OxNeGgAoZ7yuGYILRJX9NjgzTN0pCT5qAoYR4jfOP8htIByvAlz9vfNSSBoVg==", - "dev": true, - "requires": { - "@jest/console": "^29.1.2", - "@jest/types": "^29.1.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.1.2.tgz", - "integrity": "sha512-fU6dsUqqm8sA+cd85BmeF7Gu9DsXVWFdGn9taxM6xN1cKdcP/ivSgXh5QucFRFz1oZxKv3/9DYYbq0ULly3P/Q==", - "dev": true, - "requires": { - "@jest/test-result": "^29.1.2", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.1.2", - "slash": "^3.0.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "@jest/transform": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.1.2.tgz", - "integrity": "sha512-2uaUuVHTitmkx1tHF+eBjb4p7UuzBG7SXIaA/hNIkaMP6K+gXYGxP38ZcrofzqN0HeZ7A90oqsOa97WU7WZkSw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.1.2", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.1.2", - "jest-regex-util": "^29.0.0", - "jest-util": "^29.1.2", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "@jest/types": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.1.2.tgz", - "integrity": "sha512-DcXGtoTykQB5jiwCmVr8H4vdg2OJhQex3qPkG+ISyDO7xQXbt/4R6dowcRyPemRnkH7JoHvZuxPBdlq+9JxFCg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@octokit/auth-app": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-4.0.5.tgz", - "integrity": "sha512-fCbi4L/egsP3p4p1SelOFORM/m/5KxROhHdcIW5Lb17DDdW61fGT8y3wGpfiSeYNuulwF5QIfzJ7tdgtHNAymA==", - "requires": { - "@octokit/auth-oauth-app": "^5.0.0", - "@octokit/auth-oauth-user": "^2.0.0", - "@octokit/request": "^6.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^7.0.0", - "@types/lru-cache": "^5.1.0", - "deprecation": "^2.3.1", - "lru-cache": "^6.0.0", - "universal-github-app-jwt": "^1.0.1", - "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "@octokit/endpoint": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.1.tgz", - "integrity": "sha512-/wTXAJwt0HzJ2IeE4kQXO+mBScfzyCkI0hMtkIaqyXd9zg76OpOfNQfHL9FlaxAV2RsNiOXZibVWloy8EexENg==", - "requires": { - "@octokit/types": "^7.0.0", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.1.tgz", - "integrity": "sha512-gYKRCia3cpajRzDSU+3pt1q2OcuC6PK8PmFIyxZDWCzRXRSIBH8jXjFJ8ZceoygBIm0KsEUg4x1+XcYBz7dHPQ==", - "requires": { - "@octokit/endpoint": "^7.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^7.0.0", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.1.tgz", - "integrity": "sha512-ym4Bp0HTP7F3VFssV88WD1ZyCIRoE8H35pXSKwLeMizcdZAYc/t6N9X9Yr9n6t3aG9IH75XDnZ6UeZph0vHMWQ==", - "requires": { - "@octokit/types": "^7.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - } - } - }, - "@octokit/auth-oauth-app": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-5.0.2.tgz", - "integrity": "sha512-6hHchPIw4fpB9J0tUT9cPpXG8aotp9xDvuUh8owyHWaXC7uHgnJmvDmEUb2X7qoU3KXqejB3nhm3u94q2p5EIg==", - "requires": { - "@octokit/auth-oauth-device": "^4.0.0", - "@octokit/auth-oauth-user": "^2.0.0", - "@octokit/request": "^5.6.3", - "@octokit/types": "^7.0.0", - "@types/btoa-lite": "^1.0.0", - "btoa-lite": "^1.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/auth-oauth-device": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-4.0.2.tgz", - "integrity": "sha512-h5Ir0q5c6dHZwWMrSWMvgu3JyuH7qCPJ0kB9jNYDugsAob69N65ebA3E5FWPUN6hGJguZpy3CRmqejTx7aSobQ==", - "requires": { - "@octokit/oauth-methods": "^2.0.0", - "@octokit/request": "^6.0.0", - "@octokit/types": "^7.0.0", - "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "@octokit/endpoint": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.1.tgz", - "integrity": "sha512-/wTXAJwt0HzJ2IeE4kQXO+mBScfzyCkI0hMtkIaqyXd9zg76OpOfNQfHL9FlaxAV2RsNiOXZibVWloy8EexENg==", - "requires": { - "@octokit/types": "^7.0.0", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.1.tgz", - "integrity": "sha512-gYKRCia3cpajRzDSU+3pt1q2OcuC6PK8PmFIyxZDWCzRXRSIBH8jXjFJ8ZceoygBIm0KsEUg4x1+XcYBz7dHPQ==", - "requires": { - "@octokit/endpoint": "^7.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^7.0.0", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.1.tgz", - "integrity": "sha512-ym4Bp0HTP7F3VFssV88WD1ZyCIRoE8H35pXSKwLeMizcdZAYc/t6N9X9Yr9n6t3aG9IH75XDnZ6UeZph0vHMWQ==", - "requires": { - "@octokit/types": "^7.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - } - } - }, - "@octokit/auth-oauth-user": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-2.0.3.tgz", - "integrity": "sha512-NMGTTGa1j6JVtlpZUOrhi1RxBjHaogS0p59qV8HtFOx3Rgq503xfjjA8npyVbRuAf30iW/K5YueZKivMkhBITA==", - "requires": { - "@octokit/auth-oauth-device": "^4.0.0", - "@octokit/oauth-methods": "^2.0.0", - "@octokit/request": "^6.0.0", - "@octokit/types": "^7.0.0", - "btoa-lite": "^1.0.0", - "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "@octokit/endpoint": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.1.tgz", - "integrity": "sha512-/wTXAJwt0HzJ2IeE4kQXO+mBScfzyCkI0hMtkIaqyXd9zg76OpOfNQfHL9FlaxAV2RsNiOXZibVWloy8EexENg==", - "requires": { - "@octokit/types": "^7.0.0", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.1.tgz", - "integrity": "sha512-gYKRCia3cpajRzDSU+3pt1q2OcuC6PK8PmFIyxZDWCzRXRSIBH8jXjFJ8ZceoygBIm0KsEUg4x1+XcYBz7dHPQ==", - "requires": { - "@octokit/endpoint": "^7.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^7.0.0", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.1.tgz", - "integrity": "sha512-ym4Bp0HTP7F3VFssV88WD1ZyCIRoE8H35pXSKwLeMizcdZAYc/t6N9X9Yr9n6t3aG9IH75XDnZ6UeZph0vHMWQ==", - "requires": { - "@octokit/types": "^7.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - } - } - }, - "@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "requires": { - "@octokit/types": "^6.0.3" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/auth-unauthenticated": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-3.0.2.tgz", - "integrity": "sha512-wCGPQaXynH2wtqABsHukY06VPw4zHXtfbE/maEngj2k9h9o2hfzC6WAzzjExyISgRnDc9ae/HXzKakUWvWSb2Q==", - "requires": { - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^7.0.0" - }, - "dependencies": { - "@octokit/request-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.1.tgz", - "integrity": "sha512-ym4Bp0HTP7F3VFssV88WD1ZyCIRoE8H35pXSKwLeMizcdZAYc/t6N9X9Yr9n6t3aG9IH75XDnZ6UeZph0vHMWQ==", - "requires": { - "@octokit/types": "^7.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - } - } - }, - "@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "requires": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "requires": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "requires": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/oauth-authorization-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-5.0.0.tgz", - "integrity": "sha512-y1WhN+ERDZTh0qZ4SR+zotgsQUE1ysKnvBt1hvDRB2WRzYtVKQjn97HEPzoehh66Fj9LwNdlZh+p6TJatT0zzg==" - }, - "@octokit/oauth-methods": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-2.0.3.tgz", - "integrity": "sha512-XM+pPsj6TB9zXHfGszZmIp2zRShjQuwGLEKbkOQ7mZBHBPpx0TRzSYwUbwiAJsWefkPUXgr7i0qFsxLr/Uciyg==", - "requires": { - "@octokit/oauth-authorization-url": "^5.0.0", - "@octokit/request": "^6.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^7.0.0", - "btoa-lite": "^1.0.0" - }, - "dependencies": { - "@octokit/endpoint": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.1.tgz", - "integrity": "sha512-/wTXAJwt0HzJ2IeE4kQXO+mBScfzyCkI0hMtkIaqyXd9zg76OpOfNQfHL9FlaxAV2RsNiOXZibVWloy8EexENg==", - "requires": { - "@octokit/types": "^7.0.0", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.1.tgz", - "integrity": "sha512-gYKRCia3cpajRzDSU+3pt1q2OcuC6PK8PmFIyxZDWCzRXRSIBH8jXjFJ8ZceoygBIm0KsEUg4x1+XcYBz7dHPQ==", - "requires": { - "@octokit/endpoint": "^7.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^7.0.0", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.1.tgz", - "integrity": "sha512-ym4Bp0HTP7F3VFssV88WD1ZyCIRoE8H35pXSKwLeMizcdZAYc/t6N9X9Yr9n6t3aG9IH75XDnZ6UeZph0vHMWQ==", - "requires": { - "@octokit/types": "^7.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - } - } - }, - "@octokit/openapi-types": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", - "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" - }, - "@octokit/plugin-enterprise-compatibility": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-compatibility/-/plugin-enterprise-compatibility-1.3.0.tgz", - "integrity": "sha512-h34sMGdEOER/OKrZJ55v26ntdHb9OPfR1fwOx6Q4qYyyhWA104o11h9tFxnS/l41gED6WEI41Vu2G2zHDVC5lQ==", - "requires": { - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.0.3" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/plugin-paginate-rest": { - "version": "2.21.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", - "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", - "requires": { - "@octokit/types": "^6.40.0" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/plugin-rest-endpoint-methods": { - "version": "5.16.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", - "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", - "requires": { - "@octokit/types": "^6.39.0", - "deprecation": "^2.3.1" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/plugin-retry": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-3.0.9.tgz", - "integrity": "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ==", - "requires": { - "@octokit/types": "^6.0.3", - "bottleneck": "^2.15.3" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/plugin-throttling": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-3.7.0.tgz", - "integrity": "sha512-qrKT1Yl/KuwGSC6/oHpLBot3ooC9rq0/ryDYBCpkRtoj+R8T47xTMDT6Tk2CxWopFota/8Pi/2SqArqwC0JPow==", - "requires": { - "@octokit/types": "^6.0.1", - "bottleneck": "^2.15.3" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", - "requires": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "requires": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - }, - "dependencies": { - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - } - } - }, - "@octokit/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-7.2.0.tgz", - "integrity": "sha512-pYQ/a1U6mHptwhGyp6SvsiM4bWP2s3V95olUeTxas85D/2kN78yN5C8cGN+P4LwJSWUqIEyvq0Qn2WUn6NQRjw==", - "requires": { - "@octokit/openapi-types": "^13.6.0" - }, - "dependencies": { - "@octokit/openapi-types": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-13.8.0.tgz", - "integrity": "sha512-m1O4KSRRF5qieJ3MWuLrfseR9XHO0IxBsKVUbZMstbsghQlSPz/aHEgIqCWc4oj3+X/yFZXoXxGQcOhjcvQF1Q==" - } - } - }, - "@octokit/webhooks": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz", - "integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==", - "requires": { - "@octokit/request-error": "^2.0.2", - "@octokit/webhooks-methods": "^2.0.0", - "@octokit/webhooks-types": "5.8.0", - "aggregate-error": "^3.1.0" - } - }, - "@octokit/webhooks-methods": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz", - "integrity": "sha512-35cfQ4YWlnZnmZKmIxlGPUPLtbkF8lr/A/1Sk1eC0ddLMwQN06dOuLc+dI3YLQS+T+MoNt3DIQ0NynwgKPilig==" - }, - "@octokit/webhooks-types": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-5.8.0.tgz", - "integrity": "sha512-8adktjIb76A7viIdayQSFuBEwOzwhDC+9yxZpKNHjfzrlostHCw0/N7JWpWMObfElwvJMk2fY2l1noENCk9wmw==" - }, - "@probot/get-private-key": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@probot/get-private-key/-/get-private-key-1.1.1.tgz", - "integrity": "sha512-hOmBNSAhSZc6PaNkTvj6CO9R5J67ODJ+w5XQlDW9w/6mtcpHWK4L+PZcW0YwVM7PpetLZjN6rsKQIR9yqIaWlA==", - "requires": { - "@types/is-base64": "^1.1.0", - "is-base64": "^1.1.0" - } - }, - "@probot/octokit-plugin-config": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@probot/octokit-plugin-config/-/octokit-plugin-config-1.1.6.tgz", - "integrity": "sha512-L29wmnFvilzSfWn9tUgItxdLv0LJh2ICjma3FmLr80Spu3wZ9nHyRrKMo9R5/K2m7VuWmgoKnkgRt2zPzAQBEQ==", - "requires": { - "@types/js-yaml": "^4.0.5", - "js-yaml": "^4.1.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "@probot/pino": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@probot/pino/-/pino-2.3.5.tgz", - "integrity": "sha512-IiyiNZonMw1dHC4EAdD55y5owV733d9Gll/IKsrLikB7EJ54+eMCOtL/qo+OmgWN9XV3NTDfziEQF2og/OBKog==", - "requires": { - "@sentry/node": "^6.0.0", - "pino-pretty": "^6.0.0", - "pump": "^3.0.0", - "readable-stream": "^3.6.0", - "split2": "^4.0.0" - } - }, - "@sentry/core": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.19.7.tgz", - "integrity": "sha512-tOfZ/umqB2AcHPGbIrsFLcvApdTm9ggpi/kQZFkej7kMphjT+SGBiQfYtjyg9jcRW+ilAR4JXC9BGKsdEQ+8Vw==", - "requires": { - "@sentry/hub": "6.19.7", - "@sentry/minimal": "6.19.7", - "@sentry/types": "6.19.7", - "@sentry/utils": "6.19.7", - "tslib": "^1.9.3" - } - }, - "@sentry/hub": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.19.7.tgz", - "integrity": "sha512-y3OtbYFAqKHCWezF0EGGr5lcyI2KbaXW2Ik7Xp8Mu9TxbSTuwTe4rTntwg8ngPjUQU3SUHzgjqVB8qjiGqFXCA==", - "requires": { - "@sentry/types": "6.19.7", - "@sentry/utils": "6.19.7", - "tslib": "^1.9.3" - } - }, - "@sentry/minimal": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.19.7.tgz", - "integrity": "sha512-wcYmSJOdvk6VAPx8IcmZgN08XTXRwRtB1aOLZm+MVHjIZIhHoBGZJYTVQS/BWjldsamj2cX3YGbGXNunaCfYJQ==", - "requires": { - "@sentry/hub": "6.19.7", - "@sentry/types": "6.19.7", - "tslib": "^1.9.3" - } - }, - "@sentry/node": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.19.7.tgz", - "integrity": "sha512-gtmRC4dAXKODMpHXKfrkfvyBL3cI8y64vEi3fDD046uqYcrWdgoQsffuBbxMAizc6Ez1ia+f0Flue6p15Qaltg==", - "requires": { - "@sentry/core": "6.19.7", - "@sentry/hub": "6.19.7", - "@sentry/types": "6.19.7", - "@sentry/utils": "6.19.7", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - } - }, - "@sentry/types": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.19.7.tgz", - "integrity": "sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg==" - }, - "@sentry/utils": { - "version": "6.19.7", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.19.7.tgz", - "integrity": "sha512-z95ECmE3i9pbWoXQrD/7PgkBAzJYR+iXtPuTkpBjDKs86O3mT+PXOT3BAn79w2wkn7/i3vOGD2xVr1uiMl26dA==", - "requires": { - "@sentry/types": "6.19.7", - "tslib": "^1.9.3" - } - }, - "@sinclair/typebox": { - "version": "0.24.44", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.44.tgz", - "integrity": "sha512-ka0W0KN5i6LfrSocduwliMMpqVgohtPFidKdMEOUjoOFCHcOOYkKsPRxfs5f15oPNHTm6ERAm0GV/+/LTKeiWg==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", - "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-wJsiX1tosQ+J5+bY5LrSahHxr2wT+uME5UDwdN1kg4frt40euqA+wzECkmq4t5QbveHiJepfdThgQrPw6KiSlg==" - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.30", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", - "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/ioredis": { - "version": "4.28.10", - "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.28.10.tgz", - "integrity": "sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/is-base64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/is-base64/-/is-base64-1.1.1.tgz", - "integrity": "sha512-JgnGhP+MeSHEQmvxcobcwPEP4Ew56voiq9/0hmP/41lyQ/3gBw/ZCIRy2v+QkEOdeCl58lRcrf6+Y6WMlJGETA==" - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/js-yaml": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", - "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==" - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "@types/jsonwebtoken": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", - "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", - "requires": { - "@types/node": "*" - } - }, - "@types/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==" - }, - "@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, - "@types/node": { - "version": "14.0.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.26.tgz", - "integrity": "sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA==" - }, - "@types/pino": { - "version": "6.3.12", - "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.12.tgz", - "integrity": "sha512-dsLRTq8/4UtVSpJgl9aeqHvbh6pzdmjYD3C092SYgLD2TyoCqHpTJk6vp8DvCTGGc7iowZ2MoiYiVUUCcu7muw==", - "requires": { - "@types/node": "*", - "@types/pino-pretty": "*", - "@types/pino-std-serializers": "*", - "sonic-boom": "^2.1.0" - } - }, - "@types/pino-http": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@types/pino-http/-/pino-http-5.8.1.tgz", - "integrity": "sha512-A9MW6VCnx5ii7s+Fs5aFIw+aSZcBCpsZ/atpxamu8tTsvWFacxSf2Hrn1Ohn1jkVRB/LiPGOapRXcFawDBnDnA==", - "requires": { - "@types/pino": "6.3" - } - }, - "@types/pino-pretty": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/@types/pino-pretty/-/pino-pretty-4.7.5.tgz", - "integrity": "sha512-rfHe6VIknk14DymxGqc9maGsRe8/HQSvM2u46EAz2XrS92qsAJnW16dpdFejBuZKD8cRJX6Aw6uVZqIQctMpAg==", - "requires": { - "@types/node": "*", - "@types/pino": "6.3" - } - }, - "@types/pino-std-serializers": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/pino-std-serializers/-/pino-std-serializers-2.4.1.tgz", - "integrity": "sha512-17XcksO47M24IVTVKPeAByWUd3Oez7EbIjXpSbzMPhXVzgjGtrOa49gKBwxH9hb8dKv58OelsWQ+A1G1l9S3wQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", - "dev": true - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "requires": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", - "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "dependencies": { - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - } - } - }, - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "args": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/args/-/args-5.0.3.tgz", - "integrity": "sha512-h6k/zfFgusnv3i5TU08KQkVKuCPBtL/PWQbWkHUxvJrZ2nAyeaUupneemcrgn1xmqxPQsPIzwkUhOpoqPDRZuA==", - "requires": { - "camelcase": "5.0.0", - "chalk": "2.4.2", - "leven": "2.1.0", - "mri": "1.1.4" - }, - "dependencies": { - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==" - } - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - } - }, - "array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - } - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" - }, - "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, - "babel-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.1.2.tgz", - "integrity": "sha512-IuG+F3HTHryJb7gacC7SQ59A9kO56BctUsT67uJHp1mMCHUOMXpDwOHWGifWqdWVknN2WNkCVQELPjXx0aLJ9Q==", - "dev": true, - "requires": { - "@jest/transform": "^29.1.2", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.0.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz", - "integrity": "sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "29.0.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz", - "integrity": "sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.0.2", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "before-after-hook": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", - "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - } - } - }, - "bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==" - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "builtins": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-4.1.0.tgz", - "integrity": "sha512-1bPRZQtmKaO6h7qV1YHXNtr6nCK28k0Zo95KM4dXfILcZZwoHJBN1m3lfLv9LPkcOZlrSr+J1bzMaZFO98Yq0w==", - "dev": true, - "requires": { - "semver": "^7.0.0" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001414", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz", - "integrity": "sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "ci-info": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", - "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "cluster-key-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", - "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "dev": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "diff-sequences": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz", - "integrity": "sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "electron-to-chromium": { - "version": "1.4.269", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.269.tgz", - "integrity": "sha512-7mHFONwp7MNvdyto1v70fCwk28NJMFgsK79op+iYHzz1BLE8T66a1B2qW5alb8XgE0yi3FL3ZQjSYZpJpF6snw==", - "dev": true - }, - "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.5.tgz", - "integrity": "sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "dependencies": { - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - } - } - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "eslint": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz", - "integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.2.1", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - }, - "dependencies": { - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - } - } - }, - "globals": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", - "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "eslint-config-standard": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", - "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", - "dev": true - }, - "eslint-config-standard-jsx": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz", - "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", - "dev": true - }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", - "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "find-up": "^2.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - } - } - }, - "eslint-plugin-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", - "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "dev": true, - "requires": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-n": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.1.0.tgz", - "integrity": "sha512-Tgx4Z58QXv2Ha7Qzp0u4wavnZNZ3AOievZMxrAxi7nvDbzD5B/JqOD80LHYcGHFZc2HD9jDmM/+KWMPov46a4A==", - "dev": true, - "requires": { - "builtins": "^4.0.0", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.3.0", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "eslint-plugin-promise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz", - "integrity": "sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==", - "dev": true - }, - "eslint-plugin-react": { - "version": "7.29.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", - "integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==", - "dev": true, - "requires": { - "array-includes": "^3.1.4", - "array.prototype.flatmap": "^1.2.5", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.0", - "object.values": "^1.1.5", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.6" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "resolve": { - "version": "2.0.0-next.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", - "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", - "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", - "dev": true, - "requires": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "eventsource": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", - "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==" - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.1.2.tgz", - "integrity": "sha512-AuAGn1uxva5YBbBlXb+2JPxJRuemZsmlGcapPXWNSBNsQtAULfjioREGBWuI0EOvYUKjDnrCy8PW5Zlr1md5mw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.1.2", - "jest-get-type": "^29.0.0", - "jest-matcher-utils": "^29.1.2", - "jest-message-util": "^29.1.2", - "jest-util": "^29.1.2" - } - }, - "express": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", - "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.0", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.10.3", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - } - } - }, - "express-handlebars": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-6.0.6.tgz", - "integrity": "sha512-E4QHYCh+9fyfdBEb8uKJ8p6HD4qq/sUSHBq83lRNlLJp2TQKEg2nFJYbVdC+M3QzaV19dODe43lgjQWVaIpbyQ==", - "requires": { - "glob": "^8.0.2", - "graceful-fs": "^4.2.10", - "handlebars": "^4.7.7" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fast-redact": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz", - "integrity": "sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==" - }, - "fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" - }, - "fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", - "requires": { - "punycode": "^1.3.2" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - } - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - } - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatstr": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", - "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" - }, - "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", - "dev": true, - "requires": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" - }, - "dependencies": { - "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true - } - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - }, - "dependencies": { - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - } - } - }, - "hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "ioredis": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz", - "integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==", - "requires": { - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.1", - "denque": "^1.1.0", - "lodash.defaults": "^4.2.0", - "lodash.flatten": "^4.4.0", - "lodash.isarguments": "^3.1.0", - "p-map": "^2.1.0", - "redis-commands": "1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - } - } - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-base64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-base64/-/is-base64-1.1.0.tgz", - "integrity": "sha512-Nlhg7Z2dVC4/PTvIFkgVVNvPHSO2eR/Yd0XzhGiXCXEvWnptXlXa/clQ8aePPiMuxEGcWfzWbGw2Fe3d+Y3v1g==" - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - }, - "dependencies": { - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - } - } - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.1.2.tgz", - "integrity": "sha512-5wEIPpCezgORnqf+rCaYD1SK+mNN7NsstWzIsuvsnrhR/hSxXWd82oI7DkrbJ+XTD28/eG8SmxdGvukrGGK6Tw==", - "dev": true, - "requires": { - "@jest/core": "^29.1.2", - "@jest/types": "^29.1.2", - "import-local": "^3.0.2", - "jest-cli": "^29.1.2" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "jest-cli": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.1.2.tgz", - "integrity": "sha512-vsvBfQ7oS2o4MJdAH+4u9z76Vw5Q8WBQF5MchDbkylNknZdrPTX1Ix7YRJyTlOWqRaS7ue/cEAn+E4V1MWyMzw==", - "dev": true, - "requires": { - "@jest/core": "^29.1.2", - "@jest/test-result": "^29.1.2", - "@jest/types": "^29.1.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.1.2", - "jest-util": "^29.1.2", - "jest-validate": "^29.1.2", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - } - } - } - }, - "jest-changed-files": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.0.0.tgz", - "integrity": "sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - } - } - }, - "jest-circus": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.1.2.tgz", - "integrity": "sha512-ajQOdxY6mT9GtnfJRZBRYS7toNIJayiiyjDyoZcnvPRUPwJ58JX0ci0PKAKUo2C1RyzlHw0jabjLGKksO42JGA==", - "dev": true, - "requires": { - "@jest/environment": "^29.1.2", - "@jest/expect": "^29.1.2", - "@jest/test-result": "^29.1.2", - "@jest/types": "^29.1.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.1.2", - "jest-matcher-utils": "^29.1.2", - "jest-message-util": "^29.1.2", - "jest-runtime": "^29.1.2", - "jest-snapshot": "^29.1.2", - "jest-util": "^29.1.2", - "p-limit": "^3.1.0", - "pretty-format": "^29.1.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - } - } - }, - "jest-config": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.1.2.tgz", - "integrity": "sha512-EC3Zi86HJUOz+2YWQcJYQXlf0zuBhJoeyxLM6vb6qJsVmpP7KcCP1JnyF0iaqTaXdBP8Rlwsvs7hnKWQWWLwwA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.1.2", - "@jest/types": "^29.1.2", - "babel-jest": "^29.1.2", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.1.2", - "jest-environment-node": "^29.1.2", - "jest-get-type": "^29.0.0", - "jest-regex-util": "^29.0.0", - "jest-resolve": "^29.1.2", - "jest-runner": "^29.1.2", - "jest-util": "^29.1.2", - "jest-validate": "^29.1.2", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.1.2", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - } - } - }, - "jest-diff": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.1.2.tgz", - "integrity": "sha512-4GQts0aUopVvecIT4IwD/7xsBaMhKTYoM4/njE/aVw9wpw+pIUVp8Vab/KnSzSilr84GnLBkaP3JLDnQYCKqVQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.0.0", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.1.2" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-docblock": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.0.0.tgz", - "integrity": "sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.1.2.tgz", - "integrity": "sha512-AmTQp9b2etNeEwMyr4jc0Ql/LIX/dhbgP21gHAizya2X6rUspHn2gysMXaj6iwWuOJ2sYRgP8c1P4cXswgvS1A==", - "dev": true, - "requires": { - "@jest/types": "^29.1.2", - "chalk": "^4.0.0", - "jest-get-type": "^29.0.0", - "jest-util": "^29.1.2", - "pretty-format": "^29.1.2" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-environment-node": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.1.2.tgz", - "integrity": "sha512-C59yVbdpY8682u6k/lh8SUMDJPbOyCHOTgLVVi1USWFxtNV+J8fyIwzkg+RJIVI30EKhKiAGNxYaFr3z6eyNhQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.1.2", - "@jest/fake-timers": "^29.1.2", - "@jest/types": "^29.1.2", - "@types/node": "*", - "jest-mock": "^29.1.2", - "jest-util": "^29.1.2" - } - }, - "jest-get-type": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz", - "integrity": "sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==", - "dev": true - }, - "jest-haste-map": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.1.2.tgz", - "integrity": "sha512-xSjbY8/BF11Jh3hGSPfYTa/qBFrm3TPM7WU8pU93m2gqzORVLkHFWvuZmFsTEBPRKndfewXhMOuzJNHyJIZGsw==", - "dev": true, - "requires": { - "@jest/types": "^29.1.2", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.0.0", - "jest-util": "^29.1.2", - "jest-worker": "^29.1.2", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "jest-leak-detector": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.1.2.tgz", - "integrity": "sha512-TG5gAZJpgmZtjb6oWxBLf2N6CfQ73iwCe6cofu/Uqv9iiAm6g502CAnGtxQaTfpHECBdVEMRBhomSXeLnoKjiQ==", - "dev": true, - "requires": { - "jest-get-type": "^29.0.0", - "pretty-format": "^29.1.2" - } - }, - "jest-matcher-utils": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.1.2.tgz", - "integrity": "sha512-MV5XrD3qYSW2zZSHRRceFzqJ39B2z11Qv0KPyZYxnzDHFeYZGJlgGi0SW+IXSJfOewgJp/Km/7lpcFT+cgZypw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.1.2", - "jest-get-type": "^29.0.0", - "pretty-format": "^29.1.2" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-message-util": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.1.2.tgz", - "integrity": "sha512-9oJ2Os+Qh6IlxLpmvshVbGUiSkZVc2FK+uGOm6tghafnB2RyjKAxMZhtxThRMxfX1J1SOMhTn9oK3/MutRWQJQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.1.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.1.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "jest-mock": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.1.2.tgz", - "integrity": "sha512-PFDAdjjWbjPUtQPkQufvniXIS3N9Tv7tbibePEjIIprzjgo0qQlyUiVMrT4vL8FaSJo1QXifQUOuPH3HQC/aMA==", - "dev": true, - "requires": { - "@jest/types": "^29.1.2", - "@types/node": "*", - "jest-util": "^29.1.2" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true - }, - "jest-regex-util": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz", - "integrity": "sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==", - "dev": true - }, - "jest-resolve": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.1.2.tgz", - "integrity": "sha512-7fcOr+k7UYSVRJYhSmJHIid3AnDBcLQX3VmT9OSbPWsWz1MfT7bcoerMhADKGvKCoMpOHUQaDHtQoNp/P9JMGg==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.1.2", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.1.2", - "jest-validate": "^29.1.2", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "jest-resolve-dependencies": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.1.2.tgz", - "integrity": "sha512-44yYi+yHqNmH3OoWZvPgmeeiwKxhKV/0CfrzaKLSkZG9gT973PX8i+m8j6pDrTYhhHoiKfF3YUFg/6AeuHw4HQ==", - "dev": true, - "requires": { - "jest-regex-util": "^29.0.0", - "jest-snapshot": "^29.1.2" - } - }, - "jest-runner": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.1.2.tgz", - "integrity": "sha512-yy3LEWw8KuBCmg7sCGDIqKwJlULBuNIQa2eFSVgVASWdXbMYZ9H/X0tnXt70XFoGf92W2sOQDOIFAA6f2BG04Q==", - "dev": true, - "requires": { - "@jest/console": "^29.1.2", - "@jest/environment": "^29.1.2", - "@jest/test-result": "^29.1.2", - "@jest/transform": "^29.1.2", - "@jest/types": "^29.1.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.0.0", - "jest-environment-node": "^29.1.2", - "jest-haste-map": "^29.1.2", - "jest-leak-detector": "^29.1.2", - "jest-message-util": "^29.1.2", - "jest-resolve": "^29.1.2", - "jest-runtime": "^29.1.2", - "jest-util": "^29.1.2", - "jest-watcher": "^29.1.2", - "jest-worker": "^29.1.2", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - } - } - }, - "jest-runtime": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.1.2.tgz", - "integrity": "sha512-jr8VJLIf+cYc+8hbrpt412n5jX3tiXmpPSYTGnwcvNemY+EOuLNiYnHJ3Kp25rkaAcTWOEI4ZdOIQcwYcXIAZw==", - "dev": true, - "requires": { - "@jest/environment": "^29.1.2", - "@jest/fake-timers": "^29.1.2", - "@jest/globals": "^29.1.2", - "@jest/source-map": "^29.0.0", - "@jest/test-result": "^29.1.2", - "@jest/transform": "^29.1.2", - "@jest/types": "^29.1.2", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.1.2", - "jest-message-util": "^29.1.2", - "jest-mock": "^29.1.2", - "jest-regex-util": "^29.0.0", - "jest-resolve": "^29.1.2", - "jest-snapshot": "^29.1.2", - "jest-util": "^29.1.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - } - } - }, - "jest-snapshot": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.1.2.tgz", - "integrity": "sha512-rYFomGpVMdBlfwTYxkUp3sjD6usptvZcONFYNqVlaz4EpHPnDvlWjvmOQ9OCSNKqYZqLM2aS3wq01tWujLg7gg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.1.2", - "@jest/transform": "^29.1.2", - "@jest/types": "^29.1.2", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.1.2", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.1.2", - "jest-get-type": "^29.0.0", - "jest-haste-map": "^29.1.2", - "jest-matcher-utils": "^29.1.2", - "jest-message-util": "^29.1.2", - "jest-util": "^29.1.2", - "natural-compare": "^1.4.0", - "pretty-format": "^29.1.2", - "semver": "^7.3.5" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "jest-util": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.1.2.tgz", - "integrity": "sha512-vPCk9F353i0Ymx3WQq3+a4lZ07NXu9Ca8wya6o4Fe4/aO1e1awMMprZ3woPFpKwghEOW+UXgd15vVotuNN9ONQ==", - "dev": true, - "requires": { - "@jest/types": "^29.1.2", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - } - } - }, - "jest-validate": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.1.2.tgz", - "integrity": "sha512-k71pOslNlV8fVyI+mEySy2pq9KdXdgZtm7NHrBX8LghJayc3wWZH0Yr0mtYNGaCU4F1OLPXRkwZR0dBm/ClshA==", - "dev": true, - "requires": { - "@jest/types": "^29.1.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.0.0", - "leven": "^3.1.0", - "pretty-format": "^29.1.2" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-watcher": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.1.2.tgz", - "integrity": "sha512-6JUIUKVdAvcxC6bM8/dMgqY2N4lbT+jZVsxh0hCJRbwkIEnbr/aPjMQ28fNDI5lB51Klh00MWZZeVf27KBUj5w==", - "dev": true, - "requires": { - "@jest/test-result": "^29.1.2", - "@jest/types": "^29.1.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^29.1.2", - "string-length": "^4.0.1" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-worker": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.1.2.tgz", - "integrity": "sha512-AdTZJxKjTSPHbXT/AIOjQVmoFx0LHFcVabWu0sxI7PAy7rFf8c0upyvgBKgguVXdM4vY74JdwkyD4hSmpTW8jA==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.1.2", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==" - }, - "joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "jsx-ast-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", - "integrity": "sha512-HDAyJ4MNQBboGpUnHAVUNJs6X0lh058s6FuixsFGP7MgJYpD6Vasd6nzSG5iIfXu1zAYlHJ/zsOKNlrenTUBnw==", - "dev": true, - "requires": { - "array-includes": "^3.1.4", - "object.assign": "^4.1.2" - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "load-json-file": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", - "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", - "requires": { - "graceful-fs": "^4.1.15", - "parse-json": "^4.0.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0", - "type-fest": "^0.3.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" - }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "dev": true, - "requires": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "mri": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", - "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "nock": { - "version": "13.2.9", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.2.9.tgz", - "integrity": "sha512-1+XfJNYF1cjGB+TKMWi29eZ0b82QOvQs2YoLNzbpWGqFMtRQHTa57osqdGj4FrFPgkO4D4AZinzUJR9VvW3QUA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.21", - "propagate": "^2.0.0" - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", - "dev": true, - "requires": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "octokit-auth-probot": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/octokit-auth-probot/-/octokit-auth-probot-1.2.8.tgz", - "integrity": "sha512-t8K0QYoch70jtNrJ04l3JbRQbQb/FRaKbSOtGLFo7B2yxZuH6RrUOkqod33v89ZiQyKgCYxVal2QMI8RxB0E6A==", - "requires": { - "@octokit/auth-app": "^4.0.2", - "@octokit/auth-token": "^3.0.0", - "@octokit/auth-unauthenticated": "^3.0.0", - "@octokit/types": "^6.1.1" - }, - "dependencies": { - "@octokit/auth-token": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.1.tgz", - "integrity": "sha512-/USkK4cioY209wXRpund6HZzHo9GmjakpV9ycOkpMcMxMk7QVcVFVyCMtzvXYiHsB2crgDgrtNYSELYFBXhhaA==", - "requires": { - "@octokit/types": "^7.0.0" - }, - "dependencies": { - "@octokit/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-7.2.0.tgz", - "integrity": "sha512-pYQ/a1U6mHptwhGyp6SvsiM4bWP2s3V95olUeTxas85D/2kN78yN5C8cGN+P4LwJSWUqIEyvq0Qn2WUn6NQRjw==", - "requires": { - "@octokit/openapi-types": "^13.6.0" - } - } - } - }, - "@octokit/openapi-types": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-13.8.0.tgz", - "integrity": "sha512-m1O4KSRRF5qieJ3MWuLrfseR9XHO0IxBsKVUbZMstbsghQlSPz/aHEgIqCWc4oj3+X/yFZXoXxGQcOhjcvQF1Q==" - }, - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - }, - "dependencies": { - "@octokit/openapi-types": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", - "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" - } - } - } - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dev": true, - "requires": { - "url-parse": "^1.4.3" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "pino": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", - "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==", - "requires": { - "fast-redact": "^3.0.0", - "fast-safe-stringify": "^2.0.8", - "flatstr": "^1.0.12", - "pino-std-serializers": "^3.1.0", - "process-warning": "^1.0.0", - "quick-format-unescaped": "^4.0.3", - "sonic-boom": "^1.0.2" - }, - "dependencies": { - "sonic-boom": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", - "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", - "requires": { - "atomic-sleep": "^1.0.0", - "flatstr": "^1.0.12" - } - } - } - }, - "pino-http": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-5.8.0.tgz", - "integrity": "sha512-YwXiyRb9y0WCD1P9PcxuJuh3Dc5qmXde/paJE86UGYRdiFOi828hR9iUGmk5gaw6NBT9gLtKANOHFimvh19U5w==", - "requires": { - "fast-url-parser": "^1.1.3", - "pino": "^6.13.0", - "pino-std-serializers": "^4.0.0" - }, - "dependencies": { - "pino-std-serializers": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", - "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==" - } - } - }, - "pino-pretty": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-6.0.0.tgz", - "integrity": "sha512-jyeR2fXXWc68st1DTTM5NhkHlx8p+1fKZMfm84Jwq+jSw08IwAjNaZBZR6ts69hhPOfOjg/NiE1HYW7vBRPL3A==", - "requires": { - "@hapi/bourne": "^2.0.0", - "args": "^5.0.1", - "colorette": "^1.3.0", - "dateformat": "^4.5.1", - "fast-safe-stringify": "^2.0.7", - "jmespath": "^0.15.0", - "joycon": "^3.0.0", - "pump": "^3.0.0", - "readable-stream": "^3.6.0", - "rfdc": "^1.3.0", - "split2": "^3.1.1", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "requires": { - "readable-stream": "^3.0.0" - } - } - } - }, - "pino-std-serializers": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", - "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "pkg-conf": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", - "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", - "requires": { - "find-up": "^3.0.0", - "load-json-file": "^5.2.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "pretty-format": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.1.2.tgz", - "integrity": "sha512-CGJ6VVGXVRP2o2Dorl4mAwwvDWT25luIsYhkyVQW32E4nL+TgW939J7LlKT/npq5Cpq6j3s+sy+13yk7xYpBmg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - } - } - }, - "probot": { - "version": "12.2.8", - "resolved": "https://registry.npmjs.org/probot/-/probot-12.2.8.tgz", - "integrity": "sha512-O2WqJmgXUijAOTHaIBEW3jC5OU9Rq+eUg/AEBI5zkAUZaxzJMQvXvGvhVsdFE3Zt9Z3ceGN8Z2cgN+NTItSrCQ==", - "requires": { - "@octokit/core": "^3.2.4", - "@octokit/plugin-enterprise-compatibility": "^1.2.8", - "@octokit/plugin-paginate-rest": "^2.6.2", - "@octokit/plugin-rest-endpoint-methods": "^5.0.1", - "@octokit/plugin-retry": "^3.0.6", - "@octokit/plugin-throttling": "^3.3.4", - "@octokit/types": "^7.1.1", - "@octokit/webhooks": "^9.8.4", - "@probot/get-private-key": "^1.1.0", - "@probot/octokit-plugin-config": "^1.0.0", - "@probot/pino": "^2.2.0", - "@types/express": "^4.17.9", - "@types/ioredis": "^4.27.1", - "@types/pino": "^6.3.4", - "@types/pino-http": "^5.0.6", - "commander": "^6.2.0", - "deepmerge": "^4.2.2", - "deprecation": "^2.3.1", - "dotenv": "^8.2.0", - "eventsource": "^2.0.2", - "express": "^4.17.1", - "express-handlebars": "^6.0.3", - "ioredis": "^4.27.8", - "js-yaml": "^3.14.1", - "lru-cache": "^6.0.0", - "octokit-auth-probot": "^1.2.2", - "pino": "^6.7.0", - "pino-http": "^5.3.0", - "pkg-conf": "^3.1.0", - "resolve": "^1.19.0", - "semver": "^7.3.4", - "update-dotenv": "^1.1.1", - "uuid": "^8.3.2" - }, - "dependencies": { - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "probot-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/probot-config/-/probot-config-1.1.0.tgz", - "integrity": "sha512-FkTWzitDzkmGG3Pt7zLl2sTRpHuRbrUnKUbxs+7ruqoLv2dOEdc+sU1+uk0EP+fRR84sBkQfv8UjGASdeKT5lg==", - "requires": { - "deepmerge": "^2.2.1", - "js-yaml": "^3.10.0" - }, - "dependencies": { - "deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" - } - } - }, - "probot-scheduler": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/probot-scheduler/-/probot-scheduler-2.0.0-beta.1.tgz", - "integrity": "sha512-s22q0+Y+OzdV1f/i0wJw3QkgFSNV5glEBkB7C3aeV1jO3R6J02WqcDGjmJXlRz6XHb8eW9Rg9B9NLHfGR1GJpg==", - "requires": { - "bottleneck": "^2.0.1" - } - }, - "process-warning": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", - "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", - "dev": true - }, - "quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "redis-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, - "redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" - }, - "redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "requires": { - "redis-errors": "^1.0.0" - } - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "requires": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "dependencies": { - "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" - } - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "simple-update-notifier": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", - "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", - "dev": true, - "requires": { - "semver": "~7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "smee-client": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/smee-client/-/smee-client-1.2.3.tgz", - "integrity": "sha512-uDrU8u9/Ln7aRXyzGHgVaNUS8onHZZeSwQjCdkMoSL7U85xI+l+Y2NgjibkMJAyXkW7IAbb8rw9RMHIjS6lAwA==", - "dev": true, - "requires": { - "commander": "^2.19.0", - "eventsource": "^1.1.0", - "morgan": "^1.9.1", - "superagent": "^7.1.3", - "validator": "^13.7.0" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "eventsource": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz", - "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==", - "dev": true, - "requires": { - "original": "^1.0.0" - } - } - } - }, - "sonic-boom": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", - "integrity": "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==", - "requires": { - "atomic-sleep": "^1.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "standard": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/standard/-/standard-17.0.0.tgz", - "integrity": "sha512-GlCM9nzbLUkr+TYR5I2WQoIah4wHA2lMauqbyPLV/oI5gJxqhHzhjl9EG2N0lr/nRqI3KCbCvm/W3smxvLaChA==", - "dev": true, - "requires": { - "eslint": "^8.13.0", - "eslint-config-standard": "17.0.0", - "eslint-config-standard-jsx": "^11.0.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-n": "^15.1.0", - "eslint-plugin-promise": "^6.0.0", - "eslint-plugin-react": "^7.28.0", - "standard-engine": "^15.0.0" - } - }, - "standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" - }, - "standard-engine": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.0.0.tgz", - "integrity": "sha512-4xwUhJNo1g/L2cleysUqUv7/btn7GEbYJvmgKrQ2vd/8pkTmN8cpqAZg+BT8Z1hNeEH787iWUdOpL8fmApLtxA==", - "dev": true, - "requires": { - "get-stdin": "^8.0.0", - "minimist": "^1.2.6", - "pkg-conf": "^3.1.0", - "xdg-basedir": "^4.0.0" - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - }, - "dependencies": { - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - } - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - }, - "superagent": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.1.3.tgz", - "integrity": "sha512-WA6et4nAvgBCS73lJvv1D0ssI5uk5Gh+TGN/kNe+B608EtcVs/yzfl+OLXTzDs7tOBDIpvgh/WUs1K2OK1zTeQ==", - "dev": true, - "requires": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.3", - "debug": "^4.3.4", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.0.1", - "methods": "^1.1.2", - "mime": "^2.5.0", - "qs": "^6.10.3", - "readable-stream": "^3.6.0", - "semver": "^7.3.7" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "uglify-js": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.0.tgz", - "integrity": "sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg==", - "optional": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "dependencies": { - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - } - } - }, - "undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "universal-github-app-jwt": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.0.tgz", - "integrity": "sha512-3b+ocAjjz4JTyqaOT+NNBd5BtTuvJTxWElIoeHSVelUV9J3Jp7avmQTdLKCaoqi/5Ox2o/q+VK19TJ233rVXVQ==", - "requires": { - "@types/jsonwebtoken": "^8.3.3", - "jsonwebtoken": "^8.5.1" - } - }, - "universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "update-dotenv": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-dotenv/-/update-dotenv-1.1.1.tgz", - "integrity": "sha512-3cIC18In/t0X/yH793c00qqxcKD8jVCgNOPif/fGQkFpYMGecM9YAc+kaAKXuZsM2dE9I9wFI7KvAuNX22SGMQ==" - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/src/ProbotMetrics/package.json b/src/ProbotMetrics/package.json deleted file mode 100644 index e18c34cb..00000000 --- a/src/ProbotMetrics/package.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "ProbotMetrics", - "version": "1.0.0", - "private": true, - "description": "Generates a MD file with your DevOps Performance Metrics", - "author": "Chris Hanna ", - "license": "ISC", - "repository": "https://github.com/ChrisHanna/ProbotMetrics.git", - "homepage": "https://github.com/ChrisHanna/ProbotMetrics", - "bugs": "https://github.com/ChrisHanna/ProbotMetrics/issues", - "keywords": [ - "probot", - "github", - "probot-app" - ], - "scripts": { - "dev": "nodemon", - "start": "probot run ./index.js", - "lint": "standard --fix", - "test": "jest && standard", - "test:watch": "jest --watch --notify --notifyMode=change --coverage" - }, - "dependencies": { - "app-root-path": "^3.1.0", - "axios": "^0.27.2", - "fs": "0.0.1-security", - "moment": "^2.29.4", - "probot": "^12.2.8", - "probot-config": "^1.1.0", - "probot-scheduler": "^2.0.0-beta.1" - }, - "devDependencies": { - "jest": "^29.1.2", - "nock": "^13.2.9", - "nodemon": "^2.0.20", - "smee-client": "^1.2.3", - "standard": "^17.0.0" - }, - "engines": { - "node": ">= 8.3.0" - }, - "standard": { - "env": [ - "jest" - ] - }, - "nodemonConfig": { - "exec": "npm start", - "watch": [ - ".env", - "." - ] - }, - "jest": { - "testEnvironment": "node" - } -} diff --git a/src/ProbotMetrics/src/generateDevopsMetrics.js b/src/ProbotMetrics/src/generateDevopsMetrics.js deleted file mode 100644 index 24c66065..00000000 --- a/src/ProbotMetrics/src/generateDevopsMetrics.js +++ /dev/null @@ -1,88 +0,0 @@ -var axios = require('axios'); -const prepareDevOpsMetrics = require('./prepareDevOpsMetrics'); -const getGithubSettings = require('./getGithubSettings'); - -const generateDevopsMetrics = async (context) => { - - let urls = await getGithubSettings(context); - - console.log('generateDevopsMetrics'); - console.log(urls); - - let deployment_frequency_data; - let meantime_restore_data; - let lead_time_change_data; - let change_failure_rate_data; - - var deployment_frequency_config = { - method: 'get', - url: encodeURI(urls.deployment_frequency_url), - headers: { - 'Cookie': 'ARRAffinity=a0e0c7074c47387d9110460567f8cad9199f0fc29dea2a558ac9771a8088f0e7' - } - }; - - await axios(deployment_frequency_config) - .then(function (response) { - deployment_frequency_data = response.data; - }) - .catch(function (error) { - console.log(error); - }); - - var meantime_restore_config = { - method: 'get', - url: encodeURI(urls.meantime_restore_url), - headers: { - 'Cookie': 'ARRAffinity=a0e0c7074c47387d9110460567f8cad9199f0fc29dea2a558ac9771a8088f0e7' - } - }; - - await axios(meantime_restore_config) - .then(function (response) { - meantime_restore_data = response.data; - }) - .catch(function (error) { - console.log(error); - }); - - var lead_time_change_config = { - method: 'get', - url: encodeURI(urls.lead_time_change_url), - headers: { - 'Cookie': 'ARRAffinity=a0e0c7074c47387d9110460567f8cad9199f0fc29dea2a558ac9771a8088f0e7' - } - }; - - await axios(lead_time_change_config) - .then(function (response) { - lead_time_change_data = response.data; - }) - .catch(function (error) { - console.log(error); - }); - - var change_failure_rate_config = { - method: 'get', - url: encodeURI(urls.change_failure_rate_url), - headers: { - 'Cookie': 'ARRAffinity=a0e0c7074c47387d9110460567f8cad9199f0fc29dea2a558ac9771a8088f0e7' - } - }; - - await axios(change_failure_rate_config) - .then(function (response) { - change_failure_rate_data = response.data; - }) - .catch(function (error) { - console.log(error); - }); - - prepareDevOpsMetrics(context, deployment_frequency_data , meantime_restore_data, lead_time_change_data,change_failure_rate_data); - -}; - - -module.exports = { - generateDevopsMetrics, -}; diff --git a/src/ProbotMetrics/src/getGithubSettings.js b/src/ProbotMetrics/src/getGithubSettings.js deleted file mode 100644 index 0fa04e8a..00000000 --- a/src/ProbotMetrics/src/getGithubSettings.js +++ /dev/null @@ -1,71 +0,0 @@ - -var axios = require('axios'); -const getConfig = require('probot-config'); - -module.exports = async (context) => { - - const { owner, repo } = await context.repo(); - - console.log(context.repo()); - - let repo_config = await getConfig(context,'probotmetrics.yml'); - - var config = { - method: 'get', - url: repo_config.github_settings_url, - headers: { - 'Cookie': 'ARRAffinity=a0e0c7074c47387d9110460567f8cad9199f0fc29dea2a558ac9771a8088f0e7' - } - }; - - let deployment_frequency_url = repo_config.deployment_frequency_url; - let meantime_restore_url = repo_config.meantime_restore_url; - let lead_time_change_url = repo_config.lead_time_change_url; - let change_failure_rate_url = repo_config.change_failure_rate_url; - - await axios(config) - .then(function (response) { - console.log("hi"); - var as = Object.values(response.data).filter(n => n.repo == repo); - - deployment_frequency_url += "&clientId=" + as[0]["clientId"]; - deployment_frequency_url += "&clientSecret=" + as[0]["clientSecret"]; - deployment_frequency_url += "&owner=" + as[0]["owner"]; - deployment_frequency_url += "&repo=" + as[0]["repo"]; - deployment_frequency_url += "&branch=" + as[0]["branch"]; - deployment_frequency_url += "&workflowName=" + as[0]["workflowName"]; - deployment_frequency_url += "&workflowId=" + as[0]["workflowId"]; - - lead_time_change_url += "&clientId=" + as[0]["clientId"]; - lead_time_change_url += "&clientSecret=" + as[0]["clientSecret"]; - lead_time_change_url += "&owner=" + as[0]["owner"]; - lead_time_change_url += "&repo=" + as[0]["repo"]; - lead_time_change_url += "&branch=" + as[0]["branch"]; - lead_time_change_url += "&workflowName=" + as[0]["workflowName"]; - lead_time_change_url += "&workflowId=" + as[0]["workflowId"]; - - change_failure_rate_url += "&organization_owner=" + as[0]["owner"]; - change_failure_rate_url += "&project_repo=" + as[0]["repo"]; - change_failure_rate_url += "&branch=" + as[0]["branch"]; - change_failure_rate_url += "&buildName_workflowName=" + as[0]["workflowName"]; - - console.log("deployment_frequency_url"); - console.log(deployment_frequency_url); - - console.log("meantime_restore_url"); - console.log(meantime_restore_url); - - console.log("lead_time_change_url"); - console.log(lead_time_change_url); - - console.log("change_failure_rate_url"); - console.log(change_failure_rate_url); - - }) - .catch(function (error) { - console.log(error); - }); - - return {deployment_frequency_url, meantime_restore_url, lead_time_change_url, change_failure_rate_url}; - -}; diff --git a/src/ProbotMetrics/src/postDevopsMetrics.js b/src/ProbotMetrics/src/postDevopsMetrics.js deleted file mode 100644 index 1fa295aa..00000000 --- a/src/ProbotMetrics/src/postDevopsMetrics.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = async (context, { - owner, repo, title, body, labels, - }) => { - // method is used to create issues - context.log.debug('In postCreateIssues.js...'); - return context.github.issues.create({ - owner, - repo, - title, - body, - labels, - }); - }; - \ No newline at end of file diff --git a/src/ProbotMetrics/src/prepareDevOpsMetrics.js b/src/ProbotMetrics/src/prepareDevOpsMetrics.js deleted file mode 100644 index 824c58ae..00000000 --- a/src/ProbotMetrics/src/prepareDevOpsMetrics.js +++ /dev/null @@ -1,33 +0,0 @@ - -const moment = require('moment'); -const postDevOpsMetrics = require('./postDevOpsMetrics'); -const getConfig = require('probot-config'); - -module.exports = async (context, deployment_frequency_data, meantime_restore_data, lead_time_change_data,change_failure_rate_data) => { - console.log(JSON.stringify(deployment_frequency_data)); - console.log(JSON.stringify(meantime_restore_data)); - const { owner, repo } = context.repo(); - - const title = 'Daily DevOps Metrics for ' + moment().format('DD-MMM-YYYY'); - - let repo_config = await getConfig(context,'probotmetrics.yml'); - - let body = ""; - - body += '## Daily DevOps Metrics for ' + moment().format('DD-MMM-YYYY'); - body += "\n
[![Deployment frequency]("+ deployment_frequency_data["badgeWithMetricURL"] +")](" + repo_config.dashboard_url + owner + "_" + repo + ")"; - body += "\n
[![Lead time for changes]("+ lead_time_change_data["badgeWithMetricURL"] +")](" + repo_config.dashboard_url + owner + "_" + repo + ")"; - body += "\n
[![Time to restore service](" + meantime_restore_data["badgeWithMetricURL"] + ")](" + repo_config.dashboard_url + owner + "_" + repo + ")"; - body += "\n
[![Change failure rate]("+ change_failure_rate_data["badgeWithMetricURL"] +")](" + repo_config.dashboard_url + owner + "_" + repo + ")"; - - body += "\n
**Note:** metrics based on data from the last 30 days"; - body += "\n
_This issue was created with the DevOps metrics probot. Documentation about the metrics and source code is available at [https://github.com/samsmithnz/devopsmetrics](https://github.com/samsmithnz/devopsmetrics)_"; - - const labels = ['daily-devops']; - - await postDevOpsMetrics(context, { - owner, repo, title, body, labels, - }); - -}; - diff --git a/src/ProbotMetrics/test/fixtures/issues.opened.json b/src/ProbotMetrics/test/fixtures/issues.opened.json deleted file mode 100644 index dfd976c6..00000000 --- a/src/ProbotMetrics/test/fixtures/issues.opened.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "action": "opened", - "issue": { - "number": 1, - "user": { - "login": "hiimbex" - } - }, - "repository": { - "name": "testing-things", - "owner": { - "login": "hiimbex" - } - } -} diff --git a/src/ProbotMetrics/test/index.test.js b/src/ProbotMetrics/test/index.test.js deleted file mode 100644 index 5e2923af..00000000 --- a/src/ProbotMetrics/test/index.test.js +++ /dev/null @@ -1,58 +0,0 @@ -const nock = require('nock') -// Requiring our app implementation -const myProbotApp = require('..') -const { Probot } = require('probot') -// Requiring our fixtures -const payload = require('./fixtures/issues.opened') -const issueCreatedBody = { body: 'Thanks for opening this issue!' } -const fs = require('fs') -const path = require('path') - -describe('My Probot app', () => { - let probot - let mockCert - - beforeAll((done) => { - fs.readFile(path.join(__dirname, 'fixtures/mock-cert.pem'), (err, cert) => { - if (err) return done(err) - mockCert = cert - done() - }) - }) - - beforeEach(() => { - nock.disableNetConnect() - probot = new Probot({ id: 123, cert: mockCert }) - // Load our app into probot - probot.load(myProbotApp) - }) - - test('creates a comment when an issue is opened', async () => { - // Test that we correctly return a test token - nock('https://api.github.com') - .post('/app/installations/2/access_tokens') - .reply(200, { token: 'test' }) - - // Test that a comment is posted - nock('https://api.github.com') - .post('/repos/hiimbex/testing-things/issues/1/comments', (body) => { - expect(body).toMatchObject(issueCreatedBody) - return true - }) - .reply(200) - - // Receive a webhook event - await probot.receive({ name: 'issues', payload }) - }) - - afterEach(() => { - nock.cleanAll() - nock.enableNetConnect() - }) -}) - -// For more information about testing with Jest see: -// https://facebook.github.io/jest/ - -// For more information about testing with Nock see: -// https://github.com/nock/nock