diff --git a/ChangesService.Test/ChangesService.Test.csproj b/ChangesService.Test/ChangesService.Test.csproj
index 9385eb0d7..7a0b68cd3 100644
--- a/ChangesService.Test/ChangesService.Test.csproj
+++ b/ChangesService.Test/ChangesService.Test.csproj
@@ -36,7 +36,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/CodeSnippetsPipeline.Test/CodeSnippetsPipeline.Test.csproj b/CodeSnippetsPipeline.Test/CodeSnippetsPipeline.Test.csproj
index 214f1dfca..42b57fd9c 100644
--- a/CodeSnippetsPipeline.Test/CodeSnippetsPipeline.Test.csproj
+++ b/CodeSnippetsPipeline.Test/CodeSnippetsPipeline.Test.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/CodeSnippetsReflection.OData.Test/CodeSnippetsReflection.OData.Test.csproj b/CodeSnippetsReflection.OData.Test/CodeSnippetsReflection.OData.Test.csproj
index 58f59f96d..e511f7d43 100644
--- a/CodeSnippetsReflection.OData.Test/CodeSnippetsReflection.OData.Test.csproj
+++ b/CodeSnippetsReflection.OData.Test/CodeSnippetsReflection.OData.Test.csproj
@@ -12,7 +12,7 @@
all
-
+
all
diff --git a/CodeSnippetsReflection.OData/CodeSnippetsReflection.OData.csproj b/CodeSnippetsReflection.OData/CodeSnippetsReflection.OData.csproj
index 430ae3f54..cffdd8745 100644
--- a/CodeSnippetsReflection.OData/CodeSnippetsReflection.OData.csproj
+++ b/CodeSnippetsReflection.OData/CodeSnippetsReflection.OData.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/CodeSnippetsReflection.OpenAPI.Test/CSharpGeneratorTests.cs b/CodeSnippetsReflection.OpenAPI.Test/CSharpGeneratorTests.cs
index eb620e3e1..35037f88f 100644
--- a/CodeSnippetsReflection.OpenAPI.Test/CSharpGeneratorTests.cs
+++ b/CodeSnippetsReflection.OpenAPI.Test/CSharpGeneratorTests.cs
@@ -731,6 +731,69 @@ public async Task GeneratesCorrectCollectionTypeAndDerivedInstances() {
Assert.Contains("new FileAttachment", result);// Individual items are derived types
Assert.Contains("ContentBytes = Convert.FromBase64String(\"SGVsbG8gV29ybGQh\"),", result);
}
+ [Fact]
+ public async Task GeneratesPropertiesWithSpecialCharacters() {
+ var sampleJson = @"{
+ ""@odata.type"": ""#microsoft.graph.managedIOSLobApp"",
+ ""displayName"": ""Display Name value"",
+ ""description"": ""Description value"",
+ ""publisher"": ""Publisher value"",
+ ""largeIcon"": {
+ ""@odata.type"": ""microsoft.graph.mimeContent"",
+ ""type"": ""Type value"",
+ ""value"": ""dmFsdWU=""
+ },
+ ""isFeatured"": true,
+ ""privacyInformationUrl"": ""https://example.com/privacyInformationUrl/"",
+ ""informationUrl"": ""https://example.com/informationUrl/"",
+ ""owner"": ""Owner value"",
+ ""developer"": ""Developer value"",
+ ""notes"": ""Notes value"",
+ ""uploadState"": 11,
+ ""publishingState"": ""processing"",
+ ""isAssigned"": true,
+ ""roleScopeTagIds"": [
+ ""Role Scope Tag Ids value""
+ ],
+ ""dependentAppCount"": 1,
+ ""supersedingAppCount"": 3,
+ ""supersededAppCount"": 2,
+ ""appAvailability"": ""lineOfBusiness"",
+ ""version"": ""Version value"",
+ ""committedContentVersion"": ""Committed Content Version value"",
+ ""fileName"": ""File Name value"",
+ ""size"": 4,
+ ""bundleId"": ""Bundle Id value"",
+ ""applicableDeviceType"": {
+ ""@odata.type"": ""microsoft.graph.iosDeviceType"",
+ ""iPad"": true,
+ ""iPhoneAndIPod"": true
+ },
+ ""minimumSupportedOperatingSystem"": {
+ ""@odata.type"": ""microsoft.graph.iosMinimumOperatingSystem"",
+ ""v8_0"": true,
+ ""v9_0"": true,
+ ""v10_0"": true,
+ ""v11_0"": true,
+ ""v12_0"": true,
+ ""v13_0"": true,
+ ""v14_0"": true,
+ ""v15_0"": true,
+ ""v16_0"": true
+ },
+ ""expirationDateTime"": ""2016-12-31T23:57:57.2481234-08:00"",
+ ""versionNumber"": ""Version Number value"",
+ ""buildNumber"": ""Build Number value"",
+ ""identityVersion"": ""Identity Version value""
+ }";
+ using var requestPayload = new HttpRequestMessage(HttpMethod.Patch, $"{ServiceRootBetaUrl}/deviceAppManagement/mobileApps/{{mobileAppId}}"){
+ Content = new StringContent(sampleJson, Encoding.UTF8, "application/json")
+ };
+ var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
+ var result = _generator.GenerateCodeSnippet(snippetModel);
+ Assert.Contains("MinimumSupportedOperatingSystem = new IosMinimumOperatingSystem", result);
+ Assert.Contains("V80 = true,", result);//Assert that the property was pascal cased
+ }
[Fact]
public async Task CorrectlyHandlesTypeFromInUrl()
diff --git a/CodeSnippetsReflection.OpenAPI.Test/CodeSnippetsReflection.OpenAPI.Test.csproj b/CodeSnippetsReflection.OpenAPI.Test/CodeSnippetsReflection.OpenAPI.Test.csproj
index 433bfeb64..ee520909f 100644
--- a/CodeSnippetsReflection.OpenAPI.Test/CodeSnippetsReflection.OpenAPI.Test.csproj
+++ b/CodeSnippetsReflection.OpenAPI.Test/CodeSnippetsReflection.OpenAPI.Test.csproj
@@ -11,7 +11,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/CodeSnippetsReflection.OpenAPI.Test/GoGeneratorTests.cs b/CodeSnippetsReflection.OpenAPI.Test/GoGeneratorTests.cs
index 93dcbfc5c..bf9788ddf 100644
--- a/CodeSnippetsReflection.OpenAPI.Test/GoGeneratorTests.cs
+++ b/CodeSnippetsReflection.OpenAPI.Test/GoGeneratorTests.cs
@@ -97,7 +97,7 @@ public async Task GeneratesTheSnippetHeader() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me/messages");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
var result = _generator.GenerateCodeSnippet(snippetModel);
- Assert.Contains("graphClient, err := msgraphsdk.NewGraphServiceClientWithCredentials(cred, scopes)", result);
+ Assert.Contains("graphClient := msgraphsdk.NewGraphServiceClientWithCredentials(cred, scopes)", result);
}
[Fact]
public async Task GeneratesMultipleImportStatements()
diff --git a/CodeSnippetsReflection.OpenAPI.Test/GraphCliGeneratorTests.cs b/CodeSnippetsReflection.OpenAPI.Test/GraphCliGeneratorTests.cs
index 3a520b209..ace22bd08 100644
--- a/CodeSnippetsReflection.OpenAPI.Test/GraphCliGeneratorTests.cs
+++ b/CodeSnippetsReflection.OpenAPI.Test/GraphCliGeneratorTests.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using CodeSnippetsReflection.OpenAPI.Test;
@@ -408,4 +408,122 @@ public async Task GeneratesEscapedSnippetsForMultilineCommand()
// Then
Assert.Equal("mgc users create --body '{\\\n \"name\": \"test\"\\\n}'", result);
}
+
+ [Fact]
+ public async Task GeneratesSnippetsContainingOverLoadedBoundFunctionsWithDateParameter()
+ {
+ // Given
+ string url = $"{ServiceRootUrl}/reports/getYammerDeviceUsageUserDetail(date=2018-03-05)";
+ using var requestPayload = new HttpRequestMessage(HttpMethod.Get, url);
+ var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
+
+ // When
+ var result = _generator.GenerateCodeSnippet(snippetModel);
+
+ // Then
+ Assert.Equal("mgc reports get-yammer-device-usage-user-detail-with-date get --date {date-id}", result);
+ }
+
+ [Fact]
+ public async Task GeneratesSnippetsContainingOverLoadedBoundFunctionsWithDateParameterWithSingleOrDoubleQuotes()
+ {
+ // Given
+ string url = $"{ServiceRootUrl}/reports/getYammerDeviceUsageUserDetail(date='2018-03-05')";
+ using var requestPayload = new HttpRequestMessage(HttpMethod.Get, url);
+ var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
+
+ // When
+ var result = _generator.GenerateCodeSnippet(snippetModel);
+
+ // Then
+ Assert.Equal("mgc reports get-yammer-device-usage-user-detail-with-date get --date '{date-id}'", result);
+ }
+
+ [Fact]
+ public async Task GeneratesSnippetsContainingOverLoadedBoundFunctionsWithNonDateParameter()
+ {
+ // Given
+ string url = $"{ServiceRootUrl}/drives/driveid/items/driveitemid/delta(token='token')";
+ using var requestPayload = new HttpRequestMessage(HttpMethod.Get, url);
+ var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
+
+ // When
+ var result = _generator.GenerateCodeSnippet(snippetModel);
+
+ // Then
+ Assert.Equal("mgc drives items delta-with-token get --token '{token-id}' --drive-id {drive-id} --drive-item-id {driveItem-id}", result);
+ }
+
+ [Fact]
+ public async Task GeneratesSnippetsContainingUnBoundedFunctions()
+ {
+ // Given
+ string url = $"{ServiceRootUrl}/identity/identityProviders/availableProviderTypes()";
+ using var requestPayload = new HttpRequestMessage(HttpMethod.Get, url);
+ var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
+
+ // When
+ var result = _generator.GenerateCodeSnippet(snippetModel);
+
+ // Then
+ Assert.Equal("mgc identity identity-providers available-provider-types get", result);
+ }
+
+ [Fact]
+ public async Task GeneratesSnippetsWithSlashMeEndpoints()
+ {
+ // Given
+ string url = $"{ServiceRootUrl}/me/calendar/events?$filter=startsWith%28subject%2C%27All%27%29";
+ using var requestPayload = new HttpRequestMessage(HttpMethod.Get, url);
+ var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
+
+ // When
+ var result = _generator.GenerateCodeSnippet(snippetModel);
+
+ // Then
+ Assert.Equal("mgc users calendar events list --user-id {user-id} --filter \"startsWith(subject,'All')\"", result);
+ }
+ [Fact]
+ public async Task GeneratesSnippetsWithExpandQueryOptions()
+ {
+ // Given
+ string url = $"{ServiceRootUrl}/me/messages/XXXX?$expand=singleValueExtendedProperties%28$filter%3Did%20eq%20%27XXXX%27%29";
+ using var requestPayload = new HttpRequestMessage(HttpMethod.Get, url);
+ var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
+
+ // When
+ var result = _generator.GenerateCodeSnippet(snippetModel);
+
+ // Then
+ Assert.Equal("mgc users messages get --user-id {user-id} --message-id {message-id} --expand \"singleValueExtendedProperties(`$filter=id eq 'XXXX')\"", result);
+ }
+ [Fact]
+ public async Task GeneratesSnippetsWithFilterQueryOptions()
+ {
+ // Given
+ string url = $"{ServiceRootUrl}/identityGovernance/accessReviews/definitions?$filter=contains%28scope%2Fmicrosoft.graph.accessReviewQueryScope%2Fquery%2C%20%27.%2Fmembers%27%29";
+ using var requestPayload = new HttpRequestMessage(HttpMethod.Get, url);
+ var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
+
+ // When
+ var result = _generator.GenerateCodeSnippet(snippetModel);
+
+ // Then
+ Assert.Equal("mgc identity-governance access-reviews definitions list --filter \"contains(scope/microsoft.graph.accessReviewQueryScope/query, './members')\"", result);
+ }
+
+ [Fact]
+ public async Task GeneratesSnippetsForHttpSnippetsWithUrlEncodedValuesForSystemQueryOptionParameters()
+ {
+ // Given
+ string url = $"{ServiceRootUrl}/teams/XXXXXXX/members?$filter=%28microsoft.graph.aadUserConversationMember%2FdisplayName%2520eq%2520%27Harry%2520Johnson%27%2520or%2520microsoft.graph.aadUserConversationMember%2Femail%2520eq%2520%27admin%40M365x987948.OnMicrosoft.com%27%29";
+ using var requestPayload = new HttpRequestMessage(HttpMethod.Get, url);
+ var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1SnippetMetadata());
+
+ // When
+ var result = _generator.GenerateCodeSnippet(snippetModel);
+
+ // Then
+ Assert.Equal("mgc teams members list --team-id {team-id} --filter \"(microsoft.graph.aadUserConversationMember/displayName eq 'Harry Johnson' or microsoft.graph.aadUserConversationMember/email eq 'admin@M365x987948.OnMicrosoft.com')\"", result);
+ }
}
diff --git a/CodeSnippetsReflection.OpenAPI/LanguageGenerators/CSharpGenerator.cs b/CodeSnippetsReflection.OpenAPI/LanguageGenerators/CSharpGenerator.cs
index 2e82988e5..7d7ca5998 100644
--- a/CodeSnippetsReflection.OpenAPI/LanguageGenerators/CSharpGenerator.cs
+++ b/CodeSnippetsReflection.OpenAPI/LanguageGenerators/CSharpGenerator.cs
@@ -167,7 +167,7 @@ private static void WriteObjectFromCodeProperty(CodeProperty parentProperty, Cod
var isParentArray = parentProperty.PropertyType == PropertyType.Array;
var isParentMap = parentProperty.PropertyType == PropertyType.Map;
var assignmentSuffix = isParentMap ? string.Empty : ","; // no comma separator values for additionalData/maps
- var propertyAssignment = $"{indentManager.GetIndent()}{codeProperty.Name.CleanupSymbolName().ToFirstCharacterUpperCase()} = "; // default assignments to the usual "var x = xyz"
+ var propertyAssignment = $"{indentManager.GetIndent()}{codeProperty.Name.CleanupSymbolName().ToPascalCase()} = "; // default assignments to the usual "var x = xyz"
if (isParentMap)
{
propertyAssignment = $"{indentManager.GetIndent()}\"{codeProperty.Name}\" , "; // if its in the additionalData assignments happen using string value keys
diff --git a/CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs b/CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs
index ba7960948..14b14f7fa 100644
--- a/CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs
+++ b/CodeSnippetsReflection.OpenAPI/LanguageGenerators/GoGenerator.cs
@@ -184,7 +184,7 @@ private static Boolean searchProperty(CodeProperty property, PropertyType proper
private static void writeSnippet(SnippetCodeGraph codeGraph, StringBuilder builder)
{
- builder.AppendLine($"{clientVarName}, err := msgraphsdk.New{clientVarType}({clientFactoryVariables}){Environment.NewLine}{Environment.NewLine}");
+ builder.AppendLine($"{clientVarName} := msgraphsdk.New{clientVarType}({clientFactoryVariables}){Environment.NewLine}{Environment.NewLine}");
writeHeadersAndOptions(codeGraph, builder);
WriteBody(codeGraph, builder);
builder.AppendLine("");
diff --git a/CodeSnippetsReflection.OpenAPI/LanguageGenerators/GraphCliGenerator.cs b/CodeSnippetsReflection.OpenAPI/LanguageGenerators/GraphCliGenerator.cs
index b1d757499..376180d91 100644
--- a/CodeSnippetsReflection.OpenAPI/LanguageGenerators/GraphCliGenerator.cs
+++ b/CodeSnippetsReflection.OpenAPI/LanguageGenerators/GraphCliGenerator.cs
@@ -4,6 +4,7 @@
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
+using System.Web;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Services;
@@ -13,6 +14,11 @@ public partial class GraphCliGenerator : ILanguageGenerator
+ /// Checks for segments that have unbound functions
+ /// Example "identity-providers available-provider-types() get"
+ /// will be reconstructed to identity-providers available-provider-types get
+ ///
+ ///
+ private static void FetchUnBoundFunctions(List commandSegments)
+ {
+ int unboundedFunctionIndex = commandSegments.FindIndex(static u => unBoundFunctionRegex.IsMatch(u));
+ if (unboundedFunctionIndex != -1)
+ {
+ var segment = commandSegments[unboundedFunctionIndex];
+ commandSegments[unboundedFunctionIndex] = segment.Replace("(", "").Replace(")", "");
+ }
+ }
+
+ ///
+ /// Checks for segments that have overloaded bound functions with date parameter
+ /// Example of such a segment would be: getYammerDeviceUsageUserDetail(date=2018-03-05).
+ /// ProcessOverloadedBoundFunction is called to reconstruct the segment to the expected command segment.
+ ///
+ ///
+ ///
+ private static void FetchOverLoadedBoundFunctions(List commandSegments, string operationName, SnippetModel snippetModel)
+ {
+ int boundedFunctionIndex = commandSegments.FindIndex(static u => overloadedBoundedFunctionWithDateRegex.IsMatch(u)
+ || overloadedBoundedFunctionWithNoneDateRegex.IsMatch(u));
+
+ if (boundedFunctionIndex != -1)
+ {
+ int operationIndex = commandSegments.FindIndex(o => operationName.Equals(o, StringComparison.OrdinalIgnoreCase));
+ var (updatedSegment, updatedOperation) = ProcessOverloadedBoundFunctions(commandSegments[boundedFunctionIndex], operationName, snippetModel);
+ commandSegments[boundedFunctionIndex] = updatedSegment;
+ commandSegments[operationIndex] = updatedOperation;
+ }
+ }
+
+ ///
+ /// Reconstructs segments with overloaded bound functions to the expected command segments.
+ /// For example; "get-yammer-device-usage-user-detail(date={date})" get will be reconstructed to
+ /// "get-yammer-device-usage-user-detail-with-date get --date {date_id}" as expected by cli for it
+ /// to execute successfully.
+ ///
+ ///
+ ///
+ ///
+ private static (string,string) ProcessOverloadedBoundFunctions(string segment, string operation, SnippetModel snippetModel)
+ {
+ var functionItems = segment.Split("(");
+ var functionParams = functionItems[1];
+ var functionName = functionItems[0];
+ var parameter = functionParams.Split("=")[0];
+ var updatedSegment = $"{functionName}-with-{parameter}";
+ return apiPathWithSingleOrDoubleQuotesOnFunctions.IsMatch(snippetModel.Path) ? (updatedSegment,$"{operation} --{parameter}"+" '{"+parameter+"-id}'")
+ : (updatedSegment, $"{operation} --{parameter}" + " {" + parameter + "-id}");
+ }
+
+ ///
+ /// Replaces /me endpoints with users and appends --user-id parameter to the command
+ /// See issue: https://github.com/microsoftgraph/msgraph-cli/issues/278
+ ///
+ ///
+ ///
+ private static void ProcessMeSegments(List commandSegments, string operationName)
+ {
+ if (commandSegments[1].Equals("me", StringComparison.OrdinalIgnoreCase))
+ {
+ commandSegments[1] = "users";
+ int operationIndex = commandSegments.FindIndex(o => o.Equals(operationName, StringComparison.OrdinalIgnoreCase));
+ commandSegments[operationIndex] = $"{operationName} --user-id {{user-id}}";
+ }
}
private static IDictionary ProcessHeaderParameters([NotNull] in SnippetModel snippetModel)
@@ -138,16 +222,30 @@ private static IDictionary ProcessQueryParameters([NotNull] in S
IDictionary splitQueryString = new Dictionary();
if (!string.IsNullOrWhiteSpace(snippetModel.QueryString))
{
- splitQueryString = snippetModel.QueryString
- .Remove(0, 1)
- .Split('&')
- .Select(q =>
- {
- var x = q.Split('=');
- return x.Length > 1 ? (x[0], x[1]) : (x[0], string.Empty);
- })
- .Where(t => !string.IsNullOrWhiteSpace(t.Item2))
- .ToDictionary(t => t.Item1, t => t.Item2);
+ if (systemQueryOptionRegex.IsMatch(snippetModel.QueryString))
+ {
+ string pattern = "\\?\\$\\w*=";
+ string[] splittedQueryString = Regex.Split(snippetModel.QueryString, pattern, RegexOptions.Compiled, TimeSpan.FromSeconds(5));
+ string queryOptionFunction = HttpUtility.UrlDecode(splittedQueryString[1]);
+ var match = Regex.Match(snippetModel.QueryString, pattern, RegexOptions.Compiled, TimeSpan.FromSeconds(5));
+ string queryOption = match.Groups[0].Value.Replace("?", string.Empty, StringComparison.OrdinalIgnoreCase).Replace("=", string.Empty, StringComparison.OrdinalIgnoreCase);
+ queryOptionFunction = queryOptionFunction.Replace("$", "`$", StringComparison.OrdinalIgnoreCase);
+ queryOptionFunction = queryOptionFunction.Replace(queryOptionFunction, "\"" + queryOptionFunction + "\"", StringComparison.Ordinal);
+ splitQueryString.Add(queryOption, queryOptionFunction);
+ }
+ else
+ {
+ splitQueryString = snippetModel.QueryString
+ .Remove(0, 1)
+ .Split('&')
+ .Select(static q =>
+ {
+ var x = q.Split('=');
+ return x.Length > 1 ? (x[0], x[1]) : (x[0], string.Empty);
+ })
+ .Where(static t => !string.IsNullOrWhiteSpace(t.Item2))
+ .ToDictionary(static t => t.Item1, static t => t.Item2);
+ }
}
return splitQueryString;
diff --git a/CodeSnippetsReflection.OpenAPI/OpenAPISnippetsGenerator.cs b/CodeSnippetsReflection.OpenAPI/OpenAPISnippetsGenerator.cs
index f2c72be5a..ba5125da6 100644
--- a/CodeSnippetsReflection.OpenAPI/OpenAPISnippetsGenerator.cs
+++ b/CodeSnippetsReflection.OpenAPI/OpenAPISnippetsGenerator.cs
@@ -70,7 +70,8 @@ public string ProcessPayloadRequest(HttpRequestMessage requestPayload, string la
"go",
"powershell",
"php",
- "python"
+ "python",
+ "cli"
};
private static ILanguageGenerator GetLanguageGenerator(string language) {
return language.ToLowerInvariant() switch {
@@ -80,6 +81,7 @@ private static ILanguageGenerator GetLanguageG
"powershell" => new PowerShellGenerator(),
"php" => new PhpGenerator(),
"python" => new PythonGenerator(),
+ "cli" => new GraphCliGenerator(),
_ => throw new ArgumentOutOfRangeException($"Language '{language}' is not supported"),
};
}
diff --git a/CodeSnippetsReflection/StringExtensions/StringExtensions.cs b/CodeSnippetsReflection/StringExtensions/StringExtensions.cs
index 404c0ff38..61aecfdfa 100644
--- a/CodeSnippetsReflection/StringExtensions/StringExtensions.cs
+++ b/CodeSnippetsReflection/StringExtensions/StringExtensions.cs
@@ -41,13 +41,15 @@ public static string ToFirstCharacterUpperCaseAfterCharacter(this string stringV
public static string ToPascalCase(this string str)
{
+ if (string.IsNullOrEmpty(str)) return str;
string[] words = str.Split('_');
- string pascalCaseString = string.Join("", words.Select(w => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(w)));
+ string pascalCaseString = string.Join("", words.Select(ToFirstCharacterUpperCase));
return pascalCaseString;
}
public static string ToSnakeCase(this string str)
{
+ if (string.IsNullOrEmpty(str)) return str;
StringBuilder snakeCaseBuilder = new StringBuilder();
for (int i = 0; i < str.Length; i++)
{
diff --git a/ExceptionMiddleware/ExceptionMiddleware.Test.csproj b/ExceptionMiddleware/ExceptionMiddleware.Test.csproj
index b50c5d89f..9f3146e3a 100644
--- a/ExceptionMiddleware/ExceptionMiddleware.Test.csproj
+++ b/ExceptionMiddleware/ExceptionMiddleware.Test.csproj
@@ -11,7 +11,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/FileService.Test/FileService.Test.csproj b/FileService.Test/FileService.Test.csproj
index 36858efa7..c67f1823f 100644
--- a/FileService.Test/FileService.Test.csproj
+++ b/FileService.Test/FileService.Test.csproj
@@ -11,7 +11,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/GraphWebApi/Controllers/KnownIssuesController.cs b/GraphWebApi/Controllers/KnownIssuesController.cs
index 824223a7f..6e90cdd98 100644
--- a/GraphWebApi/Controllers/KnownIssuesController.cs
+++ b/GraphWebApi/Controllers/KnownIssuesController.cs
@@ -7,7 +7,6 @@
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.AspNetCore.Mvc;
-using Newtonsoft.Json;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
diff --git a/GraphWebApi/Controllers/PermissionsController.cs b/GraphWebApi/Controllers/PermissionsController.cs
index 667691f42..4dbf027d6 100644
--- a/GraphWebApi/Controllers/PermissionsController.cs
+++ b/GraphWebApi/Controllers/PermissionsController.cs
@@ -42,7 +42,7 @@ public PermissionsController(IPermissionsStore permissionsStore, TelemetryClient
// Gets the permissions scopes
[HttpGet]
[Produces("application/json")]
- public async Task GetPermissionScopes([FromQuery]ScopeType scopeType = ScopeType.DelegatedWork,
+ public async Task GetPermissionScopes([FromQuery]ScopeType? scopeType = null,
[FromQuery]string requestUrl = null,
[FromQuery]string method = null,
[FromQuery]string org = null,
diff --git a/KnownIssuesService.Test/KnownIssuesService.Test.csproj b/KnownIssuesService.Test/KnownIssuesService.Test.csproj
index 0aafa45be..d8e46d1e6 100644
--- a/KnownIssuesService.Test/KnownIssuesService.Test.csproj
+++ b/KnownIssuesService.Test/KnownIssuesService.Test.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/KnownIssuesService.Test/KnownIssuesServiceShould.cs b/KnownIssuesService.Test/KnownIssuesServiceShould.cs
index acea5de75..73e34fb80 100644
--- a/KnownIssuesService.Test/KnownIssuesServiceShould.cs
+++ b/KnownIssuesService.Test/KnownIssuesServiceShould.cs
@@ -78,7 +78,8 @@ public async Task QueryBugs()
Link = "/foo/bar",
CreatedDateTime = DateTime.Parse("01/06/2022 00:00:00"),
LastUpdatedDateTime = DateTime.Parse("01/07/2022 00:00:00"),
- SubArea = "Test notifications"
+ SubArea = "Test notifications",
+ IsPublicIssue = true
};
//Act
@@ -99,7 +100,7 @@ public async Task QueryBugs()
Assert.Equal(contract.WorkAround, items[1].WorkAround);
Assert.Equal(contract.CreatedDateTime, items[1].CreatedDateTime);
Assert.Equal(contract.LastUpdatedDateTime, items[1].LastUpdatedDateTime);
- Assert.True(items[1].IsUpdated);
+ Assert.True(items[1].IsDateUpdated);
}
}
}
diff --git a/KnownIssuesService.Test/WorkItemsStubData.cs b/KnownIssuesService.Test/WorkItemsStubData.cs
index 3779ad38f..9aec0e292 100644
--- a/KnownIssuesService.Test/WorkItemsStubData.cs
+++ b/KnownIssuesService.Test/WorkItemsStubData.cs
@@ -34,7 +34,8 @@ public static List GetWorkItems()
{"System.State","Active"},
{"System.Title","Issue A"},
{"Custom.MSGraphM365Workload","Calendar"},
- {"Custom.Workaround","Test"}
+ {"Custom.Workaround","Test"},
+ {"Custom.PublicIssue", true}
}
},
new WorkItem(){ Id = 9076 , Url = "https://microsoftgraph.visualstudio.com/_apis/wit/workItems/9076",
@@ -45,7 +46,8 @@ public static List GetWorkItems()
{"Custom.Workaround","Test"},
{"Custom.APIPathLink", "/foo/bar"},
{"Custom.Dateissuewasraised", DateTime.Parse("01/06/2022 00:00:00")},
- {"Custom.Lastupdate", DateTime.Parse("01/07/2022 00:00:00")}
+ {"Custom.Lastupdate", DateTime.Parse("01/07/2022 00:00:00")},
+ {"Custom.PublicIssue", true}
}
},
@@ -54,7 +56,8 @@ public static List GetWorkItems()
{"System.State","Resolved"},
{"System.Title","Issue K"},
{"Custom.MSGraphM365Workload","Mail"},
- {"Custom.Workaround","Limit number of requests"}
+ {"Custom.Workaround","Limit number of requests"},
+ {"Custom.PublicIssue", true}
}
},
new WorkItem(){ Id = 9078 , Url = "https://microsoftgraph.visualstudio.com/_apis/wit/workItems/9078",
@@ -62,7 +65,8 @@ public static List GetWorkItems()
{"System.State","New"},
{"System.Title","Issue F"},
{"Custom.MSGraphM365Workload","Mail"},
- {"Custom.Workaround","Limit number of requests"}
+ {"Custom.Workaround","Limit number of requests"},
+ {"Custom.PublicIssue", false}
}
},
new WorkItem()
@@ -71,7 +75,8 @@ public static List GetWorkItems()
{"System.State","New"},
{"System.Title","Issue A"},
{"Custom.MSGraphM365Workload","Calendar"},
- {"Custom.Workaround","Test"}
+ {"Custom.Workaround","Test"},
+ {"Custom.PublicIssue", true}
}
}
};
diff --git a/KnownIssuesService/Models/KnownIssue.cs b/KnownIssuesService/Models/KnownIssue.cs
index c25926aeb..68330c60c 100644
--- a/KnownIssuesService/Models/KnownIssue.cs
+++ b/KnownIssuesService/Models/KnownIssue.cs
@@ -64,8 +64,8 @@ public record KnownIssue
///
/// Determines if the Last Update is current compared to the Created date
///
- [JsonProperty(nameof(IsUpdated))]
- public bool IsUpdated => LastUpdatedDateTime > CreatedDateTime;
+ [JsonProperty(nameof(IsDateUpdated))]
+ public bool IsDateUpdated => LastUpdatedDateTime > CreatedDateTime;
///
/// Known Issues Status i.e New, Active,Resolved
@@ -81,5 +81,13 @@ public string SubArea
{
get; set;
}
+
+ ///
+ /// Microsoft Graph Issue Visibility
+ ///
+ public Boolean IsPublicIssue
+ {
+ get; set;
+ }
}
}
diff --git a/KnownIssuesService/Services/KnownIssuesService.cs b/KnownIssuesService/Services/KnownIssuesService.cs
index a95946a68..eb9fe662a 100644
--- a/KnownIssuesService/Services/KnownIssuesService.cs
+++ b/KnownIssuesService/Services/KnownIssuesService.cs
@@ -172,12 +172,13 @@ public async Task> QueryBugsAsync(string environment, Wiql work
Link = x.Fields.TryGetValue("Custom.APIPathLink", out var link) ? link.ToString() : default,
CreatedDateTime = x.Fields.TryGetValue("Custom.Dateissuewasraised", out DateTime createdDate) ? createdDate : default,
LastUpdatedDateTime = x.Fields.TryGetValue("Custom.Lastupdate", out DateTime changedDate) ? changedDate : default,
- SubArea = x.Fields.TryGetValue("Custom.MicrosoftGraphSubarea", out var subArea) ? subArea.ToString() : default
+ SubArea = x.Fields.TryGetValue("Custom.MicrosoftGraphSubarea", out var subArea) ? subArea.ToString() : default,
+ IsPublicIssue = x.Fields.TryGetValue("Custom.PublicIssue", out bool publicIssue) ? publicIssue : default
}).ToList();
foreach(var knownIssue in _knownIssuesList.ToList())
{
- if(knownIssue.State == "New" || knownIssue.State == "Closed")
+ if(knownIssue.State == "New" || knownIssue.State == "Closed" || !knownIssue.IsPublicIssue)
{
_knownIssuesList.Remove(knownIssue);
}
diff --git a/OpenAPIService.Test/OpenAPIService.Test.csproj b/OpenAPIService.Test/OpenAPIService.Test.csproj
index d1d28eb46..370a5d22f 100644
--- a/OpenAPIService.Test/OpenAPIService.Test.csproj
+++ b/OpenAPIService.Test/OpenAPIService.Test.csproj
@@ -26,7 +26,7 @@
all
-
+
diff --git a/OpenAPIService/OpenAPIService.csproj b/OpenAPIService/OpenAPIService.csproj
index 64f56c116..af204e887 100644
--- a/OpenAPIService/OpenAPIService.csproj
+++ b/OpenAPIService/OpenAPIService.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/PermissionsService.Test/PermissionsService.Test.csproj b/PermissionsService.Test/PermissionsService.Test.csproj
index 40abf80d2..bfea23d2a 100644
--- a/PermissionsService.Test/PermissionsService.Test.csproj
+++ b/PermissionsService.Test/PermissionsService.Test.csproj
@@ -33,7 +33,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
all
diff --git a/PermissionsService.Test/PermissionsStoreShould.cs b/PermissionsService.Test/PermissionsStoreShould.cs
index fc15b716b..a982a70a4 100644
--- a/PermissionsService.Test/PermissionsStoreShould.cs
+++ b/PermissionsService.Test/PermissionsStoreShould.cs
@@ -363,25 +363,38 @@ public async Task ReturnLocalizedPermissionsDescriptionsForSupportedLanguage()
}
[Fact]
- public async Task ReturnsErrorsForEmptyOrNullRequestUrls()
+ public async Task ReturnsErrorsForEmptyRequestUrl()
{
// Act
PermissionResult result =
await _permissionsStore.GetScopesAsync(
- requests: new List() {
- new RequestInfo { RequestUrl = "", HttpMethod = "GET" },
- new RequestInfo { RequestUrl = null, HttpMethod = "GET" } }
- );
+ requests: new List() {
+ new RequestInfo { RequestUrl = "", HttpMethod = "GET" } });
// Assert
Assert.Empty(result.Results);
Assert.NotEmpty(result.Errors);
- Assert.Equal(2, result.Errors.Count);
+ Assert.Single(result.Errors);
Assert.Collection(result.Errors,
item =>
{
Assert.Equal("", item.RequestUrl);
Assert.Equal("The request URL cannot be null or empty.", item.Message);
- },
+ });
+ }
+
+ [Fact]
+ public async Task ReturnsErrorsForNullRequestUrl()
+ {
+ // Act
+ PermissionResult result =
+ await _permissionsStore.GetScopesAsync(
+ requests: new List() {
+ new RequestInfo { RequestUrl = null, HttpMethod = "GET" } });
+ // Assert
+ Assert.Empty(result.Results);
+ Assert.NotEmpty(result.Errors);
+ Assert.Single(result.Errors);
+ Assert.Collection(result.Errors,
item =>
{
Assert.Null(item.RequestUrl);
diff --git a/PermissionsService/Services/PermissionsStore.cs b/PermissionsService/Services/PermissionsStore.cs
index 991034ce2..7266c9bec 100644
--- a/PermissionsService/Services/PermissionsStore.cs
+++ b/PermissionsService/Services/PermissionsStore.cs
@@ -3,6 +3,7 @@
// -------------------------------------------------------------------------------------------------------------------------------------------------------
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
@@ -332,8 +333,10 @@ public async Task GetScopesAsync(List requests =
}
else
{
- var scopesByRequestUrl = new Dictionary>();
- foreach (var request in requests)
+ var scopesByRequestUrl = new ConcurrentDictionary>();
+ var uniqueRequests = requests.DistinctBy(static x => $"{x.HttpMethod}{x.RequestUrl}", StringComparer.OrdinalIgnoreCase);
+
+ await Parallel.ForEachAsync(uniqueRequests, async (request, _) =>
{
try
{
@@ -351,21 +354,20 @@ public async Task GetScopesAsync(List requests =
Message = exception.Message
});
}
- }
+ });
var allLeastPrivilegeScopes = scopesByRequestUrl.Values
- .SelectMany(static x => x).Where(static x => x.IsLeastPrivilege == true).ToList();
+ .SelectMany(static x => x).Where(static x => x.IsLeastPrivilege == true)
+ .DistinctBy(static x => $"{x.ScopeName}{x.ScopeType}", StringComparer.OrdinalIgnoreCase).ToList();
foreach (var scopeSet in scopesByRequestUrl.Values)
{
- bool foundInOthers = false;
var higherPrivilegedScopes = scopeSet.Where(static x => x.IsLeastPrivilege == false);
// If any of the higher privilege permissions is a leastPrivilegePermissions somewhere, ignore
- if (higherPrivilegedScopes.Any(scope =>
+ bool foundInOthers = higherPrivilegedScopes.Any(scope =>
allLeastPrivilegeScopes.Any(leastScope =>
- leastScope.ScopeName.Equals(scope.ScopeName, StringComparison.OrdinalIgnoreCase) &&
- leastScope.ScopeType == scope.ScopeType)))
- foundInOthers = true;
+ leastScope.ScopeName.Equals(scope.ScopeName, StringComparison.OrdinalIgnoreCase) &&
+ leastScope.ScopeType == scope.ScopeType));
if (!foundInOthers)
scopes.AddRange(scopeSet);
diff --git a/SamplesService.Test/SamplesService.Test.csproj b/SamplesService.Test/SamplesService.Test.csproj
index f8283512e..ba522cbe9 100644
--- a/SamplesService.Test/SamplesService.Test.csproj
+++ b/SamplesService.Test/SamplesService.Test.csproj
@@ -56,7 +56,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
all
diff --git a/TelemetryService.Test/TelemetrySanitizerService.Test.csproj b/TelemetryService.Test/TelemetrySanitizerService.Test.csproj
index 288ee1302..edccb3645 100644
--- a/TelemetryService.Test/TelemetrySanitizerService.Test.csproj
+++ b/TelemetryService.Test/TelemetrySanitizerService.Test.csproj
@@ -26,7 +26,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/TourStepsService.Test/TourStepsService.Test.csproj b/TourStepsService.Test/TourStepsService.Test.csproj
index 7dcd5d45e..6f2dcdde6 100644
--- a/TourStepsService.Test/TourStepsService.Test.csproj
+++ b/TourStepsService.Test/TourStepsService.Test.csproj
@@ -53,7 +53,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/UriMatchService.Test/UriMatchingService.Test.csproj b/UriMatchService.Test/UriMatchingService.Test.csproj
index 94c1df97f..b7ea6e033 100644
--- a/UriMatchService.Test/UriMatchingService.Test.csproj
+++ b/UriMatchService.Test/UriMatchingService.Test.csproj
@@ -15,7 +15,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
all
diff --git a/UtilityService.Test/UtilityService.Test.csproj b/UtilityService.Test/UtilityService.Test.csproj
index 39092ed27..06f455293 100644
--- a/UtilityService.Test/UtilityService.Test.csproj
+++ b/UtilityService.Test/UtilityService.Test.csproj
@@ -11,7 +11,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/pipelines/snippets.yml b/pipelines/snippets.yml
index aa2427903..fbb0ab2d6 100644
--- a/pipelines/snippets.yml
+++ b/pipelines/snippets.yml
@@ -28,7 +28,7 @@ pool:
parameters:
- name: snippetLanguages
type: object
- default: ['C#', 'JavaScript', 'Java', 'Go', 'PowerShell', 'PHP', 'Python']
+ default: ['C#', 'CLI', 'Go', 'Java', 'JavaScript', 'PHP', 'PowerShell', 'Python'] # should be ordered alphabetically
displayName: 'Languages to generate snippets for'
variables: